@acorex-platform/module-task-management-client 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. package/fesm2022/acorex-platform-module-task-management-client-acorex-platform-module-task-management-client-CIwafWli.mjs +4445 -0
  2. package/fesm2022/acorex-platform-module-task-management-client-acorex-platform-module-task-management-client-CIwafWli.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-task-management-client-contracts.mjs +183 -0
  4. package/fesm2022/acorex-platform-module-task-management-client-contracts.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-module-task-management-client-list-task-board-assignees.query-Ci8_D4Vs.mjs +129 -0
  6. package/fesm2022/acorex-platform-module-task-management-client-list-task-board-assignees.query-Ci8_D4Vs.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-task-management-client-list-task-board-reporters.query-C3NAYa9b.mjs +76 -0
  8. package/fesm2022/acorex-platform-module-task-management-client-list-task-board-reporters.query-C3NAYa9b.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-task-management-client-list-task-types.query-CKLUPrBU.mjs +53 -0
  10. package/fesm2022/acorex-platform-module-task-management-client-list-task-types.query-CKLUPrBU.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-task-management-client-start-task-flow.command-ChCuTFV-.mjs +114 -0
  12. package/fesm2022/acorex-platform-module-task-management-client-start-task-flow.command-ChCuTFV-.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-module-task-management-client-task-board.page-8I8H2pFq.mjs +2743 -0
  14. package/fesm2022/acorex-platform-module-task-management-client-task-board.page-8I8H2pFq.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-module-task-management-client.mjs +2 -0
  16. package/fesm2022/acorex-platform-module-task-management-client.mjs.map +1 -0
  17. package/package.json +28 -0
  18. package/types/acorex-platform-module-task-management-client-contracts.d.ts +318 -0
  19. package/types/acorex-platform-module-task-management-client.d.ts +850 -0
