UBER-268: List views (#3270)

This commit is contained in:
Andrey Sobolev 2023-05-28 13:33:42 +07:00 committed by GitHub
parent 01a1a3fe6c
commit b46b65fe5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 252 additions and 40 deletions

View File

@ -44,6 +44,7 @@
"@hcengineering/model-notification": "^0.6.0", "@hcengineering/model-notification": "^0.6.0",
"@hcengineering/notification": "^0.6.12", "@hcengineering/notification": "^0.6.12",
"@hcengineering/model-task": "^0.6.0", "@hcengineering/model-task": "^0.6.0",
"@hcengineering/workbench": "^0.6.6" "@hcengineering/workbench": "^0.6.6",
"@hcengineering/model-tracker": "^0.6.0"
} }
} }

View File

@ -42,6 +42,7 @@ import { ViewOptionsModel } from '@hcengineering/view'
import { generateClassNotificationTypes } from '@hcengineering/model-notification' import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import lead from './plugin' import lead from './plugin'
import tracker from '@hcengineering/model-tracker'
export { leadId } from '@hcengineering/lead' export { leadId } from '@hcengineering/lead'
export { leadOperation } from './migration' export { leadOperation } from './migration'
@ -193,6 +194,13 @@ export function createModel (builder: Builder): void {
'', '',
'_class', '_class',
'leads', 'leads',
'attachments',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations
},
'comments',
'modifiedOn', 'modifiedOn',
{ {
key: '$lookup.channels', key: '$lookup.channels',
@ -200,7 +208,14 @@ export function createModel (builder: Builder): void {
sortingKey: ['$lookup.channels.lastMessage', 'channels'] sortingKey: ['$lookup.channels.lastMessage', 'channels']
} }
], ],
hiddenKeys: ['name'] hiddenKeys: ['name'],
options: {
lookup: {
_id: {
related: [tracker.class.Issue, 'relations._id']
}
}
}
}, },
lead.viewlet.TableCustomer lead.viewlet.TableCustomer
) )
@ -215,49 +230,38 @@ export function createModel (builder: Builder): void {
'', '',
'title', 'title',
'attachedTo', 'attachedTo',
'assignee',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues
},
'state', 'state',
'doneState', 'doneState',
'attachments', 'attachments',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations
},
'comments', 'comments',
'modifiedOn', 'modifiedOn',
{ {
key: '$lookup.attachedTo.$lookup.channels', key: '$lookup.attachedTo.$lookup.channels',
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'] sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
} }
] ],
options: {
lookup: {
_id: {
related: [tracker.class.Issue, 'relations._id']
}
}
}
}, },
lead.viewlet.TableLead lead.viewlet.TableLead
) )
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: lead.class.Lead,
descriptor: view.viewlet.List,
config: [
{ key: '', props: { listProps: { fixed: 'left' } } },
{ key: 'title', props: { listProps: { fixed: 'left' } } },
{ key: 'state', props: { listProps: { fixed: 'left' } } },
{ key: 'doneState', props: { listProps: { fixed: 'left' } } },
{ key: '', presenter: view.component.GrowPresenter },
'attachments',
'comments',
'assignee'
],
viewOptions: {
groupBy: ['assignee', 'state', 'attachedTo'],
orderBy: [
['assignee', -1],
['state', 1],
['attachedTo', 1],
['modifiedOn', -1]
],
other: []
}
},
lead.viewlet.ListLead
)
const leadViewOptions: ViewOptionsModel = { const leadViewOptions: ViewOptionsModel = {
groupBy: ['state', 'assignee'], groupBy: ['state', 'assignee'],
orderBy: [ orderBy: [
@ -278,6 +282,61 @@ export function createModel (builder: Builder): void {
} }
] ]
} }
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: lead.class.Lead,
descriptor: view.viewlet.List,
config: [
{ key: '', props: { listProps: { fixed: 'left', key: 'lead' } } },
{
key: '',
presenter: lead.component.TitlePresenter,
props: { listProps: { fixed: 'left', key: 'title' }, maxWidth: '10rem' }
},
{
key: '$lookup.attachedTo',
presenter: contact.component.PersonPresenter,
label: lead.string.Customer,
sortingKey: '$lookup.attachedTo.name',
props: {
_class: lead.mixin.Customer,
listProps: { fixed: 'left', key: 'talent' },
inline: true,
maxWidth: '10rem'
}
},
{ key: 'state', props: { listProps: { fixed: 'left', key: 'state' } } },
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations,
props: { listProps: { fixed: 'left', key: 'issues' } }
},
{ key: 'attachments', props: { listProps: { fixed: 'left', key: 'attachments' } } },
{ key: 'comments', props: { listProps: { fixed: 'left' }, key: 'comments' } },
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
{
key: '$lookup.attachedTo.$lookup.channels',
label: contact.string.ContactInfo,
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'],
props: {
listProps: {
fixed: 'left',
key: 'channels'
}
}
},
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
{ key: 'modifiedOn', props: { listProps: { key: 'modified', fixed: 'left' } } },
{ key: 'assignee', props: { listProps: { key: 'assignee', fixed: 'right' }, shouldShowLabel: false } }
],
viewOptions: leadViewOptions
},
lead.viewlet.ListLead
)
const lookupLeadOptions: FindOptions<Lead> = { const lookupLeadOptions: FindOptions<Lead> = {
lookup: { lookup: {

View File

@ -284,7 +284,12 @@ export function createModel (builder: Builder): void {
label: recruit.string.Applications, label: recruit.string.Applications,
createLabel: recruit.string.ApplicationCreateLabel, createLabel: recruit.string.ApplicationCreateLabel,
createComponent: recruit.component.CreateApplication, createComponent: recruit.component.CreateApplication,
descriptors: [view.viewlet.Table, task.viewlet.Kanban, recruit.viewlet.ApplicantDashboard] descriptors: [
view.viewlet.Table,
view.viewlet.List,
task.viewlet.Kanban,
recruit.viewlet.ApplicantDashboard
]
}, },
position: 'vacancy' position: 'vacancy'
}, },
@ -556,8 +561,8 @@ export function createModel (builder: Builder): void {
label: tracker.string.Issues label: tracker.string.Issues
}, },
'state', 'state',
'comments',
'attachments', 'attachments',
'comments',
'modifiedOn', 'modifiedOn',
'$lookup.space.company', '$lookup.space.company',
{ {
@ -639,6 +644,76 @@ export function createModel (builder: Builder): void {
} }
] ]
} }
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.List,
config: [
{ key: '', props: { listProps: { fixed: 'left', key: 'app' } } },
{
key: '$lookup.attachedTo',
presenter: contact.component.PersonPresenter,
label: recruit.string.Talent,
sortingKey: '$lookup.attachedTo.name',
props: {
_class: recruit.mixin.Candidate,
listProps: { fixed: 'left', key: 'talent' },
inline: true
}
},
{ key: 'state', props: { listProps: { fixed: 'left', key: 'state' }, inline: true, showLabel: false } },
{
key: '$lookup.space.company',
props: {
listProps: { fixed: 'left', key: 'company' },
inline: true,
maxWidth: '10rem'
}
},
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues,
props: { listProps: { fixed: 'left', key: 'issues' } }
},
{ key: 'attachments', props: { listProps: { fixed: 'left', key: 'attachments' } } },
{ key: 'comments', props: { listProps: { fixed: 'left' }, key: 'comments' } },
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
{
key: '$lookup.attachedTo.$lookup.channels',
label: contact.string.ContactInfo,
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'],
props: {
listProps: {
fixed: 'left',
key: 'channels'
}
}
},
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
{ key: 'modifiedOn', props: { listProps: { key: 'modified', fixed: 'left' } } },
{ key: 'assignee', props: { listProps: { key: 'assignee', fixed: 'right' }, shouldShowLabel: false } }
],
options: {
lookup: {
_id: {
related: [tracker.class.Issue, 'relations._id']
},
space: recruit.class.Vacancy
}
},
hiddenKeys: ['name', 'attachedTo'],
baseQuery: {
doneState: null,
'$lookup.space.archived': false
},
viewOptions: applicantViewOptions
},
recruit.viewlet.ListApplicant
)
builder.createDoc( builder.createDoc(
view.class.Viewlet, view.class.Viewlet,

View File

@ -113,6 +113,7 @@ export default mergeIds(recruitId, recruit, {
TableVacancy: '' as Ref<Viewlet>, TableVacancy: '' as Ref<Viewlet>,
ApplicantTable: '' as Ref<Viewlet>, ApplicantTable: '' as Ref<Viewlet>,
ApplicantKanban: '' as Ref<Viewlet>, ApplicantKanban: '' as Ref<Viewlet>,
ListApplicant: '' as Ref<Viewlet>,
TableApplicant: '' as Ref<Viewlet>, TableApplicant: '' as Ref<Viewlet>,
TableApplicantMatch: '' as Ref<Viewlet>, TableApplicantMatch: '' as Ref<Viewlet>,
CalendarReview: '' as Ref<Viewlet>, CalendarReview: '' as Ref<Viewlet>,

View File

@ -573,7 +573,12 @@ export function createModel (builder: Builder): void {
{ {
key: 'assignee', key: 'assignee',
presenter: tracker.component.AssigneePresenter, presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false } props: {
listProps: { key: 'assigee', fixed: 'right' },
key: 'assignee',
defaultClass: contact.class.Employee,
shouldShowLabel: false
}
} }
], ],
options: { options: {

View File

@ -25,6 +25,7 @@
export let value: Contact export let value: Contact
export let inline: boolean = false export let inline: boolean = false
export let disabled = false export let disabled = false
export let maxWidth = ''
function isPerson (value: Contact): boolean { function isPerson (value: Contact): boolean {
const client = getClient() const client = getClient()
@ -45,5 +46,5 @@
{:else if isPerson(value)} {:else if isPerson(value)}
<PersonPresenter {disabled} {value} {inline} /> <PersonPresenter {disabled} {value} {inline} />
{:else} {:else}
<OrganizationPresenter value={toOrg(value)} {inline} /> <OrganizationPresenter value={toOrg(value)} {inline} {maxWidth} />
{/if} {/if}

View File

@ -22,6 +22,8 @@
export let value: Ref<Contact> export let value: Ref<Contact>
export let disabled = false export let disabled = false
export let maxWidth = ''
export let inline = false
let doc: Contact | undefined let doc: Contact | undefined
const query = createQuery() const query = createQuery()
@ -29,5 +31,5 @@
</script> </script>
{#if doc} {#if doc}
<ContactPresenter value={doc} {disabled} /> <ContactPresenter value={doc} {disabled} {maxWidth} {inline} />
{/if} {/if}

View File

@ -22,11 +22,17 @@
export let value: Organization export let value: Organization
export let inline: boolean = false export let inline: boolean = false
export let maxWidth = ''
</script> </script>
{#if value} {#if value}
<DocNavLink {inline} object={value}> <DocNavLink {inline} object={value}>
<div class="flex-presenter" class:inline-presenter={inline} use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div
class="flex-presenter overflow-label"
style:max-width={maxWidth}
class:inline-presenter={inline}
use:tooltip={{ label: getEmbeddedLabel(value.name) }}
>
{#if !inline} {#if !inline}
<div class="icon circle"><Company size={'small'} /></div> <div class="icon circle"><Company size={'small'} /></div>
{/if} {/if}

View File

@ -43,6 +43,7 @@
export let enlargedText = false export let enlargedText = false
export let colorInherit: boolean = false export let colorInherit: boolean = false
export let accent: boolean = false export let accent: boolean = false
export let maxWidth = ''
const onEditClick = (evt: MouseEvent) => { const onEditClick = (evt: MouseEvent) => {
if (!disabled) { if (!disabled) {
@ -70,6 +71,7 @@
class="contentPresenter" class="contentPresenter"
class:text-base={enlargedText} class:text-base={enlargedText}
class:inline-presenter={inline} class:inline-presenter={inline}
style:max-width={maxWidth}
> >
{#if !inline && shouldShowAvatar} {#if !inline && shouldShowAvatar}
<span <span

View File

@ -34,6 +34,7 @@
export let element: HTMLElement | undefined = undefined export let element: HTMLElement | undefined = undefined
export let colorInherit: boolean = false export let colorInherit: boolean = false
export let accent: boolean = false export let accent: boolean = false
export let maxWidth = ''
function getTooltip ( function getTooltip (
tooltipLabels: PersonLabelTooltip | undefined, tooltipLabels: PersonLabelTooltip | undefined,
@ -79,6 +80,7 @@
{statusLabel} {statusLabel}
{colorInherit} {colorInherit}
{accent} {accent}
{maxWidth}
bind:element bind:element
on:accent-color on:accent-color
/> />

View File

@ -0,0 +1,55 @@
<!--
// 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 { WithLookup } from '@hcengineering/core'
import { Lead } from '@hcengineering/lead'
export let value: WithLookup<Lead>
export let shouldUseMargin: boolean = false
export let kind: 'list' | undefined = undefined
export let maxWidth = '100%'
</script>
{#if value}
<span
class="name overflow-label select-text"
class:with-margin={shouldUseMargin}
class:list={kind === 'list'}
style:max-width={'100%'}
>
{value.title}
</span>
{/if}
<style lang="scss">
.name {
flex-shrink: 1;
min-width: 1rem;
&.list {
color: var(--theme-caption-color);
}
&:hover {
text-decoration: underline;
}
&:active {
color: var(--theme-content-color);
}
}
.with-margin {
margin-left: 0.5rem;
}
</style>

View File

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

View File

@ -49,7 +49,8 @@ export default mergeIds(leadId, lead, {
LeadsPresenter: '' as AnyComponent, LeadsPresenter: '' as AnyComponent,
CreateFunnel: '' as AnyComponent, CreateFunnel: '' as AnyComponent,
EditFunnel: '' as AnyComponent, EditFunnel: '' as AnyComponent,
MyLeads: '' as AnyComponent MyLeads: '' as AnyComponent,
TitlePresenter: '' as AnyComponent
}, },
function: { function: {
LeadTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>> LeadTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>