UBER-53: My Leads view (#3259)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
Vyacheslav Tumanov 2023-05-26 08:07:38 +05:00 committed by GitHub
parent b7463a3a16
commit 1ada38e652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 2 deletions

View File

@ -124,6 +124,13 @@ export function createModel (builder: Builder): void {
hidden: false,
navigatorModel: {
specials: [
{
id: 'my-leads',
label: lead.string.MyLeads,
icon: lead.icon.Lead,
component: lead.component.MyLeads,
position: 'top'
},
{
id: 'customers',
label: lead.string.Customers,
@ -378,6 +385,10 @@ export function createModel (builder: Builder): void {
filters: ['attachedTo']
})
builder.mixin(lead.class.Lead, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['createdBy', 'assignee']
})
builder.mixin(lead.mixin.Customer, core.class.Class, view.mixin.ClassFilters, {
filters: ['_class']
})

View File

@ -13,6 +13,7 @@
"Customer": "Customer",
"Customers": "Customers",
"Leads": "Leads",
"MyLeads": "My Leads",
"SelectCustomer": "Select customer",
"Lead": "Lead",
"Assignee": "Assignee",

View File

@ -13,6 +13,7 @@
"Customer": "Клиент",
"Customers": "Клиенты",
"Leads": "Сделки",
"MyLeads": "Мои сделки",
"SelectCustomer": "Выбрать клиента",
"Lead": "Сделка",
"Assignee": "Исполнитель",

View File

@ -0,0 +1,156 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { EmployeeAccount } from '@hcengineering/contact'
import { AttachedDoc, Class, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import task, { Task } from '@hcengineering/task'
import { IModeSelector, Label, resolvedLocationStore, SearchEdit, ModeSelector, Loading } from '@hcengineering/ui'
import {
FilterButton,
getViewOptions,
makeViewletKey,
TableBrowser,
viewOptionStore
} from '@hcengineering/view-resources'
import { IntlString } from '@hcengineering/platform'
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { onDestroy } from 'svelte'
import FilterBar from '@hcengineering/view-resources/src/components/filter/FilterBar.svelte'
import lead from '../plugin'
import { Lead } from '@hcengineering/lead'
export let _class: Ref<Class<Lead>> = lead.class.Lead
export let labelTasks = lead.string.MyLeads
let search = ''
const currentUser = getCurrentAccount() as EmployeeAccount
const assigned = { assignee: currentUser.employee }
const created = { createdBy: currentUser._id }
let subscribed = { _id: { $in: [] as Ref<Task>[] } }
$: baseQuery = updateBaseQuery(mode, { assigned, created, subscribed })
function updateBaseQuery (mode: string, queries: { [key: string]: DocumentQuery<Lead> }) {
return { ...queries[mode] }
}
let searchQuery: DocumentQuery<Lead> = { ...baseQuery }
function updateSearchQuery (search: string): void {
searchQuery = search === '' ? { ...baseQuery } : { ...baseQuery, $search: search }
}
$: if (baseQuery) updateSearchQuery(search)
$: resultQuery = { ...searchQuery }
const subscribedQuery = createQuery()
function getSubscribed () {
subscribedQuery.query(
_class,
{ 'notification:mixin:Collaborators.collaborators': getCurrentAccount()._id },
(result) => {
const newSub = result.map((p) => p._id as Ref<AttachedDoc> as Ref<Lead>)
const curSub = subscribed._id.$in
if (curSub.length !== newSub.length || curSub.some((id, i) => newSub[i] !== id)) {
subscribed = { _id: { $in: newSub } }
}
},
{ sort: { _id: 1 } }
)
}
$: if (mode === 'subscribed') getSubscribed()
const config: [string, IntlString, object][] = [
['assigned', view.string.Assigned, {}],
['created', view.string.Created, {}],
['subscribed', view.string.Subscribed, {}]
]
let [[mode]] = config
function handleChangeMode (newMode: string) {
if (newMode === mode) return
mode = newMode
}
$: modeSelectorProps = {
config,
mode,
onChange: handleChangeMode
} as IModeSelector
let viewlet: Viewlet | undefined
let loading = true
let key = makeViewletKey()
let preference: ViewletPreference | undefined
onDestroy(
resolvedLocationStore.subscribe((loc) => {
key = makeViewletKey(loc)
})
)
const preferenceQuery = createQuery()
const client = getClient()
client
.findOne<Viewlet>(view.class.Viewlet, { attachTo: _class, descriptor: task.viewlet.StatusTable })
.then((res) => {
viewlet = res
preferenceQuery.query(
view.class.ViewletPreference,
{
attachedTo: res._id
},
(res) => {
preference = res[0]
loading = false
},
{ limit: 1 }
)
})
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
</script>
<div
class="ac-header full divide"
class:header-with-mode-selector={modeSelectorProps !== undefined}
class:header-without-label={!labelTasks}
>
<div class="ac-header__wrap-title">
<span class="ac-header__title"><Label label={labelTasks} /></span>
{#if modeSelectorProps !== undefined}
<ModeSelector props={modeSelectorProps} />
{/if}
</div>
</div>
<div class="ac-header full divide search-start">
<div class="ac-header-full small-gap">
<SearchEdit bind:value={search} />
<div class="buttons-divider" />
<FilterButton {_class} />
</div>
{#if viewlet}
<ViewletSettingButton bind:viewOptions {viewlet} />
{/if}
</div>
<FilterBar {_class} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
{#if viewlet}
{#if loading}
<Loading />
{:else}
<TableBrowser
{_class}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={resultQuery}
showNotification
/>
{/if}
{/if}

View File

@ -27,6 +27,7 @@ import CreateCustomer from './components/CreateCustomer.svelte'
import NewItemsHeader from './components/NewItemsHeader.svelte'
import { getLeadTitle } from './utils'
import EditFunnel from './components/EditFunnel.svelte'
import MyLeads from './components/MyLeads.svelte'
export default async (): Promise<Resources> => ({
component: {
@ -40,7 +41,8 @@ export default async (): Promise<Resources> => ({
Leads,
CreateCustomer,
NewItemsHeader,
EditFunnel
EditFunnel,
MyLeads
},
function: {
LeadTitleProvider: getLeadTitle

View File

@ -31,6 +31,7 @@ export default mergeIds(leadId, lead, {
SelectCustomer: '' as IntlString,
Customers: '' as IntlString,
Leads: '' as IntlString,
MyLeads: '' as IntlString,
NoLeadsForDocument: '' as IntlString,
LeadPlaceholder: '' as IntlString,
CreateCustomer: '' as IntlString,
@ -47,7 +48,8 @@ export default mergeIds(leadId, lead, {
CreateCustomer: '' as AnyComponent,
LeadsPresenter: '' as AnyComponent,
CreateFunnel: '' as AnyComponent,
EditFunnel: '' as AnyComponent
EditFunnel: '' as AnyComponent,
MyLeads: '' as AnyComponent
},
function: {
LeadTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>