@@ -0,0 +1,2743 @@
1
+ import { AXPRegionalSetting, runtimeQueryCollectionData, AXPKeyboardShortcutPriority } from '@acorex-platform/framework-shared/core';
2
+ import * as i7 from '@acorex/components/badge';
3
+ import { AXBadgeModule } from '@acorex/components/badge';
4
+ import { AXBreadcrumbsModule } from '@acorex/components/breadcrumbs';
5
+ import * as i1$4 from '@acorex/components/button';
6
+ import { AXButtonComponent, AXButtonModule } from '@acorex/components/button';
7
+ import { AXCalendarComponent } from '@acorex/components/calendar';
8
+ import * as i2$1 from '@acorex/components/decorators';
9
+ import { AXDecoratorIconComponent, AXDecoratorModule } from '@acorex/components/decorators';
10
+ import * as i3 from '@acorex/components/dropdown';
11
+ import { AXDropdownModule } from '@acorex/components/dropdown';
12
+ import * as i2 from '@acorex/components/menu';
13
+ import { AXMenuModule } from '@acorex/components/menu';
14
+ import * as i8 from '@acorex/components/popover';
15
+ import { AXPopoverModule } from '@acorex/components/popover';
16
+ import * as i9 from '@acorex/components/selection-list';
17
+ import { AXSelectionListModule } from '@acorex/components/selection-list';
18
+ import { AXCalendarService } from '@acorex/core/date-time';
19
+ import { AXLocaleService } from '@acorex/core/locale';
20
+ import * as i5 from '@acorex/core/translation';
21
+ import { AXTranslatorPipe, AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
22
+ import { AXUnsubscriber } from '@acorex/core/utils';
23
+ import { AXPDeviceService, AXPKeyboardShortcutRegistry } from '@acorex-platform/framework-client/core';
24
+ import { AXPCommandService, AXPQueryService } from '@acorex-platform/framework-client/runtime';
25
+ import { AXPStateMessageComponent, AXPThemeLayoutBlockComponent, AXPQueryFiltersComponent, AXPLayoutSideDetailPanelComponent, AXPLayoutSideDetailPanelSectionDirective } from '@acorex-platform/framework-client/layout/components';
26
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
27
+ import * as i1$5 from '@angular/common';
28
+ import { AsyncPipe, CommonModule } from '@angular/common';
29
+ import * as i0 from '@angular/core';
30
+ import { computed, ChangeDetectionStrategy, Component, inject, signal, effect, ViewEncapsulation, DestroyRef, Injector, viewChild, input, output, untracked, afterNextRender, model, viewChildren, HostListener } from '@angular/core';
31
+ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
32
+ import * as i1$3 from '@angular/forms';
33
+ import { FormsModule } from '@angular/forms';
34
+ import { ActivatedRoute, NavigationEnd, RouterModule } from '@angular/router';
35
+ import { isEqual, sortBy } from 'lodash';
36
+ import { filter } from 'rxjs';
37
+ import { RootConfig, AXMTaskManagementQueryKeys } from '@acorex-platform/module-task-management-client/contracts';
38
+ import { A as AXMTaskBoardViewModel, a as AXPTaskBoardService, b as AXMTaskBoardTaskPopoverOverlayService, T as TASK_BOARD_GRID_ROW_ACTION_COLUMN_WIDTH, c as TASK_BOARD_GRID_ROW_ACTION_ITEMS, d as AXPDueStatusProviderService, u as usesTaskBoardToolbarNavigator, g as getTaskBoardLayoutOptions, e as canViewAllTaskAssignees, n as normalizeTaskBoardAssigneeIds, f as filterTasksOverlappingRange, h as normalizeTaskTypeFilterNames, i as enrichTaskBoardTasks, r as resolveTaskBoardSchedulerLabels, j as isTaskBoardRangeFilterCategory, k as deriveTaskBoardDateRangeFromNavigator, l as applyTaskBoardDateRangeToViewModel, m as resolveTaskBoardCategoryLayoutFromQuery } from './acorex-platform-module-task-management-client-acorex-platform-module-task-management-client-CIwafWli.mjs';
39
+ import * as i1 from '@acorex/components/skeleton';
40
+ import { AXSkeletonModule } from '@acorex/components/skeleton';
41
+ import { AXFormatPipe } from '@acorex/core/format';
42
+ import * as i1$1 from '@acorex-platform/framework-client/layout/widget-core';
43
+ import { AXPWidgetCoreModule } from '@acorex-platform/framework-client/layout/widget-core';
44
+ import { AXSchedulerService, AXSchedulerComponent } from '@acorex/components/scheduler';
45
+ import { AXPSettingsService } from '@acorex-platform/framework-client/common';
46
+ import { AXDataSource } from '@acorex/cdk/common';
47
+ import * as i1$2 from '@acorex/components/data-table';
48
+ import { AXDataTableModule } from '@acorex/components/data-table';
49
+ import { AXKanbanComponent } from '@acorex/components/kanban';
50
+
51
+ class AXMTaskBoardContentSkeletonComponent {
52
+ constructor() {
53
+ //#endregion
54
+ //#region ---- Computed ----
55
+ /** Weekday header placeholders (Sun–Sat). */
56
+ this.dayHeaderPlaceholders = computed(() => Array.from({ length: AXMTaskBoardContentSkeletonComponent.DAY_HEADER_COUNT }, (_, index) => index), /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "dayHeaderPlaceholders" }] : /* istanbul ignore next */ []));
58
+ /** Calendar-style grid row placeholders. */
59
+ this.gridRowPlaceholders = computed(() => Array.from({ length: AXMTaskBoardContentSkeletonComponent.GRID_ROW_COUNT }, (_, index) => index), /* @ts-ignore */
60
+ ...(ngDevMode ? [{ debugName: "gridRowPlaceholders" }] : /* istanbul ignore next */ []));
61
+ /** Column placeholders per grid row. */
62
+ this.gridColumnPlaceholders = computed(() => Array.from({ length: AXMTaskBoardContentSkeletonComponent.DAY_HEADER_COUNT }, (_, index) => index), /* @ts-ignore */
63
+ ...(ngDevMode ? [{ debugName: "gridColumnPlaceholders" }] : /* istanbul ignore next */ []));
64
+ }
65
+ //#region ---- Constants ----
66
+ static { this.DAY_HEADER_COUNT = 7; }
67
+ static { this.GRID_ROW_COUNT = 6; }
68
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardContentSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
69
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardContentSkeletonComponent, isStandalone: true, selector: "axm-task-board-content-skeleton", host: { attributes: { "aria-busy": "true", "aria-live": "polite" }, classAttribute: "axm-task-board-content-skeleton" }, ngImport: i0, template: "<!-- Weekday header row -->\n<div class=\"axm-task-board-content-skeleton__header\">\n @for (index of dayHeaderPlaceholders(); track index) {\n <ax-skeleton [animated]=\"true\" class=\"axm-task-board-content-skeleton__day-header\"></ax-skeleton>\n }\n</div>\n\n<!-- Generic board grid (calendar / kanban / table placeholder) -->\n<div class=\"axm-task-board-content-skeleton__grid\">\n @for (row of gridRowPlaceholders(); track row) {\n <div class=\"axm-task-board-content-skeleton__row\">\n @for (col of gridColumnPlaceholders(); track col) {\n <div class=\"axm-task-board-content-skeleton__cell\">\n <ax-skeleton [animated]=\"true\" class=\"axm-task-board-content-skeleton__cell-date\"></ax-skeleton>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["@layer properties;:host{display:flex;min-height:0px;width:100%;min-width:0px;flex:1;flex-direction:column;align-self:stretch;overflow:hidden}:host .axm-task-board-content-skeleton__header{display:grid;flex-shrink:0;gap:calc(var(--spacing, .25rem) * 3);padding-bottom:calc(var(--spacing, .25rem) * 3);grid-template-columns:repeat(7,minmax(0,1fr))}:host .axm-task-board-content-skeleton__day-header{height:calc(var(--spacing, .25rem) * 4);width:100%;border-radius:var(--radius-md, .375rem)}:host .axm-task-board-content-skeleton__grid{display:flex;min-height:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);overflow:hidden;border-radius:var(--radius-lg, .5rem);padding:var(--spacing, .25rem);background:rgb(var(--ax-sys-color-lighter-surface))}:host .axm-task-board-content-skeleton__row{display:grid;min-height:0px;flex:1;gap:calc(var(--spacing, .25rem) * 3);grid-template-columns:repeat(7,minmax(0,1fr))}:host .axm-task-board-content-skeleton__cell{display:flex;min-height:calc(var(--spacing, .25rem) * 16);flex-direction:column;align-items:flex-end;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;padding:calc(var(--spacing, .25rem) * 2);border-color:rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-surface))}:host .axm-task-board-content-skeleton__cell-date{height:calc(var(--spacing, .25rem) * 3);width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:var(--radius-sm, .25rem)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
70
+ }
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardContentSkeletonComponent, decorators: [{
72
+ type: Component,
73
+ args: [{ selector: 'axm-task-board-content-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AXSkeletonModule], host: {
74
+ class: 'axm-task-board-content-skeleton',
75
+ 'aria-busy': 'true',
76
+ 'aria-live': 'polite',
77
+ }, template: "<!-- Weekday header row -->\n<div class=\"axm-task-board-content-skeleton__header\">\n @for (index of dayHeaderPlaceholders(); track index) {\n <ax-skeleton [animated]=\"true\" class=\"axm-task-board-content-skeleton__day-header\"></ax-skeleton>\n }\n</div>\n\n<!-- Generic board grid (calendar / kanban / table placeholder) -->\n<div class=\"axm-task-board-content-skeleton__grid\">\n @for (row of gridRowPlaceholders(); track row) {\n <div class=\"axm-task-board-content-skeleton__row\">\n @for (col of gridColumnPlaceholders(); track col) {\n <div class=\"axm-task-board-content-skeleton__cell\">\n <ax-skeleton [animated]=\"true\" class=\"axm-task-board-content-skeleton__cell-date\"></ax-skeleton>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["@layer properties;:host{display:flex;min-height:0px;width:100%;min-width:0px;flex:1;flex-direction:column;align-self:stretch;overflow:hidden}:host .axm-task-board-content-skeleton__header{display:grid;flex-shrink:0;gap:calc(var(--spacing, .25rem) * 3);padding-bottom:calc(var(--spacing, .25rem) * 3);grid-template-columns:repeat(7,minmax(0,1fr))}:host .axm-task-board-content-skeleton__day-header{height:calc(var(--spacing, .25rem) * 4);width:100%;border-radius:var(--radius-md, .375rem)}:host .axm-task-board-content-skeleton__grid{display:flex;min-height:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 3);overflow:hidden;border-radius:var(--radius-lg, .5rem);padding:var(--spacing, .25rem);background:rgb(var(--ax-sys-color-lighter-surface))}:host .axm-task-board-content-skeleton__row{display:grid;min-height:0px;flex:1;gap:calc(var(--spacing, .25rem) * 3);grid-template-columns:repeat(7,minmax(0,1fr))}:host .axm-task-board-content-skeleton__cell{display:flex;min-height:calc(var(--spacing, .25rem) * 16);flex-direction:column;align-items:flex-end;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;padding:calc(var(--spacing, .25rem) * 2);border-color:rgb(var(--ax-sys-color-border-light-surface));background:rgb(var(--ax-sys-color-surface))}:host .axm-task-board-content-skeleton__cell-date{height:calc(var(--spacing, .25rem) * 3);width:calc(var(--spacing, .25rem) * 5);flex-shrink:0;border-radius:var(--radius-sm, .25rem)}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
78
+ }] });
79
+
80
+ class AXMTaskBoardDetailPanel {
81
+ constructor() {
82
+ this.rootConfig = RootConfig;
83
+ this.vm = inject(AXMTaskBoardViewModel);
84
+ this.localeService = inject(AXLocaleService);
85
+ this.taskBoardService = inject(AXPTaskBoardService);
86
+ this.statusName = signal('', /* @ts-ignore */
87
+ ...(ngDevMode ? [{ debugName: "statusName" }] : /* istanbul ignore next */ []));
88
+ this.extraFields = signal([], /* @ts-ignore */
89
+ ...(ngDevMode ? [{ debugName: "extraFields" }] : /* istanbul ignore next */ []));
90
+ this.#selectedTaskChanged = effect(async () => {
91
+ const selectedTask = this.vm.selectedTask();
92
+ const key = selectedTask?.status.id;
93
+ const provider = selectedTask?.provider;
94
+ if (!provider)
95
+ return;
96
+ const name = await this.vm.getStatusName(key, provider);
97
+ if (name)
98
+ this.statusName.set(name);
99
+ }, /* @ts-ignore */
100
+ ...(ngDevMode ? [{ debugName: "#selectedTaskChanged" }] : /* istanbul ignore next */ []));
101
+ this.#selectedTaskTypeChanged = effect(async () => {
102
+ const selectedTask = this.vm.selectedTask();
103
+ const selectedTaskType = this.taskBoardService.getProvider(selectedTask?.provider);
104
+ const extraFields = await selectedTaskType?.getExtraFields();
105
+ if (!extraFields) {
106
+ this.extraFields.set([]);
107
+ return;
108
+ }
109
+ this.extraFields.set(extraFields);
110
+ }, /* @ts-ignore */
111
+ ...(ngDevMode ? [{ debugName: "#selectedTaskTypeChanged" }] : /* istanbul ignore next */ []));
112
+ }
113
+ #selectedTaskChanged;
114
+ #selectedTaskTypeChanged;
115
+ capitalizeFirstLetter(text) {
116
+ return String(text).charAt(0).toUpperCase() + String(text).slice(1);
117
+ }
118
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardDetailPanel, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
119
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardDetailPanel, isStandalone: true, selector: "axm-task-board-detail-panel", ngImport: i0, template: "@if (vm.isTaskSelected()) {\n @let nodeInfo = vm.selectedTask();\n <div class=\"__header\">\n <div class=\"__title\">\n <span>{{ nodeInfo?.title }}</span>\n </div>\n <ax-button [look]=\"'blank'\" class=\"ax-sm rounded-full\" (onClick)=\"vm.closeDetailPanel()\">\n <ax-icon icon=\"fa-solid fa-xmark\"></ax-icon>\n </ax-button>\n </div>\n\n <div class=\"__content\">\n <div class=\"__section\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.title' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n @if (nodeInfo?.description) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.description' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">{{ nodeInfo?.description }}</div>\n </div>\n }\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.start-date' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">\n {{\n $safeNavigationMigration(nodeInfo?.startDate)\n | format: 'datetime' : { format: 'short', calendar: localeService.activeProfile().calendar.system }\n | async\n }}\n </div>\n </div>\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.end-date' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n {{\n $safeNavigationMigration(nodeInfo?.endDate)\n | format: 'datetime' : { format: 'short', calendar: localeService.activeProfile().calendar.system }\n | async\n }}\n </div>\n </div>\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.reporter' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n {{ nodeInfo?.reporter?.fullName }}\n </div>\n </div>\n @if (nodeInfo?.assignee) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.assignee' | translate | async }}</span>\n </div>\n <div class=\"__content\">{{ nodeInfo?.assignee?.fullName }}</div>\n </div>\n }\n @if (statusName()) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.status' | translate | async }}</span>\n </div>\n <div class=\"__content\">{{ statusName() }}</div>\n </div>\n }\n @if (nodeInfo?.priority) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.priority' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">{{ capitalizeFirstLetter(nodeInfo?.priority ?? '') }}</div>\n </div>\n }\n @if (extraFields() && extraFields().length) {\n <div class=\"__row\">\n <axp-widgets-container [context]=\"nodeInfo\">\n @for (extraField of extraFields(); track extraField) {\n <div class=\"__title\">\n <span>\n {{ extraField.title | translate | async }}\n </span>\n </div>\n <div class=\"__content\">\n <ng-container axp-widget-renderer [node]=\"extraField.widget\" [mode]=\"'view'\"> </ng-container>\n </div>\n }\n </axp-widgets-container>\n </div>\n }\n </div>\n </div>\n </div>\n} @else {\n <div class=\"__header\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.title' | translate | async }}\n </span>\n </div>\n <ax-button [look]=\"'blank'\" class=\"ax-sm rounded-full\" (onClick)=\"vm.closeDetailPanel()\">\n <ax-icon icon=\"fa-solid fa-xmark\"></ax-icon>\n </ax-button>\n </div>\n\n <div class=\"__content flex items-center justify-center\">\n <axp-state-message\n icon=\"fa-light fa-clipboard-list-check\"\n [title]=\"'@task-management:task-board.detail-panel.no-task-selected.title'\"\n [description]=\"'@task-management:task-board.detail-panel.no-task-selected.description'\"\n >\n </axp-state-message>\n </div>\n}\n", styles: ["@layer properties;axm-task-board-detail-panel{display:flex;flex-direction:column;border-radius:var(--radius-lg, .5rem);border-style:var(--tw-border-style);border-width:1px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(width>=64rem){axm-task-board-detail-panel{width:calc(var(--spacing, .25rem) * 96)}}axm-task-board-detail-panel>.__header{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;padding:calc(var(--spacing, .25rem) * 4)}@media(width>=64rem){axm-task-board-detail-panel>.__header{width:calc(var(--spacing, .25rem) * 80)}}axm-task-board-detail-panel>.__header>.__title{display:flex;width:90%;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}axm-task-board-detail-panel>.__header>.__title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}axm-task-board-detail-panel>.__content{display:flex;flex-direction:column}:where(axm-task-board-detail-panel .__content>:not(:last-child)){--tw-divide-y-reverse: 0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}axm-task-board-detail-panel>.__content>.__section{display:flex;flex-direction:column;padding:calc(var(--spacing, .25rem) * 4)}axm-task-board-detail-panel>.__content>.__section>.__title{margin-bottom:calc(var(--spacing, .25rem) * 4);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}axm-task-board-detail-panel>.__content>.__section>.__content{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row{display:flex;flex-direction:column;gap:var(--spacing, .25rem)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__title{display:flex;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);opacity:90%}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__content{display:flex;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__content span{opacity:70%}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-divide-y-reverse{syntax: \"*\"; inherits: false; initial-value: 0;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-leading: initial;--tw-font-weight: initial;--tw-divide-y-reverse: 0}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i1$1.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i1$1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "component", type: AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: AXFormatPipe, name: "format" }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
120
+ }
121
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardDetailPanel, decorators: [{
122
+ type: Component,
123
+ args: [{ selector: 'axm-task-board-detail-panel', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
124
+ AsyncPipe,
125
+ AXFormatPipe,
126
+ AXTranslatorPipe,
127
+ AXButtonComponent,
128
+ AXPStateMessageComponent,
129
+ AXPWidgetCoreModule,
130
+ AXDecoratorIconComponent,
131
+ ], template: "@if (vm.isTaskSelected()) {\n @let nodeInfo = vm.selectedTask();\n <div class=\"__header\">\n <div class=\"__title\">\n <span>{{ nodeInfo?.title }}</span>\n </div>\n <ax-button [look]=\"'blank'\" class=\"ax-sm rounded-full\" (onClick)=\"vm.closeDetailPanel()\">\n <ax-icon icon=\"fa-solid fa-xmark\"></ax-icon>\n </ax-button>\n </div>\n\n <div class=\"__content\">\n <div class=\"__section\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.title' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n @if (nodeInfo?.description) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.description' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">{{ nodeInfo?.description }}</div>\n </div>\n }\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.start-date' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">\n {{\n $safeNavigationMigration(nodeInfo?.startDate)\n | format: 'datetime' : { format: 'short', calendar: localeService.activeProfile().calendar.system }\n | async\n }}\n </div>\n </div>\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.end-date' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n {{\n $safeNavigationMigration(nodeInfo?.endDate)\n | format: 'datetime' : { format: 'short', calendar: localeService.activeProfile().calendar.system }\n | async\n }}\n </div>\n </div>\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.reporter' | translate | async }}</span>\n </div>\n <div class=\"__content\">\n {{ nodeInfo?.reporter?.fullName }}\n </div>\n </div>\n @if (nodeInfo?.assignee) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.assignee' | translate | async }}</span>\n </div>\n <div class=\"__content\">{{ nodeInfo?.assignee?.fullName }}</div>\n </div>\n }\n @if (statusName()) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>{{ '@task-management:task-board.detail-panel.status' | translate | async }}</span>\n </div>\n <div class=\"__content\">{{ statusName() }}</div>\n </div>\n }\n @if (nodeInfo?.priority) {\n <div class=\"__row\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.priority' | translate | async }}\n </span>\n </div>\n <div class=\"__content\">{{ capitalizeFirstLetter(nodeInfo?.priority ?? '') }}</div>\n </div>\n }\n @if (extraFields() && extraFields().length) {\n <div class=\"__row\">\n <axp-widgets-container [context]=\"nodeInfo\">\n @for (extraField of extraFields(); track extraField) {\n <div class=\"__title\">\n <span>\n {{ extraField.title | translate | async }}\n </span>\n </div>\n <div class=\"__content\">\n <ng-container axp-widget-renderer [node]=\"extraField.widget\" [mode]=\"'view'\"> </ng-container>\n </div>\n }\n </axp-widgets-container>\n </div>\n }\n </div>\n </div>\n </div>\n} @else {\n <div class=\"__header\">\n <div class=\"__title\">\n <span>\n {{ '@task-management:task-board.detail-panel.title' | translate | async }}\n </span>\n </div>\n <ax-button [look]=\"'blank'\" class=\"ax-sm rounded-full\" (onClick)=\"vm.closeDetailPanel()\">\n <ax-icon icon=\"fa-solid fa-xmark\"></ax-icon>\n </ax-button>\n </div>\n\n <div class=\"__content flex items-center justify-center\">\n <axp-state-message\n icon=\"fa-light fa-clipboard-list-check\"\n [title]=\"'@task-management:task-board.detail-panel.no-task-selected.title'\"\n [description]=\"'@task-management:task-board.detail-panel.no-task-selected.description'\"\n >\n </axp-state-message>\n </div>\n}\n", styles: ["@layer properties;axm-task-board-detail-panel{display:flex;flex-direction:column;border-radius:var(--radius-lg, .5rem);border-style:var(--tw-border-style);border-width:1px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(width>=64rem){axm-task-board-detail-panel{width:calc(var(--spacing, .25rem) * 96)}}axm-task-board-detail-panel>.__header{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;padding:calc(var(--spacing, .25rem) * 4)}@media(width>=64rem){axm-task-board-detail-panel>.__header{width:calc(var(--spacing, .25rem) * 80)}}axm-task-board-detail-panel>.__header>.__title{display:flex;width:90%;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}axm-task-board-detail-panel>.__header>.__title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:start;--tw-leading: var(--leading-relaxed, 1.625);line-height:var(--leading-relaxed, 1.625);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}axm-task-board-detail-panel>.__content{display:flex;flex-direction:column}:where(axm-task-board-detail-panel .__content>:not(:last-child)){--tw-divide-y-reverse: 0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}axm-task-board-detail-panel>.__content>.__section{display:flex;flex-direction:column;padding:calc(var(--spacing, .25rem) * 4)}axm-task-board-detail-panel>.__content>.__section>.__title{margin-bottom:calc(var(--spacing, .25rem) * 4);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}axm-task-board-detail-panel>.__content>.__section>.__content{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 4)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row{display:flex;flex-direction:column;gap:var(--spacing, .25rem)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__title{display:flex;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);opacity:90%}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__content{display:flex;flex-direction:row;align-items:center;gap:calc(var(--spacing, .25rem) * 2)}axm-task-board-detail-panel>.__content>.__section>.__content>.__row>.__content span{opacity:70%}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-divide-y-reverse{syntax: \"*\"; inherits: false; initial-value: 0;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-leading: initial;--tw-font-weight: initial;--tw-divide-y-reverse: 0}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
132
+ }] });
133
+
134
+ const TASK_BOARD_REGIONAL_LAYOUT_SETTING_KEYS = [
135
+ AXPRegionalSetting.LocaleProfile,
136
+ AXPRegionalSetting.Calendar,
137
+ AXPRegionalSetting.FirstDayOfWeek,
138
+ AXPRegionalSetting.WeekendDays,
139
+ ];
140
+ function buildTaskBoardRegionalProfileKey(parts) {
141
+ return [parts.profileCode, parts.calendarSystem, parts.weekStartsOn, parts.weekendDays.join('-')].join('|');
142
+ }
143
+
144
+ class AXMTaskBoardCalendarViewComponent {
145
+ constructor() {
146
+ this.vm = inject(AXMTaskBoardViewModel);
147
+ this.taskBoardService = inject(AXPTaskBoardService);
148
+ this.settingService = inject(AXPSettingsService);
149
+ this.commandService = inject(AXPCommandService);
150
+ this.schedulerService = inject(AXSchedulerService);
151
+ this.calendarService = inject(AXCalendarService);
152
+ this.localeService = inject(AXLocaleService);
153
+ this.taskPopoverOverlay = inject(AXMTaskBoardTaskPopoverOverlayService);
154
+ this.destroyRef = inject(DestroyRef);
155
+ this.injector = inject(Injector);
156
+ this.schedulerComponent = viewChild(AXSchedulerComponent, /* @ts-ignore */
157
+ ...(ngDevMode ? [{ debugName: "schedulerComponent" }] : /* istanbul ignore next */ []));
158
+ this.resources = input([], /* @ts-ignore */
159
+ ...(ngDevMode ? [{ debugName: "resources" }] : /* istanbul ignore next */ []));
160
+ this.refreshNonce = input(0, /* @ts-ignore */
161
+ ...(ngDevMode ? [{ debugName: "refreshNonce" }] : /* istanbul ignore next */ []));
162
+ this.startingDate = input(new Date(), /* @ts-ignore */
163
+ ...(ngDevMode ? [{ debugName: "startingDate" }] : /* istanbul ignore next */ []));
164
+ this.selectedView = input('month', /* @ts-ignore */
165
+ ...(ngDevMode ? [{ debugName: "selectedView" }] : /* istanbul ignore next */ []));
166
+ this.dataSource = input.required(/* @ts-ignore */
167
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
168
+ this.activeCalendar = toSignal(this.calendarService.calendarChanges$, {
169
+ initialValue: this.calendarService.getActiveCalendar(),
170
+ });
171
+ /** Gates ax-scheduler in the template so we can destroy and recreate it cleanly on regional changes. */
172
+ this.schedulerMounted = signal(true, /* @ts-ignore */
173
+ ...(ngDevMode ? [{ debugName: "schedulerMounted" }] : /* istanbul ignore next */ []));
174
+ this.regionalSettingsRevision = signal(0, /* @ts-ignore */
175
+ ...(ngDevMode ? [{ debugName: "regionalSettingsRevision" }] : /* istanbul ignore next */ []));
176
+ this.previousRegionalProfileKey = null;
177
+ /**
178
+ * Bumped whenever the global holiday list is reloaded. The scheduler merges the
179
+ * app-wide `AXCalendarService.holidays` (non-reactive) with our reactive callback
180
+ * result, so we re-run the callback to force the merged holidays to reflect the
181
+ * latest global reload after a calendar selection change.
182
+ */
183
+ this.holidaysRevision = signal(0, /* @ts-ignore */
184
+ ...(ngDevMode ? [{ debugName: "holidaysRevision" }] : /* istanbul ignore next */ []));
185
+ /** Active calendar system (e.g. solar-hijri for fa-IR). Required for correct week/today rendering. */
186
+ this.schedulerCalendar = computed(() => this.activeCalendar()?.name() ?? this.localeService.activeProfile().calendar.system, /* @ts-ignore */
187
+ ...(ngDevMode ? [{ debugName: "schedulerCalendar" }] : /* istanbul ignore next */ []));
188
+ /** Detects locale profile and week-layout changes that require a fresh ax-scheduler instance. */
189
+ this.regionalProfileKey = computed(() => {
190
+ this.regionalSettingsRevision();
191
+ this.activeCalendar()?.weekdays;
192
+ const profile = this.localeService.activeProfile();
193
+ return buildTaskBoardRegionalProfileKey({
194
+ profileCode: profile.localeInfo?.code ?? '',
195
+ calendarSystem: this.schedulerCalendar(),
196
+ weekStartsOn: profile.calendar.week?.startsOn ?? 0,
197
+ weekendDays: profile.calendar.week?.weekends ?? [],
198
+ });
199
+ }, /* @ts-ignore */
200
+ ...(ngDevMode ? [{ debugName: "regionalProfileKey" }] : /* istanbul ignore next */ []));
201
+ /** Align week/month views to regional week boundaries before passing to ax-scheduler. */
202
+ this.schedulerStartingDate = computed(() => {
203
+ const date = this.startingDate();
204
+ const calendar = this.schedulerCalendar();
205
+ const axDate = this.calendarService.create(date, calendar);
206
+ switch (this.currentSchedulerView()) {
207
+ case 'week':
208
+ return axDate.startOf('week').date;
209
+ case 'month':
210
+ case 'timeline-month':
211
+ return axDate.startOf('month').date;
212
+ default:
213
+ return axDate.startOf('day').date;
214
+ }
215
+ }, /* @ts-ignore */
216
+ ...(ngDevMode ? [{ debugName: "schedulerStartingDate" }] : /* istanbul ignore next */ []));
217
+ this.currentSchedulerView = computed(() => this.selectedView() === 'kanban' || this.selectedView() === 'grid'
218
+ ? 'month'
219
+ : this.selectedView(), /* @ts-ignore */
220
+ ...(ngDevMode ? [{ debugName: "currentSchedulerView" }] : /* istanbul ignore next */ []));
221
+ this.onTaskClick = output();
222
+ this.onTaskChanged = output();
223
+ this.onMonthSlotDblClicked = output();
224
+ this.onActionClick = output();
225
+ this.component = output();
226
+ this.onTaskRightClick = output();
227
+ this.onRangeChanged = output();
228
+ this.regionalRemountEffect = effect(() => {
229
+ const profileKey = this.regionalProfileKey();
230
+ untracked(() => {
231
+ if (this.previousRegionalProfileKey === null) {
232
+ this.previousRegionalProfileKey = profileKey;
233
+ return;
234
+ }
235
+ if (profileKey === this.previousRegionalProfileKey) {
236
+ return;
237
+ }
238
+ this.previousRegionalProfileKey = profileKey;
239
+ this.remountScheduler();
240
+ });
241
+ }, /* @ts-ignore */
242
+ ...(ngDevMode ? [{ debugName: "regionalRemountEffect" }] : /* istanbul ignore next */ []));
243
+ /** Re-binds parent scheduler ref whenever ax-scheduler remounts after a regional layout change. */
244
+ this.schedulerInstanceEffect = effect(() => {
245
+ if (!this.schedulerMounted()) {
246
+ return;
247
+ }
248
+ const scheduler = this.schedulerComponent();
249
+ if (!scheduler) {
250
+ return;
251
+ }
252
+ afterNextRender(() => {
253
+ const currentScheduler = this.schedulerComponent();
254
+ if (!currentScheduler || !this.schedulerMounted()) {
255
+ return;
256
+ }
257
+ this.component.emit(currentScheduler);
258
+ this.syncSchedulerCurrentDateFromToolbar({ force: true });
259
+ void currentScheduler.refresh();
260
+ }, { injector: this.injector });
261
+ }, /* @ts-ignore */
262
+ ...(ngDevMode ? [{ debugName: "schedulerInstanceEffect" }] : /* istanbul ignore next */ []));
263
+ this.refreshNonceEffect = effect(() => {
264
+ this.refreshNonce();
265
+ if (!this.schedulerMounted()) {
266
+ return;
267
+ }
268
+ void this.schedulerComponent()?.refresh();
269
+ }, /* @ts-ignore */
270
+ ...(ngDevMode ? [{ debugName: "refreshNonceEffect" }] : /* istanbul ignore next */ []));
271
+ this.schedulerStateSyncEffect = effect(() => {
272
+ if (!this.schedulerMounted()) {
273
+ return;
274
+ }
275
+ this.startingDate();
276
+ this.schedulerStartingDate();
277
+ this.schedulerCalendar();
278
+ this.localeService.activeProfile().localeInfo?.code;
279
+ this.currentSchedulerView();
280
+ this.vm.daysCount();
281
+ this.vm.selectedTaskTypeNames();
282
+ this.vm.selectedAssigneeIds();
283
+ this.vm.selectedReporterIds();
284
+ this.vm.selectedPriorities();
285
+ this.vm.selectedDueStatusKeys();
286
+ this.refreshNonce();
287
+ queueMicrotask(() => {
288
+ this.syncSchedulerCurrentDateFromToolbar();
289
+ void this.schedulerComponent()?.refresh();
290
+ });
291
+ }, /* @ts-ignore */
292
+ ...(ngDevMode ? [{ debugName: "schedulerStateSyncEffect" }] : /* istanbul ignore next */ []));
293
+ this.holidays = (range) => {
294
+ // Track the revision so the scheduler's #holidayEffect re-runs after a global reload.
295
+ this.holidaysRevision();
296
+ const selectedEventTypeIds = this.vm.selectedCalendarEventTypeIds();
297
+ return this.taskBoardService.getEvents(range, selectedEventTypeIds);
298
+ };
299
+ this.settingService.onChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
300
+ if (event.keys.some((key) => TASK_BOARD_REGIONAL_LAYOUT_SETTING_KEYS.includes(key))) {
301
+ this.regionalSettingsRevision.update((value) => value + 1);
302
+ }
303
+ });
304
+ this.calendarService.onHolidaysChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
305
+ this.holidaysRevision.update((value) => value + 1);
306
+ });
307
+ }
308
+ /**
309
+ * ax-scheduler only consumes `[startingDate]` on first mount; afterward its internal
310
+ * `currentDate` must be updated explicitly when the external toolbar navigator changes.
311
+ */
312
+ syncSchedulerCurrentDateFromToolbar(options) {
313
+ const scheduler = this.schedulerComponent();
314
+ if (!scheduler) {
315
+ return;
316
+ }
317
+ const view = this.currentSchedulerView();
318
+ let targetDate = this.calendarService.create(this.schedulerStartingDate(), this.schedulerCalendar());
319
+ if (view === 'week' || view === 'timeline-weekly') {
320
+ targetDate = this.schedulerService.alignToWeekStart(targetDate, {});
321
+ }
322
+ if (!options?.force && targetDate.equal(scheduler.currentDate())) {
323
+ return;
324
+ }
325
+ scheduler.currentDate.set(targetDate);
326
+ }
327
+ remountScheduler() {
328
+ this.schedulerMounted.set(false);
329
+ afterNextRender(() => {
330
+ this.schedulerMounted.set(true);
331
+ }, { injector: this.injector });
332
+ }
333
+ async ngAfterViewInit() {
334
+ this.taskBoardService.refresh$
335
+ .pipe(takeUntilDestroyed(this.destroyRef))
336
+ .subscribe(async (data) => {
337
+ const active = this.taskPopoverOverlay.getActiveTask();
338
+ if (!active?.provider) {
339
+ return;
340
+ }
341
+ if (data.provider && data.provider !== active.provider) {
342
+ return;
343
+ }
344
+ await this.refreshPopoverData();
345
+ });
346
+ this.taskBoardService.close$
347
+ .pipe(takeUntilDestroyed(this.destroyRef))
348
+ .subscribe((data) => {
349
+ const active = this.taskPopoverOverlay.getActiveTask();
350
+ if (!active?.provider) {
351
+ return;
352
+ }
353
+ if (data.provider && data.provider !== active.provider) {
354
+ return;
355
+ }
356
+ this.taskPopoverOverlay.close();
357
+ });
358
+ }
359
+ onTaskDrop(event) {
360
+ if (event.slot.view === 'agenda' && event.isSameSlotDrop) {
361
+ return;
362
+ }
363
+ const dropResult = this.schedulerService.calculateAppointmentDropResult(event);
364
+ if (dropResult.isSameSlotDrop) {
365
+ return;
366
+ }
367
+ const originalTask = event.appointment;
368
+ this.schedulerService.applyDropResult(originalTask, dropResult);
369
+ this.taskBoardService
370
+ .updateTaskDateRange(originalTask, {
371
+ from: dropResult.startDate,
372
+ end: dropResult.endDate,
373
+ })
374
+ .then((persistedTask) => {
375
+ this.onTaskChanged.emit(persistedTask);
376
+ void this.schedulerComponent()?.refresh();
377
+ })
378
+ .catch((error) => {
379
+ console.error('[TaskBoardCalendar] Failed to update task date range:', error);
380
+ void this.schedulerComponent()?.refresh();
381
+ });
382
+ }
383
+ onTaskRightClickHandler(event) {
384
+ this.onTaskRightClick.emit({ nativeEvent: event.nativeEvent, task: event.appointment });
385
+ }
386
+ onSlotDblClicked(event) {
387
+ if (event.sender.selectedView() === 'month') {
388
+ this.onMonthSlotDblClicked.emit(event.slot.startDate.date);
389
+ }
390
+ }
391
+ async handleRangeChanged(event) {
392
+ this.onRangeChanged.emit(event);
393
+ }
394
+ handleActionClick(event) {
395
+ this.onActionClick.emit({ nativeEvent: event.nativeEvent, task: event.appointment });
396
+ }
397
+ async onTaskClickHandler(event) {
398
+ const task = event.appointment;
399
+ const anchor = this.resolveAppointmentAnchor(event);
400
+ if (anchor) {
401
+ await this.taskPopoverOverlay.open({
402
+ task,
403
+ anchor,
404
+ offsetX: 12,
405
+ offsetY: 12,
406
+ adaptivityEnabled: true,
407
+ });
408
+ }
409
+ this.onTaskClick.emit(task);
410
+ }
411
+ async refreshPopoverData() {
412
+ const active = this.taskPopoverOverlay.getActiveTask();
413
+ if (!active?.provider) {
414
+ return;
415
+ }
416
+ const provider = this.taskBoardService.getProvider(active.provider);
417
+ if (!provider) {
418
+ return;
419
+ }
420
+ try {
421
+ const result = await provider.getTasks();
422
+ const updatedTask = result.items.find((t) => t.id === active.id);
423
+ if (updatedTask) {
424
+ await this.taskPopoverOverlay.refreshTask({ ...updatedTask, provider: active.provider });
425
+ }
426
+ void this.schedulerComponent()?.refresh();
427
+ }
428
+ catch (error) {
429
+ console.error('[TaskPopover] Failed to refresh popover data:', error);
430
+ }
431
+ }
432
+ async openTaskDetails() {
433
+ const task = this.taskPopoverOverlay.getActiveTask();
434
+ if (!task) {
435
+ return;
436
+ }
437
+ try {
438
+ await this.commandService.execute('Entity:OpenDetails', {
439
+ entity: `${RootConfig.module.name}.${RootConfig.entities.task.name}`,
440
+ data: { id: task.id },
441
+ });
442
+ this.taskPopoverOverlay.close();
443
+ }
444
+ catch (error) {
445
+ console.error('Failed to open task details:', error);
446
+ }
447
+ }
448
+ resolveAppointmentAnchor(event) {
449
+ let targetElement = event.nativeEvent.currentTarget || event.nativeEvent.target;
450
+ let element = targetElement;
451
+ const maxDepth = 10;
452
+ let depth = 0;
453
+ while (element && element !== document.body && depth < maxDepth) {
454
+ depth++;
455
+ const isAppointment = element.classList?.contains('dx-scheduler-appointment') ||
456
+ element.classList?.contains('ax-scheduler-appointment') ||
457
+ element.hasAttribute('data-appointment-id');
458
+ if (isAppointment) {
459
+ targetElement = element;
460
+ break;
461
+ }
462
+ const parent = element.parentElement;
463
+ if (parent) {
464
+ const isParentAppointment = parent.classList?.contains('dx-scheduler-appointment') ||
465
+ parent.classList?.contains('ax-scheduler-appointment');
466
+ if (isParentAppointment) {
467
+ targetElement = parent;
468
+ break;
469
+ }
470
+ }
471
+ element = element.parentElement;
472
+ }
473
+ return targetElement;
474
+ }
475
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardCalendarViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
476
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardCalendarViewComponent, isStandalone: true, selector: "axm-task-board-calendar-view", inputs: { resources: { classPropertyName: "resources", publicName: "resources", isSignal: true, isRequired: false, transformFunction: null }, refreshNonce: { classPropertyName: "refreshNonce", publicName: "refreshNonce", isSignal: true, isRequired: false, transformFunction: null }, startingDate: { classPropertyName: "startingDate", publicName: "startingDate", isSignal: true, isRequired: false, transformFunction: null }, selectedView: { classPropertyName: "selectedView", publicName: "selectedView", isSignal: true, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onTaskClick: "onTaskClick", onTaskChanged: "onTaskChanged", onMonthSlotDblClicked: "onMonthSlotDblClicked", onActionClick: "onActionClick", component: "component", onTaskRightClick: "onTaskRightClick", onRangeChanged: "onRangeChanged" }, viewQueries: [{ propertyName: "schedulerComponent", first: true, predicate: AXSchedulerComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (schedulerMounted()) {\n <ax-scheduler\n [hasHeader]=\"false\"\n [holidays]=\"holidays\"\n [resources]=\"resources()\"\n [dataSource]=\"dataSource()\"\n [calendar]=\"schedulerCalendar()\"\n [startingDate]=\"schedulerStartingDate()\"\n [selectedView]=\"currentSchedulerView()\"\n [multiDayViewDaysCount]=\"vm.daysCount()\"\n [showCurrentTimeIndicator]=\"true\"\n (onAppointmentDrop)=\"onTaskDrop($event)\"\n (onActionClick)=\"handleActionClick($event)\"\n (onRangeChanged)=\"handleRangeChanged($event)\"\n (onSlotDblClicked)=\"onSlotDblClicked($event)\"\n (onAppointmentClicked)=\"onTaskClickHandler($event)\"\n ></ax-scheduler>\n}\n", styles: ["@layer properties;.task-popover{display:flex;flex-direction:column;overflow:hidden;background:rgba(var(--ax-sys-color-lightest-surface));border-radius:12px;min-width:280px;max-width:420px}.task-popover .task-popover__header{padding:20px 20px 16px;border-bottom:1px solid var(--ax-sys-color-border);background:linear-gradient(to bottom,var(--ax-sys-color-lightest-surface),var(--ax-sys-color-lighter-surface))}.task-popover .task-popover__title-section{display:flex;flex-direction:column;gap:8px}.task-popover .task-popover__title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-popover .task-popover__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:18px;color:var(--ax-sys-color-text-primary)}.task-popover .task-popover__close-btn{display:flex;flex-shrink:0;cursor:pointer;align-items:center;justify-content:center;border-style:var(--tw-border-style);border-width:0px;background:none;padding:4px;color:var(--ax-sys-color-text-neutral-600);transition:color .2s ease;border-radius:4px}.task-popover .task-popover__close-btn:hover{color:var(--ax-sys-color-text-primary);background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__close-btn:active{background:var(--ax-sys-color-light-surface)}.task-popover .task-popover__close-btn ax-icon{display:block;width:16px;height:16px}.task-popover .task-popover__body{display:flex;flex-direction:column}.task-popover .task-popover__description{margin:0;overflow:hidden;text-overflow:ellipsis;font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-secondary);padding:0 20px 12px;display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical}.task-popover .task-popover__content{display:flex;flex:1;flex-direction:column;padding:16px 20px;gap:12px}.task-popover .task-popover__info-item{display:flex;align-items:flex-start;gap:12px;min-height:24px}.task-popover .task-popover__icon{display:flex;flex-shrink:0;align-items:center;justify-content:center;width:20px;height:20px;margin-top:2px;color:var(--ax-sys-color-text-neutral-600);font-size:14px}.task-popover .task-popover__icon i{display:block}.task-popover .task-popover__icon--priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__icon--priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__icon--priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__icon--priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-500)}.task-popover .task-popover__info-content{display:flex;min-width:0px;flex:1;align-items:center}.task-popover .task-popover__info-text{font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-primary);word-wrap:break-word}.task-popover .task-popover__priority{--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);text-transform:capitalize}.task-popover .task-popover__priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-600)}.task-popover .task-popover__actions{display:flex;flex-direction:column;padding:12px 20px 16px;border-top:1px solid var(--ax-sys-color-border);gap:8px;background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__actions-group{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}ax-scheduler{background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);line-height:1.5}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--primary{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-primary);color:var(--color-on-primary);border-color:var(--color-border-primary)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--secondary{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary);color:var(--color-on-secondary);border-color:var(--color-border-secondary)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--success,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--success{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-success);color:var(--color-on-success);border-color:var(--color-border-success)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--warning{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-warning);color:var(--color-on-warning);border-color:var(--color-border-warning)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--danger{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-danger);color:var(--color-on-danger);border-color:var(--color-border-danger)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent1{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent1);color:var(--color-on-accent1);border-color:var(--color-border-accent1)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent2{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent2);color:var(--color-on-accent2);border-color:var(--color-border-accent2)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent3{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent3);color:var(--color-on-accent3);border-color:var(--color-border-accent3)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--neutral{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary-lightest);color:var(--color-on-secondary-lightest);border-color:var(--color-border-secondary-lightest)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--ghost{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary-lighter);color:var(--color-on-secondary-lighter);border-color:var(--color-border-secondary-lighter)}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-font-weight: initial;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXSchedulerComponent, selector: "ax-scheduler", inputs: ["calendar", "startingDate", "endDayHour", "startDayHour", "hasHeader", "readonly", "draggable", "hasActions", "dragStartDelay", "weekend", "allowFullScreen", "multiDayViewDaysCount", "showResourceHeaders", "showCurrentTimeIndicator", "scrollToCurrentTimeIndicator", "showUnassignedAppointments", "resources", "resourceTemplate", "firstDayOfWeek", "tooltipTemplate", "dataSource", "holidays", "views", "selectedView"], outputs: ["selectedViewChange", "onDataLoaded", "onRangeChanged", "onSlotClicked", "onSlotDblClicked", "onSlotRightClick", "onAppointmentDrop", "onActionClick", "onAppointmentClicked", "onAppointmentDblClicked", "onAppointmentRightClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
477
+ }
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardCalendarViewComponent, decorators: [{
479
+ type: Component,
480
+ args: [{ selector: 'axm-task-board-calendar-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXSchedulerComponent], template: "@if (schedulerMounted()) {\n <ax-scheduler\n [hasHeader]=\"false\"\n [holidays]=\"holidays\"\n [resources]=\"resources()\"\n [dataSource]=\"dataSource()\"\n [calendar]=\"schedulerCalendar()\"\n [startingDate]=\"schedulerStartingDate()\"\n [selectedView]=\"currentSchedulerView()\"\n [multiDayViewDaysCount]=\"vm.daysCount()\"\n [showCurrentTimeIndicator]=\"true\"\n (onAppointmentDrop)=\"onTaskDrop($event)\"\n (onActionClick)=\"handleActionClick($event)\"\n (onRangeChanged)=\"handleRangeChanged($event)\"\n (onSlotDblClicked)=\"onSlotDblClicked($event)\"\n (onAppointmentClicked)=\"onTaskClickHandler($event)\"\n ></ax-scheduler>\n}\n", styles: ["@layer properties;.task-popover{display:flex;flex-direction:column;overflow:hidden;background:rgba(var(--ax-sys-color-lightest-surface));border-radius:12px;min-width:280px;max-width:420px}.task-popover .task-popover__header{padding:20px 20px 16px;border-bottom:1px solid var(--ax-sys-color-border);background:linear-gradient(to bottom,var(--ax-sys-color-lightest-surface),var(--ax-sys-color-lighter-surface))}.task-popover .task-popover__title-section{display:flex;flex-direction:column;gap:8px}.task-popover .task-popover__title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-popover .task-popover__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:18px;color:var(--ax-sys-color-text-primary)}.task-popover .task-popover__close-btn{display:flex;flex-shrink:0;cursor:pointer;align-items:center;justify-content:center;border-style:var(--tw-border-style);border-width:0px;background:none;padding:4px;color:var(--ax-sys-color-text-neutral-600);transition:color .2s ease;border-radius:4px}.task-popover .task-popover__close-btn:hover{color:var(--ax-sys-color-text-primary);background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__close-btn:active{background:var(--ax-sys-color-light-surface)}.task-popover .task-popover__close-btn ax-icon{display:block;width:16px;height:16px}.task-popover .task-popover__body{display:flex;flex-direction:column}.task-popover .task-popover__description{margin:0;overflow:hidden;text-overflow:ellipsis;font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-secondary);padding:0 20px 12px;display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical}.task-popover .task-popover__content{display:flex;flex:1;flex-direction:column;padding:16px 20px;gap:12px}.task-popover .task-popover__info-item{display:flex;align-items:flex-start;gap:12px;min-height:24px}.task-popover .task-popover__icon{display:flex;flex-shrink:0;align-items:center;justify-content:center;width:20px;height:20px;margin-top:2px;color:var(--ax-sys-color-text-neutral-600);font-size:14px}.task-popover .task-popover__icon i{display:block}.task-popover .task-popover__icon--priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__icon--priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__icon--priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__icon--priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-500)}.task-popover .task-popover__info-content{display:flex;min-width:0px;flex:1;align-items:center}.task-popover .task-popover__info-text{font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-primary);word-wrap:break-word}.task-popover .task-popover__priority{--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);text-transform:capitalize}.task-popover .task-popover__priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-600)}.task-popover .task-popover__actions{display:flex;flex-direction:column;padding:12px 20px 16px;border-top:1px solid var(--ax-sys-color-border);gap:8px;background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__actions-group{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}ax-scheduler{background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);line-height:1.5}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--primary,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--primary{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-primary);color:var(--color-on-primary);border-color:var(--color-border-primary)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--secondary,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--secondary{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary);color:var(--color-on-secondary);border-color:var(--color-border-secondary)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--success,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--success,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--success{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-success);color:var(--color-on-success);border-color:var(--color-border-success)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--warning,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--warning{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-warning);color:var(--color-on-warning);border-color:var(--color-border-warning)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--danger,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--danger{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-danger);color:var(--color-on-danger);border-color:var(--color-border-danger)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent1,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent1{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent1);color:var(--color-on-accent1);border-color:var(--color-border-accent1)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent2,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent2{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent2);color:var(--color-on-accent2);border-color:var(--color-border-accent2)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--accent3,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--accent3{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-accent3);color:var(--color-on-accent3);border-color:var(--color-border-accent3)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--neutral,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--neutral{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary-lightest);color:var(--color-on-secondary-lightest);border-color:var(--color-border-secondary-lightest)}ax-scheduler .ax-scheduler-month-appointment-chip.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-month-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-day-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-week-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-week-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-timeline-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-timeline-spanning-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-header-day-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-header-week-appointment.axm-task-board-surface--ghost,ax-scheduler .ax-scheduler-agenda-appointment-time-inner.axm-task-board-surface--ghost{border-radius:var(--radius-sm, .25rem);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: solid;border-style:solid;background-color:var(--color-secondary-lighter);color:var(--color-on-secondary-lighter);border-color:var(--color-border-secondary-lighter)}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-font-weight: initial;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
481
+ }], ctorParameters: () => [], propDecorators: { schedulerComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXSchedulerComponent), { isSignal: true }] }], resources: [{ type: i0.Input, args: [{ isSignal: true, alias: "resources", required: false }] }], refreshNonce: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshNonce", required: false }] }], startingDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "startingDate", required: false }] }], selectedView: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedView", required: false }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], onTaskClick: [{ type: i0.Output, args: ["onTaskClick"] }], onTaskChanged: [{ type: i0.Output, args: ["onTaskChanged"] }], onMonthSlotDblClicked: [{ type: i0.Output, args: ["onMonthSlotDblClicked"] }], onActionClick: [{ type: i0.Output, args: ["onActionClick"] }], component: [{ type: i0.Output, args: ["component"] }], onTaskRightClick: [{ type: i0.Output, args: ["onTaskRightClick"] }], onRangeChanged: [{ type: i0.Output, args: ["onRangeChanged"] }] } });
482
+
483
+ class AXMTaskBoardGridViewComponent {
484
+ constructor() {
485
+ this.vm = inject(AXMTaskBoardViewModel);
486
+ this.localeService = inject(AXLocaleService);
487
+ this.calendarService = inject(AXCalendarService);
488
+ this.taskBoardService = inject(AXPTaskBoardService);
489
+ this.taskPopoverOverlay = inject(AXMTaskBoardTaskPopoverOverlayService);
490
+ this.translationService = inject(AXTranslationService);
491
+ this.destroyRef = inject(DestroyRef);
492
+ this.dataSource = input.required(/* @ts-ignore */
493
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
494
+ this.refreshNonce = input(0, /* @ts-ignore */
495
+ ...(ngDevMode ? [{ debugName: "refreshNonce" }] : /* istanbul ignore next */ []));
496
+ this.onTaskClick = output();
497
+ this.fetchedTasks = signal([], /* @ts-ignore */
498
+ ...(ngDevMode ? [{ debugName: "fetchedTasks" }] : /* istanbul ignore next */ []));
499
+ this.columns = signal([], /* @ts-ignore */
500
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
501
+ this.grid = viewChild('grid', /* @ts-ignore */
502
+ ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
503
+ this.taskRowCommandWidth = TASK_BOARD_GRID_ROW_ACTION_COLUMN_WIDTH;
504
+ this.taskRowCommandItems = TASK_BOARD_GRID_ROW_ACTION_ITEMS;
505
+ this.gridRemountKeys = computed(() => [this.refreshNonce()], /* @ts-ignore */
506
+ ...(ngDevMode ? [{ debugName: "gridRemountKeys" }] : /* istanbul ignore next */ []));
507
+ this.tasksDataSource = computed(() => {
508
+ const parentDataSource = this.dataSource();
509
+ void this.refreshNonce();
510
+ return new AXDataSource({
511
+ load: async (e) => {
512
+ const result = await parentDataSource({ skip: e.skip, take: e.take });
513
+ this.fetchedTasks.set(result.items);
514
+ const unassignedLabel = await this.translationService.translateAsync('@task-management:task-board.filter.unassigned');
515
+ const mappedItems = await Promise.all(result.items.map(async (task) => {
516
+ const provider = this.taskBoardService.getProvider(task.provider);
517
+ return {
518
+ id: task.id,
519
+ data: task.data,
520
+ title: task.title,
521
+ priority: task.priority,
522
+ description: task.description,
523
+ statusTitle: task.status.title,
524
+ reporter: task.reporter.fullName ?? '-',
525
+ assignee: task.assignee?.fullName ?? unassignedLabel,
526
+ type: provider ? await provider.getTitle() : undefined,
527
+ endDate: this.calendarService.format(task.endDate, 'DD/MM/YYYY HH:mm', {
528
+ locale: this.localeService.activeProfile().calendar.system,
529
+ }),
530
+ startDate: this.calendarService.format(task.startDate, 'DD/MM/YYYY HH:mm', {
531
+ locale: this.localeService.activeProfile().calendar.system,
532
+ }),
533
+ _originalTask: task,
534
+ };
535
+ }));
536
+ return { items: mappedItems, total: result.total };
537
+ },
538
+ pageSize: 10,
539
+ key: 'id',
540
+ byKey: async (key) => {
541
+ return this.fetchedTasks().find((task) => task.id === key);
542
+ },
543
+ });
544
+ }, /* @ts-ignore */
545
+ ...(ngDevMode ? [{ debugName: "tasksDataSource" }] : /* istanbul ignore next */ []));
546
+ this.#gridManualFetchEffect = effect(() => {
547
+ this.vm.selectedTaskTypeNames();
548
+ this.vm.selectedAssigneeIds();
549
+ this.vm.selectedReporterIds();
550
+ this.vm.selectedPriorities();
551
+ this.vm.currentDate();
552
+ this.vm.daysCount();
553
+ this.vm.currentViewMode();
554
+ this.refreshNonce();
555
+ const grid = this.grid();
556
+ if (!grid) {
557
+ return;
558
+ }
559
+ queueMicrotask(() => {
560
+ untracked(() => {
561
+ grid.refresh({ reset: true });
562
+ });
563
+ });
564
+ }, /* @ts-ignore */
565
+ ...(ngDevMode ? [{ debugName: "#gridManualFetchEffect" }] : /* istanbul ignore next */ []));
566
+ this.#providerChanged = effect(async () => {
567
+ const taskType = this.vm.selectedTaskTypes()[0];
568
+ if (!taskType) {
569
+ this.columns.set([]);
570
+ return;
571
+ }
572
+ taskType.getExtraFields().then((extraFields) => {
573
+ if (extraFields) {
574
+ this.columns.set(extraFields);
575
+ }
576
+ });
577
+ }, /* @ts-ignore */
578
+ ...(ngDevMode ? [{ debugName: "#providerChanged" }] : /* istanbul ignore next */ []));
579
+ this.taskBoardService.close$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => {
580
+ const active = this.taskPopoverOverlay.getActiveTask();
581
+ if (!active?.provider) {
582
+ return;
583
+ }
584
+ if (data.provider && data.provider !== active.provider) {
585
+ return;
586
+ }
587
+ this.taskPopoverOverlay.close();
588
+ });
589
+ }
590
+ #gridManualFetchEffect;
591
+ #providerChanged;
592
+ handleRowClick(event) {
593
+ if (event.data) {
594
+ const mappedItem = event.data;
595
+ const originalTask = mappedItem._originalTask || this.fetchedTasks().find((task) => task.id === mappedItem.id);
596
+ if (originalTask) {
597
+ this.onTaskClick.emit(originalTask);
598
+ }
599
+ }
600
+ }
601
+ handleTaskCommandClick(event) {
602
+ if (event.name !== 'view') {
603
+ return;
604
+ }
605
+ event.nativeEvent?.stopPropagation();
606
+ const mappedItem = event.data;
607
+ const task = mappedItem._originalTask ?? this.fetchedTasks().find((t) => t.id === mappedItem.id);
608
+ if (!task) {
609
+ return;
610
+ }
611
+ const anchor = this.taskPopoverOverlay.resolveAnchorFromCommandClick(event);
612
+ if (!anchor) {
613
+ return;
614
+ }
615
+ setTimeout(() => {
616
+ void this.taskPopoverOverlay.open({
617
+ task,
618
+ anchor,
619
+ placement: 'bottom-start',
620
+ adaptivityEnabled: false,
621
+ autoFlip: true,
622
+ });
623
+ }, 0);
624
+ }
625
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardGridViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
626
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardGridViewComponent, isStandalone: true, selector: "axm-task-board-grid-view", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, refreshNonce: { classPropertyName: "refreshNonce", publicName: "refreshNonce", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onTaskClick: "onTaskClick" }, viewQueries: [{ propertyName: "grid", first: true, predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (remountKey of gridRemountKeys(); track remountKey) {\n <ax-data-table\n #grid\n [fetchDataMode]=\"'manual'\"\n [dataSource]=\"tasksDataSource()\"\n (onRowClick)=\"handleRowClick($event)\"\n [loading]=\"{ enabled: true, animation: true }\"\n >\n <ax-text-column\n dataField=\"title\"\n [caption]=\"('@task-management:task-board.grid-headers.title' | translate | async)!\"\n [width]=\"'350px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"description\"\n [caption]=\"('@task-management:task-board.grid-headers.description' | translate | async)!\"\n [width]=\"'500px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"assignee\"\n [caption]=\"('@task-management:task-board.grid-headers.assignee' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"startDate\"\n [caption]=\"('@task-management:task-board.grid-headers.start-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"endDate\"\n [caption]=\"('@task-management:task-board.grid-headers.due-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"priority\"\n [caption]=\"('@task-management:task-board.grid-headers.priority' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n [allowResizing]=\"true\"\n dataField=\"reporter\"\n [caption]=\"('@task-management:task-board.grid-headers.reporter' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"statusTitle\"\n [caption]=\"('@task-management:task-board.grid-headers.status' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"type\"\n [caption]=\"('@task-management:task-board.grid-headers.type' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n\n @if (columns().length) {\n <ng-container>\n @for (column of columns(); track column) {\n <axp-widget-column-renderer\n [node]=\"column.widget\"\n [caption]=\"(column.title | translate | async)!\"\n ></axp-widget-column-renderer>\n }\n </ng-container>\n }\n\n <ax-command-column\n fixed=\"end\"\n [width]=\"taskRowCommandWidth\"\n [items]=\"taskRowCommandItems\"\n (onItemClick)=\"handleTaskCommandClick($event)\"\n ></ax-command-column>\n </ax-data-table>\n}\n", dependencies: [{ kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1$2.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1$2.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "component", type: i1$2.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i1$1.AXPWidgetColumnRendererComponent, selector: "axp-widget-column-renderer", inputs: ["caption", "customExpandIcon", "customCollapseIcon", "customWidth", "node", "footerTemplate", "expandHandler", "sortEnabled", "headerSortDirection", "headerSortPriority", "columnFixed", "cellTemplate", "headerTemplate"], outputs: ["sortToggle"] }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
627
+ }
628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardGridViewComponent, decorators: [{
629
+ type: Component,
630
+ args: [{ selector: 'axm-task-board-grid-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXDataTableModule, AXTranslatorPipe, AsyncPipe, AXPWidgetCoreModule], template: "@for (remountKey of gridRemountKeys(); track remountKey) {\n <ax-data-table\n #grid\n [fetchDataMode]=\"'manual'\"\n [dataSource]=\"tasksDataSource()\"\n (onRowClick)=\"handleRowClick($event)\"\n [loading]=\"{ enabled: true, animation: true }\"\n >\n <ax-text-column\n dataField=\"title\"\n [caption]=\"('@task-management:task-board.grid-headers.title' | translate | async)!\"\n [width]=\"'350px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"description\"\n [caption]=\"('@task-management:task-board.grid-headers.description' | translate | async)!\"\n [width]=\"'500px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"assignee\"\n [caption]=\"('@task-management:task-board.grid-headers.assignee' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"startDate\"\n [caption]=\"('@task-management:task-board.grid-headers.start-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"endDate\"\n [caption]=\"('@task-management:task-board.grid-headers.due-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"priority\"\n [caption]=\"('@task-management:task-board.grid-headers.priority' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n [allowResizing]=\"true\"\n dataField=\"reporter\"\n [caption]=\"('@task-management:task-board.grid-headers.reporter' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"statusTitle\"\n [caption]=\"('@task-management:task-board.grid-headers.status' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"type\"\n [caption]=\"('@task-management:task-board.grid-headers.type' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n\n @if (columns().length) {\n <ng-container>\n @for (column of columns(); track column) {\n <axp-widget-column-renderer\n [node]=\"column.widget\"\n [caption]=\"(column.title | translate | async)!\"\n ></axp-widget-column-renderer>\n }\n </ng-container>\n }\n\n <ax-command-column\n fixed=\"end\"\n [width]=\"taskRowCommandWidth\"\n [items]=\"taskRowCommandItems\"\n (onItemClick)=\"handleTaskCommandClick($event)\"\n ></ax-command-column>\n </ax-data-table>\n}\n" }]
631
+ }], ctorParameters: () => [], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], refreshNonce: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshNonce", required: false }] }], onTaskClick: [{ type: i0.Output, args: ["onTaskClick"] }], grid: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
632
+
633
+ class AXMTaskBoardKanbanViewComponent {
634
+ constructor() {
635
+ this.vm = inject(AXMTaskBoardViewModel);
636
+ this.taskBoardService = inject(AXPTaskBoardService);
637
+ this.settingService = inject(AXPSettingsService);
638
+ this.commandService = inject(AXPCommandService);
639
+ this.taskPopoverOverlay = inject(AXMTaskBoardTaskPopoverOverlayService);
640
+ this.destroyRef = inject(DestroyRef);
641
+ this.kanbanComponent = viewChild(AXKanbanComponent, /* @ts-ignore */
642
+ ...(ngDevMode ? [{ debugName: "kanbanComponent" }] : /* istanbul ignore next */ []));
643
+ this.tasks = model.required(/* @ts-ignore */
644
+ ...(ngDevMode ? [{ debugName: "tasks" }] : /* istanbul ignore next */ []));
645
+ this.onTaskClick = output();
646
+ this.onTaskChanged = output();
647
+ this.component = output();
648
+ this.onActionClick = output();
649
+ this.statuses = signal([], /* @ts-ignore */
650
+ ...(ngDevMode ? [{ debugName: "statuses" }] : /* istanbul ignore next */ []));
651
+ this.stableTasks = computed(() => this.tasks(), { ...(ngDevMode ? { debugName: "stableTasks" } : /* istanbul ignore next */ {}), equal: isEqual });
652
+ this.kanbanTasks = computed(() => {
653
+ return this.stableTasks().map((task) => ({
654
+ id: task.id,
655
+ title: task.title,
656
+ index: task.index,
657
+ cssClass: task.cssClass,
658
+ statusKey: task.status.id,
659
+ description: task.description,
660
+ }));
661
+ }, /* @ts-ignore */
662
+ ...(ngDevMode ? [{ debugName: "kanbanTasks" }] : /* istanbul ignore next */ []));
663
+ this.#providerChanged = effect(async () => {
664
+ const taskType = this.vm.selectedTaskTypes()[0];
665
+ if (!taskType) {
666
+ this.statuses.set([]);
667
+ return;
668
+ }
669
+ taskType.getStatuses().then((statuses) => {
670
+ if (statuses) {
671
+ this.statuses.set(statuses);
672
+ }
673
+ });
674
+ }, /* @ts-ignore */
675
+ ...(ngDevMode ? [{ debugName: "#providerChanged" }] : /* istanbul ignore next */ []));
676
+ }
677
+ ngAfterViewInit() {
678
+ this.taskBoardService.refresh$
679
+ .pipe(takeUntilDestroyed(this.destroyRef))
680
+ .subscribe(async (data) => {
681
+ const active = this.taskPopoverOverlay.getActiveTask();
682
+ if (!active?.provider) {
683
+ return;
684
+ }
685
+ if (data.provider && data.provider !== active.provider) {
686
+ return;
687
+ }
688
+ await this.refreshPopoverData();
689
+ });
690
+ this.taskBoardService.close$
691
+ .pipe(takeUntilDestroyed(this.destroyRef))
692
+ .subscribe((data) => {
693
+ const active = this.taskPopoverOverlay.getActiveTask();
694
+ if (!active?.provider) {
695
+ return;
696
+ }
697
+ if (data.provider && data.provider !== active.provider) {
698
+ return;
699
+ }
700
+ this.taskPopoverOverlay.close();
701
+ });
702
+ if (this.kanbanComponent()) {
703
+ this.component.emit(this.kanbanComponent());
704
+ }
705
+ }
706
+ #providerChanged;
707
+ //#region ---- Drag & Drop Handling ----
708
+ /**
709
+ * Handles the onBeforeDrop event from the kanban component.
710
+ * Uses event.wait() to register async operations that the kanban will await.
711
+ * If action is cancelled/failed, we set event.canceled = true to prevent the drop.
712
+ */
713
+ onBeforeDrop(event) {
714
+ // If no status change, allow the drop (just reordering)
715
+ if (event.previousStatusKey === event.currentStatusKey) {
716
+ return;
717
+ }
718
+ // Find the task being moved
719
+ const task = this.stableTasks().find((t) => t.id === event.item?.id);
720
+ if (!task || !task.provider) {
721
+ // Allow drop if task not found or no provider
722
+ return;
723
+ }
724
+ // Get the provider
725
+ const provider = this.taskBoardService.getProvider(task.provider);
726
+ if (!provider) {
727
+ return;
728
+ }
729
+ // Register the async operation using event.wait()
730
+ // The kanban component will await this promise before proceeding
731
+ event.wait(this.executeDropAction(event, task, provider));
732
+ }
733
+ /**
734
+ * Executes the action for a drop operation.
735
+ * Sets event.canceled = true if action fails or is cancelled.
736
+ * IMPORTANT: Do NOT update the task status in dataSource here - let the kanban handle the move.
737
+ * The kanban will update the status in onSortChanged, and we'll sync with backend there.
738
+ */
739
+ async executeDropAction(event, task, provider) {
740
+ if (!provider) {
741
+ return;
742
+ }
743
+ try {
744
+ // Get available actions for the task
745
+ const actions = await provider.getActions(task);
746
+ if (!actions || actions.length === 0) {
747
+ // No actions available, allow the drop
748
+ return;
749
+ }
750
+ // Find action that matches the new status
751
+ const matchingAction = this.findActionForStatus(actions, event.currentStatusKey);
752
+ if (!matchingAction || !matchingAction.command) {
753
+ // No matching action found, allow the drop
754
+ return;
755
+ }
756
+ // Execute the action and check result
757
+ const result = await this.taskBoardService.executeCommand(matchingAction.command, task.provider);
758
+ // Check if action was successful (not cancelled or failed)
759
+ if (result?.success === false || result === null) {
760
+ // Action was cancelled or failed - prevent the drop
761
+ event.canceled = true;
762
+ return;
763
+ }
764
+ // Action succeeded - allow the drop to proceed
765
+ // The kanban will handle the visual move, and onSortChanged will sync with backend
766
+ }
767
+ catch (error) {
768
+ console.error('[KanbanDrop] Failed to execute action during drop:', error);
769
+ // On error, prevent the drop to be safe
770
+ event.canceled = true;
771
+ }
772
+ }
773
+ /**
774
+ * Finds an action that corresponds to the target status.
775
+ * Matches actions by name patterns (e.g., "approve" action for "approved" status).
776
+ */
777
+ findActionForStatus(actions, targetStatus) {
778
+ const targetStatusStr = String(targetStatus).toLowerCase();
779
+ // Common status-to-action mappings
780
+ const statusActionMap = {
781
+ approved: ['approve'],
782
+ rejected: ['reject'],
783
+ cancelled: ['cancel'],
784
+ completed: ['complete', 'finish'],
785
+ done: ['complete', 'finish'],
786
+ inprogress: ['start', 'begin'],
787
+ 'in-progress': ['start', 'begin'],
788
+ blocked: ['block'],
789
+ unblocked: ['unblock'],
790
+ };
791
+ // Check direct mappings first
792
+ const possibleActionNames = statusActionMap[targetStatusStr] || [];
793
+ for (const actionName of possibleActionNames) {
794
+ const action = actions.find((a) => a.name?.toLowerCase().includes(actionName));
795
+ if (action) {
796
+ return action;
797
+ }
798
+ }
799
+ // Try to find action by name containing status keywords
800
+ const statusKeywords = targetStatusStr.split(/[-_\s]+/);
801
+ for (const keyword of statusKeywords) {
802
+ if (keyword.length < 3)
803
+ continue; // Skip very short keywords
804
+ const action = actions.find((a) => {
805
+ const actionName = a.name?.toLowerCase() || '';
806
+ return actionName.includes(keyword) || keyword.includes(actionName);
807
+ });
808
+ if (action) {
809
+ return action;
810
+ }
811
+ }
812
+ // Try exact match with action name
813
+ const exactMatch = actions.find((a) => a.name?.toLowerCase() === targetStatusStr);
814
+ if (exactMatch) {
815
+ return exactMatch;
816
+ }
817
+ // Try partial match (status contains action name or vice versa)
818
+ for (const action of actions) {
819
+ const actionName = action.name?.toLowerCase() || '';
820
+ if (targetStatusStr.includes(actionName) || actionName.includes(targetStatusStr)) {
821
+ return action;
822
+ }
823
+ }
824
+ return null;
825
+ }
826
+ //#endregion
827
+ onSortChanged(event) {
828
+ const tasks = this.tasks().map((item) => {
829
+ const task = event.allItems.find((t) => t.id === item.id);
830
+ if (!task) {
831
+ return item;
832
+ }
833
+ const status = this.statuses().find((s) => String(s.key) === String(task.statusKey));
834
+ if (!status) {
835
+ return { ...item, index: task.index ?? item.index };
836
+ }
837
+ return {
838
+ ...item,
839
+ index: task.index ?? item.index,
840
+ status: { id: status.key, title: status.title },
841
+ };
842
+ });
843
+ this.taskBoardService
844
+ .updateTasks(tasks)
845
+ .then((updatedTasks) => {
846
+ this.tasks.set(updatedTasks);
847
+ })
848
+ .catch((error) => {
849
+ console.error('[KanbanSort] Failed to update tasks:', error);
850
+ const provider = tasks[0]?.provider;
851
+ if (provider) {
852
+ this.taskBoardService.requestRefresh(provider);
853
+ }
854
+ });
855
+ }
856
+ async handleItemClick(event) {
857
+ const task = this.stableTasks().find((task) => task.id === event.item.id);
858
+ if (!task)
859
+ return;
860
+ // Find the kanban item container element
861
+ let targetElement = event.nativeEvent.currentTarget || event.nativeEvent.target;
862
+ // Traverse up to find the kanban item container
863
+ let element = targetElement;
864
+ const maxDepth = 10;
865
+ let depth = 0;
866
+ while (element && element !== document.body && depth < maxDepth) {
867
+ depth++;
868
+ const isKanbanItem = element.classList?.contains('dx-kanban-item') ||
869
+ element.classList?.contains('ax-kanban-item') ||
870
+ element.hasAttribute('data-item-id');
871
+ if (isKanbanItem) {
872
+ targetElement = element;
873
+ break;
874
+ }
875
+ element = element.parentElement;
876
+ }
877
+ if (targetElement) {
878
+ await this.taskPopoverOverlay.open({
879
+ task,
880
+ anchor: targetElement,
881
+ offsetX: 12,
882
+ offsetY: 12,
883
+ adaptivityEnabled: true,
884
+ });
885
+ }
886
+ this.onTaskClick.emit(task);
887
+ }
888
+ handleActionClick(event) {
889
+ const task = this.stableTasks().find((task) => task.id === event.item.id);
890
+ if (task) {
891
+ this.onActionClick.emit({ nativeEvent: event.nativeEvent, task: task });
892
+ }
893
+ }
894
+ async refreshPopoverData() {
895
+ const active = this.taskPopoverOverlay.getActiveTask();
896
+ if (!active?.provider) {
897
+ return;
898
+ }
899
+ const provider = this.taskBoardService.getProvider(active.provider);
900
+ if (!provider) {
901
+ return;
902
+ }
903
+ try {
904
+ const result = await provider.getTasks();
905
+ const updatedTasks = result.items.map((t) => ({ ...t, provider: active.provider }));
906
+ const updatedMap = new Map(updatedTasks.map((t) => [t.id, t]));
907
+ const currentTasks = this.tasks();
908
+ const mergedTasks = currentTasks.map((t) => updatedMap.get(t.id) ?? t);
909
+ const existingIds = new Set(currentTasks.map((t) => t.id));
910
+ const newTasks = updatedTasks.filter((t) => !existingIds.has(t.id));
911
+ this.tasks.set([...mergedTasks, ...newTasks]);
912
+ const updatedSelectedTask = updatedMap.get(active.id);
913
+ if (updatedSelectedTask) {
914
+ await this.taskPopoverOverlay.refreshTask({ ...updatedSelectedTask, provider: active.provider });
915
+ }
916
+ }
917
+ catch (error) {
918
+ console.error('[TaskPopover] Failed to refresh popover data:', error);
919
+ }
920
+ }
921
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardKanbanViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
922
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", type: AXMTaskBoardKanbanViewComponent, isStandalone: true, selector: "axm-task-board-kanban-view", inputs: { tasks: { classPropertyName: "tasks", publicName: "tasks", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { tasks: "tasksChange", onTaskClick: "onTaskClick", onTaskChanged: "onTaskChanged", component: "component", onActionClick: "onActionClick" }, viewQueries: [{ propertyName: "kanbanComponent", first: true, predicate: AXKanbanComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<ax-kanban\n [statuses]=\"statuses()\"\n [dataSource]=\"kanbanTasks()\"\n (onItemClick)=\"handleItemClick($event)\"\n (onBeforeDrop)=\"onBeforeDrop($event)\"\n (onSortChanged)=\"onSortChanged($event)\"\n></ax-kanban>\n", styles: ["@layer properties;.task-popover{display:flex;flex-direction:column;overflow:hidden;background:rgba(var(--ax-sys-color-lightest-surface));border-radius:12px;min-width:280px;max-width:420px}.task-popover .task-popover__header{padding:20px 20px 16px;border-bottom:1px solid var(--ax-sys-color-border);background:linear-gradient(to bottom,var(--ax-sys-color-lightest-surface),var(--ax-sys-color-lighter-surface))}.task-popover .task-popover__title-section{display:flex;flex-direction:column;gap:8px}.task-popover .task-popover__title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-popover .task-popover__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:18px;color:var(--ax-sys-color-text-primary)}.task-popover .task-popover__close-btn{display:flex;flex-shrink:0;cursor:pointer;align-items:center;justify-content:center;border-style:var(--tw-border-style);border-width:0px;background:none;padding:4px;color:var(--ax-sys-color-text-neutral-600);transition:color .2s ease;border-radius:4px}.task-popover .task-popover__close-btn:hover{color:var(--ax-sys-color-text-primary);background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__close-btn:active{background:var(--ax-sys-color-light-surface)}.task-popover .task-popover__close-btn ax-icon{display:block;width:16px;height:16px}.task-popover .task-popover__body{display:flex;flex-direction:column}.task-popover .task-popover__description{margin:0;overflow:hidden;text-overflow:ellipsis;font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-secondary);padding:0 20px 12px;display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical}.task-popover .task-popover__content{display:flex;flex:1;flex-direction:column;padding:16px 20px;gap:12px}.task-popover .task-popover__info-item{display:flex;align-items:flex-start;gap:12px;min-height:24px}.task-popover .task-popover__icon{display:flex;flex-shrink:0;align-items:center;justify-content:center;width:20px;height:20px;margin-top:2px;color:var(--ax-sys-color-text-neutral-600);font-size:14px}.task-popover .task-popover__icon i{display:block}.task-popover .task-popover__icon--priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__icon--priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__icon--priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__icon--priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-500)}.task-popover .task-popover__info-content{display:flex;min-width:0px;flex:1;align-items:center}.task-popover .task-popover__info-text{font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-primary);word-wrap:break-word}.task-popover .task-popover__priority{--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);text-transform:capitalize}.task-popover .task-popover__priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-600)}.task-popover .task-popover__actions{display:flex;flex-direction:column;padding:12px 20px 16px;border-top:1px solid var(--ax-sys-color-border);gap:8px;background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__actions-group{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}ax-kanban{--ax-comp-kanban-status-min-height: 100%;--ax-comp-kanban-status-min-width: 25rem;height:100%;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);line-height:1.5}ax-kanban .ax-kanban-item.axm-task-board-surface--primary{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-primary-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--secondary{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-secondary-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--success{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-success-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--warning{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-warning-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--danger{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-danger-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent1{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent1-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent2{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent2-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent3{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent3-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--neutral,ax-kanban .ax-kanban-item.axm-task-board-surface--ghost{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-lighter-surface))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-font-weight: initial;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXKanbanComponent, selector: "ax-kanban", inputs: ["hasActions", "dragStartDelay", "statuses", "itemTemplate", "dataSource", "keyField", "indexField", "titleField", "cssClassField", "priorityField", "statusKeyField", "descriptionField", "emptyTemplate", "headerTemplate", "footerTemplate", "contentFooterTemplate", "tooltipTemplate"], outputs: ["onBeforeDrop", "onSortChanged", "onItemClick", "onActionClick", "onItemDblClick", "onItemRightClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
923
+ }
924
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardKanbanViewComponent, decorators: [{
925
+ type: Component,
926
+ args: [{ selector: 'axm-task-board-kanban-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXKanbanComponent], template: "<ax-kanban\n [statuses]=\"statuses()\"\n [dataSource]=\"kanbanTasks()\"\n (onItemClick)=\"handleItemClick($event)\"\n (onBeforeDrop)=\"onBeforeDrop($event)\"\n (onSortChanged)=\"onSortChanged($event)\"\n></ax-kanban>\n", styles: ["@layer properties;.task-popover{display:flex;flex-direction:column;overflow:hidden;background:rgba(var(--ax-sys-color-lightest-surface));border-radius:12px;min-width:280px;max-width:420px}.task-popover .task-popover__header{padding:20px 20px 16px;border-bottom:1px solid var(--ax-sys-color-border);background:linear-gradient(to bottom,var(--ax-sys-color-lightest-surface),var(--ax-sys-color-lighter-surface))}.task-popover .task-popover__title-section{display:flex;flex-direction:column;gap:8px}.task-popover .task-popover__title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.task-popover .task-popover__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);font-size:18px;color:var(--ax-sys-color-text-primary)}.task-popover .task-popover__close-btn{display:flex;flex-shrink:0;cursor:pointer;align-items:center;justify-content:center;border-style:var(--tw-border-style);border-width:0px;background:none;padding:4px;color:var(--ax-sys-color-text-neutral-600);transition:color .2s ease;border-radius:4px}.task-popover .task-popover__close-btn:hover{color:var(--ax-sys-color-text-primary);background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__close-btn:active{background:var(--ax-sys-color-light-surface)}.task-popover .task-popover__close-btn ax-icon{display:block;width:16px;height:16px}.task-popover .task-popover__body{display:flex;flex-direction:column}.task-popover .task-popover__description{margin:0;overflow:hidden;text-overflow:ellipsis;font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-secondary);padding:0 20px 12px;display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical}.task-popover .task-popover__content{display:flex;flex:1;flex-direction:column;padding:16px 20px;gap:12px}.task-popover .task-popover__info-item{display:flex;align-items:flex-start;gap:12px;min-height:24px}.task-popover .task-popover__icon{display:flex;flex-shrink:0;align-items:center;justify-content:center;width:20px;height:20px;margin-top:2px;color:var(--ax-sys-color-text-neutral-600);font-size:14px}.task-popover .task-popover__icon i{display:block}.task-popover .task-popover__icon--priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__icon--priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__icon--priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__icon--priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-500)}.task-popover .task-popover__info-content{display:flex;min-width:0px;flex:1;align-items:center}.task-popover .task-popover__info-text{font-size:14px;line-height:1.5;color:var(--ax-sys-color-text-primary);word-wrap:break-word}.task-popover .task-popover__priority{--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);text-transform:capitalize}.task-popover .task-popover__priority[data-priority=urgent]{color:var(--ax-sys-color-danger)}.task-popover .task-popover__priority[data-priority=high]{color:var(--ax-sys-color-warning)}.task-popover .task-popover__priority[data-priority=normal]{color:rgb(var(--ax-sys-color-secondary-500))}.task-popover .task-popover__priority[data-priority=low]{color:var(--ax-sys-color-text-neutral-600)}.task-popover .task-popover__actions{display:flex;flex-direction:column;padding:12px 20px 16px;border-top:1px solid var(--ax-sys-color-border);gap:8px;background:var(--ax-sys-color-lighter-surface)}.task-popover .task-popover__actions-group{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}ax-kanban{--ax-comp-kanban-status-min-height: 100%;--ax-comp-kanban-status-min-width: 25rem;height:100%;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);line-height:1.5}ax-kanban .ax-kanban-item.axm-task-board-surface--primary{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-primary-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--secondary{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-secondary-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--success{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-success-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--warning{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-warning-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--danger{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-danger-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent1{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent1-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent2{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent2-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--accent3{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-accent3-surface))}ax-kanban .ax-kanban-item.axm-task-board-surface--neutral,ax-kanban .ax-kanban-item.axm-task-board-surface--ghost{border-style:var(--tw-border-style);border-width:1px;border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);border-color:rgba(var(--ax-sys-color-lightest-surface));border-inline-start-color:rgba(var(--ax-sys-color-lighter-surface))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-font-weight: initial;--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
927
+ }], propDecorators: { kanbanComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXKanbanComponent), { isSignal: true }] }], tasks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tasks", required: true }] }, { type: i0.Output, args: ["tasksChange"] }], onTaskClick: [{ type: i0.Output, args: ["onTaskClick"] }], onTaskChanged: [{ type: i0.Output, args: ["onTaskChanged"] }], component: [{ type: i0.Output, args: ["component"] }], onActionClick: [{ type: i0.Output, args: ["onActionClick"] }] } });
928
+
929
+ function statusGroupTrackKey(nonce, group) {
930
+ return `${nonce}:${String(group.statusInfo.key)}`;
931
+ }
932
+ function taskMatchesStatus(task, status) {
933
+ return String(task.status.id) === String(status.key);
934
+ }
935
+ class AXMTaskBoardStatusGroupedGridViewComponent {
936
+ constructor() {
937
+ this.vm = inject(AXMTaskBoardViewModel);
938
+ this.localeService = inject(AXLocaleService);
939
+ this.calendarService = inject(AXCalendarService);
940
+ this.taskBoardService = inject(AXPTaskBoardService);
941
+ this.taskPopoverOverlay = inject(AXMTaskBoardTaskPopoverOverlayService);
942
+ this.translationService = inject(AXTranslationService);
943
+ this.destroyRef = inject(DestroyRef);
944
+ this.provider = input(/* @ts-ignore */
945
+ ...(ngDevMode ? [undefined, { debugName: "provider" }] : /* istanbul ignore next */ []));
946
+ this.dataSource = input.required(/* @ts-ignore */
947
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
948
+ this.refreshNonce = input(0, /* @ts-ignore */
949
+ ...(ngDevMode ? [{ debugName: "refreshNonce" }] : /* istanbul ignore next */ []));
950
+ this.onTaskClick = output();
951
+ this.groupedTasks = signal([], /* @ts-ignore */
952
+ ...(ngDevMode ? [{ debugName: "groupedTasks" }] : /* istanbul ignore next */ []));
953
+ this.allFetchedTasks = signal([], /* @ts-ignore */
954
+ ...(ngDevMode ? [{ debugName: "allFetchedTasks" }] : /* istanbul ignore next */ []));
955
+ this.columns = signal([], /* @ts-ignore */
956
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
957
+ this.statuses = signal([], /* @ts-ignore */
958
+ ...(ngDevMode ? [{ debugName: "statuses" }] : /* istanbul ignore next */ []));
959
+ this.grids = viewChildren('grid', /* @ts-ignore */
960
+ ...(ngDevMode ? [{ debugName: "grids" }] : /* istanbul ignore next */ []));
961
+ this.taskRowCommandWidth = TASK_BOARD_GRID_ROW_ACTION_COLUMN_WIDTH;
962
+ this.taskRowCommandItems = TASK_BOARD_GRID_ROW_ACTION_ITEMS;
963
+ this.trackStatusGroup = (_index, group) => statusGroupTrackKey(this.refreshNonce(), group);
964
+ this.groupedViewRemountKeys = computed(() => [this.refreshNonce()], /* @ts-ignore */
965
+ ...(ngDevMode ? [{ debugName: "groupedViewRemountKeys" }] : /* istanbul ignore next */ []));
966
+ this.#dataChanged = effect(() => {
967
+ this.vm.selectedTaskTypeNames();
968
+ this.vm.selectedAssigneeIds();
969
+ this.vm.selectedReporterIds();
970
+ this.vm.selectedPriorities();
971
+ this.vm.currentDate();
972
+ this.vm.daysCount();
973
+ this.vm.currentViewMode();
974
+ this.refreshNonce();
975
+ const allStatuses = this.statuses();
976
+ const parentDataSource = this.dataSource();
977
+ if (!parentDataSource || allStatuses.length === 0) {
978
+ this.groupedTasks.set([]);
979
+ this.allFetchedTasks.set([]);
980
+ return;
981
+ }
982
+ void parentDataSource().then((result) => {
983
+ this.allFetchedTasks.set(result.items);
984
+ const allTasks = result.items;
985
+ const statusGroups = allStatuses.map((status) => {
986
+ const tasksForStatus = allTasks.filter((task) => taskMatchesStatus(task, status));
987
+ return {
988
+ statusInfo: status,
989
+ tasks: this.createTaskDataSourceForGroup(tasksForStatus),
990
+ };
991
+ });
992
+ this.groupedTasks.set(statusGroups);
993
+ untracked(() => {
994
+ setTimeout(() => {
995
+ this.groupedTasks()?.forEach((group) => {
996
+ group.tasks.refresh();
997
+ });
998
+ this.grids().forEach((grid) => {
999
+ grid.refresh({ reset: true });
1000
+ });
1001
+ }, 0);
1002
+ });
1003
+ });
1004
+ }, /* @ts-ignore */
1005
+ ...(ngDevMode ? [{ debugName: "#dataChanged" }] : /* istanbul ignore next */ []));
1006
+ this.#providerChanged = effect(async () => {
1007
+ const provider = this.vm.selectedTaskTypes()[0];
1008
+ provider?.getExtraFields().then((extraFields) => {
1009
+ if (extraFields) {
1010
+ this.columns.set(extraFields);
1011
+ }
1012
+ });
1013
+ provider?.getStatuses().then((statuses) => {
1014
+ if (statuses) {
1015
+ this.statuses.set(statuses);
1016
+ }
1017
+ });
1018
+ }, /* @ts-ignore */
1019
+ ...(ngDevMode ? [{ debugName: "#providerChanged" }] : /* istanbul ignore next */ []));
1020
+ this.#gridsChanged = effect(() => {
1021
+ const groups = this.groupedTasks();
1022
+ untracked(() => {
1023
+ setTimeout(() => {
1024
+ this.grids().forEach((grid) => {
1025
+ grid.refresh({ reset: true });
1026
+ });
1027
+ }, 100);
1028
+ });
1029
+ }, /* @ts-ignore */
1030
+ ...(ngDevMode ? [{ debugName: "#gridsChanged" }] : /* istanbul ignore next */ []));
1031
+ afterNextRender(() => {
1032
+ untracked(() => {
1033
+ setTimeout(() => {
1034
+ this.groupedTasks()?.forEach((group) => {
1035
+ group.tasks.refresh();
1036
+ });
1037
+ this.grids().forEach((grid) => {
1038
+ grid.refresh({ reset: true });
1039
+ });
1040
+ }, 200);
1041
+ });
1042
+ });
1043
+ this.taskBoardService.close$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => {
1044
+ const active = this.taskPopoverOverlay.getActiveTask();
1045
+ if (!active?.provider) {
1046
+ return;
1047
+ }
1048
+ if (data.provider && data.provider !== active.provider) {
1049
+ return;
1050
+ }
1051
+ this.taskPopoverOverlay.close();
1052
+ });
1053
+ }
1054
+ #dataChanged;
1055
+ #providerChanged;
1056
+ #gridsChanged;
1057
+ createTaskDataSourceForGroup(tasks) {
1058
+ return new AXDataSource({
1059
+ load: async (e) => {
1060
+ const unassignedLabel = await this.translationService.translateAsync('@task-management:task-board.filter.unassigned');
1061
+ const items = tasks.map((task) => ({
1062
+ id: task.id,
1063
+ data: task.data,
1064
+ title: task.title,
1065
+ priority: task.priority,
1066
+ description: task.description,
1067
+ reporter: task.reporter.fullName ?? '-',
1068
+ assignee: task.assignee?.fullName ?? unassignedLabel,
1069
+ endDate: this.calendarService.format(task.endDate, 'DD/MM/YYYY HH:mm', {
1070
+ locale: this.localeService.activeProfile().calendar.system,
1071
+ }),
1072
+ startDate: this.calendarService.format(task.startDate, 'DD/MM/YYYY HH:mm', {
1073
+ locale: this.localeService.activeProfile().calendar.system,
1074
+ }),
1075
+ _originalTask: task,
1076
+ }));
1077
+ return {
1078
+ items: items.slice(e.skip, e.skip + e.take),
1079
+ total: items.length,
1080
+ };
1081
+ },
1082
+ pageSize: 5,
1083
+ key: 'id',
1084
+ byKey: async (key) => {
1085
+ return tasks.find((task) => task.id === key);
1086
+ },
1087
+ });
1088
+ }
1089
+ handleRowClick(event) {
1090
+ if (!event.data) {
1091
+ return;
1092
+ }
1093
+ const mappedItem = event.data;
1094
+ const originalTask = mappedItem._originalTask ?? this.allFetchedTasks().find((task) => task.id === mappedItem.id);
1095
+ if (originalTask) {
1096
+ this.onTaskClick.emit(originalTask);
1097
+ }
1098
+ }
1099
+ handleTaskCommandClick(event) {
1100
+ if (event.name !== 'view') {
1101
+ return;
1102
+ }
1103
+ event.nativeEvent?.stopPropagation();
1104
+ const mappedItem = event.data;
1105
+ const task = mappedItem._originalTask ?? this.allFetchedTasks().find((t) => t.id === mappedItem.id);
1106
+ if (!task) {
1107
+ return;
1108
+ }
1109
+ const anchor = this.taskPopoverOverlay.resolveAnchorFromCommandClick(event);
1110
+ if (!anchor) {
1111
+ return;
1112
+ }
1113
+ setTimeout(() => {
1114
+ void this.taskPopoverOverlay.open({
1115
+ task,
1116
+ anchor,
1117
+ placement: 'bottom-start',
1118
+ adaptivityEnabled: false,
1119
+ autoFlip: true,
1120
+ });
1121
+ }, 0);
1122
+ }
1123
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardStatusGroupedGridViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1124
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardStatusGroupedGridViewComponent, isStandalone: true, selector: "axm-task-board-status-grouped-grid-view", inputs: { provider: { classPropertyName: "provider", publicName: "provider", isSignal: true, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, refreshNonce: { classPropertyName: "refreshNonce", publicName: "refreshNonce", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onTaskClick: "onTaskClick" }, viewQueries: [{ propertyName: "grids", predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (remountKey of groupedViewRemountKeys(); track remountKey) {\n @for (group of groupedTasks(); track trackStatusGroup($index, group)) {\n <h1 class=\"text-xl font-bold\">{{ group.statusInfo.title }}</h1>\n <ax-data-table\n #grid\n class=\"h-[300px]\"\n [fetchDataMode]=\"'manual'\"\n [dataSource]=\"group.tasks\"\n [loading]=\"{ enabled: true, animation: true }\"\n (onRowClick)=\"handleRowClick($event)\"\n >\n <ax-text-column\n dataField=\"title\"\n [caption]=\"('@task-management:task-board.grid-headers.title' | translate | async)!\"\n [width]=\"'350px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"description\"\n [caption]=\"('@task-management:task-board.grid-headers.description' | translate | async)!\"\n [width]=\"'500px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"startDate\"\n [caption]=\"('@task-management:task-board.grid-headers.start-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"endDate\"\n [caption]=\"('@task-management:task-board.grid-headers.due-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"priority\"\n [caption]=\"('@task-management:task-board.grid-headers.priority' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"reporter\"\n [caption]=\"('@task-management:task-board.grid-headers.reporter' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"assignee\"\n [caption]=\"('@task-management:task-board.grid-headers.assignee' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n\n <ax-command-column\n fixed=\"end\"\n [width]=\"taskRowCommandWidth\"\n [items]=\"taskRowCommandItems\"\n (onItemClick)=\"handleTaskCommandClick($event)\"\n ></ax-command-column>\n </ax-data-table>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1$2.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1$2.AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }, { kind: "component", type: i1$2.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1125
+ }
1126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardStatusGroupedGridViewComponent, decorators: [{
1127
+ type: Component,
1128
+ args: [{ selector: 'axm-task-board-status-grouped-grid-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXDataTableModule, AXTranslatorPipe, AsyncPipe, AXPWidgetCoreModule], template: "@for (remountKey of groupedViewRemountKeys(); track remountKey) {\n @for (group of groupedTasks(); track trackStatusGroup($index, group)) {\n <h1 class=\"text-xl font-bold\">{{ group.statusInfo.title }}</h1>\n <ax-data-table\n #grid\n class=\"h-[300px]\"\n [fetchDataMode]=\"'manual'\"\n [dataSource]=\"group.tasks\"\n [loading]=\"{ enabled: true, animation: true }\"\n (onRowClick)=\"handleRowClick($event)\"\n >\n <ax-text-column\n dataField=\"title\"\n [caption]=\"('@task-management:task-board.grid-headers.title' | translate | async)!\"\n [width]=\"'350px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"description\"\n [caption]=\"('@task-management:task-board.grid-headers.description' | translate | async)!\"\n [width]=\"'500px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"startDate\"\n [caption]=\"('@task-management:task-board.grid-headers.start-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"endDate\"\n [caption]=\"('@task-management:task-board.grid-headers.due-date' | translate | async)!\"\n [width]=\"'150px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"priority\"\n [caption]=\"('@task-management:task-board.grid-headers.priority' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"reporter\"\n [caption]=\"('@task-management:task-board.grid-headers.reporter' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n <ax-text-column\n dataField=\"assignee\"\n [caption]=\"('@task-management:task-board.grid-headers.assignee' | translate | async)!\"\n [width]=\"'125px'\"\n ></ax-text-column>\n\n <ax-command-column\n fixed=\"end\"\n [width]=\"taskRowCommandWidth\"\n [items]=\"taskRowCommandItems\"\n (onItemClick)=\"handleTaskCommandClick($event)\"\n ></ax-command-column>\n </ax-data-table>\n }\n}\n" }]
1129
+ }], ctorParameters: () => [], propDecorators: { provider: [{ type: i0.Input, args: [{ isSignal: true, alias: "provider", required: false }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], refreshNonce: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshNonce", required: false }] }], onTaskClick: [{ type: i0.Output, args: ["onTaskClick"] }], grids: [{ type: i0.ViewChildren, args: ['grid', { isSignal: true }] }] } });
1130
+
1131
+ class AXMTaskBoardPage extends AXPPageLayoutBaseComponent {
1132
+ constructor() {
1133
+ super(...arguments);
1134
+ this.rootConfig = RootConfig;
1135
+ this.deviceService = inject(AXPDeviceService);
1136
+ this.vm = inject(AXMTaskBoardViewModel);
1137
+ this.unsubscribe = inject(AXUnsubscriber);
1138
+ this.activatedRoute = inject(ActivatedRoute);
1139
+ this.localeService = inject(AXLocaleService);
1140
+ this.calendarService = inject(AXCalendarService);
1141
+ this.taskBoardService = inject(AXPTaskBoardService);
1142
+ this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
1143
+ this.destroyRef = inject(DestroyRef);
1144
+ this.dueStatusProviderService = inject(AXPDueStatusProviderService);
1145
+ this.queryService = inject(AXPQueryService);
1146
+ this.translationService = inject(AXTranslationService);
1147
+ this.sessionUser = toSignal(this.sessionService.user$, { initialValue: null });
1148
+ this.sessionPermissions = toSignal(this.sessionService.permissions$, { initialValue: [] });
1149
+ this.activeCalendar = toSignal(this.calendarService.calendarChanges$, {
1150
+ initialValue: this.calendarService.getActiveCalendar(),
1151
+ });
1152
+ this.profileChanged = toSignal(this.localeService.profileChanged$, {
1153
+ initialValue: this.localeService.activeProfile(),
1154
+ });
1155
+ this.navigatorTimeTextVersion = 0;
1156
+ this.colorByItems = [
1157
+ { key: 'dueDate', titleKey: '@task-management:task-board.color-by.due-date' },
1158
+ { key: 'priority', titleKey: '@task-management:task-board.color-by.priority' },
1159
+ { key: 'status', titleKey: '@task-management:task-board.color-by.status' },
1160
+ ];
1161
+ this.popover = viewChild('popover', /* @ts-ignore */
1162
+ ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
1163
+ this.calendar = viewChild('calendar', /* @ts-ignore */
1164
+ ...(ngDevMode ? [{ debugName: "calendar" }] : /* istanbul ignore next */ []));
1165
+ this.isReady = signal(false, /* @ts-ignore */
1166
+ ...(ngDevMode ? [{ debugName: "isReady" }] : /* istanbul ignore next */ []));
1167
+ this.isSettingsPanelOpen = signal(false, /* @ts-ignore */
1168
+ ...(ngDevMode ? [{ debugName: "isSettingsPanelOpen" }] : /* istanbul ignore next */ []));
1169
+ this.settingsPanelCopy = signal({
1170
+ title: '',
1171
+ colorBy: '',
1172
+ calendars: '',
1173
+ }, /* @ts-ignore */
1174
+ ...(ngDevMode ? [{ debugName: "settingsPanelCopy" }] : /* istanbul ignore next */ []));
1175
+ this.filtersReady = signal(false, /* @ts-ignore */
1176
+ ...(ngDevMode ? [{ debugName: "filtersReady" }] : /* istanbul ignore next */ []));
1177
+ this.calendarEventTypes = signal([], /* @ts-ignore */
1178
+ ...(ngDevMode ? [{ debugName: "calendarEventTypes" }] : /* istanbul ignore next */ []));
1179
+ this.resources = signal([], /* @ts-ignore */
1180
+ ...(ngDevMode ? [{ debugName: "resources" }] : /* istanbul ignore next */ []));
1181
+ this.isQueryParam = signal(false, /* @ts-ignore */
1182
+ ...(ngDevMode ? [{ debugName: "isQueryParam" }] : /* istanbul ignore next */ []));
1183
+ this.refreshNonce = signal(0, /* @ts-ignore */
1184
+ ...(ngDevMode ? [{ debugName: "refreshNonce" }] : /* istanbul ignore next */ []));
1185
+ this.navigatorTimeText = signal('', /* @ts-ignore */
1186
+ ...(ngDevMode ? [{ debugName: "navigatorTimeText" }] : /* istanbul ignore next */ []));
1187
+ this.panelInitialFilters = signal([], /* @ts-ignore */
1188
+ ...(ngDevMode ? [{ debugName: "panelInitialFilters" }] : /* istanbul ignore next */ []));
1189
+ this.currentTask = signal(null, /* @ts-ignore */
1190
+ ...(ngDevMode ? [{ debugName: "currentTask" }] : /* istanbul ignore next */ []));
1191
+ this.schedulerComponent = signal(null, /* @ts-ignore */
1192
+ ...(ngDevMode ? [{ debugName: "schedulerComponent" }] : /* istanbul ignore next */ []));
1193
+ this.boardTasksLoadVersion = 0;
1194
+ this.filtersPanelInitialized = false;
1195
+ this.lastLocaleProfileCode = null;
1196
+ /** Ctrl enables range selection in kanban/table navigator popover calendars. */
1197
+ this.navigatorRangeSelectActive = signal(false, /* @ts-ignore */
1198
+ ...(ngDevMode ? [{ debugName: "navigatorRangeSelectActive" }] : /* istanbul ignore next */ []));
1199
+ /** True after a plain (non-Ctrl) single-day click in the navigator calendar. */
1200
+ this.navigatorPlainSingleDaySelected = false;
1201
+ this.modifierRangeBasedViews = ['grid', 'kanban', 'status-grouped-grid'];
1202
+ this.alwaysRangeBasedViews = ['agenda', 'timeline-weekly'];
1203
+ this.dayDataCache = signal(new Map(), /* @ts-ignore */
1204
+ ...(ngDevMode ? [{ debugName: "dayDataCache" }] : /* istanbul ignore next */ []));
1205
+ /** Tasks loaded for the widest navigator + view range (single fetch). */
1206
+ this.boardTasksRaw = signal([], /* @ts-ignore */
1207
+ ...(ngDevMode ? [{ debugName: "boardTasksRaw" }] : /* istanbul ignore next */ []));
1208
+ /** Tasks visible in the active view range (derived from {@link boardTasksRaw}). */
1209
+ this.boardTasks = signal([], /* @ts-ignore */
1210
+ ...(ngDevMode ? [{ debugName: "boardTasks" }] : /* istanbul ignore next */ []));
1211
+ this.showToolbarNavigator = computed(() => usesTaskBoardToolbarNavigator(this.vm.currentViewMode()), /* @ts-ignore */
1212
+ ...(ngDevMode ? [{ debugName: "showToolbarNavigator" }] : /* istanbul ignore next */ []));
1213
+ this.showViewLayoutSwitcher = computed(() => getTaskBoardLayoutOptions(this.vm.currentViewCategory()).length > 0, /* @ts-ignore */
1214
+ ...(ngDevMode ? [{ debugName: "showViewLayoutSwitcher" }] : /* istanbul ignore next */ []));
1215
+ this.viewLayoutItems = computed(() => getTaskBoardLayoutOptions(this.vm.currentViewCategory()), /* @ts-ignore */
1216
+ ...(ngDevMode ? [{ debugName: "viewLayoutItems" }] : /* istanbul ignore next */ []));
1217
+ this.selectedViewLayout = computed(() => this.vm.getLayoutForCategory(this.vm.currentViewCategory()), /* @ts-ignore */
1218
+ ...(ngDevMode ? [{ debugName: "selectedViewLayout" }] : /* istanbul ignore next */ []));
1219
+ this.selectedViewLayoutTitleKey = computed(() => {
1220
+ const selectedLayout = this.selectedViewLayout();
1221
+ const item = this.viewLayoutItems().find((option) => option.key === selectedLayout);
1222
+ return item ? this.viewLayoutTranslationKey(item.text) : '';
1223
+ }, /* @ts-ignore */
1224
+ ...(ngDevMode ? [{ debugName: "selectedViewLayoutTitleKey" }] : /* istanbul ignore next */ []));
1225
+ this.selectedViewLayoutIcon = computed(() => {
1226
+ const selectedLayout = this.selectedViewLayout();
1227
+ const item = this.viewLayoutItems().find((option) => option.key === selectedLayout);
1228
+ return item?.icon ?? 'fa-light fa-sliders';
1229
+ }, /* @ts-ignore */
1230
+ ...(ngDevMode ? [{ debugName: "selectedViewLayoutIcon" }] : /* istanbul ignore next */ []));
1231
+ this.effectiveColorBy = computed(() => this.vm.effectiveColorBy(), /* @ts-ignore */
1232
+ ...(ngDevMode ? [{ debugName: "effectiveColorBy" }] : /* istanbul ignore next */ []));
1233
+ this.settingsPanelHeader = computed(() => ({
1234
+ primaryTitle: this.settingsPanelCopy().title,
1235
+ icon: {
1236
+ iconClass: 'fa-fw fa-lg fa-light fa-gear',
1237
+ },
1238
+ }), /* @ts-ignore */
1239
+ ...(ngDevMode ? [{ debugName: "settingsPanelHeader" }] : /* istanbul ignore next */ []));
1240
+ this.settingsPanelSections = computed(() => {
1241
+ const copy = this.settingsPanelCopy();
1242
+ const sections = [{ title: copy.colorBy, projectionKey: 'colorBy' }];
1243
+ if (this.showCalendarEventButton()) {
1244
+ sections.push({ title: copy.calendars, projectionKey: 'calendars' });
1245
+ }
1246
+ return sections;
1247
+ }, /* @ts-ignore */
1248
+ ...(ngDevMode ? [{ debugName: "settingsPanelSections" }] : /* istanbul ignore next */ []));
1249
+ this.settingsPanelI18nEffect = effect(() => {
1250
+ this.localeService.activeProfile().localeInfo?.code;
1251
+ void this.loadSettingsPanelCopy();
1252
+ }, /* @ts-ignore */
1253
+ ...(ngDevMode ? [{ debugName: "settingsPanelI18nEffect" }] : /* istanbul ignore next */ []));
1254
+ /** Resolves navigator date label asynchronously so month names react to locale/profile changes. */
1255
+ this.navigatorTimeTextEffect = effect(() => {
1256
+ this.profileChanged();
1257
+ this.vm.currentDate();
1258
+ this.vm.currentViewMode();
1259
+ this.vm.daysCount();
1260
+ this.localeService.activeProfile().localeInfo?.code;
1261
+ this.calendarType();
1262
+ this.refreshNonce();
1263
+ const version = ++this.navigatorTimeTextVersion;
1264
+ untracked(() => {
1265
+ void this.resolveNavigatorTimeText().then((text) => {
1266
+ if (version === this.navigatorTimeTextVersion) {
1267
+ this.navigatorTimeText.set(text);
1268
+ }
1269
+ });
1270
+ });
1271
+ }, /* @ts-ignore */
1272
+ ...(ngDevMode ? [{ debugName: "navigatorTimeTextEffect" }] : /* istanbul ignore next */ []));
1273
+ /** Re-render navigator calendar when regional week layout changes. */
1274
+ this.navigatorCalendarLayoutEffect = effect(() => {
1275
+ this.localeService.activeProfile().localeInfo?.code;
1276
+ this.calendarService.getActiveCalendar()?.weekdays;
1277
+ this.calendarType();
1278
+ untracked(() => {
1279
+ queueMicrotask(() => {
1280
+ this.calendar()?.render();
1281
+ });
1282
+ });
1283
+ }, /* @ts-ignore */
1284
+ ...(ngDevMode ? [{ debugName: "navigatorCalendarLayoutEffect" }] : /* istanbul ignore next */ []));
1285
+ this.canFilterByAllAssignees = computed(() => {
1286
+ this.sessionUser();
1287
+ this.sessionPermissions();
1288
+ return canViewAllTaskAssignees(this.sessionService);
1289
+ }, /* @ts-ignore */
1290
+ ...(ngDevMode ? [{ debugName: "canFilterByAllAssignees" }] : /* istanbul ignore next */ []));
1291
+ this.calendarEventTypeItems = computed(() => this.calendarEventTypes().map((eventType) => ({
1292
+ id: eventType.id,
1293
+ text: eventType.title,
1294
+ icon: eventType.icon,
1295
+ })), /* @ts-ignore */
1296
+ ...(ngDevMode ? [{ debugName: "calendarEventTypeItems" }] : /* istanbul ignore next */ []));
1297
+ this.showCalendarEventButton = computed(() => !this.isNonCalendarView() && this.calendarEventTypeItems().length > 0, /* @ts-ignore */
1298
+ ...(ngDevMode ? [{ debugName: "showCalendarEventButton" }] : /* istanbul ignore next */ []));
1299
+ this.selectedCalendarEventTypeCount = computed(() => this.vm.selectedCalendarEventTypeIds().length, /* @ts-ignore */
1300
+ ...(ngDevMode ? [{ debugName: "selectedCalendarEventTypeCount" }] : /* istanbul ignore next */ []));
1301
+ this.filterDefinitions = computed(() => {
1302
+ const canFilterByAll = this.canFilterByAllAssignees();
1303
+ const assigneeScopeKey = `${this.sessionUser()?.id ?? ''}:${canFilterByAll}`;
1304
+ const isMultiTaskType = this.isMultiSelectTaskTypeEnabled();
1305
+ const definitions = [
1306
+ {
1307
+ field: 'type',
1308
+ operator: { type: 'equal' },
1309
+ title: '@task-management:task-board.filter.task-type',
1310
+ icon: 'fa-light fa-list',
1311
+ widget: {
1312
+ type: 'select-filter',
1313
+ options: {
1314
+ dataSource: AXMTaskManagementQueryKeys.ListTaskTypes,
1315
+ valueField: 'name',
1316
+ textField: 'title',
1317
+ multiple: isMultiTaskType,
1318
+ allowClear: isMultiTaskType,
1319
+ },
1320
+ },
1321
+ filterType: { advance: true, inline: false },
1322
+ },
1323
+ ];
1324
+ // Without ViewAllTasks, assignee is always the current user — hide the filter entirely.
1325
+ if (canFilterByAll) {
1326
+ definitions.push({
1327
+ field: 'assignee',
1328
+ operator: { type: 'equal' },
1329
+ title: '@task-management:task-board.filter.assignee',
1330
+ icon: 'fa-light fa-user',
1331
+ widget: {
1332
+ type: 'select-filter',
1333
+ options: {
1334
+ multiple: true,
1335
+ valueField: 'id',
1336
+ textField: 'displayName',
1337
+ allowSearch: true,
1338
+ allowClear: true,
1339
+ readonly: false,
1340
+ dataSource: AXMTaskManagementQueryKeys.TaskBoardAssignees,
1341
+ assigneeScopeKey,
1342
+ },
1343
+ },
1344
+ filterType: { advance: true, inline: false },
1345
+ });
1346
+ }
1347
+ definitions.push({
1348
+ field: 'reporter',
1349
+ operator: { type: 'equal' },
1350
+ title: '@task-management:task-board.filter.reporter',
1351
+ icon: 'fa-light fa-user-pen',
1352
+ widget: {
1353
+ type: 'select-filter',
1354
+ options: {
1355
+ multiple: true,
1356
+ valueField: 'id',
1357
+ textField: 'displayName',
1358
+ allowSearch: true,
1359
+ dataSource: AXMTaskManagementQueryKeys.TaskBoardReporters,
1360
+ },
1361
+ },
1362
+ filterType: { advance: true, inline: false },
1363
+ });
1364
+ return definitions;
1365
+ }, /* @ts-ignore */
1366
+ ...(ngDevMode ? [{ debugName: "filterDefinitions" }] : /* istanbul ignore next */ []));
1367
+ this.lastCanFilterByAllAssignees = null;
1368
+ /** Re-scope assignee filter when user or permissions change; refresh chips when ViewAllTasks changes. */
1369
+ this.assigneeScopeEffect = effect(() => {
1370
+ this.sessionUser();
1371
+ this.sessionPermissions();
1372
+ const canFilterByAll = this.canFilterByAllAssignees();
1373
+ const permissionChanged = this.lastCanFilterByAllAssignees !== canFilterByAll;
1374
+ this.lastCanFilterByAllAssignees = canFilterByAll;
1375
+ if (this.vm.isLoading()) {
1376
+ return;
1377
+ }
1378
+ const currentIds = this.vm.selectedAssigneeIds();
1379
+ const scopedIds = normalizeTaskBoardAssigneeIds(this.sessionService, currentIds);
1380
+ const assigneeIdsChanged = !isEqual(sortBy(currentIds.map(String)), sortBy(scopedIds.map(String)));
1381
+ if (assigneeIdsChanged) {
1382
+ this.vm.setSelectedAssigneeIds(scopedIds, this.isQueryParam(), false);
1383
+ }
1384
+ if (this.filtersPanelInitialized && (assigneeIdsChanged || permissionChanged)) {
1385
+ untracked(() => void this.syncPanelInitialFilters());
1386
+ }
1387
+ }, /* @ts-ignore */
1388
+ ...(ngDevMode ? [{ debugName: "assigneeScopeEffect" }] : /* istanbul ignore next */ []));
1389
+ this.calendarType = computed(() => this.activeCalendar()?.name() ?? this.localeService.activeProfile().calendar.system, /* @ts-ignore */
1390
+ ...(ngDevMode ? [{ debugName: "calendarType" }] : /* istanbul ignore next */ []));
1391
+ this.todayButtonTextKey = computed(() => {
1392
+ switch (this.vm.currentViewMode()) {
1393
+ case 'week':
1394
+ return '@task-management:task-board.navigator.current-week';
1395
+ case 'month':
1396
+ case 'timeline-month':
1397
+ return '@task-management:task-board.navigator.current-month';
1398
+ case 'timeline-year':
1399
+ return '@task-management:task-board.navigator.current-year';
1400
+ default:
1401
+ return '@task-management:task-board.navigator.today';
1402
+ }
1403
+ }, /* @ts-ignore */
1404
+ ...(ngDevMode ? [{ debugName: "todayButtonTextKey" }] : /* istanbul ignore next */ []));
1405
+ this.calendarDepth = computed(() => {
1406
+ if (this.vm.currentViewMode() === 'grid' ||
1407
+ this.vm.currentViewMode() === 'kanban' ||
1408
+ this.vm.currentViewMode() === 'status-grouped-grid') {
1409
+ return 'day';
1410
+ }
1411
+ return this.schedulerComponent()?.calendarDepth() || 'day';
1412
+ }, /* @ts-ignore */
1413
+ ...(ngDevMode ? [{ debugName: "calendarDepth" }] : /* istanbul ignore next */ []));
1414
+ this.currentDateForCalendar = computed(() => {
1415
+ const pendingRangeStart = this.vm.rangeStartDate();
1416
+ if (pendingRangeStart && this.isModifierRangeNavigatorView()) {
1417
+ return pendingRangeStart;
1418
+ }
1419
+ const date = this.vm.currentDate();
1420
+ return date || new Date();
1421
+ }, /* @ts-ignore */
1422
+ ...(ngDevMode ? [{ debugName: "currentDateForCalendar" }] : /* istanbul ignore next */ []));
1423
+ this.filter = computed(() => {
1424
+ const taskTypeNames = this.vm.selectedTaskTypeNames();
1425
+ const assigneeIds = this.vm.selectedAssigneeIds();
1426
+ const reporterIds = this.vm.selectedReporterIds();
1427
+ const currentDate = this.vm.currentDate() || new Date();
1428
+ let from = this.calendarService.create(currentDate, this.localeService.activeProfile().calendar.system);
1429
+ let end;
1430
+ const currentView = this.vm.currentViewMode();
1431
+ if (currentView === 'grid') {
1432
+ // List/grid is always scoped to the current month, regardless of any day-range
1433
+ // selected from the navigator in other views.
1434
+ from = from.startOf('month');
1435
+ end = from.endOf('month');
1436
+ }
1437
+ else if (currentView === 'kanban' ||
1438
+ currentView === 'agenda' ||
1439
+ currentView === 'timeline-weekly' ||
1440
+ currentView === 'status-grouped-grid') {
1441
+ end = from.add('day', this.vm.daysCount() - 1);
1442
+ }
1443
+ else if (currentView === 'month' || currentView === 'timeline-month') {
1444
+ from = from.startOf('month');
1445
+ end = from.endOf('month');
1446
+ }
1447
+ else {
1448
+ end = from.endOf('day');
1449
+ }
1450
+ return {
1451
+ // Empty filter means "all items" for task type/provider.
1452
+ ...(taskTypeNames.length > 0 && { types: taskTypeNames }),
1453
+ ...(assigneeIds.length > 0 && { assigneeIds }),
1454
+ ...(reporterIds.length > 0 && { reporterIds }),
1455
+ range: { from: from.date, end: end.date },
1456
+ };
1457
+ }, /* @ts-ignore */
1458
+ ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
1459
+ this.contextMenu = viewChild('rootContextMenu', /* @ts-ignore */
1460
+ ...(ngDevMode ? [{ debugName: "contextMenu" }] : /* istanbul ignore next */ []));
1461
+ this.schedulerDataSource = (schedulerFilter) => {
1462
+ const pageFilter = this.filter();
1463
+ const visibleRange = schedulerFilter?.range ?? pageFilter.range;
1464
+ if (!visibleRange) {
1465
+ return Promise.resolve([]);
1466
+ }
1467
+ const items = filterTasksOverlappingRange(this.boardTasksRaw(), visibleRange);
1468
+ this.validateSelectedTask(items);
1469
+ return Promise.resolve(items);
1470
+ };
1471
+ this.gridDataSource = (filter) => {
1472
+ const items = this.boardTasks();
1473
+ const skip = filter?.skip ?? 0;
1474
+ const take = filter?.take ?? 10;
1475
+ this.validateSelectedTask(items);
1476
+ return Promise.resolve({
1477
+ items: items.slice(skip, skip + take),
1478
+ total: items.length,
1479
+ });
1480
+ };
1481
+ this.resolvedTasks = signal([], /* @ts-ignore */
1482
+ ...(ngDevMode ? [{ debugName: "resolvedTasks" }] : /* istanbul ignore next */ []));
1483
+ /** Single board task loader — one API fan-out per filter change. */
1484
+ this.boardTasksLoadEffect = effect(() => {
1485
+ if (!this.isReady()) {
1486
+ return;
1487
+ }
1488
+ this.vm.selectedTaskTypeNames();
1489
+ this.vm.selectedAssigneeIds();
1490
+ this.vm.selectedReporterIds();
1491
+ this.vm.currentDate();
1492
+ this.vm.daysCount();
1493
+ this.vm.currentViewMode();
1494
+ this.localeService.activeProfile().localeInfo?.code;
1495
+ const version = ++this.boardTasksLoadVersion;
1496
+ void this.loadBoardTasks(version);
1497
+ }, /* @ts-ignore */
1498
+ ...(ngDevMode ? [{ debugName: "boardTasksLoadEffect" }] : /* istanbul ignore next */ []));
1499
+ /** Re-normalizes filter keys when locale/profile changes (titles must not drive filtering). */
1500
+ this.localeFilterRefreshEffect = effect(() => {
1501
+ const profileCode = this.localeService.activeProfile().localeInfo?.code ?? '';
1502
+ if (!profileCode || this.vm.isLoading() || !this.filtersPanelInitialized) {
1503
+ return;
1504
+ }
1505
+ const previousCode = this.lastLocaleProfileCode;
1506
+ this.lastLocaleProfileCode = profileCode;
1507
+ if (previousCode == null || previousCode === profileCode) {
1508
+ return;
1509
+ }
1510
+ untracked(() => {
1511
+ const taskTypeOptions = this.vm.taskTypeOptions();
1512
+ const normalizedTypes = normalizeTaskTypeFilterNames(this.vm.selectedTaskTypeNames(), taskTypeOptions);
1513
+ if (!isEqual(sortBy(normalizedTypes), sortBy(this.vm.selectedTaskTypeNames()))) {
1514
+ this.vm.setSelectedTaskTypeNames(normalizedTypes);
1515
+ }
1516
+ void this.vm.reloadTaskTypes({ force: true }).then(() => {
1517
+ void this.syncPanelInitialFilters();
1518
+ this.refreshNonce.update((value) => value + 1);
1519
+ });
1520
+ });
1521
+ }, /* @ts-ignore */
1522
+ ...(ngDevMode ? [{ debugName: "localeFilterRefreshEffect" }] : /* istanbul ignore next */ []));
1523
+ this.refreshEffect = effect(() => {
1524
+ this.vm.selectedTaskTypeNames();
1525
+ this.vm.selectedAssigneeIds();
1526
+ this.vm.selectedReporterIds();
1527
+ this.vm.effectiveColorBy();
1528
+ this.vm.currentDate();
1529
+ this.vm.daysCount();
1530
+ this.localeService.activeProfile().localeInfo?.code;
1531
+ this.refreshNonce();
1532
+ void this.getResources();
1533
+ }, /* @ts-ignore */
1534
+ ...(ngDevMode ? [{ debugName: "refreshEffect" }] : /* istanbul ignore next */ []));
1535
+ this.calendarEventTypeRefreshEffect = effect(() => {
1536
+ this.vm.selectedCalendarEventTypeIds();
1537
+ this.localeService.activeProfile().localeInfo?.code;
1538
+ }, /* @ts-ignore */
1539
+ ...(ngDevMode ? [{ debugName: "calendarEventTypeRefreshEffect" }] : /* istanbul ignore next */ []));
1540
+ this.calendarLocalizationEffect = effect(() => {
1541
+ this.localeService.activeProfile().localeInfo?.code;
1542
+ void this.loadCalendarFilterOptions();
1543
+ }, /* @ts-ignore */
1544
+ ...(ngDevMode ? [{ debugName: "calendarLocalizationEffect" }] : /* istanbul ignore next */ []));
1545
+ this.getCellClass = (date) => {
1546
+ const currentView = this.vm.currentViewMode();
1547
+ const selectionStartDate = this.vm.rangeStartDate();
1548
+ if (!this.usesNavigatorRangeSelection(currentView)) {
1549
+ const currentDay = this.calendarService.create(this.vm.currentDate());
1550
+ if (date.equal(currentDay, 'day')) {
1551
+ return 'ax-range-start ax-range-end';
1552
+ }
1553
+ return null;
1554
+ }
1555
+ if (this.modifierRangeBasedViews.includes(currentView) || this.alwaysRangeBasedViews.includes(currentView)) {
1556
+ if (selectionStartDate) {
1557
+ const axSelectionStartDate = this.calendarService.create(selectionStartDate);
1558
+ if (date.equal(axSelectionStartDate, 'day')) {
1559
+ return 'ax-range-start ax-range-end';
1560
+ }
1561
+ return null;
1562
+ }
1563
+ const fromRange = this.calendarService.create(this.vm.currentDate());
1564
+ const endRange = fromRange.add('day', this.vm.daysCount() - 1);
1565
+ const isStart = date.equal(fromRange, 'day');
1566
+ const isEnd = date.equal(endRange, 'day');
1567
+ if (isStart && isEnd)
1568
+ return 'ax-range-start ax-range-end';
1569
+ if (isStart)
1570
+ return 'ax-range-start';
1571
+ if (isEnd)
1572
+ return 'ax-range-end';
1573
+ if (date.compare(fromRange, 'day') > 0 && date.compare(endRange, 'day') < 0) {
1574
+ return 'ax-range-between';
1575
+ }
1576
+ return null;
1577
+ }
1578
+ if (currentView === 'week') {
1579
+ const from = this.calendarService.create(this.vm.currentDate());
1580
+ const end = from.add('day', 6);
1581
+ if (date.equal(from, 'day'))
1582
+ return 'ax-range-start';
1583
+ if (date.equal(end, 'day'))
1584
+ return 'ax-range-end';
1585
+ if (date.compare(from, 'day') > 0 && date.compare(end, 'day') < 0) {
1586
+ return 'ax-range-between';
1587
+ }
1588
+ }
1589
+ return null;
1590
+ };
1591
+ this.isNonCalendarView = computed(() => {
1592
+ const mode = this.vm.currentViewMode();
1593
+ return mode === 'kanban' || mode === 'grid' || mode === 'status-grouped-grid';
1594
+ }, /* @ts-ignore */
1595
+ ...(ngDevMode ? [{ debugName: "isNonCalendarView" }] : /* istanbul ignore next */ []));
1596
+ }
1597
+ enrichTasks(tasks) {
1598
+ return enrichTaskBoardTasks(tasks, this.vm.effectiveColorBy(), this.dueStatusProviderService);
1599
+ }
1600
+ async enrichTasksForViews(tasks) {
1601
+ const labeled = await resolveTaskBoardSchedulerLabels(tasks, this.translateService, this.formatService, this.settingsService);
1602
+ return this.enrichTasks(labeled);
1603
+ }
1604
+ async ngOnInit() {
1605
+ await super.ngOnInit();
1606
+ this.taskBoardService.refresh$.subscribe(async () => {
1607
+ await this.reloadTaskBoardData();
1608
+ });
1609
+ await this.vm.initialize();
1610
+ const calendars = await this.loadCalendarFilterOptions();
1611
+ await this.loadFromRoute();
1612
+ if (this.activatedRoute.snapshot.queryParamMap.has('calendarEvent')) {
1613
+ void this.router.navigate([], {
1614
+ queryParams: { calendarEvent: null },
1615
+ queryParamsHandling: 'merge',
1616
+ replaceUrl: true,
1617
+ });
1618
+ }
1619
+ if (this.vm.selectedCalendarEventTypeIds().length === 0 && calendars.length > 0) {
1620
+ this.vm.setSelectedCalendarEventTypeIds(calendars.map((calendar) => calendar.id));
1621
+ }
1622
+ this.filtersReady.set(true);
1623
+ this.isReady.set(true);
1624
+ this.registerKeyboardShortcuts();
1625
+ await this.getResources();
1626
+ this.router.events
1627
+ .pipe(this.unsubscribe.takeUntilDestroy, filter((event) => event instanceof NavigationEnd))
1628
+ .subscribe(() => {
1629
+ void this.loadFromRoute();
1630
+ });
1631
+ this.taskBoardService.openTask$.subscribe(async ({ task, provider, instanceId, activityId }) => {
1632
+ let match = task;
1633
+ if (!match && instanceId && activityId) {
1634
+ match = this.boardTasksRaw().find((t) => {
1635
+ const data = t.data;
1636
+ return data?.instanceId === instanceId && data?.activityId === activityId;
1637
+ });
1638
+ }
1639
+ if (!match) {
1640
+ return;
1641
+ }
1642
+ this.vm.selectTask({
1643
+ ...match,
1644
+ provider: match.provider ?? provider,
1645
+ });
1646
+ });
1647
+ }
1648
+ async getPageTitle() {
1649
+ const category = this.vm.currentViewCategory();
1650
+ const layout = this.vm.getLayoutForCategory(category);
1651
+ return await this.translateService.translateAsync(this.viewPageTitleTranslationKey(category, layout));
1652
+ }
1653
+ getTitleActions() {
1654
+ const currentCategory = this.vm.currentViewCategory();
1655
+ return this.vm.visibleViewCategories().map((category) => ({
1656
+ title: this.viewCategoryTranslationKey(category),
1657
+ icon: currentCategory === category ? 'fa-check' : '',
1658
+ command: {
1659
+ name: 'task-board-category',
1660
+ metadata: { category, isSelected: currentCategory === category, hideSelectionCheckSuffix: true },
1661
+ },
1662
+ }));
1663
+ }
1664
+ async getPageBreadcrumbs() {
1665
+ return [
1666
+ {
1667
+ title: await this.translateService.translateAsync('@task-management:task-board.page-title'),
1668
+ },
1669
+ ];
1670
+ }
1671
+ async getPrimaryMenuItems() {
1672
+ return [
1673
+ // {
1674
+ // ...getActionButton('refresh'),
1675
+ // command: { name: 'refresh' },
1676
+ // },
1677
+ ];
1678
+ }
1679
+ async execute(command) {
1680
+ if (command.name === 'task-board-category') {
1681
+ const category = command.metadata?.['category'];
1682
+ if (category) {
1683
+ this.applyViewCategory(category);
1684
+ }
1685
+ return;
1686
+ }
1687
+ // if (command.name === 'refresh') {
1688
+ // this.taskBoardService.requestRefresh();
1689
+ // }
1690
+ }
1691
+ schedulerComponentChanged(component) {
1692
+ this.schedulerComponent.set(component);
1693
+ }
1694
+ async reloadTaskBoardData() {
1695
+ this.taskBoardService.invalidateTaskCache();
1696
+ await this.vm.reloadTaskTypes();
1697
+ if (this.filtersPanelInitialized) {
1698
+ await this.syncPanelInitialFilters();
1699
+ }
1700
+ await this.getResources();
1701
+ const version = ++this.boardTasksLoadVersion;
1702
+ await this.loadBoardTasks(version);
1703
+ }
1704
+ async loadBoardTasks(expectedVersion) {
1705
+ const filter = this.buildBoardFetchFilter();
1706
+ const result = await this.taskBoardService.getTasks(filter);
1707
+ if (expectedVersion !== this.boardTasksLoadVersion) {
1708
+ return;
1709
+ }
1710
+ const enriched = await this.enrichTasksForViews(result.items);
1711
+ if (expectedVersion !== this.boardTasksLoadVersion) {
1712
+ return;
1713
+ }
1714
+ this.boardTasksRaw.set(enriched);
1715
+ this.applyBoardTasksForCurrentView(enriched);
1716
+ this.rebuildDayDataCacheFromTasks(enriched);
1717
+ this.refreshNonce.update((value) => value + 1);
1718
+ }
1719
+ buildBoardFetchFilter() {
1720
+ const pageFilter = this.filter();
1721
+ const navigatorRange = this.getNavigatorDataRange();
1722
+ const pageRange = pageFilter.range;
1723
+ if (!pageRange) {
1724
+ return { ...pageFilter, range: navigatorRange };
1725
+ }
1726
+ return {
1727
+ ...pageFilter,
1728
+ range: {
1729
+ from: pageRange.from < navigatorRange.from ? pageRange.from : navigatorRange.from,
1730
+ end: pageRange.end > navigatorRange.end ? pageRange.end : navigatorRange.end,
1731
+ },
1732
+ };
1733
+ }
1734
+ getNavigatorDataRange() {
1735
+ const currentDate = this.vm.currentDate();
1736
+ const depth = this.calendarDepth();
1737
+ if (depth === 'month') {
1738
+ return {
1739
+ from: this.calendarService.create(currentDate).startOf('year').date,
1740
+ end: this.calendarService.create(currentDate).endOf('year').date,
1741
+ };
1742
+ }
1743
+ return {
1744
+ from: this.calendarService.create(currentDate).startOf('month').date,
1745
+ end: this.calendarService.create(currentDate).endOf('month').date,
1746
+ };
1747
+ }
1748
+ applyBoardTasksForCurrentView(tasks) {
1749
+ const viewRange = this.filter().range;
1750
+ const visible = viewRange ? filterTasksOverlappingRange(tasks, viewRange) : tasks;
1751
+ this.boardTasks.set(visible);
1752
+ this.validateSelectedTask(visible);
1753
+ const viewMode = this.vm.currentViewMode();
1754
+ if (viewMode === 'kanban' || viewMode === 'status-grouped-grid') {
1755
+ this.resolvedTasks.set(visible);
1756
+ }
1757
+ }
1758
+ reEnrichBoardTasksFromRaw() {
1759
+ const raw = this.boardTasksRaw();
1760
+ if (raw.length === 0) {
1761
+ return;
1762
+ }
1763
+ const reEnriched = this.enrichTasks(raw);
1764
+ this.boardTasksRaw.set(reEnriched);
1765
+ this.applyBoardTasksForCurrentView(reEnriched);
1766
+ this.rebuildDayDataCacheFromTasks(reEnriched);
1767
+ }
1768
+ async handleContextMenuOnOpening(event) {
1769
+ const currentTask = this.currentTask();
1770
+ if (!currentTask)
1771
+ return;
1772
+ const providerName = currentTask.provider;
1773
+ if (!providerName)
1774
+ return;
1775
+ const provider = this.taskBoardService.getProvider(providerName);
1776
+ if (!provider)
1777
+ return;
1778
+ const actions = await provider.getActions(currentTask);
1779
+ for (const item of actions) {
1780
+ const t = item.title;
1781
+ const text = typeof t === 'string'
1782
+ ? t.startsWith('@')
1783
+ ? await this.translateService.translateAsync(t)
1784
+ : t
1785
+ : this.translateService.resolve(t);
1786
+ event.items.push({
1787
+ text,
1788
+ icon: item.icon,
1789
+ color: item.color,
1790
+ break: item.break,
1791
+ data: {
1792
+ command: item.command,
1793
+ },
1794
+ });
1795
+ }
1796
+ }
1797
+ handleContextMenu(event) {
1798
+ this.currentTask.set(event.task);
1799
+ const x = event.nativeEvent.clientX;
1800
+ const y = event.nativeEvent.clientY;
1801
+ const element = event.nativeEvent.target;
1802
+ this.contextMenu()?.showAt({ x, y }, element);
1803
+ }
1804
+ handleMonthSlotDblClicked(date) {
1805
+ this.vm.setCurrentDate(date);
1806
+ this.vm.setViewLayout('calendar', 'daily');
1807
+ }
1808
+ viewCategoryTranslationKey(category) {
1809
+ return `@task-management:task-board.${category}`;
1810
+ }
1811
+ viewPageTitleTranslationKey(category, layout) {
1812
+ if (category === 'kanban') {
1813
+ return '@task-management:task-board.view-page-title.kanban';
1814
+ }
1815
+ if (layout) {
1816
+ return `@task-management:task-board.view-page-title.${category}.${layout}`;
1817
+ }
1818
+ return this.viewCategoryTranslationKey(category);
1819
+ }
1820
+ viewLayoutTranslationKey(layoutText) {
1821
+ return `@task-management:task-board.view.${layoutText}`;
1822
+ }
1823
+ isViewLayoutSelected(layout) {
1824
+ return this.selectedViewLayout() === layout;
1825
+ }
1826
+ handleViewLayoutChange(layout) {
1827
+ this.vm.setViewLayout(this.vm.currentViewCategory(), layout);
1828
+ void this.navigateViewState();
1829
+ this.recompute();
1830
+ }
1831
+ applyViewCategory(category) {
1832
+ const previousCategory = this.vm.currentViewCategory();
1833
+ const previousMode = this.vm.currentViewMode();
1834
+ if (isTaskBoardRangeFilterCategory(category) && !isTaskBoardRangeFilterCategory(previousCategory)) {
1835
+ this.applySmartDateRangeFromNavigator(previousMode);
1836
+ }
1837
+ this.vm.setViewCategory(category);
1838
+ void this.navigateViewState();
1839
+ this.popover()?.close();
1840
+ this.recompute();
1841
+ }
1842
+ applySmartDateRangeFromNavigator(sourceViewMode) {
1843
+ const range = deriveTaskBoardDateRangeFromNavigator(this.calendarService, sourceViewMode, this.vm.currentDate(), this.vm.daysCount());
1844
+ applyTaskBoardDateRangeToViewModel(range, (date) => this.vm.setCurrentDate(date), (days) => this.vm.setDaysCount(days));
1845
+ }
1846
+ navigateViewState() {
1847
+ const queryParams = this.buildFilterQueryParams();
1848
+ if (areTaskBoardQueryParamsEquivalent(this.activatedRoute.snapshot.queryParamMap, queryParams)) {
1849
+ return;
1850
+ }
1851
+ void this.router.navigate([], {
1852
+ queryParams,
1853
+ queryParamsHandling: 'merge',
1854
+ });
1855
+ }
1856
+ toggleSettingsPanel() {
1857
+ this.isSettingsPanelOpen.update((open) => !open);
1858
+ }
1859
+ closeSettingsPanel() {
1860
+ this.isSettingsPanelOpen.set(false);
1861
+ }
1862
+ async loadSettingsPanelCopy() {
1863
+ const [title, colorBy, calendars] = await Promise.all([
1864
+ this.translationService.translateAsync('@task-management:task-board.settings.title'),
1865
+ this.translationService.translateAsync('@task-management:task-board.settings.color-by'),
1866
+ this.translationService.translateAsync('@task-management:task-board.filter.calendars'),
1867
+ ]);
1868
+ this.settingsPanelCopy.set({
1869
+ title: title ?? '',
1870
+ colorBy: colorBy ?? '',
1871
+ calendars: calendars ?? '',
1872
+ });
1873
+ }
1874
+ handleCalendarEventTypesSelectionChange(event) {
1875
+ if (!event.isUserInteraction) {
1876
+ return;
1877
+ }
1878
+ const next = Array.isArray(event.value)
1879
+ ? event.value.map(String)
1880
+ : event.value != null
1881
+ ? [String(event.value)]
1882
+ : [];
1883
+ this.vm.setSelectedCalendarEventTypeIds(next);
1884
+ }
1885
+ handleFiltersChanged(filters) {
1886
+ if (!this.filtersPanelInitialized) {
1887
+ return;
1888
+ }
1889
+ // User-driven filter changes should persist and override URL-seeded state.
1890
+ this.isQueryParam.set(false);
1891
+ const typeFilter = filters.find((f) => f.field === 'type');
1892
+ const assigneeFilter = filters.find((f) => f.field === 'assignee');
1893
+ const reporterFilter = filters.find((f) => f.field === 'reporter');
1894
+ this.syncTaskTypesToViewModel(typeFilter?.value);
1895
+ if (this.canFilterByAllAssignees()) {
1896
+ this.syncAssigneesToViewModel(assigneeFilter?.value);
1897
+ }
1898
+ this.syncReportersToViewModel(reporterFilter?.value);
1899
+ void this.navigateViewState();
1900
+ }
1901
+ async syncPanelInitialFilters() {
1902
+ this.panelInitialFilters.set(await this.buildInitialFiltersFromViewModel());
1903
+ }
1904
+ async buildInitialFiltersFromViewModel() {
1905
+ const filters = [];
1906
+ const types = this.vm.selectedTaskTypeNames();
1907
+ const assignees = this.vm.selectedAssigneeIds();
1908
+ const reporters = this.vm.selectedReporterIds();
1909
+ if (types.length > 0) {
1910
+ const typeValue = this.isMultiSelectTaskTypeEnabled() ? types : types[0];
1911
+ filters.push({
1912
+ field: 'type',
1913
+ operator: { type: 'equal' },
1914
+ value: typeValue,
1915
+ displayText: await this.resolveSelectFilterDisplayText(AXMTaskManagementQueryKeys.ListTaskTypes, 'name', 'title', typeValue),
1916
+ });
1917
+ }
1918
+ if (this.canFilterByAllAssignees() && assignees.length > 0) {
1919
+ filters.push({
1920
+ field: 'assignee',
1921
+ operator: { type: 'equal' },
1922
+ value: assignees,
1923
+ displayText: await this.resolveSelectFilterDisplayText(AXMTaskManagementQueryKeys.TaskBoardAssignees, 'id', 'displayName', assignees),
1924
+ });
1925
+ }
1926
+ if (reporters.length > 0) {
1927
+ filters.push({
1928
+ field: 'reporter',
1929
+ operator: { type: 'equal' },
1930
+ value: reporters,
1931
+ displayText: await this.resolveSelectFilterDisplayText(AXMTaskManagementQueryKeys.TaskBoardReporters, 'id', 'displayName', reporters),
1932
+ });
1933
+ }
1934
+ return filters;
1935
+ }
1936
+ async resolveSelectFilterDisplayText(dataSourceName, valueField, textField, values) {
1937
+ const ids = Array.isArray(values) ? values : values != null && values !== '' ? [values] : [];
1938
+ if (ids.length === 0) {
1939
+ return '';
1940
+ }
1941
+ if (!this.queryService.exists(dataSourceName)) {
1942
+ return ids.map((id) => String(this.unwrapFilterValue(id))).join(', ');
1943
+ }
1944
+ const labels = await Promise.all(ids.map(async (id) => {
1945
+ const rawId = this.unwrapFilterValue(id);
1946
+ const result = await this.queryService.fetch(dataSourceName, {
1947
+ skip: 0,
1948
+ take: 1,
1949
+ filter: {
1950
+ field: valueField,
1951
+ operator: { type: 'equal' },
1952
+ value: rawId,
1953
+ },
1954
+ });
1955
+ const page = runtimeQueryCollectionData(result);
1956
+ const item = page?.items?.[0];
1957
+ if (item && typeof item === 'object') {
1958
+ const label = item[textField];
1959
+ if (label != null && label !== '') {
1960
+ return String(label);
1961
+ }
1962
+ }
1963
+ return String(rawId);
1964
+ }));
1965
+ return labels.join(', ');
1966
+ }
1967
+ buildFilterQueryParams() {
1968
+ const types = this.vm.selectedTaskTypeNames();
1969
+ const assignees = this.vm.selectedAssigneeIds();
1970
+ const reporters = this.vm.selectedReporterIds();
1971
+ const category = this.vm.currentViewCategory();
1972
+ const layout = this.vm.getLayoutForCategory(category);
1973
+ const canFilterByAll = this.canFilterByAllAssignees();
1974
+ return {
1975
+ view: category,
1976
+ layout: layout ?? null,
1977
+ type: types.length > 0 ? types : null,
1978
+ assignee: canFilterByAll && assignees.length > 0 ? assignees.map(String) : null,
1979
+ reporter: reporters.length > 0 ? reporters.map(String) : null,
1980
+ calendarEvent: null,
1981
+ };
1982
+ }
1983
+ unwrapFilterValue(value) {
1984
+ let v = value;
1985
+ while (v != null && typeof v === 'object' && !Array.isArray(v) && 'value' in v) {
1986
+ v = v['value'];
1987
+ }
1988
+ return v;
1989
+ }
1990
+ normalizeStringArrayFilterValue(value) {
1991
+ const v = this.unwrapFilterValue(value);
1992
+ if (v == null || v === '') {
1993
+ return [];
1994
+ }
1995
+ if (Array.isArray(v)) {
1996
+ return v
1997
+ .filter((item) => item != null)
1998
+ .map((item) => {
1999
+ if (typeof item === 'string') {
2000
+ return item;
2001
+ }
2002
+ if (typeof item === 'object' && item != null && 'name' in item) {
2003
+ return String(item.name);
2004
+ }
2005
+ return String(item);
2006
+ });
2007
+ }
2008
+ if (typeof v === 'object' && v != null && 'name' in v) {
2009
+ return [String(v.name)];
2010
+ }
2011
+ return [String(v)];
2012
+ }
2013
+ normalizeIdArrayFilterValue(value) {
2014
+ const v = this.unwrapFilterValue(value);
2015
+ if (v == null || v === '') {
2016
+ return [];
2017
+ }
2018
+ if (Array.isArray(v)) {
2019
+ return v
2020
+ .filter((item) => item != null)
2021
+ .map((item) => {
2022
+ if (typeof item === 'string' || typeof item === 'number') {
2023
+ return item;
2024
+ }
2025
+ if (typeof item === 'object' && item != null && 'id' in item) {
2026
+ return item.id;
2027
+ }
2028
+ return String(item);
2029
+ });
2030
+ }
2031
+ if (typeof v === 'object' && v != null && 'id' in v) {
2032
+ return [v.id];
2033
+ }
2034
+ if (typeof v === 'string' || typeof v === 'number') {
2035
+ return [v];
2036
+ }
2037
+ return [String(v)];
2038
+ }
2039
+ syncTaskTypesToViewModel(value) {
2040
+ const raw = this.normalizeStringArrayFilterValue(value);
2041
+ const newNames = normalizeTaskTypeFilterNames(raw, this.vm.taskTypeOptions());
2042
+ const currentNames = this.vm.selectedTaskTypeNames();
2043
+ if (!isEqual(sortBy(currentNames), sortBy(newNames))) {
2044
+ this.vm.setSelectedTaskTypeNames(newNames, this.isQueryParam());
2045
+ }
2046
+ }
2047
+ syncAssigneesToViewModel(value) {
2048
+ const newIds = this.normalizeIdArrayFilterValue(value);
2049
+ const currentIds = this.vm.selectedAssigneeIds();
2050
+ if (!isEqual(sortBy(currentIds.map(String)), sortBy(newIds.map(String)))) {
2051
+ // Always persist an explicit UI change (including clearing to "all assignees").
2052
+ this.vm.setSelectedAssigneeIds(newIds, false, true);
2053
+ }
2054
+ }
2055
+ syncReportersToViewModel(value) {
2056
+ const newIds = this.normalizeIdArrayFilterValue(value);
2057
+ const currentIds = this.vm.selectedReporterIds();
2058
+ if (!isEqual(sortBy(currentIds.map(String)), sortBy(newIds.map(String)))) {
2059
+ this.vm.setSelectedReporterIds(newIds, this.isQueryParam());
2060
+ }
2061
+ }
2062
+ handleColorBySelectionChange(event) {
2063
+ if (!event.isUserInteraction) {
2064
+ return;
2065
+ }
2066
+ this.handleColorByChange(event.value);
2067
+ }
2068
+ handleColorByChange(key) {
2069
+ this.vm.setColorBy(key);
2070
+ this.reEnrichBoardTasksFromRaw();
2071
+ this.refreshNonce.update((value) => value + 1);
2072
+ }
2073
+ isMultiSelectTaskTypeEnabled() {
2074
+ const viewMode = this.vm.currentViewMode();
2075
+ return viewMode !== 'kanban' && viewMode !== 'status-grouped-grid';
2076
+ }
2077
+ async loadCalendarFilterOptions() {
2078
+ const calendars = await this.taskBoardService.getCalendarEventTypes();
2079
+ this.calendarEventTypes.set(calendars.map((calendar) => ({
2080
+ id: calendar.id,
2081
+ title: calendar.title,
2082
+ })));
2083
+ return calendars;
2084
+ }
2085
+ rebuildDayDataCacheFromTasks(tasks) {
2086
+ const navigatorRange = this.getNavigatorDataRange();
2087
+ const tasksInNavigatorRange = filterTasksOverlappingRange(tasks, navigatorRange);
2088
+ const dayCountMap = new Map();
2089
+ tasksInNavigatorRange.forEach((task) => {
2090
+ const taskStart = new Date(task.startDate);
2091
+ const taskEnd = new Date(task.endDate);
2092
+ const startDay = this.calendarService.create(taskStart).startOf('day');
2093
+ const endDay = this.calendarService.create(taskEnd).startOf('day');
2094
+ let currentDay = startDay;
2095
+ while (currentDay.compare(endDay, 'day') <= 0) {
2096
+ const dayKey = currentDay.format('YYYY-MM-DD');
2097
+ dayCountMap.set(dayKey, (dayCountMap.get(dayKey) || 0) + 1);
2098
+ currentDay = currentDay.add('day', 1);
2099
+ }
2100
+ });
2101
+ this.dayDataCache.set(dayCountMap);
2102
+ this.calendar()?.render();
2103
+ }
2104
+ async resolveNavigatorTimeText() {
2105
+ const formatted = this.formatNavigatorDateText();
2106
+ return this.resolveFormattedDateTimeLabels(formatted);
2107
+ }
2108
+ /** Resolves leftover `dateTime.*` tokens via translateAsync (translateSync may run before acorex scope loads). */
2109
+ async resolveFormattedDateTimeLabels(formatted) {
2110
+ const keyPattern = /(?:@acorex:)?dateTime\.[A-Za-z0-9._-]+/g;
2111
+ const matches = [...new Set(formatted.match(keyPattern) ?? [])];
2112
+ if (matches.length === 0) {
2113
+ return formatted;
2114
+ }
2115
+ let result = formatted;
2116
+ for (const match of matches) {
2117
+ const key = match.startsWith('@') ? match : `@acorex:${match}`;
2118
+ const translated = await this.translateService.translateAsync(key);
2119
+ result = result.replaceAll(match, translated);
2120
+ }
2121
+ return result;
2122
+ }
2123
+ /**
2124
+ * Builds toolbar navigator date text with calendar-aware AXDateTime tokens.
2125
+ * Month/weekday tokens may remain as i18n keys until {@link resolveFormattedDateTimeLabels} runs.
2126
+ */
2127
+ formatNavigatorDateText() {
2128
+ const currentDate = this.vm.currentDate();
2129
+ if (!currentDate) {
2130
+ return '';
2131
+ }
2132
+ const calendarSystem = this.calendarType();
2133
+ const formatOptions = { locale: calendarSystem };
2134
+ const axDate = this.calendarService.create(currentDate, calendarSystem);
2135
+ const viewMode = this.vm.currentViewMode();
2136
+ if (viewMode === 'grid') {
2137
+ // List/grid is locked to the current month, so the navigator shows the month label.
2138
+ return axDate.format('MMMM yyyy', formatOptions);
2139
+ }
2140
+ if (viewMode === 'kanban' || viewMode === 'status-grouped-grid') {
2141
+ const startText = axDate.format('DD MMMM yyyy', formatOptions);
2142
+ if (this.vm.daysCount() <= 1) {
2143
+ return startText;
2144
+ }
2145
+ const endText = axDate.add('day', this.vm.daysCount() - 1).format('DD MMMM yyyy', formatOptions);
2146
+ return `${startText} - ${endText}`;
2147
+ }
2148
+ switch (viewMode) {
2149
+ case 'day':
2150
+ case 'timeline-day':
2151
+ return axDate.format('DD MMMM yyyy', formatOptions);
2152
+ case 'week': {
2153
+ const startText = axDate.format('DD MMMM yyyy', formatOptions);
2154
+ const endText = axDate.add('week', 1).add('day', -1).format('DD MMMM yyyy', formatOptions);
2155
+ return `${startText} - ${endText}`;
2156
+ }
2157
+ case 'agenda':
2158
+ case 'timeline-weekly': {
2159
+ const startText = axDate.format('DD MMMM yyyy', formatOptions);
2160
+ const endText = axDate.add('day', this.vm.daysCount() - 1).format('DD MMMM yyyy', formatOptions);
2161
+ return `${startText} - ${endText}`;
2162
+ }
2163
+ case 'month':
2164
+ case 'timeline-month':
2165
+ return axDate.format('MMMM yyyy', formatOptions);
2166
+ case 'timeline-year':
2167
+ return axDate.format('yyyy', formatOptions);
2168
+ default:
2169
+ return '';
2170
+ }
2171
+ }
2172
+ /** Normalizes ax-calendar slot labels (e.g. `dateTime.months.*`) for the translate pipe. */
2173
+ toAcorexTranslationKey(text) {
2174
+ if (text == null || text === '') {
2175
+ return '';
2176
+ }
2177
+ const value = String(text);
2178
+ if (value.startsWith('@')) {
2179
+ return value;
2180
+ }
2181
+ if (value.startsWith('dateTime.')) {
2182
+ return `@acorex:${value}`;
2183
+ }
2184
+ return value;
2185
+ }
2186
+ isDateTimeTranslationKey(text) {
2187
+ if (text == null) {
2188
+ return false;
2189
+ }
2190
+ const value = String(text);
2191
+ return value.startsWith('@') || value.startsWith('dateTime.');
2192
+ }
2193
+ getDayDataCount(date) {
2194
+ if (!date) {
2195
+ return 0;
2196
+ }
2197
+ try {
2198
+ // Get the native Date object from AXDateTime
2199
+ const dateObj = date.date || date._date;
2200
+ if (!dateObj) {
2201
+ return 0;
2202
+ }
2203
+ // Create a new AXDateTime from the date object for consistent formatting
2204
+ const nativeDate = dateObj instanceof Date ? dateObj : new Date(dateObj);
2205
+ const axDate = this.calendarService.create(nativeDate);
2206
+ const dayKey = axDate.format('YYYY-MM-DD');
2207
+ return this.dayDataCache().get(dayKey) || 0;
2208
+ }
2209
+ catch (error) {
2210
+ console.warn('Error getting day data count:', error);
2211
+ return 0;
2212
+ }
2213
+ }
2214
+ /**
2215
+ * Aggregates the day-level data counts for an entire month.
2216
+ * Used by the monthCellTemplate to show badges when the sidebar calendar depth is 'month'.
2217
+ */
2218
+ getMonthDataCount(date) {
2219
+ if (!date) {
2220
+ return 0;
2221
+ }
2222
+ try {
2223
+ const dateObj = date.date || date._date;
2224
+ if (!dateObj) {
2225
+ return 0;
2226
+ }
2227
+ const nativeDate = dateObj instanceof Date ? dateObj : new Date(dateObj);
2228
+ const axDate = this.calendarService.create(nativeDate);
2229
+ const monthStart = axDate.startOf('month');
2230
+ const monthEnd = axDate.endOf('month');
2231
+ let total = 0;
2232
+ let current = monthStart;
2233
+ while (current.compare(monthEnd, 'day') <= 0) {
2234
+ const dayKey = current.format('YYYY-MM-DD');
2235
+ total += this.dayDataCache().get(dayKey) || 0;
2236
+ current = current.add('day', 1);
2237
+ }
2238
+ return total;
2239
+ }
2240
+ catch (error) {
2241
+ console.warn('Error getting month data count:', error);
2242
+ return 0;
2243
+ }
2244
+ }
2245
+ async handleContextMenuItemClick(event) {
2246
+ const command = event.item.data?.command;
2247
+ const provider = this.currentTask()?.provider ?? '';
2248
+ try {
2249
+ if (command) {
2250
+ await this.taskBoardService.executeCommand(command, provider);
2251
+ }
2252
+ }
2253
+ catch (error) {
2254
+ console.error('[TaskBoard] Context menu command failed:', error);
2255
+ }
2256
+ finally {
2257
+ if (command) {
2258
+ this.taskBoardService.requestRefresh(provider || undefined);
2259
+ }
2260
+ }
2261
+ }
2262
+ handleNavigatorRangeModifierDown() {
2263
+ this.navigatorRangeSelectActive.set(true);
2264
+ }
2265
+ handleNavigatorRangeModifierUp() {
2266
+ this.navigatorRangeSelectActive.set(false);
2267
+ }
2268
+ /**
2269
+ * Registers page keyboard shortcuts with the platform shortcut registry.
2270
+ */
2271
+ registerKeyboardShortcuts() {
2272
+ this.registerTaskBoardNavigatorShortcuts();
2273
+ this.registerTaskBoardPanelShortcuts();
2274
+ this.registerTaskBoardViewCategoryShortcuts();
2275
+ this.registerTaskBoardViewLayoutShortcuts();
2276
+ }
2277
+ registerTaskBoardNavigatorShortcuts() {
2278
+ this.shortcutRegistry.register({
2279
+ owner: this.destroyRef,
2280
+ id: 'task-board-navigator',
2281
+ priority: AXPKeyboardShortcutPriority.Page,
2282
+ scope: '@task-management:task-board.page-title',
2283
+ group: '@general:keyboard-shortcuts.groups.navigation',
2284
+ when: () => this.isReady() && this.showToolbarNavigator(),
2285
+ shortcuts: [
2286
+ {
2287
+ keys: ['arrowleft'],
2288
+ title: '@task-management:task-board.navigator.previous-period',
2289
+ allowInEditableFields: true,
2290
+ handler: () => this.changeDate(false),
2291
+ },
2292
+ {
2293
+ keys: ['arrowright'],
2294
+ title: '@task-management:task-board.navigator.next-period',
2295
+ allowInEditableFields: true,
2296
+ handler: () => this.changeDate(true),
2297
+ },
2298
+ {
2299
+ keys: ['alt+t'],
2300
+ title: '@task-management:task-board.shortcuts.go-to-today',
2301
+ allowInEditableFields: true,
2302
+ handler: () => this.handleTodayClick(),
2303
+ },
2304
+ ],
2305
+ });
2306
+ }
2307
+ registerTaskBoardPanelShortcuts() {
2308
+ this.shortcutRegistry.register({
2309
+ owner: this.destroyRef,
2310
+ id: 'task-board-panels-settings',
2311
+ priority: AXPKeyboardShortcutPriority.Page,
2312
+ scope: '@task-management:task-board.page-title',
2313
+ group: '@task-management:task-board.shortcuts.groups.panels',
2314
+ when: () => this.isReady(),
2315
+ shortcuts: [
2316
+ {
2317
+ keys: ['ctrl+,'],
2318
+ title: '@task-management:task-board.shortcuts.toggle-settings',
2319
+ allowInEditableFields: true,
2320
+ handler: () => this.toggleSettingsPanel(),
2321
+ },
2322
+ ],
2323
+ });
2324
+ this.shortcutRegistry.register({
2325
+ owner: this.destroyRef,
2326
+ id: 'task-board-panels-dismiss',
2327
+ priority: AXPKeyboardShortcutPriority.Page,
2328
+ scope: '@task-management:task-board.page-title',
2329
+ group: '@task-management:task-board.shortcuts.groups.panels',
2330
+ when: () => this.isReady() && this.hasKeyboardDismissTarget(),
2331
+ shortcuts: [
2332
+ {
2333
+ keys: ['escape'],
2334
+ title: '@task-management:task-board.shortcuts.dismiss-overlays',
2335
+ handler: () => this.handleKeyboardDismiss(),
2336
+ },
2337
+ ],
2338
+ });
2339
+ }
2340
+ registerTaskBoardViewCategoryShortcuts() {
2341
+ const categories = ['calendar', 'timeline', 'table', 'kanban'];
2342
+ const titleKeys = {
2343
+ calendar: '@task-management:task-board.shortcuts.switch-to-calendar',
2344
+ timeline: '@task-management:task-board.shortcuts.switch-to-timeline',
2345
+ table: '@task-management:task-board.shortcuts.switch-to-table',
2346
+ kanban: '@task-management:task-board.shortcuts.switch-to-kanban',
2347
+ };
2348
+ for (let index = 0; index < categories.length; index++) {
2349
+ for (const category of categories) {
2350
+ this.shortcutRegistry.register({
2351
+ owner: this.destroyRef,
2352
+ id: `task-board-view-category-${category}-slot-${index + 1}`,
2353
+ priority: AXPKeyboardShortcutPriority.Page,
2354
+ scope: '@task-management:task-board.page-title',
2355
+ group: '@task-management:task-board.shortcuts.groups.views',
2356
+ when: () => this.isReady() && this.getVisibleViewCategoryByShortcutIndex(index) === category,
2357
+ shortcuts: [
2358
+ {
2359
+ keys: [`alt+${index + 1}`],
2360
+ title: titleKeys[category],
2361
+ handler: () => this.switchToViewCategory(category),
2362
+ },
2363
+ ],
2364
+ });
2365
+ }
2366
+ }
2367
+ }
2368
+ registerTaskBoardViewLayoutShortcuts() {
2369
+ this.shortcutRegistry.register({
2370
+ owner: this.destroyRef,
2371
+ id: 'task-board-view-layout',
2372
+ priority: AXPKeyboardShortcutPriority.Page,
2373
+ scope: '@task-management:task-board.page-title',
2374
+ group: '@task-management:task-board.shortcuts.groups.views',
2375
+ when: () => this.isReady() && this.canCycleViewLayout(),
2376
+ shortcuts: [
2377
+ {
2378
+ keys: ['['],
2379
+ title: '@task-management:task-board.shortcuts.previous-layout',
2380
+ handler: () => this.cycleViewLayout(false),
2381
+ },
2382
+ {
2383
+ keys: [']'],
2384
+ title: '@task-management:task-board.shortcuts.next-layout',
2385
+ handler: () => this.cycleViewLayout(true),
2386
+ },
2387
+ ],
2388
+ });
2389
+ }
2390
+ getVisibleViewCategoryByShortcutIndex(index) {
2391
+ return this.vm.visibleViewCategories()[index] ?? null;
2392
+ }
2393
+ switchToViewCategory(category) {
2394
+ if (!this.vm.visibleViewCategories().includes(category)) {
2395
+ return;
2396
+ }
2397
+ if (this.vm.currentViewCategory() !== category) {
2398
+ this.applyViewCategory(category);
2399
+ }
2400
+ }
2401
+ canCycleViewLayout() {
2402
+ return this.showViewLayoutSwitcher() && this.viewLayoutItems().length > 1;
2403
+ }
2404
+ cycleViewLayout(forward) {
2405
+ const items = this.viewLayoutItems();
2406
+ if (items.length <= 1) {
2407
+ return;
2408
+ }
2409
+ const currentLayout = this.selectedViewLayout();
2410
+ const currentIndex = items.findIndex((item) => item.key === currentLayout);
2411
+ const safeIndex = currentIndex >= 0 ? currentIndex : 0;
2412
+ const offset = forward ? 1 : -1;
2413
+ const nextIndex = (safeIndex + offset + items.length) % items.length;
2414
+ this.handleViewLayoutChange(items[nextIndex].key);
2415
+ }
2416
+ hasKeyboardDismissTarget() {
2417
+ return (this.isSettingsPanelOpen() ||
2418
+ this.popover()?.isOpen === true ||
2419
+ this.vm.isDetailPanelOpen() ||
2420
+ this.vm.isTaskSelected());
2421
+ }
2422
+ handleKeyboardDismiss() {
2423
+ if (this.isSettingsPanelOpen()) {
2424
+ this.closeSettingsPanel();
2425
+ return;
2426
+ }
2427
+ if (this.popover()?.isOpen) {
2428
+ this.popover()?.close();
2429
+ return;
2430
+ }
2431
+ if (this.vm.isDetailPanelOpen()) {
2432
+ this.vm.closeDetailPanel();
2433
+ return;
2434
+ }
2435
+ if (this.vm.isTaskSelected()) {
2436
+ this.vm.clearSelectedTask();
2437
+ }
2438
+ }
2439
+ isModifierRangeNavigatorView() {
2440
+ return this.modifierRangeBasedViews.includes(this.vm.currentViewMode());
2441
+ }
2442
+ usesNavigatorRangeSelection(view) {
2443
+ if (this.alwaysRangeBasedViews.includes(view)) {
2444
+ return true;
2445
+ }
2446
+ if (this.modifierRangeBasedViews.includes(view)) {
2447
+ return this.vm.rangeStartDate() != null || this.vm.daysCount() > 1;
2448
+ }
2449
+ return false;
2450
+ }
2451
+ isNavigatorRangeModifierActive(event) {
2452
+ const nativeEvent = event?.nativeEvent;
2453
+ if (nativeEvent instanceof MouseEvent) {
2454
+ return nativeEvent.ctrlKey || nativeEvent.metaKey;
2455
+ }
2456
+ return this.navigatorRangeSelectActive();
2457
+ }
2458
+ normalizeNavigatorDay(date) {
2459
+ const calendarSystem = this.localeService.activeProfile().calendar.system;
2460
+ return this.calendarService.create(date, calendarSystem).startOf('day').date;
2461
+ }
2462
+ applyNavigatorDateRange(start, end) {
2463
+ const startTime = start.getTime();
2464
+ const endTime = end.getTime();
2465
+ const newStartDate = new Date(Math.min(startTime, endTime));
2466
+ const newEndDate = new Date(Math.max(startTime, endTime));
2467
+ const differenceInDays = this.calculateDifferenceInDays(newStartDate, newEndDate);
2468
+ this.vm.setRangeStartDate(null);
2469
+ this.navigatorPlainSingleDaySelected = false;
2470
+ this.vm.setCurrentDate(newStartDate);
2471
+ this.vm.setDaysCount(differenceInDays);
2472
+ this.calendar()?.render();
2473
+ }
2474
+ handleNavigatorRangeSlotClick(clickedDate) {
2475
+ const clickedDay = this.normalizeNavigatorDay(clickedDate);
2476
+ const selectionStartDate = this.vm.rangeStartDate();
2477
+ if (selectionStartDate) {
2478
+ this.applyNavigatorDateRange(selectionStartDate, clickedDay);
2479
+ return;
2480
+ }
2481
+ if (this.navigatorPlainSingleDaySelected && this.vm.daysCount() === 1) {
2482
+ const selectedDay = this.normalizeNavigatorDay(this.vm.currentDate());
2483
+ this.applyNavigatorDateRange(selectedDay, clickedDay);
2484
+ return;
2485
+ }
2486
+ this.vm.setRangeStartDate(clickedDay);
2487
+ this.navigatorPlainSingleDaySelected = false;
2488
+ this.calendar()?.render();
2489
+ }
2490
+ handleNavigatorSingleDaySlotClick(clickedDate) {
2491
+ const dateToSet = this.normalizeNavigatorDay(clickedDate);
2492
+ this.vm.setRangeStartDate(null);
2493
+ this.navigatorPlainSingleDaySelected = true;
2494
+ this.vm.setCurrentDate(dateToSet);
2495
+ this.vm.setDaysCount(1);
2496
+ this.popover()?.close();
2497
+ this.calendar()?.render();
2498
+ }
2499
+ changeDate(forward) {
2500
+ this.navigatorPlainSingleDaySelected = false;
2501
+ const date = this.calendarService.create(this.vm.currentDate(), this.localeService.activeProfile().calendar.system);
2502
+ switch (this.vm.currentViewMode()) {
2503
+ case 'day':
2504
+ case 'timeline-day':
2505
+ this.vm.setCurrentDate(date.add('day', forward ? 1 : -1).date);
2506
+ break;
2507
+ case 'week':
2508
+ this.vm.setCurrentDate(date.add('week', forward ? 1 : -1).date);
2509
+ break;
2510
+ case 'agenda':
2511
+ case 'kanban':
2512
+ case 'timeline-weekly':
2513
+ case 'status-grouped-grid':
2514
+ this.vm.setCurrentDate(date.add('day', forward ? this.vm.daysCount() : -this.vm.daysCount()).date);
2515
+ break;
2516
+ case 'grid':
2517
+ case 'month':
2518
+ case 'timeline-month':
2519
+ this.vm.setCurrentDate(date.add('month', forward ? 1 : -1).date);
2520
+ break;
2521
+ case 'timeline-year':
2522
+ this.vm.setCurrentDate(date.add('year', forward ? 1 : -1).date);
2523
+ break;
2524
+ }
2525
+ this.popover()?.close();
2526
+ this.calendar()?.render();
2527
+ }
2528
+ handleCalendarSlotClick(event) {
2529
+ if (event.component.activeView !== event.component.depth)
2530
+ return;
2531
+ const clickedDate = event.item;
2532
+ const currentView = this.vm.currentViewMode();
2533
+ if (this.modifierRangeBasedViews.includes(currentView)) {
2534
+ if (this.isNavigatorRangeModifierActive(event)) {
2535
+ this.handleNavigatorRangeSlotClick(clickedDate);
2536
+ }
2537
+ else {
2538
+ this.handleNavigatorSingleDaySlotClick(clickedDate);
2539
+ }
2540
+ return;
2541
+ }
2542
+ if (this.alwaysRangeBasedViews.includes(currentView)) {
2543
+ this.handleNavigatorRangeSlotClick(clickedDate);
2544
+ return;
2545
+ }
2546
+ const calendarSystem = this.localeService.activeProfile().calendar.system;
2547
+ let dateToSet = this.calendarService.create(clickedDate, calendarSystem).startOf('day').date;
2548
+ if (currentView === 'week') {
2549
+ const weekDate = this.calendarService.create(dateToSet, calendarSystem);
2550
+ dateToSet = weekDate.startOf('week').date;
2551
+ }
2552
+ this.vm.setCurrentDate(dateToSet);
2553
+ this.popover()?.close();
2554
+ this.calendar()?.render();
2555
+ }
2556
+ /**
2557
+ * Helper function to calculate the difference in whole days between two dates.
2558
+ * The result is inclusive (e.g., May 5 to May 6 is a 2-day range).
2559
+ */
2560
+ calculateDifferenceInDays(start, end) {
2561
+ if (!start || !end) {
2562
+ return 1;
2563
+ }
2564
+ const startDate = new Date(start.getFullYear(), start.getMonth(), start.getDate());
2565
+ const endDate = new Date(end.getFullYear(), end.getMonth(), end.getDate());
2566
+ const timeDifference = endDate.getTime() - startDate.getTime();
2567
+ const daysDifference = Math.round(timeDifference / (1000 * 3600 * 24));
2568
+ return daysDifference + 1;
2569
+ }
2570
+ handleTodayClick() {
2571
+ this.vm.setRangeStartDate(null);
2572
+ this.navigatorPlainSingleDaySelected = false;
2573
+ let currentDate;
2574
+ if (this.vm.currentViewMode() === 'week') {
2575
+ currentDate = this.calendarService
2576
+ .create(new Date(), this.localeService.activeProfile().calendar.system)
2577
+ .startOf('week').date;
2578
+ }
2579
+ else {
2580
+ currentDate = new Date();
2581
+ }
2582
+ this.vm.setCurrentDate(currentDate);
2583
+ if (this.isModifierRangeNavigatorView()) {
2584
+ this.vm.setDaysCount(1);
2585
+ }
2586
+ this.popover()?.close();
2587
+ this.calendar()?.render();
2588
+ }
2589
+ async loadFromRoute() {
2590
+ const snapshot = this.activatedRoute.snapshot.queryParamMap;
2591
+ const viewParam = snapshot.get('view');
2592
+ const layoutParam = snapshot.get('layout');
2593
+ const allowedCategories = new Set(this.vm.visibleViewCategories());
2594
+ const viewBefore = this.vm.currentViewCategory();
2595
+ const layoutBefore = this.vm.getLayoutForCategory(viewBefore);
2596
+ if (viewParam && allowedCategories.has(viewParam)) {
2597
+ const category = viewParam;
2598
+ const layout = resolveTaskBoardCategoryLayoutFromQuery(category, layoutParam);
2599
+ if (layout) {
2600
+ this.vm.setViewLayout(category, layout);
2601
+ }
2602
+ this.vm.setViewCategory(category);
2603
+ }
2604
+ const layoutAfter = this.vm.getLayoutForCategory(this.vm.currentViewCategory());
2605
+ if (viewBefore !== this.vm.currentViewCategory() || layoutBefore !== layoutAfter) {
2606
+ this.recompute();
2607
+ }
2608
+ const type = snapshot.getAll('type');
2609
+ const assignee = snapshot.getAll('assignee');
2610
+ const reporter = snapshot.getAll('reporter');
2611
+ if (type.length || assignee.length || reporter.length) {
2612
+ this.isQueryParam.set(true);
2613
+ this.vm.setSelectedTaskTypeNames(normalizeTaskTypeFilterNames(type, this.vm.taskTypeOptions()), true);
2614
+ this.vm.setSelectedAssigneeIds(assignee, true);
2615
+ this.vm.setSelectedReporterIds(reporter, true);
2616
+ }
2617
+ else if (this.isQueryParam()) {
2618
+ this.isQueryParam.set(false);
2619
+ await this.vm.restoreQueryFiltersFromSettings();
2620
+ }
2621
+ const dateRange = snapshot.get('dateRange');
2622
+ if (dateRange) {
2623
+ const range = this.taskBoardService.rangeCalculator(dateRange);
2624
+ if (range) {
2625
+ this.vm.setCurrentDate(range.from);
2626
+ this.vm.setDaysCount(this.calculateDifferenceInDays(range.from, range.end));
2627
+ }
2628
+ }
2629
+ else if (!this.filtersPanelInitialized && this.isModifierRangeNavigatorView()) {
2630
+ this.vm.setRangeStartDate(null);
2631
+ this.navigatorPlainSingleDaySelected = false;
2632
+ this.vm.setDaysCount(1);
2633
+ }
2634
+ await this.syncPanelInitialFilters();
2635
+ this.filtersPanelInitialized = true;
2636
+ }
2637
+ /**
2638
+ * Checks if the currently selected task is still present in the list of visible tasks.
2639
+ * If not, it clears the selection to prevent showing details for a hidden task.
2640
+ * @param visibleTasks The new array of tasks that will be displayed.
2641
+ */
2642
+ validateSelectedTask(visibleTasks) {
2643
+ const currentSelectedTask = this.vm.selectedTask();
2644
+ // Proceed only if a task is actually selected
2645
+ if (currentSelectedTask) {
2646
+ // Check if any task in the new list has the same ID as the selected one
2647
+ const isSelectedTaskVisible = visibleTasks.some((task) => task.id === currentSelectedTask.id);
2648
+ // If the selected task is NOT in the new list, clear the selection.
2649
+ if (!isSelectedTaskVisible) {
2650
+ this.vm.selectTask(null);
2651
+ }
2652
+ }
2653
+ }
2654
+ async getResources() {
2655
+ if (this.vm.selectedTaskTypeNames().length !== 1) {
2656
+ this.resources.set([]);
2657
+ return;
2658
+ }
2659
+ const resources = await this.taskBoardService.getResources(this.vm.selectedTaskTypeNames()[0] ?? '');
2660
+ this.resources.set(resources);
2661
+ }
2662
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardPage, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2663
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardPage, isStandalone: true, selector: "ng-component", host: { listeners: { "document:keydown.control": "handleNavigatorRangeModifierDown()", "document:keyup.control": "handleNavigatorRangeModifierUp()" } }, providers: [
2664
+ {
2665
+ provide: AXPPageLayoutBase,
2666
+ useExisting: AXMTaskBoardPage,
2667
+ },
2668
+ ], viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }, { propertyName: "calendar", first: true, predicate: ["calendar"], descendants: true, isSignal: true }, { propertyName: "contextMenu", first: true, predicate: ["rootContextMenu"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Task board page shell -->\n<axp-page-layout *translate=\"let t\">\n <!-- Toolbar: date navigator, filters, view layout, and settings -->\n <axp-page-toolbar class=\"axm-task-board-toolbar\"\n [class.axm-task-board-toolbar--stack-filters]=\"!deviceService.isLarge()\">\n <div class=\"axm-task-board-toolbar__primary-row\">\n <axp-layout-prefix class=\"flex min-w-0 flex-nowrap items-center gap-2\"\n [class.flex-1]=\"deviceService.isLarge()\">\n <!-- Date navigator with calendar popover -->\n @if (showToolbarNavigator()) {\n <div class=\"scheduler-header shrink-0 w-max ax-sm sm:ax-md\">\n <ax-button look=\"blank\" (onClick)=\"changeDate(false)\">\n <ax-icon class=\"ax-icon text-xl ax-icon-chevron-left arrow-icon\"></ax-icon>\n </ax-button>\n <ax-button look=\"blank\" [text]=\"navigatorTimeText()\" #date></ax-button>\n <ax-button look=\"blank\" (onClick)=\"changeDate(true)\">\n <ax-icon class=\"ax-icon text-xl ax-icon-chevron-right arrow-icon\"></ax-icon>\n </ax-button>\n <!-- TODO(acorex-ui): Do not wrap ax-calendar in @for/@if here \u2014 ax-popover requires a direct projected child.\n Week layout comes from AXCalendarService (AXPRegionalCalendarService); navigatorCalendarLayoutEffect re-renders on profile/calendar changes.\n Remove manual render() when ax-calendar reacts to calendar service week layout changes.\n See README.md in this folder. -->\n <ax-popover [target]=\"date\" [placement]=\"'bottom'\" #popover [adaptivityEnabled]=\"true\">\n <ax-calendar #calendar [type]=\"calendarType()\" [depth]=\"calendarDepth()\" [cellClass]=\"getCellClass\"\n [ngModel]=\"currentDateForCalendar()\" [dayCellTemplate]=\"dayCellTemplate\"\n [monthCellTemplate]=\"monthCellTemplate\"\n class=\"single-range-calendar border rounded-lg\"\n (onSlotClick)=\"handleCalendarSlotClick($event)\">\n <ax-footer>\n <ax-button look=\"link\" color=\"primary\" [text]=\"(todayButtonTextKey() | translate | async)!\"\n (onClick)=\"handleTodayClick()\"></ax-button>\n </ax-footer>\n </ax-calendar>\n </ax-popover>\n </div>\n }\n\n <!-- Inline query filters (large screens only) -->\n @if (deviceService.isLarge() && filtersReady()) {\n <axp-query-filters class=\"axm-task-board-toolbar__filters-inline\" [filtersDefinitions]=\"filterDefinitions()\"\n [initialFilters]=\"panelInitialFilters()\" (onFiltersChanged)=\"handleFiltersChanged($event)\"></axp-query-filters>\n }\n </axp-layout-prefix>\n\n <axp-layout-suffix class=\"flex shrink-0 gap-2\">\n <!-- View layout switcher (kanban, grid, calendar, etc.) -->\n @if (showViewLayoutSwitcher()) {\n <ax-button class=\"axm-task-board-toolbar__view-layout-dropdown\" look=\"outline\"\n [text]=\"(selectedViewLayoutTitleKey() | translate | async)!\">\n <ax-prefix>\n <ax-icon [icon]=\"selectedViewLayoutIcon()\"></ax-icon>\n </ax-prefix>\n <ax-dropdown-panel>\n <ax-button-item-list>\n @for (item of viewLayoutItems(); track item.key) {\n <ax-button-item [text]=\"(viewLayoutTranslationKey(item.text) | translate | async)!\"\n (onClick)=\"handleViewLayoutChange(item.key)\">\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-check\" class=\"axm-task-board-toolbar__dropdown-menu-check\"\n [class.--placeholder]=\"!isViewLayoutSelected(item.key)\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n <!-- Settings panel toggle with active calendar-type badge -->\n <div class=\"axm-task-board-toolbar__settings-slot\">\n <ax-button class=\"axm-task-board-toolbar__settings-button\" look=\"outline\" [iconOnly]=\"true\"\n [title]=\"('@task-management:task-board.settings.title' | translate | async)!\"\n (onClick)=\"toggleSettingsPanel()\">\n <ax-prefix>\n <ax-icon [icon]=\"isSettingsPanelOpen() ? 'fa-solid fa-gear' : 'fa-light fa-gear'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n @if (selectedCalendarEventTypeCount() > 0) {\n <div class=\"axm-task-board-toolbar__settings-badge\" [class.--xs]=\"selectedCalendarEventTypeCount() > 9\">\n <ax-badge color=\"primary\" [text]=\"selectedCalendarEventTypeCount().toString()\"></ax-badge>\n </div>\n }\n </div>\n </axp-layout-suffix>\n </div>\n\n <!-- Stacked query filters (small screens) -->\n @if (!deviceService.isLarge() && filtersReady()) {\n <div class=\"axm-task-board-toolbar__filters-row\">\n <axp-query-filters [filtersDefinitions]=\"filterDefinitions()\" [initialFilters]=\"panelInitialFilters()\"\n (onFiltersChanged)=\"handleFiltersChanged($event)\"></axp-query-filters>\n </div>\n }\n </axp-page-toolbar>\n\n <axp-page-content class=\"flex flex-row gap-2 min-h-0\">\n <div class=\"axm-task-board-main flex flex-row gap-4 flex-1 min-w-0 min-h-0 relative\">\n <div class=\"axm-task-board-main__content flex flex-row gap-2 flex-1 min-w-0 min-h-0\">\n @if (isReady()) {\n <!-- Active view: kanban, grid, status-grouped grid, or calendar/scheduler -->\n @switch (vm.currentViewMode()) {\n @case ('kanban') {\n <axm-task-board-kanban-view class=\"axp-kanban-container h-full w-full min-w-0\" [(tasks)]=\"resolvedTasks\"\n (onTaskClick)=\"vm.selectTask($event)\" (onActionClick)=\"handleContextMenu($event)\"></axm-task-board-kanban-view>\n }\n @case ('grid') {\n <axm-task-board-grid-view class=\"h-full w-full min-w-0\" [dataSource]=\"gridDataSource\"\n [refreshNonce]=\"refreshNonce()\" (onTaskClick)=\"vm.selectTask($event)\"></axm-task-board-grid-view>\n }\n @case ('status-grouped-grid') {\n <axm-task-board-status-grouped-grid-view class=\"flex flex-col gap-2 w-full min-w-0\"\n [dataSource]=\"gridDataSource\" [refreshNonce]=\"refreshNonce()\"\n (onTaskClick)=\"vm.selectTask($event)\"></axm-task-board-status-grouped-grid-view>\n }\n @default {\n <axm-task-board-calendar-view class=\"axp-scheduler-container h-full w-full min-w-0\"\n [resources]=\"resources()\" [startingDate]=\"vm.currentDate()\" [dataSource]=\"schedulerDataSource\"\n [refreshNonce]=\"refreshNonce()\" (onTaskClick)=\"vm.selectTask($event)\" [selectedView]=\"vm.currentViewMode()\"\n (onActionClick)=\"handleContextMenu($event)\" (onTaskRightClick)=\"handleContextMenu($event)\"\n (component)=\"schedulerComponentChanged($event)\"\n (onMonthSlotDblClicked)=\"handleMonthSlotDblClicked($event)\"></axm-task-board-calendar-view>\n }\n }\n\n <!-- Shared task context menu -->\n <ax-context-menu #rootContextMenu [closeOn]=\"'leave'\" [orientation]=\"'vertical'\"\n (onOpening)=\"handleContextMenuOnOpening($event)\" (onItemClick)=\"handleContextMenuItemClick($event)\">\n </ax-context-menu>\n\n <!-- Task detail side panel (large screens only) -->\n @if (vm.isDetailPanelOpen() && deviceService.isLarge()) {\n <axm-task-board-detail-panel></axm-task-board-detail-panel>\n }\n } @else {\n <!-- Initial bootstrap loading state -->\n <axm-task-board-content-skeleton class=\"flex-1 min-h-0 w-full self-stretch\"></axm-task-board-content-skeleton>\n }\n </div>\n\n <!-- Board settings side panel -->\n @if (isSettingsPanelOpen()) {\n @if (!deviceService.isLarge()) {\n <button type=\"button\" class=\"axm-task-board-settings-backdrop\" (click)=\"closeSettingsPanel()\"\n [attr.aria-label]=\"('@general:actions.close.title' | translate | async)!\"></button>\n }\n <axp-layout-side-detail-panel class=\"sticky top-0 z-10 max-h-full flex-none\"\n [class.axm-task-board-settings-panel--overlay]=\"!deviceService.isLarge()\" [header]=\"settingsPanelHeader()\"\n [sections]=\"settingsPanelSections()\" (panelClose)=\"closeSettingsPanel()\">\n <!-- Color-by dimension selector -->\n <ng-template axpLayoutSideDetailPanelSection=\"colorBy\">\n <ax-selection-list class=\"w-full\" valueField=\"key\" textField=\"titleKey\" [direction]=\"'vertical'\"\n [look]=\"'solid'\" [multiple]=\"false\" [items]=\"colorByItems\" [ngModel]=\"effectiveColorBy()\"\n [customTemplate]=\"colorByItemTemplate\" (onValueChanged)=\"handleColorBySelectionChange($event)\">\n <ng-template #colorByItemTemplate let-item>\n {{ (item.data.titleKey | translate | async)! }}\n </ng-template>\n </ax-selection-list>\n </ng-template>\n\n <!-- Calendar event type visibility (calendar views only) -->\n @if (showCalendarEventButton()) {\n <ng-template axpLayoutSideDetailPanelSection=\"calendars\">\n <ax-selection-list class=\"w-full\" valueField=\"id\" textField=\"text\" [direction]=\"'vertical'\"\n [look]=\"'solid'\" [multiple]=\"true\" [items]=\"calendarEventTypeItems()\"\n [ngModel]=\"vm.selectedCalendarEventTypeIds()\"\n (onValueChanged)=\"handleCalendarEventTypesSelectionChange($event)\"></ax-selection-list>\n </ng-template>\n }\n </axp-layout-side-detail-panel>\n }\n </div>\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Calendar popover: day cell template with task-count badge -->\n<ng-template #dayCellTemplate let-slot>\n <div class=\"flex flex-col items-center justify-between w-full gap-1\"\n [style.padding-bottom]=\"'0.25rem'\">\n <span class=\"flex-1 text-center\">\n @if (isDateTimeTranslationKey(slot.slot.text)) {\n {{ (toAcorexTranslationKey(slot.slot.text) | translate | async)! }}\n } @else {\n {{ slot.slot.text ?? slot.slot.date?.day ?? '' }}\n }\n </span>\n @if (slot.slot.date && getDayDataCount(slot.slot.date) > 0) {\n <ax-badge color=\"warning\"></ax-badge>\n }\n </div>\n</ng-template>\n\n<!-- Calendar popover: month cell template with task-count badge -->\n<ng-template #monthCellTemplate let-slot>\n <div class=\"flex flex-col items-center justify-between w-full gap-1\"\n [style.padding-bottom]=\"'0.25rem'\">\n <span class=\"flex-1 text-center\">\n @if (isDateTimeTranslationKey(slot.slot.text)) {\n {{ (toAcorexTranslationKey(slot.slot.text) | translate | async)! }}\n } @else {\n {{ slot.slot.text ?? '' }}\n }\n </span>\n @if (slot.slot.date && getMonthDataCount(slot.slot.date) > 0) {\n <ax-badge color=\"warning\"></ax-badge>\n }\n </div>\n</ng-template>\n", styles: ["@layer properties;.axm-task-board-toolbar{flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row{display:flex;width:100%;min-width:0px;flex:auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row>axp-layout-prefix{flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row>axp-layout-suffix{margin-inline-start:auto;flex-shrink:0}.axm-task-board-toolbar:not(.axm-task-board-toolbar--stack-filters) .axm-task-board-toolbar__primary-row{display:contents}.axm-task-board-toolbar .axm-task-board-toolbar__filters-inline,.axm-task-board-toolbar .axm-task-board-toolbar__filters-row axp-query-filters{flex:1 1 0;min-width:0px;width:auto}.axm-task-board-toolbar .axm-task-board-toolbar__filters-inline>.flex,.axm-task-board-toolbar .axm-task-board-toolbar__filters-row axp-query-filters>.flex{padding-top:0;padding-bottom:0}.axm-task-board-toolbar.axm-task-board-toolbar--stack-filters{flex-wrap:wrap;row-gap:.5rem}.axm-task-board-toolbar.axm-task-board-toolbar--stack-filters .axm-task-board-toolbar__primary-row{width:100%;flex:1 1 100%}.axm-task-board-toolbar .axm-task-board-toolbar__filters-row{width:100%;min-width:0px;flex:1 1 100%}.axm-task-board-toolbar .axm-task-board-toolbar__view-layout-dropdown,.axm-task-board-toolbar .axm-task-board-toolbar__settings-slot{flex-shrink:0}.axm-task-board-toolbar .axm-task-board-toolbar__settings-slot{position:relative}.axm-task-board-toolbar .axm-task-board-toolbar__settings-badge{pointer-events:none;position:absolute;top:-.25rem;inset-inline-end:-.25rem}.axm-task-board-toolbar .axm-task-board-toolbar__settings-badge.--xs ax-badge{font-size:.625rem}.axm-task-board-toolbar .axm-task-board-toolbar__dropdown-menu-check{display:inline-flex;width:calc(var(--spacing, .25rem) * 4);justify-content:center}.axm-task-board-toolbar .axm-task-board-toolbar__dropdown-menu-check.--placeholder{visibility:hidden}html[dir=rtl] axp-page-layout axp-page-toolbar axp-layout-prefix .scheduler-header .arrow-icon:before{-moz-transform:scale(-1,1);-webkit-transform:scale(-1,1);-o-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scaleX(-1)}.single-range-calendar .range-start,.single-range-calendar .range-end{color:rgba(var(--ax-sys-color-on-primary-surface))!important;background-color:rgba(var(--ax-sys-color-primary-surface),.85)!important}:is(.single-range-calendar .range-start,.single-range-calendar .range-end):hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}:is(.single-range-calendar .range-start,.single-range-calendar .range-end).state-holiday{background-color:rgba(var(--ax-sys-color-danger-surface),.85)!important}.single-range-calendar .range-between{color:rgba(var(--ax-sys-color-on-primary-surface))!important;background-color:rgba(var(--ax-sys-color-primary-surface),.65)}.single-range-calendar .range-between.state-holiday{background-color:rgba(var(--ax-sys-color-danger-surface),.65)}.single-range-calendar ax-footer{display:flex;justify-content:center;border-color:rgba(var(--ax-comp-calendar-view-header-border-color));border-top-width:var(--ax-comp-calendar-view-header-border-width, 1px)}.axm-task-board-main,.axm-task-board-main .axm-task-board-main__content{min-height:0px}.axm-task-board-main .axm-task-board-main__content:has(>axm-task-board-content-skeleton){height:100%;width:100%;align-self:stretch}.axm-task-board-settings-backdrop{position:fixed;inset:0;cursor:pointer;border-style:var(--tw-border-style);border-width:0px;padding:0;z-index:15;background:rgba(var(--ax-sys-color-darkest-surface),.45)}.axm-task-board-settings-panel--overlay{position:fixed;max-height:100vh;top:0;inset-inline-end:0;bottom:0;z-index:20;width:min(20rem,100vw)}axp-page-content:not(:has(axm-task-board-status-grouped-grid-view,axm-task-board-time-grouped-grid-view)){overflow-y:hidden}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type:
2669
+ //
2670
+ AXMenuModule }, { kind: "component", type: i2.AXContextMenuComponent, selector: "ax-context-menu", inputs: ["orientation", "openOn", "closeOn", "closeOnRouteChange", "items", "target"], outputs: ["onItemClick", "onOpening", "onClose"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$4.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i1$4.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1$4.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i5.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXBreadcrumbsModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i7.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "component", type:
2671
+ //
2672
+ AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPQueryFiltersComponent, selector: "axp-query-filters", inputs: ["filtersDefinitions", "initialFilters", "quickSearch"], outputs: ["onFiltersChanged"] }, { kind: "component", type: AXMTaskBoardContentSkeletonComponent, selector: "axm-task-board-content-skeleton" }, { kind: "component", type: AXMTaskBoardCalendarViewComponent, selector: "axm-task-board-calendar-view", inputs: ["resources", "refreshNonce", "startingDate", "selectedView", "dataSource"], outputs: ["onTaskClick", "onTaskChanged", "onMonthSlotDblClicked", "onActionClick", "component", "onTaskRightClick", "onRangeChanged"] }, { kind: "component", type: AXMTaskBoardKanbanViewComponent, selector: "axm-task-board-kanban-view", inputs: ["tasks"], outputs: ["tasksChange", "onTaskClick", "onTaskChanged", "component", "onActionClick"] }, { kind: "component", type: AXMTaskBoardGridViewComponent, selector: "axm-task-board-grid-view", inputs: ["dataSource", "refreshNonce"], outputs: ["onTaskClick"] }, { kind: "component", type: AXMTaskBoardStatusGroupedGridViewComponent, selector: "axm-task-board-status-grouped-grid-view", inputs: ["provider", "dataSource", "refreshNonce"], outputs: ["onTaskClick"] }, { kind: "component", type: AXMTaskBoardDetailPanel, selector: "axm-task-board-detail-panel" }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i8.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "component", type: AXCalendarComponent, selector: "ax-calendar", inputs: ["rtl", "readonly", "value", "name", "disabled", "depth", "activeView", "minValue", "maxValue", "disabledDates", "holidayDates", "type", "dayCellTemplate", "monthCellTemplate", "yearCellTemplate", "cellClass", "showNavigation", "count", "id", "weekend", "weekdays", "look", "yearRange", "holidays"], outputs: ["onOptionChanged", "valueChange", "onValueChanged", "minValueChange", "maxValueChange", "onBlur", "onFocus", "depthChange", "typeChange", "activeViewChange", "disabledDatesChange", "holidayDatesChange", "onNavigate", "onSlotClick", "countChange"] }, { kind: "ngmodule", type: AXSelectionListModule }, { kind: "component", type: i9.AXSelectionListComponent, selector: "ax-selection-list", inputs: ["id", "name", "disabled", "readonly", "tabIndex", "size", "value", "valueField", "textField", "disabledField", "readonlyField", "multiple", "direction", "customTemplate", "showControl", "items", "look"], outputs: ["onValueChanged", "onBlur", "onFocus"] }, { kind: "component", type: AXPLayoutSideDetailPanelComponent, selector: "axp-layout-side-detail-panel", inputs: ["multiSelectLabel", "header", "sections", "rows", "rowsSectionTitle", "noRowsMessage", "emptyState"], outputs: ["panelClose"] }, { kind: "directive", type: AXPLayoutSideDetailPanelSectionDirective, selector: "[axpLayoutSideDetailPanelSection]", inputs: ["axpLayoutSideDetailPanelSection"] }, { kind: "pipe", type: i1$5.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2673
+ }
2674
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardPage, decorators: [{
2675
+ type: Component,
2676
+ args: [{ imports: [
2677
+ FormsModule,
2678
+ CommonModule,
2679
+ RouterModule,
2680
+ //
2681
+ AXMenuModule,
2682
+ AXButtonModule,
2683
+ AXDropdownModule,
2684
+ AXTranslationModule,
2685
+ AXDecoratorModule,
2686
+ AXBreadcrumbsModule,
2687
+ AXBadgeModule,
2688
+ //
2689
+ AXPThemeLayoutBlockComponent,
2690
+ AXPPageLayoutComponent,
2691
+ AXPQueryFiltersComponent,
2692
+ AXMTaskBoardContentSkeletonComponent,
2693
+ AXMTaskBoardCalendarViewComponent,
2694
+ AXMTaskBoardKanbanViewComponent,
2695
+ AXMTaskBoardGridViewComponent,
2696
+ AXMTaskBoardStatusGroupedGridViewComponent,
2697
+ AXMTaskBoardDetailPanel,
2698
+ AXPopoverModule,
2699
+ AXCalendarComponent,
2700
+ AXSelectionListModule,
2701
+ AXPLayoutSideDetailPanelComponent,
2702
+ AXPLayoutSideDetailPanelSectionDirective,
2703
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
2704
+ {
2705
+ provide: AXPPageLayoutBase,
2706
+ useExisting: AXMTaskBoardPage,
2707
+ },
2708
+ ], template: "<!-- Task board page shell -->\n<axp-page-layout *translate=\"let t\">\n <!-- Toolbar: date navigator, filters, view layout, and settings -->\n <axp-page-toolbar class=\"axm-task-board-toolbar\"\n [class.axm-task-board-toolbar--stack-filters]=\"!deviceService.isLarge()\">\n <div class=\"axm-task-board-toolbar__primary-row\">\n <axp-layout-prefix class=\"flex min-w-0 flex-nowrap items-center gap-2\"\n [class.flex-1]=\"deviceService.isLarge()\">\n <!-- Date navigator with calendar popover -->\n @if (showToolbarNavigator()) {\n <div class=\"scheduler-header shrink-0 w-max ax-sm sm:ax-md\">\n <ax-button look=\"blank\" (onClick)=\"changeDate(false)\">\n <ax-icon class=\"ax-icon text-xl ax-icon-chevron-left arrow-icon\"></ax-icon>\n </ax-button>\n <ax-button look=\"blank\" [text]=\"navigatorTimeText()\" #date></ax-button>\n <ax-button look=\"blank\" (onClick)=\"changeDate(true)\">\n <ax-icon class=\"ax-icon text-xl ax-icon-chevron-right arrow-icon\"></ax-icon>\n </ax-button>\n <!-- TODO(acorex-ui): Do not wrap ax-calendar in @for/@if here \u2014 ax-popover requires a direct projected child.\n Week layout comes from AXCalendarService (AXPRegionalCalendarService); navigatorCalendarLayoutEffect re-renders on profile/calendar changes.\n Remove manual render() when ax-calendar reacts to calendar service week layout changes.\n See README.md in this folder. -->\n <ax-popover [target]=\"date\" [placement]=\"'bottom'\" #popover [adaptivityEnabled]=\"true\">\n <ax-calendar #calendar [type]=\"calendarType()\" [depth]=\"calendarDepth()\" [cellClass]=\"getCellClass\"\n [ngModel]=\"currentDateForCalendar()\" [dayCellTemplate]=\"dayCellTemplate\"\n [monthCellTemplate]=\"monthCellTemplate\"\n class=\"single-range-calendar border rounded-lg\"\n (onSlotClick)=\"handleCalendarSlotClick($event)\">\n <ax-footer>\n <ax-button look=\"link\" color=\"primary\" [text]=\"(todayButtonTextKey() | translate | async)!\"\n (onClick)=\"handleTodayClick()\"></ax-button>\n </ax-footer>\n </ax-calendar>\n </ax-popover>\n </div>\n }\n\n <!-- Inline query filters (large screens only) -->\n @if (deviceService.isLarge() && filtersReady()) {\n <axp-query-filters class=\"axm-task-board-toolbar__filters-inline\" [filtersDefinitions]=\"filterDefinitions()\"\n [initialFilters]=\"panelInitialFilters()\" (onFiltersChanged)=\"handleFiltersChanged($event)\"></axp-query-filters>\n }\n </axp-layout-prefix>\n\n <axp-layout-suffix class=\"flex shrink-0 gap-2\">\n <!-- View layout switcher (kanban, grid, calendar, etc.) -->\n @if (showViewLayoutSwitcher()) {\n <ax-button class=\"axm-task-board-toolbar__view-layout-dropdown\" look=\"outline\"\n [text]=\"(selectedViewLayoutTitleKey() | translate | async)!\">\n <ax-prefix>\n <ax-icon [icon]=\"selectedViewLayoutIcon()\"></ax-icon>\n </ax-prefix>\n <ax-dropdown-panel>\n <ax-button-item-list>\n @for (item of viewLayoutItems(); track item.key) {\n <ax-button-item [text]=\"(viewLayoutTranslationKey(item.text) | translate | async)!\"\n (onClick)=\"handleViewLayoutChange(item.key)\">\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-check\" class=\"axm-task-board-toolbar__dropdown-menu-check\"\n [class.--placeholder]=\"!isViewLayoutSelected(item.key)\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n <!-- Settings panel toggle with active calendar-type badge -->\n <div class=\"axm-task-board-toolbar__settings-slot\">\n <ax-button class=\"axm-task-board-toolbar__settings-button\" look=\"outline\" [iconOnly]=\"true\"\n [title]=\"('@task-management:task-board.settings.title' | translate | async)!\"\n (onClick)=\"toggleSettingsPanel()\">\n <ax-prefix>\n <ax-icon [icon]=\"isSettingsPanelOpen() ? 'fa-solid fa-gear' : 'fa-light fa-gear'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n @if (selectedCalendarEventTypeCount() > 0) {\n <div class=\"axm-task-board-toolbar__settings-badge\" [class.--xs]=\"selectedCalendarEventTypeCount() > 9\">\n <ax-badge color=\"primary\" [text]=\"selectedCalendarEventTypeCount().toString()\"></ax-badge>\n </div>\n }\n </div>\n </axp-layout-suffix>\n </div>\n\n <!-- Stacked query filters (small screens) -->\n @if (!deviceService.isLarge() && filtersReady()) {\n <div class=\"axm-task-board-toolbar__filters-row\">\n <axp-query-filters [filtersDefinitions]=\"filterDefinitions()\" [initialFilters]=\"panelInitialFilters()\"\n (onFiltersChanged)=\"handleFiltersChanged($event)\"></axp-query-filters>\n </div>\n }\n </axp-page-toolbar>\n\n <axp-page-content class=\"flex flex-row gap-2 min-h-0\">\n <div class=\"axm-task-board-main flex flex-row gap-4 flex-1 min-w-0 min-h-0 relative\">\n <div class=\"axm-task-board-main__content flex flex-row gap-2 flex-1 min-w-0 min-h-0\">\n @if (isReady()) {\n <!-- Active view: kanban, grid, status-grouped grid, or calendar/scheduler -->\n @switch (vm.currentViewMode()) {\n @case ('kanban') {\n <axm-task-board-kanban-view class=\"axp-kanban-container h-full w-full min-w-0\" [(tasks)]=\"resolvedTasks\"\n (onTaskClick)=\"vm.selectTask($event)\" (onActionClick)=\"handleContextMenu($event)\"></axm-task-board-kanban-view>\n }\n @case ('grid') {\n <axm-task-board-grid-view class=\"h-full w-full min-w-0\" [dataSource]=\"gridDataSource\"\n [refreshNonce]=\"refreshNonce()\" (onTaskClick)=\"vm.selectTask($event)\"></axm-task-board-grid-view>\n }\n @case ('status-grouped-grid') {\n <axm-task-board-status-grouped-grid-view class=\"flex flex-col gap-2 w-full min-w-0\"\n [dataSource]=\"gridDataSource\" [refreshNonce]=\"refreshNonce()\"\n (onTaskClick)=\"vm.selectTask($event)\"></axm-task-board-status-grouped-grid-view>\n }\n @default {\n <axm-task-board-calendar-view class=\"axp-scheduler-container h-full w-full min-w-0\"\n [resources]=\"resources()\" [startingDate]=\"vm.currentDate()\" [dataSource]=\"schedulerDataSource\"\n [refreshNonce]=\"refreshNonce()\" (onTaskClick)=\"vm.selectTask($event)\" [selectedView]=\"vm.currentViewMode()\"\n (onActionClick)=\"handleContextMenu($event)\" (onTaskRightClick)=\"handleContextMenu($event)\"\n (component)=\"schedulerComponentChanged($event)\"\n (onMonthSlotDblClicked)=\"handleMonthSlotDblClicked($event)\"></axm-task-board-calendar-view>\n }\n }\n\n <!-- Shared task context menu -->\n <ax-context-menu #rootContextMenu [closeOn]=\"'leave'\" [orientation]=\"'vertical'\"\n (onOpening)=\"handleContextMenuOnOpening($event)\" (onItemClick)=\"handleContextMenuItemClick($event)\">\n </ax-context-menu>\n\n <!-- Task detail side panel (large screens only) -->\n @if (vm.isDetailPanelOpen() && deviceService.isLarge()) {\n <axm-task-board-detail-panel></axm-task-board-detail-panel>\n }\n } @else {\n <!-- Initial bootstrap loading state -->\n <axm-task-board-content-skeleton class=\"flex-1 min-h-0 w-full self-stretch\"></axm-task-board-content-skeleton>\n }\n </div>\n\n <!-- Board settings side panel -->\n @if (isSettingsPanelOpen()) {\n @if (!deviceService.isLarge()) {\n <button type=\"button\" class=\"axm-task-board-settings-backdrop\" (click)=\"closeSettingsPanel()\"\n [attr.aria-label]=\"('@general:actions.close.title' | translate | async)!\"></button>\n }\n <axp-layout-side-detail-panel class=\"sticky top-0 z-10 max-h-full flex-none\"\n [class.axm-task-board-settings-panel--overlay]=\"!deviceService.isLarge()\" [header]=\"settingsPanelHeader()\"\n [sections]=\"settingsPanelSections()\" (panelClose)=\"closeSettingsPanel()\">\n <!-- Color-by dimension selector -->\n <ng-template axpLayoutSideDetailPanelSection=\"colorBy\">\n <ax-selection-list class=\"w-full\" valueField=\"key\" textField=\"titleKey\" [direction]=\"'vertical'\"\n [look]=\"'solid'\" [multiple]=\"false\" [items]=\"colorByItems\" [ngModel]=\"effectiveColorBy()\"\n [customTemplate]=\"colorByItemTemplate\" (onValueChanged)=\"handleColorBySelectionChange($event)\">\n <ng-template #colorByItemTemplate let-item>\n {{ (item.data.titleKey | translate | async)! }}\n </ng-template>\n </ax-selection-list>\n </ng-template>\n\n <!-- Calendar event type visibility (calendar views only) -->\n @if (showCalendarEventButton()) {\n <ng-template axpLayoutSideDetailPanelSection=\"calendars\">\n <ax-selection-list class=\"w-full\" valueField=\"id\" textField=\"text\" [direction]=\"'vertical'\"\n [look]=\"'solid'\" [multiple]=\"true\" [items]=\"calendarEventTypeItems()\"\n [ngModel]=\"vm.selectedCalendarEventTypeIds()\"\n (onValueChanged)=\"handleCalendarEventTypesSelectionChange($event)\"></ax-selection-list>\n </ng-template>\n }\n </axp-layout-side-detail-panel>\n }\n </div>\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Calendar popover: day cell template with task-count badge -->\n<ng-template #dayCellTemplate let-slot>\n <div class=\"flex flex-col items-center justify-between w-full gap-1\"\n [style.padding-bottom]=\"'0.25rem'\">\n <span class=\"flex-1 text-center\">\n @if (isDateTimeTranslationKey(slot.slot.text)) {\n {{ (toAcorexTranslationKey(slot.slot.text) | translate | async)! }}\n } @else {\n {{ slot.slot.text ?? slot.slot.date?.day ?? '' }}\n }\n </span>\n @if (slot.slot.date && getDayDataCount(slot.slot.date) > 0) {\n <ax-badge color=\"warning\"></ax-badge>\n }\n </div>\n</ng-template>\n\n<!-- Calendar popover: month cell template with task-count badge -->\n<ng-template #monthCellTemplate let-slot>\n <div class=\"flex flex-col items-center justify-between w-full gap-1\"\n [style.padding-bottom]=\"'0.25rem'\">\n <span class=\"flex-1 text-center\">\n @if (isDateTimeTranslationKey(slot.slot.text)) {\n {{ (toAcorexTranslationKey(slot.slot.text) | translate | async)! }}\n } @else {\n {{ slot.slot.text ?? '' }}\n }\n </span>\n @if (slot.slot.date && getMonthDataCount(slot.slot.date) > 0) {\n <ax-badge color=\"warning\"></ax-badge>\n }\n </div>\n</ng-template>\n", styles: ["@layer properties;.axm-task-board-toolbar{flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row{display:flex;width:100%;min-width:0px;flex:auto;align-items:center;gap:calc(var(--spacing, .25rem) * 2);flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row>axp-layout-prefix{flex-wrap:nowrap}.axm-task-board-toolbar .axm-task-board-toolbar__primary-row>axp-layout-suffix{margin-inline-start:auto;flex-shrink:0}.axm-task-board-toolbar:not(.axm-task-board-toolbar--stack-filters) .axm-task-board-toolbar__primary-row{display:contents}.axm-task-board-toolbar .axm-task-board-toolbar__filters-inline,.axm-task-board-toolbar .axm-task-board-toolbar__filters-row axp-query-filters{flex:1 1 0;min-width:0px;width:auto}.axm-task-board-toolbar .axm-task-board-toolbar__filters-inline>.flex,.axm-task-board-toolbar .axm-task-board-toolbar__filters-row axp-query-filters>.flex{padding-top:0;padding-bottom:0}.axm-task-board-toolbar.axm-task-board-toolbar--stack-filters{flex-wrap:wrap;row-gap:.5rem}.axm-task-board-toolbar.axm-task-board-toolbar--stack-filters .axm-task-board-toolbar__primary-row{width:100%;flex:1 1 100%}.axm-task-board-toolbar .axm-task-board-toolbar__filters-row{width:100%;min-width:0px;flex:1 1 100%}.axm-task-board-toolbar .axm-task-board-toolbar__view-layout-dropdown,.axm-task-board-toolbar .axm-task-board-toolbar__settings-slot{flex-shrink:0}.axm-task-board-toolbar .axm-task-board-toolbar__settings-slot{position:relative}.axm-task-board-toolbar .axm-task-board-toolbar__settings-badge{pointer-events:none;position:absolute;top:-.25rem;inset-inline-end:-.25rem}.axm-task-board-toolbar .axm-task-board-toolbar__settings-badge.--xs ax-badge{font-size:.625rem}.axm-task-board-toolbar .axm-task-board-toolbar__dropdown-menu-check{display:inline-flex;width:calc(var(--spacing, .25rem) * 4);justify-content:center}.axm-task-board-toolbar .axm-task-board-toolbar__dropdown-menu-check.--placeholder{visibility:hidden}html[dir=rtl] axp-page-layout axp-page-toolbar axp-layout-prefix .scheduler-header .arrow-icon:before{-moz-transform:scale(-1,1);-webkit-transform:scale(-1,1);-o-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scaleX(-1)}.single-range-calendar .range-start,.single-range-calendar .range-end{color:rgba(var(--ax-sys-color-on-primary-surface))!important;background-color:rgba(var(--ax-sys-color-primary-surface),.85)!important}:is(.single-range-calendar .range-start,.single-range-calendar .range-end):hover{background-color:rgba(var(--ax-sys-color-primary-surface))!important}:is(.single-range-calendar .range-start,.single-range-calendar .range-end).state-holiday{background-color:rgba(var(--ax-sys-color-danger-surface),.85)!important}.single-range-calendar .range-between{color:rgba(var(--ax-sys-color-on-primary-surface))!important;background-color:rgba(var(--ax-sys-color-primary-surface),.65)}.single-range-calendar .range-between.state-holiday{background-color:rgba(var(--ax-sys-color-danger-surface),.65)}.single-range-calendar ax-footer{display:flex;justify-content:center;border-color:rgba(var(--ax-comp-calendar-view-header-border-color));border-top-width:var(--ax-comp-calendar-view-header-border-width, 1px)}.axm-task-board-main,.axm-task-board-main .axm-task-board-main__content{min-height:0px}.axm-task-board-main .axm-task-board-main__content:has(>axm-task-board-content-skeleton){height:100%;width:100%;align-self:stretch}.axm-task-board-settings-backdrop{position:fixed;inset:0;cursor:pointer;border-style:var(--tw-border-style);border-width:0px;padding:0;z-index:15;background:rgba(var(--ax-sys-color-darkest-surface),.45)}.axm-task-board-settings-panel--overlay{position:fixed;max-height:100vh;top:0;inset-inline-end:0;bottom:0;z-index:20;width:min(20rem,100vw)}axp-page-content:not(:has(axm-task-board-status-grouped-grid-view,axm-task-board-time-grouped-grid-view)){overflow-y:hidden}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2709
+ }], propDecorators: { popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }], calendar: [{ type: i0.ViewChild, args: ['calendar', { isSignal: true }] }], contextMenu: [{ type: i0.ViewChild, args: ['rootContextMenu', { isSignal: true }] }], handleNavigatorRangeModifierDown: [{
2710
+ type: HostListener,
2711
+ args: ['document:keydown.control']
2712
+ }], handleNavigatorRangeModifierUp: [{
2713
+ type: HostListener,
2714
+ args: ['document:keyup.control']
2715
+ }] } });
2716
+ //#region ----- Query Param Helpers -----
2717
+ function areTaskBoardQueryParamsEquivalent(current, next) {
2718
+ const keys = ['view', 'layout', 'type', 'assignee', 'reporter', 'calendarEvent'];
2719
+ for (const key of keys) {
2720
+ const nextValue = next[key];
2721
+ if (nextValue == null) {
2722
+ if (current.has(key)) {
2723
+ return false;
2724
+ }
2725
+ continue;
2726
+ }
2727
+ if (Array.isArray(nextValue)) {
2728
+ const currentValues = current.getAll(key);
2729
+ if (currentValues.length !== nextValue.length ||
2730
+ currentValues.some((value, index) => value !== String(nextValue[index]))) {
2731
+ return false;
2732
+ }
2733
+ continue;
2734
+ }
2735
+ if (current.get(key) !== String(nextValue)) {
2736
+ return false;
2737
+ }
2738
+ }
2739
+ return true;
2740
+ }
2741
+
2742
+ export { AXMTaskBoardPage };
2743
+ //# sourceMappingURL=acorex-platform-module-task-management-client-task-board.page-8I8H2pFq.mjs.map