diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 520ad6b26c..07dc0019b5 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -34,10 +34,10 @@ import { import attachment from '@anticrm/model-attachment' import chunter from '@anticrm/model-chunter' import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace, TType } from '@anticrm/model-core' -import { createAction } from '@anticrm/model-view' +import view, { createAction } from '@anticrm/model-view' +import { KeyBinding } from '@anticrm/view' import workbench, { createNavigateAction } from '@anticrm/model-workbench' import { Asset, IntlString } from '@anticrm/platform' -import view, { KeyBinding } from '@anticrm/view' import setting from '@anticrm/setting' import { Document, @@ -249,6 +249,40 @@ export class TProject extends TDoc implements Project { export function createModel (builder: Builder): void { builder.createModel(TTeam, TProject, TIssue, TIssueStatus, TIssueStatusCategory, TTypeIssuePriority) + builder.createDoc(view.class.Viewlet, core.space.Model, { + attachTo: tracker.class.Issue, + descriptor: tracker.viewlet.List, + config: [ + { key: '', presenter: tracker.component.PriorityEditor }, + { key: '', presenter: tracker.component.IssuePresenter }, + { key: '', presenter: tracker.component.StatusEditor }, + { key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } }, + { key: '', presenter: tracker.component.DueDatePresenter }, + { + key: '', + presenter: tracker.component.ProjectEditor, + props: { kind: 'secondary', size: 'small', shape: 'round', shouldShowPlaceholder: false } + }, + { key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter }, + { + key: '$lookup.assignee', + presenter: tracker.component.AssigneePresenter, + props: { defaultClass: contact.class.Employee, shouldShowLabel: false } + } + ] + }) + + builder.createDoc( + view.class.ViewletDescriptor, + core.space.Model, + { + label: view.string.Table, + icon: view.icon.Table, + component: tracker.component.ListView + }, + tracker.viewlet.List + ) + builder.createDoc( tracker.class.IssueStatusCategory, core.space.Model, @@ -387,7 +421,10 @@ export function createModel (builder: Builder): void { id: issuesId, label: tracker.string.Issues, icon: tracker.icon.Issues, - component: tracker.component.Issues + component: tracker.component.IssuesView, + componentProps: { + title: tracker.string.Issues + } }, { id: activeId, @@ -504,4 +541,8 @@ export function createModel (builder: Builder): void { }, tracker.action.SetParent ) + + builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ClassFilters, { + filters: ['status', 'priority', 'project'] + }) } diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 6ad6fc9057..eb4447a882 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -19,6 +19,7 @@ import { IntlString, mergeIds } from '@anticrm/platform' import { Team, trackerId } from '@anticrm/tracker' import tracker from '@anticrm/tracker-resources/src/plugin' import type { AnyComponent } from '@anticrm/ui' +import { ViewletDescriptor } from '@anticrm/view' import { Application } from '@anticrm/workbench' export default mergeIds(trackerId, tracker, { @@ -41,5 +42,8 @@ export default mergeIds(trackerId, tracker, { }, app: { Tracker: '' as Ref + }, + viewlet: { + List: '' as Ref } }) diff --git a/plugins/tracker-resources/src/components/issues/IssuesContent.svelte b/plugins/tracker-resources/src/components/issues/IssuesContent.svelte new file mode 100644 index 0000000000..9b37be59b4 --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/IssuesContent.svelte @@ -0,0 +1,31 @@ + + +{#if viewlet?.$lookup?.descriptor?.component} + +{/if} diff --git a/plugins/tracker-resources/src/components/issues/IssuesHeader.svelte b/plugins/tracker-resources/src/components/issues/IssuesHeader.svelte new file mode 100644 index 0000000000..c3ff576b33 --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/IssuesHeader.svelte @@ -0,0 +1,59 @@ + + +
+
+
+ {label} +
+
+ {#if viewlets.length > 1} +
+ {#each viewlets as v} + + + + {/each} +
+ {/if} +
diff --git a/plugins/tracker-resources/src/components/issues/IssuesView.svelte b/plugins/tracker-resources/src/components/issues/IssuesView.svelte new file mode 100644 index 0000000000..d38b814492 --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/IssuesView.svelte @@ -0,0 +1,68 @@ + + +{#if currentSpace} + + (resultQuery = e.detail)} /> +
+
+ +
+ {#if $$slots.aside !== undefined} +
+ +
+ {/if} +
+{/if} diff --git a/plugins/tracker-resources/src/components/issues/ListView.svelte b/plugins/tracker-resources/src/components/issues/ListView.svelte new file mode 100644 index 0000000000..5fa179474e --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/ListView.svelte @@ -0,0 +1,85 @@ + + + + + diff --git a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte index 46c88979c0..5e9815088f 100644 --- a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte @@ -14,7 +14,7 @@ --> -{#if value} +{#if value && statuses} {#if isEditable} {#if object} - +
@@ -44,5 +44,5 @@
- + {/if} diff --git a/plugins/tracker-resources/src/index.ts b/plugins/tracker-resources/src/index.ts index 5244fe6aa3..eb215dc6d5 100644 --- a/plugins/tracker-resources/src/index.ts +++ b/plugins/tracker-resources/src/index.ts @@ -49,6 +49,8 @@ import EditProject from './components/projects/EditProject.svelte' import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte' import EditIssue from './components/issues/edit/EditIssue.svelte' import NewIssueHeader from './components/NewIssueHeader.svelte' +import ListView from './components/issues/ListView.svelte' +import IssuesView from './components/issues/IssuesView.svelte' export default async (): Promise => ({ component: { @@ -83,7 +85,9 @@ export default async (): Promise => ({ ProjectStatusPresenter, SetDueDateActionPopup, SetParentIssueActionPopup, - EditProject + EditProject, + IssuesView, + ListView }, function: { ProjectVisible: () => false diff --git a/plugins/tracker-resources/src/plugin.ts b/plugins/tracker-resources/src/plugin.ts index c7879db374..0938cd522b 100644 --- a/plugins/tracker-resources/src/plugin.ts +++ b/plugins/tracker-resources/src/plugin.ts @@ -189,7 +189,9 @@ export default mergeIds(trackerId, tracker, { ProjectStatusPresenter: '' as AnyComponent, SetDueDateActionPopup: '' as AnyComponent, SetParentIssueActionPopup: '' as AnyComponent, - EditProject: '' as AnyComponent + EditProject: '' as AnyComponent, + IssuesView: '' as AnyComponent, + ListView: '' as AnyComponent }, function: { ProjectVisible: '' as '' as Resource<(spaces: Space[]) => boolean> diff --git a/plugins/tracker-resources/src/utils.ts b/plugins/tracker-resources/src/utils.ts index 0e749c6523..7fead2fed8 100644 --- a/plugins/tracker-resources/src/utils.ts +++ b/plugins/tracker-resources/src/utils.ts @@ -13,6 +13,7 @@ // limitations under the License. // +import { Employee, formatName } from '@anticrm/contact' import { DocumentQuery, Ref, SortingOrder } from '@anticrm/core' import type { Asset, IntlString } from '@anticrm/platform' import { @@ -22,7 +23,8 @@ import { IssuesOrdering, Issue, IssuesDateModificationPeriod, - ProjectStatus + ProjectStatus, + IssueStatus } from '@anticrm/tracker' import { AnyComponent, AnySvelteComponent, getMillisecondsInMonth, MILLISECONDS_IN_WEEK } from '@anticrm/ui' import tracker from './plugin' @@ -294,3 +296,85 @@ export const projectsTitleMap: Record = Object.fre active: tracker.string.ActiveProjects, closed: tracker.string.ClosedProjects }) + +export function getCategories ( + key: IssuesGroupByKeys | undefined, + elements: Issue[], + shouldShowAll: boolean, + statuses: IssueStatus[], + employees: Employee[] +): any[] { + if (key === undefined) { + return [undefined] // No grouping + } + + const defaultStatuses = Object.values(statuses).map((x) => x._id) + + const existingCategories = Array.from( + new Set( + elements.map((x) => { + return x[key] + }) + ) + ) + + if (shouldShowAll) { + if (key === 'status') { + return defaultStatuses + } + + if (key === 'priority') { + return defaultPriorities + } + } + + if (key === 'status') { + existingCategories.sort((s1, s2) => { + const i1 = defaultStatuses.findIndex((x) => x === s1) + const i2 = defaultStatuses.findIndex((x) => x === s2) + + return i1 - i2 + }) + } + + if (key === 'priority') { + existingCategories.sort((p1, p2) => { + const i1 = defaultPriorities.findIndex((x) => x === p1) + const i2 = defaultPriorities.findIndex((x) => x === p2) + + return i1 - i2 + }) + } + + if (key === 'assignee') { + existingCategories.sort((a1, a2) => { + const employeeId1 = a1 as Ref | null + const employeeId2 = a2 as Ref | null + + if (employeeId1 === null && employeeId2 !== null) { + return 1 + } + + if (employeeId1 !== null && employeeId2 === null) { + return -1 + } + + if (employeeId1 !== null && employeeId2 !== null) { + const name1 = formatName(employees.find((x) => x?._id === employeeId1)?.name ?? '') + const name2 = formatName(employees.find((x) => x?._id === employeeId2)?.name ?? '') + + if (name1 > name2) { + return 1 + } else if (name2 > name1) { + return -1 + } + + return 0 + } + + return 0 + }) + } + + return existingCategories +}