// // 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. // import { DocumentQuery, Ref, SortingOrder } from '@anticrm/core' import type { Asset, IntlString } from '@anticrm/platform' import { IssuePriority, Team, IssuesGrouping, IssuesOrdering, Issue, IssuesDateModificationPeriod, ProjectStatus } from '@anticrm/tracker' import { AnyComponent, AnySvelteComponent, getMillisecondsInMonth, MILLISECONDS_IN_WEEK } from '@anticrm/ui' import tracker from './plugin' export interface NavigationItem { id: string label: IntlString icon: Asset component: AnyComponent componentProps?: Record top: boolean } export interface Selection { currentTeam?: Ref currentSpecial?: string } export const issuePriorities: Record = { [IssuePriority.NoPriority]: { icon: tracker.icon.PriorityNoPriority, label: tracker.string.NoPriority }, [IssuePriority.Urgent]: { icon: tracker.icon.PriorityUrgent, label: tracker.string.Urgent }, [IssuePriority.High]: { icon: tracker.icon.PriorityHigh, label: tracker.string.High }, [IssuePriority.Medium]: { icon: tracker.icon.PriorityMedium, label: tracker.string.Medium }, [IssuePriority.Low]: { icon: tracker.icon.PriorityLow, label: tracker.string.Low } } export const issuesGroupByOptions: Record = { [IssuesGrouping.Status]: tracker.string.Status, [IssuesGrouping.Assignee]: tracker.string.Assignee, [IssuesGrouping.Priority]: tracker.string.Priority, [IssuesGrouping.NoGrouping]: tracker.string.NoGrouping } export const issuesOrderByOptions: Record = { [IssuesOrdering.Status]: tracker.string.Status, [IssuesOrdering.Priority]: tracker.string.Priority, [IssuesOrdering.LastUpdated]: tracker.string.LastUpdated, [IssuesOrdering.DueDate]: tracker.string.DueDate } export const issuesDateModificationPeriodOptions: Record = { [IssuesDateModificationPeriod.All]: tracker.string.All, [IssuesDateModificationPeriod.PastWeek]: tracker.string.PastWeek, [IssuesDateModificationPeriod.PastMonth]: tracker.string.PastMonth } export type IssuesGroupByKeys = keyof Pick export type IssuesOrderByKeys = keyof Pick export const issuesGroupKeyMap: Record = { [IssuesGrouping.Status]: 'status', [IssuesGrouping.Priority]: 'priority', [IssuesGrouping.Assignee]: 'assignee', [IssuesGrouping.NoGrouping]: undefined } export const issuesOrderKeyMap: Record = { [IssuesOrdering.Status]: 'status', [IssuesOrdering.Priority]: 'priority', [IssuesOrdering.LastUpdated]: 'modifiedOn', [IssuesOrdering.DueDate]: 'dueDate' } export const issuesSortOrderMap: Record = { status: SortingOrder.Ascending, priority: SortingOrder.Ascending, modifiedOn: SortingOrder.Descending, dueDate: SortingOrder.Descending } export const issuesGroupEditorMap: Record<'status' | 'priority', AnyComponent | undefined> = { status: tracker.component.StatusEditor, priority: tracker.component.PriorityEditor } export const getIssuesModificationDatePeriodTime = (period: IssuesDateModificationPeriod | null): number => { const today = new Date(Date.now()) switch (period) { case IssuesDateModificationPeriod.PastWeek: { return today.getTime() - MILLISECONDS_IN_WEEK } case IssuesDateModificationPeriod.PastMonth: { return today.getTime() - getMillisecondsInMonth(today) } default: { return 0 } } } export const defaultProjectStatuses = [ ProjectStatus.Backlog, ProjectStatus.Planned, ProjectStatus.InProgress, ProjectStatus.Paused, ProjectStatus.Completed, ProjectStatus.Canceled ] export const projectStatusAssets: Record = { [ProjectStatus.Backlog]: { icon: tracker.icon.ProjectStatusBacklog, label: tracker.string.Backlog }, [ProjectStatus.Planned]: { icon: tracker.icon.ProjectStatusPlanned, label: tracker.string.Planned }, [ProjectStatus.InProgress]: { icon: tracker.icon.ProjectStatusInProgress, label: tracker.string.InProgress }, [ProjectStatus.Paused]: { icon: tracker.icon.ProjectStatusPaused, label: tracker.string.Paused }, [ProjectStatus.Completed]: { icon: tracker.icon.ProjectStatusCompleted, label: tracker.string.Completed }, [ProjectStatus.Canceled]: { icon: tracker.icon.ProjectStatusCanceled, label: tracker.string.Canceled } } export const groupBy = (data: any, key: any): { [key: string]: any[] } => { return data.reduce((storage: { [key: string]: any[] }, item: any) => { const group = item[key] storage[group] = storage[group] ?? [] storage[group].push(item) return storage }, {}) } export interface FilterAction { icon?: Asset | AnySvelteComponent label?: IntlString onSelect: (event: MouseEvent | KeyboardEvent) => void } export interface FilterSectionElement extends Omit { title?: string count?: number isSelected?: boolean } export interface IssueFilter { mode: '$in' | '$nin' query: DocumentQuery } export const getGroupedIssues = ( key: IssuesGroupByKeys | undefined, elements: Issue[], orderedCategories?: any[] ): { [p: string]: Issue[] } => { if (key === undefined) { return { [undefined as any]: elements } } const unorderedIssues = groupBy(elements, key) if (orderedCategories === undefined || orderedCategories.length === 0) { return unorderedIssues } return Object.keys(unorderedIssues) .sort((o1, o2) => { const key1 = o1 === 'null' ? null : o1 const key2 = o2 === 'null' ? null : o2 const i1 = orderedCategories.findIndex((x) => x === key1) const i2 = orderedCategories.findIndex((x) => x === key2) return i1 - i2 }) .reduce((obj: { [p: string]: any[] }, objKey) => { obj[objKey] = unorderedIssues[objKey] return obj }, {}) } export const getIssueFilterAssetsByType = (type: string): { icon: Asset, label: IntlString } | undefined => { switch (type) { case 'status': { return { icon: tracker.icon.CategoryBacklog, label: tracker.string.Status } } case 'priority': { return { icon: tracker.icon.PriorityHigh, label: tracker.string.Priority } } default: { return undefined } } } export const defaultPriorities = [ IssuePriority.NoPriority, IssuePriority.Urgent, IssuePriority.High, IssuePriority.Medium, IssuePriority.Low ] export const getArraysIntersection = (a: any[], b: any[]): any[] => { const setB = new Set(b) const intersection = new Set(a.filter((x) => setB.has(x))) return Array.from(intersection) } export const getArraysUnion = (a: any[], b: any[]): any[] => { const setB = new Set(b) const union = new Set(a) for (const element of setB) { union.add(element) } return Array.from(union) } const WARNING_DAYS = 7 export const getDueDateIconModifier = ( isOverdue: boolean, daysDifference: number | null ): 'overdue' | 'critical' | 'warning' | undefined => { if (isOverdue) { return 'overdue' } if (daysDifference === 0) { return 'critical' } if (daysDifference !== null && daysDifference <= WARNING_DAYS) { return 'warning' } } export type ProjectsViewMode = 'all' | 'backlog' | 'active' | 'closed' export const getIncludedProjectStatuses = (mode: ProjectsViewMode): ProjectStatus[] => { switch (mode) { case 'all': { return defaultProjectStatuses } case 'active': { return [ProjectStatus.Planned, ProjectStatus.InProgress, ProjectStatus.Paused] } case 'backlog': { return [ProjectStatus.Backlog] } case 'closed': { return [ProjectStatus.Completed, ProjectStatus.Canceled] } default: { return [] } } } export const projectsTitleMap: Record = Object.freeze({ all: tracker.string.AllProjects, backlog: tracker.string.BacklogProjects, active: tracker.string.ActiveProjects, closed: tracker.string.ClosedProjects })