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/notification": "^0.6.12",
"@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 notification from '@hcengineering/notification'
import lead from './plugin'
import tracker from '@hcengineering/model-tracker'
export { leadId } from '@hcengineering/lead'
export { leadOperation } from './migration'
@ -193,6 +194,13 @@ export function createModel (builder: Builder): void {
'',
'_class',
'leads',
'attachments',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations
},
'comments',
'modifiedOn',
{
key: '$lookup.channels',
@ -200,7 +208,14 @@ export function createModel (builder: Builder): void {
sortingKey: ['$lookup.channels.lastMessage', 'channels']
}
],
hiddenKeys: ['name']
hiddenKeys: ['name'],
options: {
lookup: {
_id: {
related: [tracker.class.Issue, 'relations._id']
}
}
}
},
lead.viewlet.TableCustomer
)
@ -215,49 +230,38 @@ export function createModel (builder: Builder): void {
'',
'title',
'attachedTo',
'assignee',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues
},
'state',
'doneState',
'attachments',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations
},
'comments',
'modifiedOn',
{
key: '$lookup.attachedTo.$lookup.channels',
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
}
]
],
options: {
lookup: {
_id: {
related: [tracker.class.Issue, 'relations._id']
}
}
}
},
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 = {
groupBy: ['state', 'assignee'],
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> = {
lookup: {

View File

@ -284,7 +284,12 @@ export function createModel (builder: Builder): void {
label: recruit.string.Applications,
createLabel: recruit.string.ApplicationCreateLabel,
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'
},
@ -556,8 +561,8 @@ export function createModel (builder: Builder): void {
label: tracker.string.Issues
},
'state',
'comments',
'attachments',
'comments',
'modifiedOn',
'$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(
view.class.Viewlet,

View File

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

View File

@ -573,7 +573,12 @@ export function createModel (builder: Builder): void {
{
key: 'assignee',
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: {

View File

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

View File

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

View File

@ -22,11 +22,17 @@
export let value: Organization
export let inline: boolean = false
export let maxWidth = ''
</script>
{#if 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}
<div class="icon circle"><Company size={'small'} /></div>
{/if}

View File

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

View File

@ -34,6 +34,7 @@
export let element: HTMLElement | undefined = undefined
export let colorInherit: boolean = false
export let accent: boolean = false
export let maxWidth = ''
function getTooltip (
tooltipLabels: PersonLabelTooltip | undefined,
@ -79,6 +80,7 @@
{statusLabel}
{colorInherit}
{accent}
{maxWidth}
bind:element
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 EditFunnel from './components/EditFunnel.svelte'
import MyLeads from './components/MyLeads.svelte'
import TitlePresenter from './components/TitlePresenter.svelte'
export default async (): Promise<Resources> => ({
component: {
@ -42,7 +43,8 @@ export default async (): Promise<Resources> => ({
CreateCustomer,
NewItemsHeader,
EditFunnel,
MyLeads
MyLeads,
TitlePresenter
},
function: {
LeadTitleProvider: getLeadTitle

View File

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