mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
Uber 189 Uber 190 (#3300)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
5009a5b518
commit
0d6aa799f3
@ -120,7 +120,9 @@ export function createModel (builder: Builder): void {
|
||||
descriptor: calendar.viewlet.Calendar,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
config: [''],
|
||||
configOptions: {
|
||||
hiddenKeys: ['title', 'date']
|
||||
}
|
||||
},
|
||||
calendar.viewlet.CalendarEvent
|
||||
)
|
||||
|
@ -253,7 +253,9 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
'modifiedOn'
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'contact']
|
||||
}
|
||||
},
|
||||
contact.viewlet.TableMember
|
||||
)
|
||||
@ -282,7 +284,9 @@ export function createModel (builder: Builder): void {
|
||||
sortingKey: ['$lookup.channels.lastMessage', 'channels']
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name']
|
||||
}
|
||||
},
|
||||
contact.viewlet.TableContact
|
||||
)
|
||||
|
@ -391,8 +391,7 @@ export function createModel (builder: Builder): void {
|
||||
sortingKey: ['$lookup.channels.lastMessage', 'channels']
|
||||
},
|
||||
'modifiedOn'
|
||||
],
|
||||
hiddenKeys: []
|
||||
]
|
||||
},
|
||||
hr.viewlet.TableMember
|
||||
)
|
||||
@ -403,8 +402,7 @@ export function createModel (builder: Builder): void {
|
||||
{
|
||||
attachTo: hr.mixin.Staff,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: [''],
|
||||
hiddenKeys: []
|
||||
config: ['']
|
||||
},
|
||||
hr.viewlet.StaffStats
|
||||
)
|
||||
|
@ -208,7 +208,9 @@ export function createModel (builder: Builder): void {
|
||||
sortingKey: ['$lookup.channels.lastMessage', 'channels']
|
||||
}
|
||||
],
|
||||
hiddenKeys: ['name'],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name']
|
||||
},
|
||||
options: {
|
||||
lookup: {
|
||||
_id: {
|
||||
@ -288,50 +290,56 @@ export function createModel (builder: Builder): void {
|
||||
{
|
||||
attachTo: lead.class.Lead,
|
||||
descriptor: view.viewlet.List,
|
||||
configOptions: {
|
||||
hiddenKeys: ['title'],
|
||||
extraProps: {
|
||||
displayProps: {
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
config: [
|
||||
{ key: '', props: { listProps: { fixed: 'left', key: 'lead' } } },
|
||||
{ key: '', displayProps: { fixed: 'left', key: 'lead' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: lead.component.TitlePresenter,
|
||||
props: { listProps: { fixed: 'left', key: 'title' }, maxWidth: '10rem' }
|
||||
label: lead.string.Title,
|
||||
displayProps: { fixed: 'left', key: 'title' },
|
||||
props: { maxWidth: '10rem' }
|
||||
},
|
||||
{
|
||||
key: '$lookup.attachedTo',
|
||||
presenter: contact.component.PersonPresenter,
|
||||
label: lead.string.Customer,
|
||||
sortingKey: '$lookup.attachedTo.name',
|
||||
displayProps: { fixed: 'left', key: 'talent' },
|
||||
props: {
|
||||
_class: lead.mixin.Customer,
|
||||
listProps: { fixed: 'left', key: 'talent' },
|
||||
inline: true,
|
||||
maxWidth: '10rem'
|
||||
}
|
||||
},
|
||||
{ key: 'state', props: { listProps: { fixed: 'left', key: 'state' } } },
|
||||
{ key: 'state', displayProps: { fixed: 'left', key: 'state' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.RelatedIssueSelector,
|
||||
label: tracker.string.Relations,
|
||||
props: { listProps: { fixed: 'left', key: 'issues' } }
|
||||
displayProps: { 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: 'attachments', displayProps: { fixed: 'left', key: 'attachments' } },
|
||||
{ key: 'comments', displayProps: { fixed: 'left', key: 'comments' } },
|
||||
{
|
||||
key: '$lookup.attachedTo.$lookup.channels',
|
||||
label: contact.string.ContactInfo,
|
||||
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'],
|
||||
props: {
|
||||
listProps: {
|
||||
displayProps: {
|
||||
fixed: 'left',
|
||||
key: 'channels'
|
||||
}
|
||||
key: 'channels',
|
||||
dividerBefore: true
|
||||
}
|
||||
},
|
||||
{ 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 } }
|
||||
{ key: 'modifiedOn', displayProps: { key: 'modified', fixed: 'left', dividerBefore: true } },
|
||||
{ key: 'assignee', displayProps: { key: 'assignee', fixed: 'right' }, props: { shouldShowLabel: false } }
|
||||
],
|
||||
viewOptions: leadViewOptions
|
||||
},
|
||||
@ -399,7 +407,10 @@ export function createModel (builder: Builder): void {
|
||||
groupDepth: 1
|
||||
},
|
||||
options: lookupLeadOptions,
|
||||
config: []
|
||||
config: ['attachedTo', 'attachments', 'comments', 'dueDate', 'assignee'],
|
||||
configOptions: {
|
||||
strict: true
|
||||
}
|
||||
},
|
||||
lead.viewlet.KanbanLead
|
||||
)
|
||||
|
@ -399,7 +399,9 @@ export function createModel (builder: Builder): void {
|
||||
sortingKey: ['$lookup.channels.lastMessage', 'channels']
|
||||
}
|
||||
],
|
||||
hiddenKeys: ['name'],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name']
|
||||
},
|
||||
options: {
|
||||
lookup: {
|
||||
_id: {
|
||||
@ -457,7 +459,9 @@ export function createModel (builder: Builder): void {
|
||||
label: core.string.ModifiedDate
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'space', 'modifiedOn']
|
||||
}
|
||||
},
|
||||
recruit.viewlet.TableVacancy
|
||||
)
|
||||
@ -487,7 +491,9 @@ export function createModel (builder: Builder): void {
|
||||
label: core.string.ModifiedDate
|
||||
}
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'space', 'modifiedOn']
|
||||
}
|
||||
},
|
||||
recruit.viewlet.TableVacancyList
|
||||
)
|
||||
@ -526,7 +532,9 @@ export function createModel (builder: Builder): void {
|
||||
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
|
||||
}
|
||||
],
|
||||
hiddenKeys: ['name', 'attachedTo'],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'attachedTo']
|
||||
},
|
||||
options: {
|
||||
lookup: {
|
||||
_id: {
|
||||
@ -579,7 +587,9 @@ export function createModel (builder: Builder): void {
|
||||
space: recruit.class.Vacancy
|
||||
}
|
||||
},
|
||||
hiddenKeys: ['name', 'attachedTo'],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'attachedTo']
|
||||
},
|
||||
baseQuery: {
|
||||
doneState: null,
|
||||
'$lookup.space.archived': false
|
||||
@ -595,7 +605,6 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: recruit.class.ApplicantMatch,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: ['', 'response', 'attachedTo', 'space', 'modifiedOn'],
|
||||
hiddenKeys: [],
|
||||
options: {
|
||||
lookup: {
|
||||
space: recruit.class.Vacancy
|
||||
@ -657,23 +666,23 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: recruit.class.Applicant,
|
||||
descriptor: view.viewlet.List,
|
||||
config: [
|
||||
{ key: '', props: { listProps: { fixed: 'left', key: 'app' } } },
|
||||
{ key: '', displayProps: { fixed: 'left', key: 'app' } },
|
||||
{
|
||||
key: '$lookup.attachedTo',
|
||||
presenter: contact.component.PersonPresenter,
|
||||
label: recruit.string.Talent,
|
||||
sortingKey: '$lookup.attachedTo.name',
|
||||
displayProps: { fixed: 'left', key: 'talent' },
|
||||
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: 'state', displayProps: { fixed: 'left', key: 'state' }, props: { inline: true, showLabel: false } },
|
||||
{
|
||||
key: '$lookup.space.company',
|
||||
displayProps: { fixed: 'left', key: 'company' },
|
||||
props: {
|
||||
listProps: { fixed: 'left', key: 'company' },
|
||||
inline: true,
|
||||
maxWidth: '10rem'
|
||||
}
|
||||
@ -682,26 +691,26 @@ export function createModel (builder: Builder): void {
|
||||
key: '',
|
||||
presenter: tracker.component.RelatedIssueSelector,
|
||||
label: tracker.string.Issues,
|
||||
props: { listProps: { fixed: 'left', key: 'issues' } }
|
||||
displayProps: { 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: 'attachments', displayProps: { fixed: 'left', key: 'attachments' } },
|
||||
{ key: 'comments', displayProps: { fixed: 'left', key: 'comments' } },
|
||||
{
|
||||
key: '$lookup.attachedTo.$lookup.channels',
|
||||
label: contact.string.ContactInfo,
|
||||
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels'],
|
||||
props: {
|
||||
listProps: {
|
||||
length: 'full',
|
||||
size: 'inline'
|
||||
},
|
||||
displayProps: {
|
||||
fixed: 'left',
|
||||
key: 'channels'
|
||||
}
|
||||
key: 'channels',
|
||||
dividerBefore: true
|
||||
}
|
||||
},
|
||||
{ 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 } }
|
||||
{ key: 'modifiedOn', displayProps: { key: 'modified', fixed: 'left', dividerBefore: true } },
|
||||
{ key: 'assignee', displayProps: { key: 'assignee', fixed: 'right' }, props: { shouldShowLabel: false } }
|
||||
],
|
||||
options: {
|
||||
lookup: {
|
||||
@ -711,7 +720,14 @@ export function createModel (builder: Builder): void {
|
||||
space: recruit.class.Vacancy
|
||||
}
|
||||
},
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'attachedTo'],
|
||||
extraProps: {
|
||||
displayProps: {
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
baseQuery: {
|
||||
doneState: null,
|
||||
'$lookup.space.archived': false
|
||||
@ -739,7 +755,25 @@ export function createModel (builder: Builder): void {
|
||||
options: {
|
||||
lookup: applicantKanbanLookup
|
||||
},
|
||||
config: []
|
||||
configOptions: {
|
||||
strict: true
|
||||
},
|
||||
config: [
|
||||
'space',
|
||||
'assignee',
|
||||
'state',
|
||||
'attachments',
|
||||
'dueDate',
|
||||
'comments',
|
||||
{
|
||||
key: 'company',
|
||||
label: recruit.string.Company
|
||||
},
|
||||
{
|
||||
key: 'channels',
|
||||
label: contact.string.ContactInfo
|
||||
}
|
||||
]
|
||||
},
|
||||
recruit.viewlet.ApplicantKanban
|
||||
)
|
||||
|
@ -487,86 +487,125 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: issuesOptions(false),
|
||||
configOptions: {
|
||||
hiddenKeys: [
|
||||
'title',
|
||||
'blockedBy',
|
||||
'relations',
|
||||
'description',
|
||||
'number',
|
||||
'titile',
|
||||
'reportedTime',
|
||||
'reports',
|
||||
'priority',
|
||||
'component',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'status',
|
||||
'dueDate',
|
||||
'attachedTo'
|
||||
],
|
||||
extraProps: {
|
||||
displayProps: {
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Priority,
|
||||
presenter: tracker.component.PriorityEditor,
|
||||
props: { type: 'priority', kind: 'list', size: 'small' }
|
||||
props: { type: 'priority', kind: 'list', size: 'small' },
|
||||
displayProps: { key: 'priority' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Identifier,
|
||||
presenter: tracker.component.IssuePresenter,
|
||||
props: { type: 'issue', listProps: { key: 'issue', fixed: 'left' } }
|
||||
displayProps: { key: 'issue', fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Status,
|
||||
presenter: tracker.component.StatusEditor,
|
||||
props: { kind: 'list', size: 'small', justify: 'center' }
|
||||
props: { kind: 'list', size: 'small', justify: 'center' },
|
||||
displayProps: {
|
||||
key: 'status'
|
||||
}
|
||||
},
|
||||
{ key: '', presenter: tracker.component.TitlePresenter, props: {} },
|
||||
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Title,
|
||||
presenter: tracker.component.TitlePresenter,
|
||||
props: {},
|
||||
displayProps: { key: 'title' }
|
||||
},
|
||||
{ key: '', label: tracker.string.SubIssues, presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||
{
|
||||
key: 'labels',
|
||||
presenter: tags.component.LabelsPresenter,
|
||||
props: { kind: 'list', full: false, listProps: { optional: true, compression: true } }
|
||||
displayProps: { optional: true, compression: true },
|
||||
props: { kind: 'list', full: false }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.DueDate,
|
||||
presenter: tracker.component.DueDatePresenter,
|
||||
props: { kind: 'list', listProps: { optional: true, compression: true } }
|
||||
displayProps: { key: 'dueDate', optional: true, compression: true },
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Component,
|
||||
presenter: tracker.component.ComponentEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shape: 'round',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: {
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: {
|
||||
key: 'component',
|
||||
excludeByKey: 'component',
|
||||
compression: true,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Milestone,
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shape: 'round',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: {
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: {
|
||||
key: 'milestone',
|
||||
excludeByKey: 'milestone',
|
||||
compression: true,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: view.component.DividerPresenter,
|
||||
props: { type: 'divider', listProps: { compression: true } }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Estimation,
|
||||
presenter: tracker.component.EstimationEditor,
|
||||
props: { kind: 'list', size: 'small', listProps: { key: 'estimation', fixed: 'left', compression: true } }
|
||||
props: { kind: 'list', size: 'small' },
|
||||
displayProps: { key: 'estimation', fixed: 'left', compression: true, dividerBefore: true }
|
||||
},
|
||||
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
props: { listProps: { key: 'modified', fixed: 'left' } }
|
||||
displayProps: { key: 'modified', fixed: 'left', dividerBefore: true }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
presenter: tracker.component.AssigneePresenter,
|
||||
displayProps: { key: 'assigee', fixed: 'right' },
|
||||
props: {
|
||||
listProps: { key: 'assigee', fixed: 'right' },
|
||||
key: 'assignee',
|
||||
defaultClass: contact.class.Employee,
|
||||
shouldShowLabel: false
|
||||
@ -613,7 +652,8 @@ export function createModel (builder: Builder): void {
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.IssuePresenter,
|
||||
props: { type: 'issue', listProps: { fixed: 'left' } }
|
||||
props: { type: 'issue' },
|
||||
displayProps: { fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
@ -622,8 +662,11 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true, showParent: false } },
|
||||
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.DueDatePresenter,
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
@ -631,22 +674,23 @@ export function createModel (builder: Builder): void {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shape: 'round',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: {
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: {
|
||||
excludeByKey: 'milestone',
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.EstimationEditor,
|
||||
props: { kind: 'list', size: 'small', listProps: { optional: true } }
|
||||
props: { kind: 'list', size: 'small' },
|
||||
displayProps: { optional: true }
|
||||
},
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
props: { listProps: { fixed: 'right', optional: true } }
|
||||
displayProps: { fixed: 'right', optional: true }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
@ -674,6 +718,10 @@ export function createModel (builder: Builder): void {
|
||||
],
|
||||
other: [showColorsViewOption]
|
||||
},
|
||||
configOptions: {
|
||||
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description'],
|
||||
extraProps: { displayProps: { optional: true } }
|
||||
},
|
||||
config: [
|
||||
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
|
||||
{
|
||||
@ -681,42 +729,43 @@ export function createModel (builder: Builder): void {
|
||||
presenter: tracker.component.IssueTemplatePresenter,
|
||||
props: { type: 'issue', shouldUseMargin: true }
|
||||
},
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
// { key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.ComponentEditor,
|
||||
label: tracker.string.Component,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: { optional: true, compression: true }
|
||||
}
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: { key: 'component', optional: true, compression: true }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Milestone,
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: { optional: true, compression: true }
|
||||
}
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: { key: 'milestone', optional: true, compression: true }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Estimation,
|
||||
presenter: tracker.component.TemplateEstimationEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
listProps: { optional: true, compression: true }
|
||||
}
|
||||
size: 'small'
|
||||
},
|
||||
displayProps: { key: 'estimation', optional: true, compression: true }
|
||||
},
|
||||
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
props: { listProps: { fixed: 'right' } }
|
||||
displayProps: { fixed: 'right', dividerBefore: true }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
@ -738,7 +787,10 @@ export function createModel (builder: Builder): void {
|
||||
...issuesOptions(true),
|
||||
groupDepth: 1
|
||||
},
|
||||
config: []
|
||||
configOptions: {
|
||||
strict: true
|
||||
},
|
||||
config: ['subIssues', 'priority', 'component', 'dueDate', 'labels', 'estimation', 'attachments', 'comments']
|
||||
},
|
||||
tracker.viewlet.IssueKanban
|
||||
)
|
||||
@ -1807,14 +1859,22 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: tracker.class.Milestone,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: milestoneOptions,
|
||||
configOptions: {
|
||||
hiddenKeys: ['targetDate', 'label', 'description'],
|
||||
extraProps: { displayProps: { optional: true } }
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: 'status',
|
||||
props: { width: '1rem', kind: 'list', size: 'small', justify: 'center' }
|
||||
},
|
||||
{ key: '', presenter: tracker.component.MilestonePresenter, props: { shouldUseMargin: true } },
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{ key: '', presenter: tracker.component.MilestoneDatePresenter, props: { field: 'targetDate' } }
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.TargetDate,
|
||||
presenter: tracker.component.MilestoneDatePresenter,
|
||||
props: { field: 'targetDate' }
|
||||
}
|
||||
]
|
||||
},
|
||||
tracker.viewlet.MilestoneList
|
||||
@ -1879,23 +1939,24 @@ export function createModel (builder: Builder): void {
|
||||
attachTo: tracker.class.Component,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: componentListViewOptions,
|
||||
configOptions: {
|
||||
hiddenKeys: ['label', 'description'],
|
||||
extraProps: { displayProps: { optional: true } }
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.ComponentPresenter,
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
|
||||
{
|
||||
key: '$lookup.lead',
|
||||
presenter: tracker.component.LeadPresenter,
|
||||
props: { _class: tracker.class.Component, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
displayProps: {
|
||||
dividerBefore: true,
|
||||
key: 'lead'
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.DeleteComponentPresenter,
|
||||
props: { kind: 'transparent', size: 'small' }
|
||||
props: { _class: tracker.class.Component, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -244,6 +244,9 @@
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
&.small {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
&.link-bordered {
|
||||
padding: 0 0.5rem;
|
||||
|
@ -134,6 +134,7 @@
|
||||
}
|
||||
&.small {
|
||||
height: 1.5rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
&.medium {
|
||||
height: 2rem;
|
||||
@ -239,6 +240,10 @@
|
||||
color: var(--theme-dark-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&.small {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
|
@ -28,9 +28,9 @@
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
|
||||
$: employee = $employeeByIdStore.get((value as EmployeeAccount).employee)
|
||||
$: employee = $employeeByIdStore.get((value as EmployeeAccount)?.employee)
|
||||
|
||||
const valueLabel = value.email === systemAccountEmail ? core.string.System : getEmbeddedLabel(value.email)
|
||||
const valueLabel = value?.email === systemAccountEmail ? core.string.System : getEmbeddedLabel(value?.email)
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
|
@ -21,8 +21,8 @@
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import type { Lead } from '@hcengineering/lead'
|
||||
import { ActionIcon, Component, DueDatePresenter, IconMoreH, showPanel, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
import view, { BuildModelKey } from '@hcengineering/view'
|
||||
import { ContextMenu, enabledConfig } from '@hcengineering/view-resources'
|
||||
import lead from '../plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@ -30,6 +30,8 @@
|
||||
import LeadPresenter from './LeadPresenter.svelte'
|
||||
|
||||
export let object: WithLookup<Lead>
|
||||
export let config: (string | BuildModelKey)[]
|
||||
|
||||
const client = getClient()
|
||||
const assigneeAttribute = client.getHierarchy().getAttribute(lead.class.Lead, 'assignee')
|
||||
|
||||
@ -63,31 +65,23 @@
|
||||
</div>
|
||||
<div class="flex-col">
|
||||
<div class="flex-between">
|
||||
{#if object.$lookup?.attachedTo}
|
||||
{#if enabledConfig(config, 'attachedTo') && object.$lookup?.attachedTo}
|
||||
<ContactPresenter value={object.$lookup.attachedTo} />
|
||||
{/if}
|
||||
<div class="flex-row-center">
|
||||
{#if (object.attachments ?? 0) > 0}
|
||||
<div class="step-lr75">
|
||||
<div class="flex-row-center gap-3">
|
||||
{#if enabledConfig(config, 'attachments') && (object.attachments ?? 0) > 0}
|
||||
<AttachmentsPresenter value={object.attachments} {object} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.comments ?? 0) > 0}
|
||||
<div class="step-lr75">
|
||||
{#if enabledConfig(config, 'comments') && (object.comments ?? 0) > 0}
|
||||
<CommentsPresenter value={object.comments} {object} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-reverse flex-between mt-2">
|
||||
<AssigneePresenter
|
||||
value={object.assignee}
|
||||
issueId={object._id}
|
||||
defaultClass={contact.class.Employee}
|
||||
currentSpace={object.space}
|
||||
placeholderLabel={assigneeAttribute.label}
|
||||
/>
|
||||
<div class="flex-row-center flex-between mt-2">
|
||||
<LeadPresenter value={object} />
|
||||
{#if enabledConfig(config, 'dueDate')}
|
||||
<DueDatePresenter
|
||||
size={'small'}
|
||||
value={object.dueDate}
|
||||
shouldRender={object.dueDate !== null && object.dueDate !== undefined}
|
||||
shouldIgnoreOverdue={object.doneState !== null}
|
||||
@ -95,7 +89,16 @@
|
||||
await client.update(object, { dueDate: e })
|
||||
}}
|
||||
/>
|
||||
<LeadPresenter value={object} />
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'assignee')}
|
||||
<AssigneePresenter
|
||||
value={object.assignee}
|
||||
issueId={object._id}
|
||||
defaultClass={contact.class.Employee}
|
||||
currentSpace={object.space}
|
||||
placeholderLabel={assigneeAttribute.label}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,13 +24,14 @@
|
||||
import { AssigneePresenter, StateRefPresenter } from '@hcengineering/task-resources'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import { Component, DueDatePresenter, showPanel } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import view, { BuildModelKey } from '@hcengineering/view'
|
||||
import { ObjectPresenter, enabledConfig } from '@hcengineering/view-resources'
|
||||
import ApplicationPresenter from './ApplicationPresenter.svelte'
|
||||
|
||||
export let object: WithLookup<Applicant>
|
||||
export let dragged: boolean
|
||||
export let groupByKey: string
|
||||
export let config: (string | BuildModelKey)[]
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -48,14 +49,16 @@
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="flex-col pt-2 pb-2 pr-4 pl-4 cursor-pointer" on:click={showCandidate}>
|
||||
<div class="p-1 flex-between">
|
||||
{#if enabledConfig(config, 'space') || enabledConfig(config, 'company')}
|
||||
<div class="p-1 flex-between gap-2">
|
||||
{#if enabledConfig(config, 'space')}
|
||||
<ObjectPresenter _class={recruit.class.Vacancy} objectId={object.space} value={object.$lookup?.space} />
|
||||
{#if company}
|
||||
<div class="ml-2">
|
||||
{/if}
|
||||
{#if company && enabledConfig(config, 'company')}
|
||||
<ObjectPresenter _class={contact.class.Organization} objectId={company} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-between mb-3">
|
||||
<div class="flex-row-center">
|
||||
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
|
||||
@ -63,7 +66,7 @@
|
||||
<div class="fs-title over-underline lines-limit-2">
|
||||
{object.$lookup?.attachedTo ? getName(object.$lookup.attachedTo) : ''}
|
||||
</div>
|
||||
{#if !isTitleHidden}
|
||||
{#if !isTitleHidden && enabledConfig(config, 'title')}
|
||||
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -75,13 +78,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if channels && channels.length > 0}
|
||||
{#if channels && channels.length > 0 && enabledConfig(config, 'channels')}
|
||||
<div class="tool mr-1 flex-row-center">
|
||||
<div class="step-lr75">
|
||||
<Component
|
||||
showLoading={false}
|
||||
is={contact.component.ChannelsPresenter}
|
||||
props={{ value: channels, object: object.$lookup?.attachedTo, length: 'tiny' }}
|
||||
props={{ value: channels, object: object.$lookup?.attachedTo, length: 'tiny', size: 'inline' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -91,11 +94,13 @@
|
||||
<div class="flex-row-center">
|
||||
<div class="sm-tool-icon step-lr75">
|
||||
<div class="mr-2">
|
||||
<ApplicationPresenter value={object} />
|
||||
<ApplicationPresenter value={object} inline />
|
||||
</div>
|
||||
<Component showLoading={false} is={tracker.component.RelatedIssueSelector} props={{ object }} />
|
||||
</div>
|
||||
{#if enabledConfig(config, 'dueDate')}
|
||||
<DueDatePresenter
|
||||
size={'small'}
|
||||
value={object.dueDate}
|
||||
shouldRender={object.dueDate !== null && object.dueDate !== undefined}
|
||||
shouldIgnoreOverdue={object.doneState !== null}
|
||||
@ -103,13 +108,12 @@
|
||||
await client.update(object, { dueDate: e })
|
||||
}}
|
||||
/>
|
||||
{#if (object.attachments ?? 0) > 0}
|
||||
<div class="step-lr75">
|
||||
<AttachmentsPresenter value={object.attachments} {object} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.comments ?? 0) > 0 || (object.$lookup?.attachedTo !== undefined && (object.$lookup.attachedTo.comments ?? 0) > 0)}
|
||||
<div class="step-lr75">
|
||||
<div class="flex-row-center gap-3">
|
||||
{#if (object.attachments ?? 0) > 0 && enabledConfig(config, 'attachments')}
|
||||
<AttachmentsPresenter value={object.attachments} {object} />
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'comments')}
|
||||
{#if (object.comments ?? 0) > 0}
|
||||
<CommentsPresenter value={object.comments} {object} />
|
||||
{/if}
|
||||
@ -120,9 +124,10 @@
|
||||
withInput={false}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if enabledConfig(config, 'assignee')}
|
||||
<AssigneePresenter
|
||||
value={object.assignee}
|
||||
issueId={object._id}
|
||||
@ -130,9 +135,11 @@
|
||||
currentSpace={object.space}
|
||||
placeholderLabel={assigneeAttribute.label}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{#if groupByKey !== 'state'}
|
||||
{#if groupByKey !== 'state' && enabledConfig(config, 'state')}
|
||||
<StateRefPresenter
|
||||
size={'small'}
|
||||
value={object.state}
|
||||
onChange={(state) => {
|
||||
client.update(object, { state })
|
||||
|
@ -38,6 +38,7 @@
|
||||
} from '@hcengineering/ui'
|
||||
import {
|
||||
AttributeModel,
|
||||
BuildModelKey,
|
||||
CategoryOption,
|
||||
Viewlet,
|
||||
ViewOptionModel,
|
||||
@ -70,6 +71,7 @@
|
||||
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||
export let viewOptions: ViewOptions
|
||||
export let viewlet: Viewlet
|
||||
export let config: (string | BuildModelKey)[]
|
||||
|
||||
export let options: FindOptions<Task> | undefined
|
||||
|
||||
@ -296,7 +298,7 @@
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="card" let:object let:dragged>
|
||||
<svelte:component this={presenter} {object} {dragged} {groupByKey} />
|
||||
<svelte:component this={presenter} {object} {dragged} {groupByKey} {config} />
|
||||
</svelte:fragment>
|
||||
<!-- eslint-disable-next-line no-undef -->
|
||||
<svelte:fragment slot="doneBar" let:onDone>
|
||||
|
@ -16,6 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { Ref, StatusValue } from '@hcengineering/core'
|
||||
import { statusStore } from '@hcengineering/presentation'
|
||||
import type { ButtonSize } from '@hcengineering/ui'
|
||||
import { State } from '@hcengineering/task'
|
||||
import StateEditor from './StateEditor.svelte'
|
||||
import StatePresenter from './StatePresenter.svelte'
|
||||
@ -24,12 +25,13 @@
|
||||
export let onChange: ((value: Ref<State>) => void) | undefined = undefined
|
||||
export let colorInherit: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let size: ButtonSize = 'medium'
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
{@const state = $statusStore.get(typeof value === 'string' ? value : value.values?.[0]?._id)}
|
||||
{#if onChange !== undefined && state !== undefined}
|
||||
<StateEditor value={state._id} space={state.space} {onChange} kind="link" size="medium" />
|
||||
<StateEditor value={state._id} space={state.space} {onChange} kind="link" {size} />
|
||||
{:else}
|
||||
<StatePresenter value={state} {colorInherit} {accent} on:accent-color />
|
||||
{/if}
|
||||
|
@ -15,26 +15,47 @@
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Component } from '@hcengineering/tracker'
|
||||
import { Component as ViewComponent } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||
import { Loading, Component as ViewComponent } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import tracker from '../../plugin'
|
||||
import CreateComponent from './NewComponent.svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
export let viewlet: WithLookup<Viewlet>
|
||||
export let viewOptions: ViewOptions
|
||||
export let query: DocumentQuery<Component> = {}
|
||||
export let space: Ref<Space> | undefined
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = true
|
||||
|
||||
$: viewlet &&
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const createItemDialog = CreateComponent
|
||||
const createItemLabel = tracker.string.Component
|
||||
</script>
|
||||
|
||||
{#if viewlet?.$lookup?.descriptor?.component}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<ViewComponent
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: tracker.class.Component,
|
||||
config: viewlet.config,
|
||||
config: preference?.config ?? viewlet.config,
|
||||
options: viewlet.options,
|
||||
createItemDialog,
|
||||
createItemLabel,
|
||||
@ -45,4 +66,5 @@
|
||||
query
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||
import { Component, Loading } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import tracker from '../../plugin'
|
||||
import CreateIssue from '../CreateIssue.svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
export let viewlet: WithLookup<Viewlet>
|
||||
export let query: DocumentQuery<Issue> = {}
|
||||
@ -12,16 +13,36 @@
|
||||
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = true
|
||||
|
||||
$: viewlet &&
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const createItemDialog = CreateIssue
|
||||
const createItemLabel = tracker.string.AddIssueTooltip
|
||||
</script>
|
||||
|
||||
{#if viewlet?.$lookup?.descriptor?.component}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<Component
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: tracker.class.Issue,
|
||||
config: viewlet.config,
|
||||
config: preference?.config ?? viewlet.config,
|
||||
options: viewlet.options,
|
||||
createItemDialog,
|
||||
createItemLabel,
|
||||
@ -32,4 +53,5 @@
|
||||
query
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -50,6 +50,7 @@
|
||||
} from '@hcengineering/ui'
|
||||
import {
|
||||
AttributeModel,
|
||||
BuildModelKey,
|
||||
CategoryOption,
|
||||
Viewlet,
|
||||
ViewOptionModel,
|
||||
@ -57,6 +58,7 @@
|
||||
ViewQueryOption
|
||||
} from '@hcengineering/view'
|
||||
import {
|
||||
enabledConfig,
|
||||
focusStore,
|
||||
getCategories,
|
||||
getCategorySpaces,
|
||||
@ -90,6 +92,7 @@
|
||||
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||
export let viewOptions: ViewOptions
|
||||
export let viewlet: Viewlet
|
||||
export let config: (string | BuildModelKey)[]
|
||||
|
||||
$: currentSpace = space || tracker.project.DefaultProject
|
||||
$: groupByKey = (viewOptions.groupBy[0] ?? noCategory) as IssuesGrouping
|
||||
@ -251,6 +254,21 @@
|
||||
space: doc.space
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowFooter (
|
||||
config: (string | BuildModelKey)[],
|
||||
reports: number,
|
||||
estimations: number,
|
||||
issue: WithLookup<Issue>
|
||||
): boolean {
|
||||
if (enabledConfig(config, 'estimation') && (reports > 0 || estimations > 0)) return true
|
||||
if (enabledConfig(config, 'comments')) {
|
||||
if ((issue.comments ?? 0) > 0) return true
|
||||
if ((issue.$lookup?.attachedTo?.comments ?? 0) > 0) return true
|
||||
}
|
||||
if (enabledConfig(config, 'attachments') && (issue.attachments ?? 0) > 0) return true
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if categories.length === 0}
|
||||
@ -361,10 +379,19 @@
|
||||
{object.title}
|
||||
</div>
|
||||
<div class="card-labels">
|
||||
{#if issue && issue.subIssues > 0}
|
||||
{#if enabledConfig(config, 'subIssues') && issue && issue.subIssues > 0}
|
||||
<SubIssuesSelector value={issue} {currentProject} size={'small'} />
|
||||
{/if}
|
||||
<PriorityEditor value={issue} isEditable={true} kind={'link-bordered'} size={'small'} justify={'center'} />
|
||||
{#if enabledConfig(config, 'priority')}
|
||||
<PriorityEditor
|
||||
value={issue}
|
||||
isEditable={true}
|
||||
kind={'link-bordered'}
|
||||
size={'small'}
|
||||
justify={'center'}
|
||||
/>
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'component')}
|
||||
<ComponentEditor
|
||||
value={issue}
|
||||
isEditable={true}
|
||||
@ -374,8 +401,12 @@
|
||||
width={''}
|
||||
bind:onlyIcon={fullFilled[issueId]}
|
||||
/>
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'dueDate')}
|
||||
<DueDatePresenter value={issue} size={'small'} kind={'link-bordered'} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if enabledConfig(config, 'labels')}
|
||||
<div
|
||||
class="card-labels labels"
|
||||
use:tooltip={{
|
||||
@ -391,14 +422,17 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if reports > 0 || estimations > 0 || (object.comments ?? 0) > 0 || (object.$lookup?.attachedTo !== undefined && (object.$lookup.attachedTo.comments ?? 0) > 0)}
|
||||
{/if}
|
||||
{#if shouldShowFooter(config, reports, estimations, object)}
|
||||
<div class="card-footer flex-between">
|
||||
{#if enabledConfig(config, 'estimation')}
|
||||
<EstimationEditor kind={'list'} size={'small'} value={issue} />
|
||||
<!-- {@debug issue} -->
|
||||
{/if}
|
||||
<div class="flex-row-center gap-3 reverse">
|
||||
{#if (object.attachments ?? 0) > 0}
|
||||
{#if enabledConfig(config, 'attachments') && (object.attachments ?? 0) > 0}
|
||||
<AttachmentsPresenter value={object.attachments} {object} />
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'comments')}
|
||||
{#if (object.comments ?? 0) > 0}
|
||||
<CommentsPresenter value={object.comments} {object} />
|
||||
{/if}
|
||||
@ -409,6 +443,7 @@
|
||||
withInput={false}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -28,11 +28,13 @@
|
||||
$: text = project ? `${getIssueId(project, issue)} ${issue.title}` : issue.title
|
||||
</script>
|
||||
|
||||
{#if status}
|
||||
<div class="flex-row-center">
|
||||
{#if status}
|
||||
<div class="icon mr-2">
|
||||
<IssueStatusIcon value={status} {size} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="label" class:text-base={huge}>
|
||||
{/if}
|
||||
<span class="label" class:text-base={huge}>
|
||||
<span>{text}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Milestone } from '@hcengineering/tracker'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||
import { Component, Loading } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import tracker from '../../plugin'
|
||||
import NewMilestone from './NewMilestone.svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
export let viewlet: WithLookup<Viewlet>
|
||||
export let query: DocumentQuery<Milestone> = {}
|
||||
@ -13,16 +14,36 @@
|
||||
// Extra properties
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = true
|
||||
|
||||
$: viewlet &&
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const createItemDialog = NewMilestone
|
||||
const createItemLabel = tracker.string.CreateMilestone
|
||||
</script>
|
||||
|
||||
{#if viewlet?.$lookup?.descriptor?.component}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<Component
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: tracker.class.Milestone,
|
||||
config: viewlet.config,
|
||||
config: preference?.config ?? viewlet.config,
|
||||
options: viewlet.options,
|
||||
createItemDialog,
|
||||
createItemLabel,
|
||||
@ -34,4 +55,5 @@
|
||||
props: {}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -15,7 +15,6 @@
|
||||
<script lang="ts">
|
||||
import core, { AttachedDoc, Doc, SortingOrder, TxCollectionCUD, TxCUD } from '@hcengineering/core'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import view from '@hcengineering/view'
|
||||
import { groupBy, List } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import ChangedObjectPresenter from './ChangedObjectPresenter.svelte'
|
||||
@ -101,7 +100,6 @@
|
||||
presenter: ChangedObjectPresenter,
|
||||
props: { onNavigate }
|
||||
},
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
|
@ -13,13 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import core, { Doc, Ref, SortingOrder, TxCollectionCUD, TxCreateDoc, TxCUD, TxUpdateDoc } from '@hcengineering/core'
|
||||
import { Issue, TimeSpendReport } from '@hcengineering/tracker'
|
||||
import tracker from '../../plugin'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import view from '@hcengineering/view'
|
||||
import core, { Doc, Ref, SortingOrder, TxCollectionCUD, TxCreateDoc, TxCUD, TxUpdateDoc } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Issue, TimeSpendReport } from '@hcengineering/tracker'
|
||||
import { List } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
type TimeSpendByEmployee = { [key: Ref<Employee>]: number | undefined }
|
||||
type TimeSpendByIssue = { [key: Ref<Issue>]: TimeSpendByEmployee | undefined }
|
||||
@ -165,19 +164,22 @@
|
||||
props: { shouldUseMargin: true, showParent: false, onClick: onNavigate }
|
||||
},
|
||||
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list', isEditable: false } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.DueDatePresenter,
|
||||
props: { kind: 'list', isEditable: false }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
displayProps: {
|
||||
excludeByKey: 'milestone'
|
||||
},
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shape: 'round',
|
||||
shouldShowPlaceholder: false,
|
||||
listProps: {
|
||||
excludeByKey: 'milestone'
|
||||
},
|
||||
isEditable: false
|
||||
}
|
||||
},
|
||||
|
@ -2,15 +2,33 @@
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { IssueTemplate } from '@hcengineering/tracker'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import tracker from '../../plugin'
|
||||
import CreateIssueTemplate from './CreateIssueTemplate.svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
export let viewlet: WithLookup<Viewlet>
|
||||
export let viewOptions: ViewOptions
|
||||
export let query: DocumentQuery<IssueTemplate> = {}
|
||||
export let space: Ref<Space> | undefined
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = true
|
||||
|
||||
$: viewlet &&
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const createItemDialog = CreateIssueTemplate
|
||||
const createItemLabel = tracker.string.IssueTemplate
|
||||
</script>
|
||||
@ -20,7 +38,7 @@
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: tracker.class.IssueTemplate,
|
||||
config: viewlet.config,
|
||||
config: preference?.config ?? viewlet.config,
|
||||
options: viewlet.options,
|
||||
createItemDialog,
|
||||
createItemLabel,
|
||||
|
@ -136,6 +136,7 @@ export interface Milestone extends Doc {
|
||||
* @public
|
||||
*/
|
||||
export interface Issue extends AttachedDoc {
|
||||
attachedTo: Ref<Issue>
|
||||
title: string
|
||||
description: Markup
|
||||
status: Ref<IssueStatus>
|
||||
|
@ -96,4 +96,8 @@
|
||||
<symbol id="filter" viewBox="0 0 24 24">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3,4.2C2.4,3.9,2.7,3.8,3,3.8h18c0.3,0,0.6,0.2,0.7,0.4c0.1,0.3,0.1,0.6-0.1,0.8l-7,8.2V21c0,0.3-0.1,0.5-0.4,0.6c-0.2,0.1-0.5,0.2-0.7,0l-3.6-1.8c-0.3-0.1-0.4-0.4-0.4-0.7v-6L2.4,5C2.2,4.8,2.2,4.5,2.3,4.2z M4.6,5.2l6.1,7.2c0.1,0.1,0.2,0.3,0.2,0.5v5.8l2.1,1v-6.9c0-0.2,0.1-0.4,0.2-0.5l6.1-7.2H4.6z" />
|
||||
</symbol>
|
||||
<symbol id="configure" viewBox="0 0 16 16">
|
||||
<path d="M15 4.5C15 4.22386 14.7761 4 14.5 4H12.95C12.7 2.85 11.7 2 10.5 2C9.3 2 8.3 2.85 8.05 4H1.5C1.22386 4 1 4.22386 1 4.5C1 4.77614 1.22386 5 1.5 5H8.05C8.3 6.15 9.3 7 10.5 7C11.7 7 12.7 6.15 12.95 5H14.5C14.7761 5 15 4.77614 15 4.5ZM10.5 6C9.65 6 9 5.35 9 4.5C9 3.65 9.65 3 10.5 3C11.35 3 12 3.65 12 4.5C12 5.35 11.35 6 10.5 6Z"/>
|
||||
<path d="M1 11.5C1 11.7761 1.22386 12 1.5 12H3.05C3.3 13.15 4.3 14 5.5 14C6.7 14 7.7 13.15 7.95 12H14.5C14.7761 12 15 11.7761 15 11.5C15 11.2239 14.7761 11 14.5 11H7.95C7.7 9.85 6.7 9 5.5 9C4.3 9 3.3 9.85 3.05 11H1.5C1.22386 11 1 11.2239 1 11.5ZM5.5 10C6.35 10 7 10.65 7 11.5C7 12.35 6.35 13 5.5 13C4.65 13 4 12.35 4 11.5C4 10.65 4.65 10 5.5 10Z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@ -97,6 +97,7 @@
|
||||
"SaveAs": "Save as",
|
||||
"And": "and",
|
||||
"Between": "is between",
|
||||
"ShowColors": "Use colors"
|
||||
"ShowColors": "Use colors",
|
||||
"Show": "Show"
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@
|
||||
"SaveAs": "Сохранить как",
|
||||
"And": "и",
|
||||
"Between": "между",
|
||||
"ShowColors": "Использовать цвета"
|
||||
"ShowColors": "Использовать цвета",
|
||||
"Show": "Отображение"
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ loadMetadata(view.icon, {
|
||||
Model: `${icons}#model`,
|
||||
DevModel: `${icons}#devmodel`,
|
||||
ViewButton: `${icons}#viewButton`,
|
||||
Filter: `${icons}#filter`
|
||||
Filter: `${icons}#filter`,
|
||||
Configure: `${icons}#configure`
|
||||
})
|
||||
|
||||
addStringsLoader(viewId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
@ -0,0 +1,63 @@
|
||||
<!--
|
||||
// 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 { Button, ButtonKind, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../plugin'
|
||||
import { focusStore } from '../selection'
|
||||
import { setViewOptions } from '../viewOptions'
|
||||
import ViewOptionsEditor from './ViewOptions.svelte'
|
||||
|
||||
export let viewlet: Viewlet | undefined
|
||||
export let kind: ButtonKind = 'secondary'
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let btn: HTMLButtonElement
|
||||
|
||||
function clickHandler (event: MouseEvent) {
|
||||
showPopup(
|
||||
ViewOptionsEditor,
|
||||
{ viewlet, config: viewlet?.viewOptions, viewOptions },
|
||||
eventToHTMLElement(event),
|
||||
undefined,
|
||||
(result) => {
|
||||
if (result?.key === undefined) return
|
||||
if (viewlet) {
|
||||
viewOptions = { ...viewOptions, [result.key]: result.value }
|
||||
|
||||
// Clear selection on view settings change.
|
||||
focusStore.set({})
|
||||
|
||||
dispatch('viewOptions', viewOptions)
|
||||
setViewOptions(viewlet, viewOptions)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if viewlet?.viewOptions !== undefined}
|
||||
<Button
|
||||
icon={view.icon.ViewButton}
|
||||
label={view.string.View}
|
||||
{kind}
|
||||
showTooltip={{ label: view.string.CustomizeView }}
|
||||
bind:input={btn}
|
||||
on:click={clickHandler}
|
||||
/>
|
||||
{/if}
|
@ -16,26 +16,10 @@
|
||||
import core, { AnyAttribute, ArrOf, Class, Doc, Ref, Type } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import preferencePlugin from '@hcengineering/preference'
|
||||
import presentation, {
|
||||
Card,
|
||||
createQuery,
|
||||
getAttributePresenterClass,
|
||||
getClient,
|
||||
hasResource
|
||||
} from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
getEventPositionElement,
|
||||
getPlatformColorForText,
|
||||
Loading,
|
||||
SelectPopup,
|
||||
showPopup,
|
||||
themeStore,
|
||||
ToggleButton
|
||||
} from '@hcengineering/ui'
|
||||
import { createQuery, getAttributePresenterClass, getClient, hasResource } from '@hcengineering/presentation'
|
||||
import { Loading, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../plugin'
|
||||
import { buildConfigLookup, getKeyLabel } from '../utils'
|
||||
|
||||
@ -53,7 +37,7 @@
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
attributes = getConfig(viewlet, preference)
|
||||
enabled = attributes.filter((p) => p.enabled)
|
||||
classes = groupByClasses(attributes)
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
@ -64,9 +48,7 @@
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
let attributes: AttributeConfig[] = []
|
||||
let enabled: AttributeConfig[] = []
|
||||
let loading = true
|
||||
|
||||
interface AttributeConfig {
|
||||
@ -94,6 +76,7 @@
|
||||
const clazz = hierarchy.getClass(viewlet.attachTo)
|
||||
for (const param of viewlet.config) {
|
||||
if (typeof param === 'string') {
|
||||
if (viewlet.configOptions?.hiddenKeys?.includes(param)) continue
|
||||
if (param.length === 0) {
|
||||
result.push(getObjectConfig(viewlet.attachTo, param))
|
||||
} else {
|
||||
@ -106,9 +89,10 @@
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (viewlet.configOptions?.hiddenKeys?.includes(param.key)) continue
|
||||
result.push({
|
||||
value: param,
|
||||
label: param.label as IntlString,
|
||||
label: param.label ?? getKeyLabel(client, viewlet.attachTo, param.key, lookup),
|
||||
enabled: true,
|
||||
_class: viewlet.attachTo,
|
||||
icon: clazz.icon
|
||||
@ -130,11 +114,14 @@
|
||||
|
||||
function processAttribute (attribute: AnyAttribute, result: AttributeConfig[], useMixinProxy = false): void {
|
||||
if (attribute.hidden === true || attribute.label === undefined) return
|
||||
if (viewlet.hiddenKeys?.includes(attribute.name)) return
|
||||
if (viewlet.configOptions?.hiddenKeys?.includes(attribute.name)) return
|
||||
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) return
|
||||
const value = getValue(attribute.name, attribute.type)
|
||||
if (result.findIndex((p) => p.value === attribute.name) !== -1) return
|
||||
if (result.findIndex((p) => p.value === value) !== -1) return
|
||||
for (const res of result) {
|
||||
const key = typeof res.value === 'string' ? res.value : res.value.key
|
||||
if (key === attribute.name) return
|
||||
if (key === value) return
|
||||
}
|
||||
const { attrClass, category } = getAttributePresenterClass(hierarchy, attribute)
|
||||
const mixin =
|
||||
category === 'object'
|
||||
@ -145,7 +132,7 @@
|
||||
const presenter = hierarchy.classHierarchyMixin(attrClass, mixin, (m) => hasResource(m.presenter))?.presenter
|
||||
if (presenter === undefined) return
|
||||
const clazz = hierarchy.getClass(attribute.attributeOf)
|
||||
|
||||
const extraProps = viewlet.configOptions?.extraProps
|
||||
if (useMixinProxy) {
|
||||
const newValue = {
|
||||
value: attribute.attributeOf + '.' + attribute.name,
|
||||
@ -159,7 +146,7 @@
|
||||
}
|
||||
} else {
|
||||
const newValue = {
|
||||
value,
|
||||
value: extraProps ? { ...extraProps, key: value } : value,
|
||||
label: attribute.label,
|
||||
enabled: false,
|
||||
_class: attribute.attributeOf,
|
||||
@ -184,6 +171,7 @@
|
||||
function getConfig (viewlet: Viewlet, preference: ViewletPreference | undefined): AttributeConfig[] {
|
||||
const result = getBaseConfig(viewlet)
|
||||
|
||||
if (viewlet.configOptions?.strict !== true) {
|
||||
const allAttributes = hierarchy.getAllAttributes(viewlet.attachTo)
|
||||
for (const [, attribute] of allAttributes) {
|
||||
processAttribute(attribute, result)
|
||||
@ -207,12 +195,16 @@
|
||||
processAttribute(attr, result, true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return preference === undefined ? result : setStatus(result, preference)
|
||||
}
|
||||
|
||||
async function save (): Promise<void> {
|
||||
const config = enabled.map((p) => p.value)
|
||||
const config = Array.from(classes.values())
|
||||
.flat()
|
||||
.filter((p) => p.enabled)
|
||||
.map((p) => p.value)
|
||||
if (preference !== undefined) {
|
||||
await client.update(preference, {
|
||||
config
|
||||
@ -225,69 +217,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
function restoreDefault (): void {
|
||||
attributes = getConfig(viewlet, undefined)
|
||||
enabled = attributes.filter((p) => p.enabled)
|
||||
}
|
||||
// function restoreDefault (): void {
|
||||
// attributes = getConfig(viewlet, undefined)
|
||||
// classes = groupByClasses(attributes)
|
||||
// }
|
||||
|
||||
function setStatus (result: AttributeConfig[], preference: ViewletPreference): AttributeConfig[] {
|
||||
for (const key of result) {
|
||||
key.enabled = preference.config.findIndex((p) => deepEqual(p, key.value)) !== -1
|
||||
}
|
||||
result.sort((a, b) => {
|
||||
if (a.enabled !== b.enabled) {
|
||||
return a.enabled ? -1 : 1
|
||||
}
|
||||
return (
|
||||
preference.config.findIndex((p) => deepEqual(p, a.value)) -
|
||||
preference.config.findIndex((p) => deepEqual(p, b.value))
|
||||
)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const elements: HTMLElement[] = []
|
||||
let selected: number | undefined
|
||||
|
||||
function dragswap (ev: MouseEvent, i: number, s: number): boolean {
|
||||
if (i < s) {
|
||||
if (elements[i].offsetTop !== elements[s].offsetTop) {
|
||||
return ev.offsetY < elements[i].offsetHeight / 2
|
||||
} else {
|
||||
return ev.offsetX < elements[i].offsetWidth / 2
|
||||
}
|
||||
} else if (i > s) {
|
||||
if (elements[i].offsetTop !== elements[s].offsetTop) {
|
||||
return ev.offsetY > elements[i].offsetHeight / 2
|
||||
} else {
|
||||
return ev.offsetX > elements[i].offsetWidth / 2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function dragover (ev: MouseEvent, i: number) {
|
||||
const s = selected as number
|
||||
if (dragswap(ev, i, s)) {
|
||||
;[enabled[i], enabled[s]] = [enabled[s], enabled[i]]
|
||||
selected = i
|
||||
}
|
||||
}
|
||||
|
||||
function getColor (attribute: AttributeConfig, black: boolean): string {
|
||||
const color = getPlatformColorForText(attribute._class, black)
|
||||
return `${color + (attribute.enabled ? 'cc' : '33')};`
|
||||
}
|
||||
|
||||
function getStyle (attribute: AttributeConfig, black: boolean): string {
|
||||
const color = getPlatformColorForText(attribute._class, black)
|
||||
return `border: 1px solid ${color + (attribute.enabled ? 'ff' : 'cc')};`
|
||||
}
|
||||
|
||||
function groupByClasses (attributes: AttributeConfig[]): Map<Ref<Class<Doc>>, AttributeConfig[]> {
|
||||
const res = new Map()
|
||||
for (const attribute of attributes) {
|
||||
if (attribute.enabled) continue
|
||||
const arr = res.get(attribute._class) ?? []
|
||||
arr.push(attribute)
|
||||
res.set(attribute._class, arr)
|
||||
@ -295,88 +239,42 @@
|
||||
return res
|
||||
}
|
||||
|
||||
$: classes = groupByClasses(attributes)
|
||||
|
||||
function getClassLabel (_class: Ref<Class<Doc>>): IntlString {
|
||||
return hierarchy.getClass(_class).label
|
||||
}
|
||||
let classes: Map<Ref<Class<Doc>>, AttributeConfig[]> = new Map()
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={view.string.CustomizeView}
|
||||
okAction={save}
|
||||
okLabel={presentation.string.Save}
|
||||
canSave={true}
|
||||
gap={'gapV-4'}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
on:changeContent
|
||||
>
|
||||
<div class="selectPopup p-2">
|
||||
<div class="scroll">
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="flex-row-stretch flex-wrap">
|
||||
{#each enabled as attribute, i}
|
||||
<div
|
||||
class="m-0-5 border-radius-1 overflow-label"
|
||||
style={getStyle(attribute, $themeStore.dark)}
|
||||
bind:this={elements[i]}
|
||||
draggable={true}
|
||||
on:dragover|preventDefault={(ev) => {
|
||||
dragover(ev, i)
|
||||
}}
|
||||
on:drop|preventDefault
|
||||
on:dragstart={() => {
|
||||
selected = i
|
||||
}}
|
||||
on:dragend={() => {
|
||||
selected = undefined
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
backgroundColor={getColor(attribute, $themeStore.dark)}
|
||||
icon={attribute.icon}
|
||||
label={attribute.label}
|
||||
bind:value={attribute.enabled}
|
||||
on:change={() => {
|
||||
enabled.splice(i, 1)
|
||||
enabled = enabled
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-wrap">
|
||||
{#each Array.from(classes.keys()) as _class}
|
||||
<div class="m-0-5">
|
||||
<Button
|
||||
label={getClassLabel(_class)}
|
||||
on:click={(e) => {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: classes.get(_class)?.map((it) => ({ id: it.value, label: it.label }))
|
||||
},
|
||||
getEventPositionElement(e),
|
||||
(val) => {
|
||||
if (val !== undefined) {
|
||||
const value = classes.get(_class)?.find((it) => it.value === val)
|
||||
if (value) {
|
||||
value.enabled = true
|
||||
enabled.push(value)
|
||||
enabled = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#each Array.from(classes.keys()) as _class, i}
|
||||
{@const items = classes.get(_class) ?? []}
|
||||
{#if i !== 0}
|
||||
<div class="menu-separator" />
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<Button label={view.string.RestoreDefaults} on:click={restoreDefault} />
|
||||
</svelte:fragment>
|
||||
</Card>
|
||||
{#each items as item}
|
||||
<div class="item">
|
||||
<ToggleWithLabel
|
||||
on={item.enabled}
|
||||
label={item.label}
|
||||
on:change={(e) => {
|
||||
item.enabled = e.detail
|
||||
save()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.item {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
&:hover {
|
||||
background-color: var(--theme-button-hovered);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -13,58 +13,33 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, ButtonKind, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { Button, ButtonKind, showPopup } from '@hcengineering/ui'
|
||||
import { ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../plugin'
|
||||
import { focusStore } from '../selection'
|
||||
import { setViewOptions } from '../viewOptions'
|
||||
import ViewOptionsEditor from './ViewOptions.svelte'
|
||||
import ViewOptionsButton from './ViewOptionsButton.svelte'
|
||||
import ViewletSetting from './ViewletSetting.svelte'
|
||||
import IconArrowDown from './icons/ArrowDown.svelte'
|
||||
|
||||
export let viewlet: Viewlet | undefined
|
||||
export let kind: ButtonKind = 'secondary'
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let btn: HTMLButtonElement
|
||||
|
||||
function clickHandler (event: MouseEvent) {
|
||||
if (viewlet?.viewOptions !== undefined) {
|
||||
showPopup(
|
||||
ViewOptionsEditor,
|
||||
{ viewlet, config: viewlet.viewOptions, viewOptions },
|
||||
eventToHTMLElement(event),
|
||||
undefined,
|
||||
(result) => {
|
||||
if (result?.key === undefined) return
|
||||
if (viewlet) {
|
||||
viewOptions = { ...viewOptions, [result.key]: result.value }
|
||||
|
||||
// Clear selection on view settings change.
|
||||
focusStore.set({})
|
||||
|
||||
dispatch('viewOptions', viewOptions)
|
||||
setViewOptions(viewlet, viewOptions)
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showPopup(ViewletSetting, { viewlet }, btn)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if viewlet}
|
||||
<div class="flex-row-center">
|
||||
<div class="mr-3"><ViewOptionsButton {viewlet} {kind} {viewOptions} /></div>
|
||||
<Button
|
||||
icon={view.icon.ViewButton}
|
||||
label={view.string.View}
|
||||
iconRight={IconArrowDown}
|
||||
icon={view.icon.Configure}
|
||||
label={view.string.Show}
|
||||
{kind}
|
||||
showTooltip={{ label: view.string.CustomizeView }}
|
||||
bind:input={btn}
|
||||
on:click={clickHandler}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -115,9 +115,9 @@
|
||||
$: buildModel({ client, _class, keys: config, lookup }).then((res) => {
|
||||
itemModels = res
|
||||
res.forEach((m) => {
|
||||
if (m.props?.listProps?.key !== undefined) {
|
||||
const key = `list_item_${m.props.listProps.key}`
|
||||
if (m.props.listProps.fixed) {
|
||||
if (m.displayProps?.key !== undefined) {
|
||||
const key = `list_item_${m.displayProps.key}`
|
||||
if (m.displayProps.fixed) {
|
||||
$fixedWidthStore[key] = 0
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { FixedColumn } from '../..'
|
||||
import view from '../../plugin'
|
||||
import GrowPresenter from './GrowPresenter.svelte'
|
||||
import DividerPresenter from './DividerPresenter.svelte'
|
||||
|
||||
export let docObject: Doc
|
||||
export let model: AttributeModel[]
|
||||
@ -64,11 +66,7 @@
|
||||
}
|
||||
|
||||
function joinProps (attribute: AttributeModel, object: Doc, props: Record<string, any>) {
|
||||
let clearAttributeProps = attribute.props
|
||||
if (attribute.props?.listProps !== undefined) {
|
||||
const { listProps, ...other } = attribute.props as any
|
||||
clearAttributeProps = other
|
||||
}
|
||||
const clearAttributeProps = attribute.props
|
||||
if (attribute.attribute?.type._class === core.class.EnumOf) {
|
||||
return { ...clearAttributeProps, type: attribute.attribute.type, ...props }
|
||||
}
|
||||
@ -79,12 +77,14 @@
|
||||
$: if (model) {
|
||||
noCompressed = -1
|
||||
model.forEach((m, i) => {
|
||||
if (m.props?.listProps?.compression) noCompressed = i
|
||||
if (m.displayProps?.compression) noCompressed = i
|
||||
})
|
||||
}
|
||||
onMount(() => {
|
||||
dispatch('on-mount')
|
||||
})
|
||||
|
||||
$: growBefore = Math.ceil(model.filter((p) => p.displayProps?.optional !== true).length / 2)
|
||||
</script>
|
||||
|
||||
<div
|
||||
@ -131,36 +131,22 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#each model.filter((m) => !m.props?.listProps?.optional) as attributeModel, i}
|
||||
{@const listProps = attributeModel.props?.listProps}
|
||||
{#if attributeModel.props?.type === 'grow'}
|
||||
<svelte:component this={attributeModel.presenter} />
|
||||
|
||||
{#if !compactMode}
|
||||
<div class="optional-bar">
|
||||
{#each model.filter((m) => m.props?.listProps?.optional) as attrModel, j}
|
||||
{@const lp = attrModel.props?.listProps}
|
||||
{@const v = getObjectValue(attrModel.key, docObject)}
|
||||
{#if lp?.excludeByKey !== groupByKey && v !== undefined}
|
||||
<svelte:component
|
||||
this={attrModel.presenter}
|
||||
value={getObjectValue(attrModel.key, docObject) ?? ''}
|
||||
onChange={getOnChange(docObject, attrModel)}
|
||||
kind={'list'}
|
||||
compression
|
||||
{...joinProps(attrModel, docObject, props)}
|
||||
/>
|
||||
{#each model.filter((p) => !p.displayProps?.optional) as attributeModel, i}
|
||||
{@const displayProps = attributeModel.displayProps}
|
||||
{#if !groupByKey || displayProps?.excludeByKey !== groupByKey}
|
||||
{#if !(compactMode && displayProps?.compression)}
|
||||
{#if i === growBefore}
|
||||
<GrowPresenter />
|
||||
{#each model.filter((p) => p.displayProps?.optional) as attributeModel, i}
|
||||
{@const dp = attributeModel.displayProps}
|
||||
{#if dp?.dividerBefore === true}
|
||||
<DividerPresenter />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if (!groupByKey || listProps?.excludeByKey !== groupByKey) && !listProps?.optional}
|
||||
{#if !(compactMode && listProps?.compression)}
|
||||
{#if listProps?.fixed}
|
||||
<FixedColumn key={`list_item_${attributeModel.props?.listProps.key}`} justify={listProps.fixed}>
|
||||
{#if dp?.fixed}
|
||||
<FixedColumn key={`list_item_${dp.key}`} justify={dp.fixed}>
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
kind={'list'}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
@ -169,10 +155,35 @@
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
kind={'list'}
|
||||
compression={listProps?.compression && i !== noCompressed}
|
||||
compression={dp?.compression && i !== noCompressed}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if i !== 0 && displayProps?.dividerBefore === true}
|
||||
<DividerPresenter />
|
||||
{/if}
|
||||
{#if displayProps?.fixed}
|
||||
<FixedColumn key={`list_item_${displayProps.key}`} justify={displayProps.fixed}>
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
kind={'list'}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
/>
|
||||
</FixedColumn>
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
kind={'list'}
|
||||
compression={displayProps?.compression && i !== noCompressed}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
/>
|
||||
{/if}
|
||||
@ -193,13 +204,16 @@
|
||||
<IconCircles />
|
||||
</div>
|
||||
<div class="scroll-box gap-2">
|
||||
{#each model.filter((m) => m.props?.listProps?.optional || m.props?.listProps?.compression) as attributeModel}
|
||||
{@const listProps = attributeModel.props?.listProps}
|
||||
{#each model.filter((m) => m.displayProps?.optional || m.displayProps?.compression) as attributeModel, j}
|
||||
{@const displayProps = attributeModel.displayProps}
|
||||
{@const value = getObjectValue(attributeModel.key, docObject)}
|
||||
{#if listProps?.excludeByKey !== groupByKey && value !== undefined}
|
||||
{#if displayProps?.excludeByKey !== groupByKey && value !== undefined}
|
||||
{#if j !== 0 && displayProps?.dividerBefore === true}
|
||||
<DividerPresenter />
|
||||
{/if}
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
kind={'list'}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
|
@ -88,7 +88,8 @@ export default mergeIds(viewId, view, {
|
||||
SaveAs: '' as IntlString,
|
||||
And: '' as IntlString,
|
||||
Between: '' as IntlString,
|
||||
ShowColors: '' as IntlString
|
||||
ShowColors: '' as IntlString,
|
||||
Show: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
StatusSort: '' as SortFunc
|
||||
|
@ -107,6 +107,7 @@ export async function getObjectPresenter (
|
||||
_class,
|
||||
label: preserveKey.label ?? clazz.label,
|
||||
presenter,
|
||||
displayProps: preserveKey.displayProps,
|
||||
props: preserveKey.props,
|
||||
sortingKey,
|
||||
collectionAttr: isCollectionAttr,
|
||||
@ -167,6 +168,7 @@ async function getAttributePresenter (
|
||||
label: preserveKey.label ?? attribute.shortLabel ?? attribute.label,
|
||||
presenter,
|
||||
props: preserveKey.props,
|
||||
displayProps: preserveKey.displayProps,
|
||||
icon: presenterMixin.icon,
|
||||
attribute,
|
||||
collectionAttr: isCollectionAttr,
|
||||
@ -191,6 +193,7 @@ export async function getPresenter<T extends Doc> (
|
||||
label: label as IntlString,
|
||||
presenter: typeof presenter === 'string' ? await getResource(presenter) : presenter,
|
||||
props: preserveKey.props,
|
||||
displayProps: preserveKey.displayProps,
|
||||
collectionAttr: isCollectionAttr,
|
||||
isLookup: false
|
||||
}
|
||||
@ -796,6 +799,9 @@ export function getKeyLabel<T extends Doc> (
|
||||
const lookupProperty = getLookupProperty(key)
|
||||
const lookupKey = { key: lookupProperty[0] }
|
||||
return getLookupLabel(client, lookupClass[1], lookupClass[0], lookupKey, lookupProperty[1])
|
||||
} else if (key.length === 0) {
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
return clazz.label
|
||||
} else {
|
||||
const attribute = client.getHierarchy().getAttribute(_class, key)
|
||||
return attribute.label
|
||||
@ -953,3 +959,14 @@ export async function statusSort (
|
||||
export function isAttachedDoc (doc: Doc | AttachedDoc): doc is AttachedDoc {
|
||||
return 'attachedTo' in doc
|
||||
}
|
||||
|
||||
export function enabledConfig (config: Array<string | BuildModelKey>, key: string): boolean {
|
||||
for (const value of config) {
|
||||
if (typeof value === 'string') {
|
||||
if (value === key) return true
|
||||
} else {
|
||||
if (value.key === key) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -317,11 +317,20 @@ export interface Viewlet extends Doc {
|
||||
descriptor: Ref<ViewletDescriptor>
|
||||
options?: FindOptions<Doc>
|
||||
config: (BuildModelKey | string)[]
|
||||
hiddenKeys?: string[]
|
||||
configOptions?: ViewletConfigOptions
|
||||
viewOptions?: ViewOptionsModel
|
||||
variant?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ViewletConfigOptions {
|
||||
hiddenKeys?: string[]
|
||||
strict?: boolean
|
||||
extraProps?: Omit<BuildModelKey, 'key'>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -456,6 +465,18 @@ export interface PreviewPresenter extends Class<Doc> {
|
||||
*/
|
||||
export const viewId = 'view' as Plugin
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface DisplayProps {
|
||||
key?: string
|
||||
excludeByKey?: string
|
||||
fixed?: 'left' | 'right' // using for align items in row
|
||||
optional?: boolean
|
||||
compression?: boolean
|
||||
dividerBefore?: boolean // should show divider before
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -464,6 +485,8 @@ export interface BuildModelKey {
|
||||
presenter?: AnyComponent | AnySvelteComponent
|
||||
// A set of extra props passed to presenter.
|
||||
props?: Record<string, any>
|
||||
// A set of extra props which using for display.
|
||||
displayProps?: DisplayProps
|
||||
|
||||
label?: IntlString
|
||||
sortingKey?: string | string[]
|
||||
@ -482,6 +505,7 @@ export interface AttributeModel {
|
||||
presenter: AnySvelteComponent
|
||||
// Extra properties for component
|
||||
props?: Record<string, any>
|
||||
displayProps?: DisplayProps
|
||||
sortingKey: string | string[]
|
||||
// Extra icon if applicable
|
||||
icon?: Asset
|
||||
@ -752,7 +776,8 @@ const view = plugin(viewId, {
|
||||
Model: '' as Asset,
|
||||
DevModel: '' as Asset,
|
||||
ViewButton: '' as Asset,
|
||||
Filter: '' as Asset
|
||||
Filter: '' as Asset,
|
||||
Configure: '' as Asset
|
||||
},
|
||||
category: {
|
||||
General: '' as Ref<ActionCategory>,
|
||||
|
Loading…
Reference in New Issue
Block a user