@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,4445 @@
1
+ import { AXPBroadcastEventService, AXPEntityBroadcastService, AXP_MODULE_MANIFEST_PROVIDER, AXP_CATALOG_SCOPE_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/core';
2
+ import * as i1$1 from '@angular/common';
3
+ import { CommonModule, DOCUMENT } from '@angular/common';
4
+ import * as i0 from '@angular/core';
5
+ import { inject, InjectionToken, Injector, DestroyRef, Injectable, computed, input, signal, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgModule, output } from '@angular/core';
6
+ import { RootConfig, AXPProjectsWorkPermissionKeys, AXPTaskManagementMenuKeys, AXMTaskBoardSettings, AXPTaskManagementCommandKeys, AXMTaskManagementQueryKeys, TASK_MANAGEMENT_CATALOG_SCOPE_TASKS } from '@acorex-platform/module-task-management-client/contracts';
7
+ import { applyDateFilterPreset, AXPSettingsService, AXPMenuService, AXP_PROTECTED_ROUTE_GUARDS, AXP_HOME_PAGES, AXPSearchCommandProvider, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER } from '@acorex-platform/framework-client/common';
8
+ import { AXPSessionService, AXPSessionStatus, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
9
+ import { AXPStateMessageComponent, AXPTaskBadgeProvider, AXP_TASK_BADGE_PROVIDERS } from '@acorex-platform/framework-client/layout/components';
10
+ import { isNil, get, defaultTo } from 'lodash';
11
+ import { AXCalendarService } from '@acorex/core/date-time';
12
+ import { mapCalendarOccasionToHolidayDate, AXMCalendarManagementService, normalizeRegionalCalendarEventIds, AXMCalendarManagementModule } from '@acorex-platform/module-calendar-management-client';
13
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14
+ import { Subject, merge, filter, distinctUntilChanged } from 'rxjs';
15
+ import { AXPPlatformScope, AXPRegionalSetting, AXPSystemStatuses, AXPWidgetsCatalog, AXPWidgetsList } from '@acorex-platform/framework-shared/core';
16
+ import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
17
+ import { AXLocaleService } from '@acorex/core/locale';
18
+ import * as i1 from '@acorex/components/button';
19
+ import { AXButtonModule, AXButtonComponent } from '@acorex/components/button';
20
+ import * as i2 from '@acorex/components/decorators';
21
+ import { AXDecoratorModule, AXDecoratorIconComponent, AXDecoratorGenericComponent } from '@acorex/components/decorators';
22
+ import * as i3 from '@acorex/components/dropdown';
23
+ import { AXDropdownModule } from '@acorex/components/dropdown';
24
+ import * as i5 from '@acorex/core/translation';
25
+ import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
26
+ import { AXOverlayService } from '@acorex/cdk/overlay';
27
+ import { AXPlatform } from '@acorex/core/platform';
28
+ import { AXFormatService, AXFormatPipe } from '@acorex/core/format';
29
+ import { AXP_ENTITY_CONFIG_TOKEN, AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
30
+ import { AXPRuntimeModule, provideCommandSetups, provideQuerySetups, AXPCommandEventsService } from '@acorex-platform/framework-client/runtime';
31
+ import { ROUTES, RouterLink } from '@angular/router';
32
+ import { AXPLayoutBaseWidgetComponent, AXPWidgetCoreModule, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/layout/widget-core';
33
+ import { cloneProperty, AXPWidgetGroupEnum } from '@acorex-platform/framework-shared/widget-core';
34
+ import { AXP_APPEARANCE_PROPERTY_GROUP, AXP_DATA_PATH_PROPERTY } from '@acorex-platform/framework-client/layout/widgets';
35
+ import { defineDashboardWidgetDimensions, layoutFromDimensions } from '@acorex-platform/module-dashboard-management-client';
36
+ import { AXChipsComponent } from '@acorex/components/chips';
37
+ import { AXPCommonPermissionGroupKeys, AXPCommonMenuKeys } from '@acorex-platform/module-common-client/contracts';
38
+
39
+ //#region ---- Imports ----
40
+ //#endregion
41
+ //#region ---- Manifest Definition ----
42
+ /**
43
+ * Task Management Module Manifest.
44
+ * Defines module metadata, features, and provider references.
45
+ */
46
+ const TaskManagementManifest = {
47
+ name: RootConfig.module.name,
48
+ version: '1.0.0',
49
+ title: RootConfig.module.title,
50
+ icon: RootConfig.module.icon,
51
+ i18n: RootConfig.config.i18n,
52
+ type: 'domain',
53
+ domain: 'operations',
54
+ // Module dependencies (optional)
55
+ };
56
+ //#endregion
57
+
58
+ function toTaskBoardHolidayDate(occasion) {
59
+ return mapCalendarOccasionToHolidayDate(occasion);
60
+ }
61
+
62
+ //#region ----- Imports -----
63
+ //#endregion
64
+ //#region ----- Constants -----
65
+ /** Persisted task rows shown on the task board. */
66
+ const TASK_BOARD_TASK_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.task.name}`;
67
+ /** Assignment rows that change who a task is assigned to. */
68
+ const TASK_BOARD_TASK_ASSIGNMENT_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.taskAssignment.name}`;
69
+ /**
70
+ * Workflow human-task cards come from suspended instance bookmarks, not Task rows.
71
+ * Keep this string in sync with `WorkflowManagement.WorkflowInstance` (do not import workflow-management — it already depends on this package).
72
+ */
73
+ const TASK_BOARD_WORKFLOW_INSTANCE_ENTITY = 'WorkflowManagement.WorkflowInstance';
74
+ const TASK_BOARD_REALTIME_ENTITIES = new Set([
75
+ TASK_BOARD_TASK_ENTITY,
76
+ TASK_BOARD_TASK_ASSIGNMENT_ENTITY,
77
+ TASK_BOARD_WORKFLOW_INSTANCE_ENTITY,
78
+ ]);
79
+ //#endregion
80
+ //#region ----- Helpers -----
81
+ /** True when a layout-refresh / entity-mutation name should refresh the task board or its menu badge. */
82
+ function isTaskBoardRealtimeEntity(name) {
83
+ return name != null && TASK_BOARD_REALTIME_ENTITIES.has(name);
84
+ }
85
+ //#endregion
86
+
87
+ function serializeDate(value) {
88
+ if (!value) {
89
+ return null;
90
+ }
91
+ return new Date(value).toISOString();
92
+ }
93
+ /** Stable cache key for {@link AXPTaskBoardService.getTasks} deduplication. */
94
+ function serializeTaskFilterForCache(options) {
95
+ if (!options) {
96
+ return '{}';
97
+ }
98
+ const normalized = {
99
+ types: options.types?.slice().sort(),
100
+ assigneeIds: options.assigneeIds?.map(String).sort(),
101
+ reporterIds: options.reporterIds?.map(String).sort(),
102
+ tags: options.tags?.slice().sort(),
103
+ priorities: options.priorities?.slice().sort(),
104
+ dueStatusKeys: options.dueStatusKeys?.slice().sort(),
105
+ subjectRefTypes: options.subjectRefTypes?.slice().sort(),
106
+ subjectRefIds: options.subjectRefIds?.map(String).sort(),
107
+ skip: options.skip ?? 0,
108
+ take: options.take ?? null,
109
+ range: options.range
110
+ ? {
111
+ from: serializeDate(options.range.from),
112
+ end: serializeDate(options.range.end),
113
+ }
114
+ : null,
115
+ };
116
+ return JSON.stringify(normalized);
117
+ }
118
+
119
+ /**
120
+ * An abstract class for a workflow task provider, which is responsible for
121
+ * fetching and manipulating task data from a specific source.
122
+ * @template T - The type of custom data associated with the tasks.
123
+ */
124
+ class AXPWorkflowTaskProvider {
125
+ constructor() {
126
+ this.eventService = inject(AXPBroadcastEventService);
127
+ }
128
+ async getResources() {
129
+ return [];
130
+ }
131
+ refresh() {
132
+ this.eventService.publish('task-board.refresh', {
133
+ provider: this.name,
134
+ });
135
+ }
136
+ close() {
137
+ this.eventService.publish('task-board.close', {
138
+ provider: this.name,
139
+ });
140
+ }
141
+ /**
142
+ * Opens a task on the task board (after refresh, the board selects this task).
143
+ */
144
+ requestTaskBoardOpenTask(task) {
145
+ this.eventService.publish('task-board.open-task', {
146
+ provider: this.name,
147
+ task: { ...task, provider: this.name },
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Interface that task popover components must implement.
153
+ * Components provided by task providers should implement this interface to receive the task data.
154
+ */
155
+ class AXPTaskPopoverComponent {
156
+ constructor() {
157
+ this.eventService = inject(AXPBroadcastEventService);
158
+ }
159
+ /**
160
+ * Requests the task board to refresh popover/task data.
161
+ * Provider defaults to the current task provider.
162
+ */
163
+ refreshPopover(provider) {
164
+ this.eventService.publish('task-board.refresh', {
165
+ provider: provider ?? this.task()?.provider,
166
+ });
167
+ }
168
+ /**
169
+ * Requests the host task board view to close the active popover.
170
+ * Provider defaults to the current task provider.
171
+ */
172
+ closePopover(provider) {
173
+ this.eventService.publish('task-board.close', {
174
+ provider: provider ?? this.task()?.provider,
175
+ });
176
+ }
177
+ }
178
+ /**
179
+ * Optional resolver registered by workflow-management to discover task providers on demand.
180
+ */
181
+ const AXP_WORKFLOW_TASK_PROVIDER_RESOLVER = new InjectionToken('AXP_WORKFLOW_TASK_PROVIDER_RESOLVER');
182
+ /**
183
+ * Injection token for registering multiple workflow task providers.
184
+ */
185
+ const AXP_WORKFLOW_TASK_PROVIDER = new InjectionToken('AXP_WORKFLOW_TASK_PROVIDER');
186
+
187
+ class AXPTaskBoardService {
188
+ constructor() {
189
+ this.calendarService = inject(AXCalendarService);
190
+ this.calendarManagementService = inject(AXMCalendarManagementService);
191
+ this.injector = inject(Injector);
192
+ this.providerResolver = inject(AXP_WORKFLOW_TASK_PROVIDER_RESOLVER, { optional: true });
193
+ this.destroyRef = inject(DestroyRef);
194
+ this.eventService = inject(AXPBroadcastEventService);
195
+ this.entityEvents = inject(AXPEntityBroadcastService);
196
+ /** Cached resolved providers; set after first call to resolveProviders(). */
197
+ this.resolvedProviders = null;
198
+ /** Cached resolved provider metadata; set together with {@link resolvedProviders}. */
199
+ this.resolvedTaskTypeOptions = null;
200
+ this.taskResultCache = new Map();
201
+ this.taskResultInFlight = new Map();
202
+ this.taskCacheTtlMs = 5_000;
203
+ this.calendarEventTypesCache = null;
204
+ this.calendarEventTypesInFlight = null;
205
+ this.refresh$ = new Subject();
206
+ this.close$ = new Subject();
207
+ this.openTask$ = new Subject();
208
+ this.refresh$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
209
+ this.resolvedProviders = null;
210
+ this.resolvedTaskTypeOptions = null;
211
+ this.invalidateTaskCache();
212
+ });
213
+ //
214
+ this.eventService
215
+ .listen('task-board.refresh')
216
+ .pipe(takeUntilDestroyed(this.destroyRef))
217
+ .subscribe(({ data }) => {
218
+ this.refresh$.next(data ?? {});
219
+ });
220
+ this.eventService
221
+ .listen('task-board.close')
222
+ .pipe(takeUntilDestroyed(this.destroyRef))
223
+ .subscribe(({ data }) => {
224
+ this.close$.next(data ?? {});
225
+ });
226
+ this.eventService
227
+ .listen('task-board.open-task')
228
+ .pipe(takeUntilDestroyed(this.destroyRef))
229
+ .subscribe(({ data }) => {
230
+ if (data?.task || (data?.instanceId && data?.activityId)) {
231
+ this.openTask$.next(data);
232
+ }
233
+ });
234
+ // Keep board in sync when Task / assignment / workflow-instance rows change elsewhere
235
+ // (local CRUD + realtime `entity.mutation` → layout refresh). Refresh all providers:
236
+ // site-inspection cards also come from workflow human-task bookmarks, not only Task rows.
237
+ merge(this.entityEvents.listenDeleted(), this.entityEvents.listenCreated(), this.entityEvents.listenModified(), this.entityEvents.listenLayoutRefresh())
238
+ .pipe(filter(({ data }) => isTaskBoardRealtimeEntity(data?.name)), takeUntilDestroyed(this.destroyRef))
239
+ .subscribe(() => {
240
+ this.requestRefresh();
241
+ });
242
+ }
243
+ /**
244
+ * Requests task board refresh through broadcast events.
245
+ */
246
+ requestRefresh(provider) {
247
+ this.eventService.publish('task-board.refresh', { provider });
248
+ }
249
+ /**
250
+ * Requests active task popover close through broadcast events.
251
+ */
252
+ requestClose(provider) {
253
+ this.eventService.publish('task-board.close', { provider });
254
+ }
255
+ /**
256
+ * Opens a task on the task board (refresh, then select the task in the active view).
257
+ */
258
+ requestOpenTask(taskOrRef, provider) {
259
+ if ('id' in taskOrRef && taskOrRef.title !== undefined) {
260
+ const task = taskOrRef;
261
+ this.eventService.publish('task-board.open-task', { provider: provider ?? task.provider, task });
262
+ return;
263
+ }
264
+ const ref = taskOrRef;
265
+ this.eventService.publish('task-board.open-task', ref);
266
+ }
267
+ /** Clears cached workflow task providers so the next resolve re-reads DI registrations. */
268
+ clearProviderCache() {
269
+ this.resolvedProviders = null;
270
+ this.resolvedTaskTypeOptions = null;
271
+ }
272
+ /** Clears cached task query results and calendar metadata. */
273
+ invalidateTaskCache() {
274
+ this.taskResultCache.clear();
275
+ this.taskResultInFlight.clear();
276
+ this.calendarEventTypesCache = null;
277
+ this.calendarEventTypesInFlight = null;
278
+ this.eventService.publish('task-board.invalidate-task-cache', {});
279
+ }
280
+ /**
281
+ * Resolves workflow task providers. Uses the optional workflow resolver when registered so discovery
282
+ * can be re-run after workflow definitions become available (first app load before mock seed).
283
+ */
284
+ async resolveProviders(options) {
285
+ if (!options?.force &&
286
+ this.resolvedProviders !== null &&
287
+ this.resolvedProviders.length > 0) {
288
+ return this.resolvedProviders;
289
+ }
290
+ let providers = [];
291
+ if (this.providerResolver) {
292
+ providers = await this.providerResolver(options);
293
+ if (providers.length === 0) {
294
+ providers = await this.providerResolver({ ...options, force: true });
295
+ }
296
+ }
297
+ else {
298
+ providers = await this.flattenProviderEntries(this.getInjectedProviderEntries());
299
+ }
300
+ if (providers.length > 0) {
301
+ this.resolvedProviders = providers;
302
+ this.resolvedTaskTypeOptions = await this.resolveTaskTypeOptions(providers);
303
+ }
304
+ else {
305
+ this.resolvedProviders = null;
306
+ this.resolvedTaskTypeOptions = null;
307
+ }
308
+ return providers;
309
+ }
310
+ async resolveTaskTypeOptions(providers) {
311
+ return Promise.all(providers.map(async (provider) => ({
312
+ name: provider.name,
313
+ title: await provider.getTitle(),
314
+ icon: await provider.getIcon(),
315
+ })));
316
+ }
317
+ getInjectedProviderEntries() {
318
+ const injected = this.injector.get(AXP_WORKFLOW_TASK_PROVIDER, null, { optional: true });
319
+ if (injected == null) {
320
+ return [];
321
+ }
322
+ return Array.isArray(injected) ? injected : [injected];
323
+ }
324
+ async flattenProviderEntries(entries) {
325
+ const resolved = await Promise.all(entries.map((entry) => Promise.resolve(entry)));
326
+ const flattened = resolved.flatMap((entry) => (Array.isArray(entry) ? entry : [entry]));
327
+ return flattened.filter((provider) => provider != null && typeof provider.getTasks === 'function');
328
+ }
329
+ async getTaskTypes(options) {
330
+ return this.resolveProviders(options);
331
+ }
332
+ /**
333
+ * Resolved provider metadata for UI filters and datasources.
334
+ * Call {@link resolveProviders} or {@link getTaskTypes} first.
335
+ */
336
+ getTaskTypeOptions() {
337
+ return this.resolvedTaskTypeOptions ?? [];
338
+ }
339
+ getTaskTypeOption(name) {
340
+ if (!name) {
341
+ return null;
342
+ }
343
+ return this.resolvedTaskTypeOptions?.find((option) => option.name === name) ?? null;
344
+ }
345
+ async getTasks(options) {
346
+ const cacheKey = serializeTaskFilterForCache(options);
347
+ const now = Date.now();
348
+ const cached = this.taskResultCache.get(cacheKey);
349
+ if (cached && cached.expiresAt > now) {
350
+ return cached.result;
351
+ }
352
+ const inFlight = this.taskResultInFlight.get(cacheKey);
353
+ if (inFlight) {
354
+ return inFlight;
355
+ }
356
+ const request = this.fetchTasksFromProviders(options)
357
+ .then((result) => {
358
+ this.taskResultCache.set(cacheKey, {
359
+ result,
360
+ expiresAt: Date.now() + this.taskCacheTtlMs,
361
+ });
362
+ return result;
363
+ })
364
+ .finally(() => {
365
+ this.taskResultInFlight.delete(cacheKey);
366
+ });
367
+ this.taskResultInFlight.set(cacheKey, request);
368
+ return request;
369
+ }
370
+ async fetchTasksFromProviders(options) {
371
+ const providers = await this.resolveProviders();
372
+ if (providers.length === 0) {
373
+ console.warn('No task providers available');
374
+ return { items: [], total: 0 };
375
+ }
376
+ const filteredProviders = providers.filter((provider) => (options?.types?.length ?? 0) === 0 || options?.types?.includes(provider.name));
377
+ if (filteredProviders.length === 0) {
378
+ return { items: [], total: 0 };
379
+ }
380
+ // Single provider case - handles pagination efficiently at the source.
381
+ if (filteredProviders.length === 1) {
382
+ const provider = filteredProviders[0];
383
+ const result = await provider.getTasks(options);
384
+ // Add provider name to each task for identification.
385
+ const itemsWithProvider = result.items.map((t) => ({ ...t, provider: provider.name }));
386
+ return { items: itemsWithProvider, total: result.total };
387
+ }
388
+ // Multi-provider case - fetch filtered data then paginate client-side.
389
+ const mergeData = [];
390
+ // Separate pagination from filtering to pass correct options to providers.
391
+ const { skip, take, ...providerOptions } = options ?? {};
392
+ const listTake = options?.take != null && options.take > 0 ? options.take : 5000;
393
+ const promises = filteredProviders.map((provider) => provider
394
+ .getTasks({ ...providerOptions, skip: 0, take: listTake })
395
+ .then((result) => result.items.map((t) => ({ ...t, provider: provider.name }))));
396
+ const results = await Promise.all(promises);
397
+ for (const resultArray of results) {
398
+ mergeData.push(...resultArray);
399
+ }
400
+ // Apply pagination client-side after merging the correctly filtered data.
401
+ const total = mergeData.length;
402
+ const finalSkip = skip ?? 0;
403
+ const finalTake = take ?? total;
404
+ const items = mergeData.slice(finalSkip, finalSkip + finalTake);
405
+ return { items, total };
406
+ }
407
+ async updateTaskDateRange(task, range) {
408
+ const providers = await this.resolveProviders();
409
+ const provider = providers.find((p) => p.name === task.provider) ?? null;
410
+ if (!provider) {
411
+ throw new Error(`Task provider '${task.provider}' not found`);
412
+ }
413
+ // Create a task update object and call the unified method
414
+ const taskWithNewRange = { ...task, startDate: range.from, endDate: range.end };
415
+ const updatedTasks = await provider.updateTasks([taskWithNewRange]);
416
+ if (updatedTasks.length > 0) {
417
+ return updatedTasks[0];
418
+ }
419
+ else {
420
+ throw new Error('Task update failed or task not found in provider.');
421
+ }
422
+ }
423
+ async updateTasks(tasks) {
424
+ if (!tasks || tasks.length === 0) {
425
+ return [];
426
+ }
427
+ // Ensure all tasks are from the same provider before performing a bulk update
428
+ if (!tasks.every((t) => t.provider === tasks[0].provider)) {
429
+ throw new Error('All tasks must have the same provider for a bulk update.');
430
+ }
431
+ const providers = await this.resolveProviders();
432
+ const provider = providers.find((p) => p.name === tasks[0].provider) ?? null;
433
+ if (!provider) {
434
+ throw new Error(`Task provider '${tasks[0].provider}' not found`);
435
+ }
436
+ return provider.updateTasks(tasks);
437
+ }
438
+ async executeCommand(command, providerName) {
439
+ const providers = await this.resolveProviders();
440
+ const provider = providers.find((p) => p.name === providerName) ?? null;
441
+ if (!provider) {
442
+ throw new Error(`Task provider '${providerName}' not found`);
443
+ }
444
+ return await provider.executeCommand(command);
445
+ }
446
+ async getStatusName(key, providerName) {
447
+ const providers = await this.resolveProviders();
448
+ const provider = providers.find((p) => p.name === providerName) ?? null;
449
+ if (!provider) {
450
+ throw new Error(`Task provider '${providerName}' not found`);
451
+ }
452
+ const statuses = await provider.getStatuses();
453
+ return statuses.find((s) => s.key === key)?.title ?? null;
454
+ }
455
+ async getEvents(range, eventTypeIds) {
456
+ const events = await this.calendarManagementService.getOccasions(range, eventTypeIds);
457
+ return events.map((event) => toTaskBoardHolidayDate(event));
458
+ }
459
+ async getCalendarEventTypes() {
460
+ if (this.calendarEventTypesCache) {
461
+ return this.calendarEventTypesCache;
462
+ }
463
+ if (this.calendarEventTypesInFlight) {
464
+ return this.calendarEventTypesInFlight;
465
+ }
466
+ this.calendarEventTypesInFlight = this.calendarManagementService
467
+ .getCalendars()
468
+ .then((result) => {
469
+ this.calendarEventTypesCache = result ?? [];
470
+ return this.calendarEventTypesCache;
471
+ })
472
+ .catch((error) => {
473
+ console.error(error);
474
+ this.calendarEventTypesCache = [];
475
+ return this.calendarEventTypesCache;
476
+ })
477
+ .finally(() => {
478
+ this.calendarEventTypesInFlight = null;
479
+ });
480
+ return this.calendarEventTypesInFlight;
481
+ }
482
+ /**
483
+ * Returns the provider by name. Uses cached resolved providers; returns null if not resolved yet.
484
+ * Ensure resolveProviders() has been called (e.g. via getTaskTypes() or getTasks()) before using this.
485
+ */
486
+ getProvider(name) {
487
+ if (!name)
488
+ return null;
489
+ return this.resolvedProviders?.find((p) => p.name === name) ?? null;
490
+ }
491
+ rangeCalculator(type) {
492
+ switch (type) {
493
+ case 'today':
494
+ return {
495
+ from: this.calendarService.create(new Date()).startOf('day').date,
496
+ end: this.calendarService.create(new Date()).endOf('day').date,
497
+ };
498
+ case 'this-week':
499
+ return {
500
+ from: this.calendarService.create(new Date()).startOf('week').date,
501
+ end: this.calendarService.create(new Date()).endOf('week').date,
502
+ };
503
+ case 'this-month':
504
+ return {
505
+ from: this.calendarService.create(new Date()).startOf('month').date,
506
+ end: this.calendarService.create(new Date()).endOf('month').date,
507
+ };
508
+ default:
509
+ return undefined;
510
+ }
511
+ }
512
+ async getResources(providerName) {
513
+ const providers = await this.resolveProviders();
514
+ const provider = providers.find((p) => p.name === providerName) ?? null;
515
+ if (!provider) {
516
+ throw new Error(`Task provider '${providerName}' not found`);
517
+ }
518
+ return provider.getResources();
519
+ }
520
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
521
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardService, providedIn: 'root' }); }
522
+ }
523
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardService, decorators: [{
524
+ type: Injectable,
525
+ args: [{
526
+ providedIn: 'root',
527
+ }]
528
+ }], ctorParameters: () => [] });
529
+
530
+ //#region ---- Imports ----
531
+ //#endregion
532
+ //#region ---- Constants ----
533
+ /** Sentinel id for the task-board assignee filter option: tasks with no assignee. */
534
+ const AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID = 'TaskManagement:TaskBoard:Assignee:Unassigned';
535
+ //#endregion
536
+ //#region ---- Permission Helpers ----
537
+ /** Permission to view and filter tasks across all assignees on the task board. */
538
+ const AXM_TASK_VIEW_ALL_PERMISSION = AXPProjectsWorkPermissionKeys.ViewAllTasks;
539
+ /**
540
+ * Returns whether the current session may filter tasks by any assignee.
541
+ */
542
+ function canViewAllTaskAssignees(sessionService) {
543
+ return sessionService.authorize(AXM_TASK_VIEW_ALL_PERMISSION);
544
+ }
545
+ function isTaskBoardUnassignedAssigneeId(id) {
546
+ return String(id) === AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID;
547
+ }
548
+ /** Whether the user has a persisted assignee filter (including an explicit empty selection). */
549
+ function hasTaskBoardSavedAssigneePreference(savedAssigneeIds) {
550
+ return savedAssigneeIds !== undefined && savedAssigneeIds !== null;
551
+ }
552
+ /**
553
+ * Resolves saved assignee ids from settings. When the setting is unset (`undefined`/`null`):
554
+ * - Users with view-all permission default to no assignee filter (all tasks).
555
+ * - Everyone else defaults to the signed-in user only.
556
+ * An explicit empty array means "no assignee filter" only for view-all users;
557
+ * for everyone else it is coerced to the signed-in user.
558
+ */
559
+ function resolveTaskBoardSavedAssigneeIds(sessionService, savedAssigneeIds) {
560
+ if (hasTaskBoardSavedAssigneePreference(savedAssigneeIds)) {
561
+ const saved = savedAssigneeIds ?? [];
562
+ if (saved.length === 0 && !canViewAllTaskAssignees(sessionService)) {
563
+ const currentUserId = sessionService.user?.id;
564
+ return currentUserId != null ? [currentUserId] : [];
565
+ }
566
+ return saved;
567
+ }
568
+ if (canViewAllTaskAssignees(sessionService)) {
569
+ return [];
570
+ }
571
+ const currentUserId = sessionService.user?.id;
572
+ return currentUserId != null ? [currentUserId] : [];
573
+ }
574
+ /** Assignee picker item for the signed-in user (used by task-board assignee datasource). */
575
+ function resolveTaskBoardCurrentUserAssigneeItem(sessionService) {
576
+ const currentUser = sessionService.user;
577
+ if (currentUser?.id == null) {
578
+ return null;
579
+ }
580
+ return {
581
+ id: currentUser.id,
582
+ displayName: currentUser.title || currentUser.name,
583
+ username: currentUser.name,
584
+ };
585
+ }
586
+ /**
587
+ * Restricts assignee filter ids to the current user when {@link canViewAllTaskAssignees} is false.
588
+ * Strips the unassigned sentinel for users without view-all permission.
589
+ * Empty selection is only allowed for view-all users (means all assignees).
590
+ */
591
+ function normalizeTaskBoardAssigneeIds(sessionService, assigneeIds) {
592
+ if (canViewAllTaskAssignees(sessionService)) {
593
+ return assigneeIds;
594
+ }
595
+ const currentUserId = sessionService.user?.id;
596
+ return currentUserId != null ? [currentUserId] : [];
597
+ }
598
+ /**
599
+ * Whether a task matches the selected assignee filter ids (OR semantics).
600
+ * Supports {@link AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID} for tasks without an assignee.
601
+ */
602
+ function matchesTaskBoardAssigneeFilter(assigneeId, filterIds) {
603
+ if (filterIds.length === 0) {
604
+ return true;
605
+ }
606
+ const filterSet = new Set(filterIds.map(String));
607
+ const wantsUnassigned = filterSet.has(AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID);
608
+ const hasAssignee = assigneeId != null && assigneeId !== '';
609
+ if (!hasAssignee) {
610
+ return wantsUnassigned;
611
+ }
612
+ return filterSet.has(String(assigneeId));
613
+ }
614
+ /**
615
+ * Whether a workflow task matches assignee filters using assigned/candidate user ids.
616
+ */
617
+ function matchesTaskBoardAssigneeUserIdsFilter(userIds, filterIds) {
618
+ if (filterIds.length === 0) {
619
+ return true;
620
+ }
621
+ const filterSet = new Set(filterIds.map(String));
622
+ const wantsUnassigned = filterSet.has(AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID);
623
+ if (userIds.length === 0) {
624
+ return wantsUnassigned;
625
+ }
626
+ return userIds.some((id) => filterSet.has(String(id)));
627
+ }
628
+ //#endregion
629
+
630
+ //#region ---- Imports ----
631
+ const AXM_TASK_BOARD_VIEW_CATEGORIES = [
632
+ 'calendar',
633
+ 'timeline',
634
+ 'table',
635
+ 'kanban',
636
+ ];
637
+ const AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY = {
638
+ calendar: 'monthly',
639
+ timeline: 'daily',
640
+ table: 'simple',
641
+ kanban: null,
642
+ };
643
+ //#endregion
644
+ //#region ---- Category ↔ Leaf Mode ----
645
+ const CALENDAR_LAYOUT_TO_MODE = {
646
+ daily: 'day',
647
+ weekly: 'week',
648
+ monthly: 'month',
649
+ agenda: 'agenda',
650
+ };
651
+ const TIMELINE_LAYOUT_TO_MODE = {
652
+ daily: 'timeline-day',
653
+ weekly: 'timeline-weekly',
654
+ monthly: 'timeline-month',
655
+ yearly: 'timeline-year',
656
+ };
657
+ const TABLE_LAYOUT_TO_MODE = {
658
+ simple: 'grid',
659
+ grouped: 'status-grouped-grid',
660
+ };
661
+ function resolveTaskBoardViewCategory(viewMode) {
662
+ if (viewMode === 'kanban') {
663
+ return 'kanban';
664
+ }
665
+ if (viewMode === 'grid' || viewMode === 'status-grouped-grid') {
666
+ return 'table';
667
+ }
668
+ if (viewMode.startsWith('timeline-')) {
669
+ return 'timeline';
670
+ }
671
+ return 'calendar';
672
+ }
673
+ function resolveTaskBoardViewLayout(viewMode) {
674
+ if (viewMode === 'kanban') {
675
+ return null;
676
+ }
677
+ const calendarEntry = Object.entries(CALENDAR_LAYOUT_TO_MODE).find(([, mode]) => mode === viewMode);
678
+ if (calendarEntry) {
679
+ return calendarEntry[0];
680
+ }
681
+ const timelineEntry = Object.entries(TIMELINE_LAYOUT_TO_MODE).find(([, mode]) => mode === viewMode);
682
+ if (timelineEntry) {
683
+ return timelineEntry[0];
684
+ }
685
+ const tableEntry = Object.entries(TABLE_LAYOUT_TO_MODE).find(([, mode]) => mode === viewMode);
686
+ if (tableEntry) {
687
+ return tableEntry[0];
688
+ }
689
+ return AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY.calendar;
690
+ }
691
+ function resolveTaskBoardLeafViewMode(category, layout) {
692
+ switch (category) {
693
+ case 'calendar':
694
+ return CALENDAR_LAYOUT_TO_MODE[layout ?? 'monthly'] ?? 'month';
695
+ case 'timeline':
696
+ return TIMELINE_LAYOUT_TO_MODE[layout ?? 'daily'] ?? 'timeline-day';
697
+ case 'table':
698
+ return TABLE_LAYOUT_TO_MODE[layout ?? 'simple'] ?? 'grid';
699
+ case 'kanban':
700
+ return 'kanban';
701
+ default:
702
+ return 'month';
703
+ }
704
+ }
705
+ function isTaskBoardRangeFilterCategory(category) {
706
+ return category === 'table' || category === 'kanban';
707
+ }
708
+ function usesTaskBoardToolbarNavigator(_viewMode) {
709
+ return true;
710
+ }
711
+ function getTaskBoardLayoutOptions(category) {
712
+ switch (category) {
713
+ case 'calendar':
714
+ return [
715
+ { key: 'daily', text: 'daily', icon: 'fa-light fa-calendar-day' },
716
+ { key: 'weekly', text: 'weekly', icon: 'fa-light fa-calendar-week' },
717
+ { key: 'monthly', text: 'monthly', icon: 'fa-light fa-calendar-alt' },
718
+ { key: 'agenda', text: 'agenda', icon: 'fa-light fa-calendar-range' },
719
+ ];
720
+ case 'timeline':
721
+ return [
722
+ { key: 'daily', text: 'daily', icon: 'fa-light fa-calendar-day' },
723
+ { key: 'weekly', text: 'weekly', icon: 'fa-light fa-calendar-week' },
724
+ { key: 'monthly', text: 'monthly', icon: 'fa-light fa-calendar-alt' },
725
+ { key: 'yearly', text: 'yearly', icon: 'fa-light fa-calendar-range' },
726
+ ];
727
+ case 'table':
728
+ return [
729
+ { key: 'simple', text: 'grid', icon: 'fa-light fa-table' },
730
+ { key: 'grouped', text: 'status-grouped-grid', icon: 'fa-light fa-table' },
731
+ ];
732
+ default:
733
+ return [];
734
+ }
735
+ }
736
+ function deriveTaskBoardDateRangeFromNavigator(calendarService, viewMode, currentDate, daysCount) {
737
+ const cal = calendarService.create(currentDate);
738
+ switch (viewMode) {
739
+ case 'month':
740
+ case 'timeline-month':
741
+ return {
742
+ from: cal.startOf('month').date,
743
+ end: cal.endOf('month').date,
744
+ };
745
+ case 'week':
746
+ return {
747
+ from: cal.startOf('week').date,
748
+ end: cal.add('day', 6).date,
749
+ };
750
+ case 'timeline-weekly':
751
+ return {
752
+ from: cal.date,
753
+ end: cal.add('day', Math.max(1, daysCount) - 1).date,
754
+ };
755
+ case 'timeline-year':
756
+ return {
757
+ from: cal.startOf('year').date,
758
+ end: cal.endOf('year').date,
759
+ };
760
+ case 'agenda':
761
+ case 'grid':
762
+ case 'status-grouped-grid':
763
+ case 'kanban':
764
+ return {
765
+ from: cal.date,
766
+ end: cal.add('day', Math.max(1, daysCount) - 1).date,
767
+ };
768
+ case 'day':
769
+ case 'timeline-day':
770
+ default:
771
+ return {
772
+ from: cal.startOf('day').date,
773
+ end: cal.endOf('day').date,
774
+ };
775
+ }
776
+ }
777
+ function createTodayTaskBoardDateFilterValue(calendarService) {
778
+ return applyDateFilterPreset('today', calendarService) ?? {
779
+ operation: { type: 'between' },
780
+ value: {
781
+ from: calendarService.create(new Date()).startOf('day').date.toISOString(),
782
+ to: calendarService.create(new Date()).endOf('day').date.toISOString(),
783
+ },
784
+ };
785
+ }
786
+ function parseTaskBoardDateFilterValue(value) {
787
+ if (value == null) {
788
+ return null;
789
+ }
790
+ const raw = value;
791
+ const payload = raw.value;
792
+ if (payload == null) {
793
+ return null;
794
+ }
795
+ if (typeof payload === 'string') {
796
+ const date = new Date(payload);
797
+ return Number.isNaN(date.getTime()) ? null : { from: date, end: date };
798
+ }
799
+ const from = payload.from ? new Date(payload.from) : null;
800
+ const end = payload.to ? new Date(payload.to) : from;
801
+ if (!from || !end || Number.isNaN(from.getTime()) || Number.isNaN(end.getTime())) {
802
+ return null;
803
+ }
804
+ return { from, end };
805
+ }
806
+ function toTaskBoardDateFilterValue(range) {
807
+ return {
808
+ operation: { type: 'between' },
809
+ value: {
810
+ from: range.from.toISOString(),
811
+ to: range.end.toISOString(),
812
+ },
813
+ };
814
+ }
815
+ async function formatTaskBoardDateRangeDisplayText(range, calendarService, format) {
816
+ const from = calendarService.create(range.from).format(format);
817
+ const end = calendarService.create(range.end).format(format);
818
+ if (from === end) {
819
+ return from;
820
+ }
821
+ return `${from} – ${end}`;
822
+ }
823
+ function applyTaskBoardDateRangeToViewModel(range, setCurrentDate, setDaysCount) {
824
+ setCurrentDate(range.from);
825
+ const start = new Date(range.from.getFullYear(), range.from.getMonth(), range.from.getDate());
826
+ const end = new Date(range.end.getFullYear(), range.end.getMonth(), range.end.getDate());
827
+ const days = Math.round((end.getTime() - start.getTime()) / (1000 * 3600 * 24)) + 1;
828
+ setDaysCount(Math.max(1, days));
829
+ }
830
+ //#endregion
831
+ //#region ---- Settings Visibility ----
832
+ function normalizeVisibleTaskBoardCategories(flags) {
833
+ const categories = [];
834
+ if (flags.calendar !== false) {
835
+ categories.push('calendar');
836
+ }
837
+ if (flags.timeline !== false) {
838
+ categories.push('timeline');
839
+ }
840
+ if (flags.table !== false) {
841
+ categories.push('table');
842
+ }
843
+ if (flags.kanban !== false) {
844
+ categories.push('kanban');
845
+ }
846
+ return categories.length > 0 ? categories : [...AXM_TASK_BOARD_VIEW_CATEGORIES];
847
+ }
848
+ function resolveTaskBoardCategoryLayoutFromQuery(category, layout) {
849
+ if (category === 'kanban') {
850
+ return null;
851
+ }
852
+ const options = getTaskBoardLayoutOptions(category);
853
+ const match = options.find((option) => option.key === layout);
854
+ return (match?.key ?? AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY[category]);
855
+ }
856
+ //#endregion
857
+
858
+ //#region ----- Imports -----
859
+ //#endregion
860
+ //#region ----- Menu Path -----
861
+ /**
862
+ * Builds the task-board menu / navigate path including persisted view + layout query params.
863
+ * Example: `safetyminder/TaskManagement/task-board?view=calendar&layout=monthly`
864
+ */
865
+ function buildTaskBoardMenuPath(applicationName, moduleName, viewMode, layouts = {}) {
866
+ const effectiveMode = (viewMode || 'month');
867
+ const category = resolveTaskBoardViewCategory(effectiveMode);
868
+ const layout = resolveTaskBoardMenuLayout(category, effectiveMode, layouts);
869
+ const base = `${applicationName}/${moduleName}/task-board`;
870
+ const params = new URLSearchParams({ view: category });
871
+ if (layout) {
872
+ params.set('layout', layout);
873
+ }
874
+ return `${base}?${params.toString()}`;
875
+ }
876
+ function resolveTaskBoardMenuLayout(category, viewMode, layouts) {
877
+ switch (category) {
878
+ case 'calendar':
879
+ return (layouts.calendar ??
880
+ resolveTaskBoardViewLayout(viewMode) ??
881
+ AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY.calendar);
882
+ case 'timeline':
883
+ return (layouts.timeline ??
884
+ resolveTaskBoardViewLayout(viewMode) ??
885
+ AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY.timeline);
886
+ case 'table':
887
+ return (layouts.table ??
888
+ resolveTaskBoardViewLayout(viewMode) ??
889
+ AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY.table);
890
+ case 'kanban':
891
+ return null;
892
+ default:
893
+ return AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY.calendar;
894
+ }
895
+ }
896
+ //#endregion
897
+
898
+ const AXPTASK_PRIORITIES = ['urgent', 'high', 'normal', 'low'];
899
+ const TASK_PRIORITY_ORDER = {
900
+ urgent: 0,
901
+ high: 1,
902
+ normal: 2,
903
+ low: 3,
904
+ no_priority: 4,
905
+ };
906
+ /** Normalizes workflow or legacy task-board priority values to the canonical enum. */
907
+ function normalizeTaskPriority(value) {
908
+ const normalized = String(value ?? 'normal')
909
+ .trim()
910
+ .toLowerCase();
911
+ if (normalized === 'urgent' || normalized === 'high' || normalized === 'normal' || normalized === 'low') {
912
+ return normalized;
913
+ }
914
+ const legacyMap = {
915
+ highest: 'urgent',
916
+ medium: 'normal',
917
+ lowest: 'low',
918
+ };
919
+ return legacyMap[normalized] ?? 'normal';
920
+ }
921
+ function getTaskPriorityColor(priority) {
922
+ switch (priority) {
923
+ case 'urgent':
924
+ return 'danger';
925
+ case 'high':
926
+ return 'warning';
927
+ case 'normal':
928
+ return 'secondary';
929
+ case 'low':
930
+ return 'neutral';
931
+ }
932
+ }
933
+ const LEGACY_PRIORITY_FILTER_MAP = {
934
+ highest: 'urgent',
935
+ medium: 'normal',
936
+ lowest: 'low',
937
+ };
938
+ /** Maps persisted or query-string priority filter keys to the canonical filter enum. */
939
+ function normalizePriorityFilterKey(value) {
940
+ const normalized = String(value ?? '')
941
+ .trim()
942
+ .toLowerCase();
943
+ if (normalized === 'no_priority') {
944
+ return 'no_priority';
945
+ }
946
+ if (normalized in LEGACY_PRIORITY_FILTER_MAP) {
947
+ return LEGACY_PRIORITY_FILTER_MAP[normalized];
948
+ }
949
+ return normalizeTaskPriority(normalized);
950
+ }
951
+ function normalizePriorityFilterKeys(values) {
952
+ if (!values?.length) {
953
+ return [];
954
+ }
955
+ return [...new Set(values.map(normalizePriorityFilterKey))];
956
+ }
957
+ function getTaskPriorityFilterKey(task) {
958
+ return task.priority ? normalizeTaskPriority(task.priority) : 'no_priority';
959
+ }
960
+ function matchesTaskPriorityFilter(task, priorities) {
961
+ if (priorities.length === 0) {
962
+ return true;
963
+ }
964
+ const filterSet = new Set(priorities.map(normalizePriorityFilterKey));
965
+ return filterSet.has(getTaskPriorityFilterKey(task));
966
+ }
967
+
968
+ /**
969
+ * Keeps task-type filter values on stable provider {@link AXPTaskBoardTaskTypeOption.name} keys.
970
+ * Display titles are locale-dependent and must never be used for filtering.
971
+ */
972
+ function normalizeTaskTypeFilterNames(values, taskTypes) {
973
+ if (!values?.length) {
974
+ return [];
975
+ }
976
+ if (taskTypes.length === 0) {
977
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
978
+ }
979
+ const names = new Set(taskTypes.map((type) => type.name));
980
+ const normalized = values.flatMap((value) => {
981
+ const trimmed = value.trim();
982
+ if (!trimmed) {
983
+ return [];
984
+ }
985
+ if (names.has(trimmed)) {
986
+ return [trimmed];
987
+ }
988
+ const matchedByTitle = taskTypes.find((type) => type.title.trim() === trimmed);
989
+ if (matchedByTitle) {
990
+ return [matchedByTitle.name];
991
+ }
992
+ return [];
993
+ });
994
+ return [...new Set(normalized)];
995
+ }
996
+ const DUE_STATUS_FILTER_NAMES = new Set([
997
+ 'completed',
998
+ 'no-due-date',
999
+ 'overdue',
1000
+ 'due-today',
1001
+ 'due-tomorrow',
1002
+ 'due-this-week',
1003
+ 'upcoming',
1004
+ ]);
1005
+ /** Maps due-status filter chips back to stable keys (never localized labels). */
1006
+ function normalizeDueStatusFilterKeys(values) {
1007
+ if (!values?.length) {
1008
+ return [];
1009
+ }
1010
+ const titleToName = {
1011
+ completed: 'completed',
1012
+ 'no due date': 'no-due-date',
1013
+ overdue: 'overdue',
1014
+ 'due today': 'due-today',
1015
+ 'due tomorrow': 'due-tomorrow',
1016
+ 'due this week': 'due-this-week',
1017
+ upcoming: 'upcoming',
1018
+ };
1019
+ const normalized = values.flatMap((value) => {
1020
+ const trimmed = String(value ?? '').trim();
1021
+ if (!trimmed) {
1022
+ return [];
1023
+ }
1024
+ if (DUE_STATUS_FILTER_NAMES.has(trimmed)) {
1025
+ return [trimmed];
1026
+ }
1027
+ const fromTitle = titleToName[trimmed.toLowerCase()];
1028
+ return fromTitle ? [fromTitle] : [];
1029
+ });
1030
+ return [...new Set(normalized)];
1031
+ }
1032
+ /** Re-exports priority normalization for filter panels that may receive display labels. */
1033
+ function normalizeTaskBoardPriorityFilterKeys(values) {
1034
+ return normalizePriorityFilterKeys(values);
1035
+ }
1036
+
1037
+ const AXMTaskBoardViewModel = signalStore({ providedIn: 'root' }, withState(() => ({
1038
+ daysCount: 7,
1039
+ taskTypes: [],
1040
+ taskTypeOptions: [],
1041
+ isLoading: true,
1042
+ detailPanel: false,
1043
+ selectedTask: null,
1044
+ rangeStartDate: null,
1045
+ currentDate: new Date(),
1046
+ currentViewMode: 'month',
1047
+ visibleViewCategories: ['calendar', 'timeline', 'table', 'kanban'],
1048
+ calendarLayout: 'monthly',
1049
+ timelineLayout: 'daily',
1050
+ tableLayout: 'simple',
1051
+ selectedTaskTypeNames: [],
1052
+ selectedAssigneeIds: [],
1053
+ assigneeFilterHasSavedPreference: false,
1054
+ selectedPriorities: [],
1055
+ selectedDueStatusKeys: [],
1056
+ colorBy: 'dueDate',
1057
+ selectedReporterIds: [],
1058
+ selectedCalendarEventTypeIds: [],
1059
+ })), withComputed((store) => ({
1060
+ currentViewCategory: computed(() => resolveTaskBoardViewCategory(store.currentViewMode())),
1061
+ selectedTaskTypes: computed(() => {
1062
+ const names = store.selectedTaskTypeNames();
1063
+ if (!names || names.length === 0) {
1064
+ return [];
1065
+ }
1066
+ return store.taskTypes().filter((tt) => names.includes(tt.name));
1067
+ }),
1068
+ isTaskSelected: computed(() => store.selectedTask() !== null),
1069
+ isDetailPanelOpen: computed(() => store.detailPanel()),
1070
+ effectiveColorBy: computed(() => store.colorBy()),
1071
+ endDate: computed(() => {
1072
+ const currentDate = store.currentDate();
1073
+ const daysCount = store.daysCount();
1074
+ // Calculate end of the last day in the range (23:59:59.999)
1075
+ const endDate = new Date(currentDate);
1076
+ endDate.setDate(endDate.getDate() + daysCount - 1);
1077
+ endDate.setHours(23, 59, 59, 999);
1078
+ return endDate;
1079
+ }),
1080
+ activeFilterCount: computed(() => {
1081
+ let count = 0;
1082
+ if (store.currentViewMode() !== 'kanban' &&
1083
+ store.currentViewMode() !== 'status-grouped-grid' &&
1084
+ store.selectedTaskTypeNames().length > 0) {
1085
+ count++;
1086
+ }
1087
+ if (store.selectedAssigneeIds().length > 0) {
1088
+ count++;
1089
+ }
1090
+ if (store.selectedReporterIds().length > 0) {
1091
+ count++;
1092
+ }
1093
+ return count;
1094
+ }),
1095
+ })), withMethods((store, settingsService = inject(AXPSettingsService), taskBoardService = inject(AXPTaskBoardService), sessionService = inject(AXPSessionService), menuService = inject(AXPMenuService)) => {
1096
+ const userScopedSettings = () => settingsService.scope(AXPPlatformScope.User);
1097
+ let settingsSyncInitialized = false;
1098
+ const syncTaskBoardMenuPath = () => {
1099
+ const applicationName = sessionService.application?.name;
1100
+ if (!applicationName) {
1101
+ return;
1102
+ }
1103
+ menuService.updateItem(AXPTaskManagementMenuKeys.TaskBoard, {
1104
+ path: buildTaskBoardMenuPath(applicationName, RootConfig.module.name, store.currentViewMode(), {
1105
+ calendar: store.calendarLayout(),
1106
+ timeline: store.timelineLayout(),
1107
+ table: store.tableLayout(),
1108
+ }),
1109
+ });
1110
+ };
1111
+ const syncSettingsFromChangeEvent = (keys, values) => {
1112
+ const patch = {};
1113
+ let shouldSyncMenuPath = false;
1114
+ if (keys.includes(AXMTaskBoardSettings.ColorBy)) {
1115
+ patch.colorBy = values[AXMTaskBoardSettings.ColorBy] ?? 'dueDate';
1116
+ }
1117
+ if (keys.includes(AXPRegionalSetting.CalendarEvents)) {
1118
+ patch.selectedCalendarEventTypeIds = normalizeRegionalCalendarEventIds(values[AXPRegionalSetting.CalendarEvents]);
1119
+ }
1120
+ if (keys.includes(AXMTaskBoardSettings.ViewMode)) {
1121
+ const mode = values[AXMTaskBoardSettings.ViewMode];
1122
+ if (mode) {
1123
+ patch.currentViewMode = mode;
1124
+ shouldSyncMenuPath = true;
1125
+ }
1126
+ }
1127
+ if (keys.includes(AXMTaskBoardSettings.CalendarLayout)) {
1128
+ const layout = values[AXMTaskBoardSettings.CalendarLayout];
1129
+ if (layout) {
1130
+ patch.calendarLayout = layout;
1131
+ shouldSyncMenuPath = true;
1132
+ }
1133
+ }
1134
+ if (keys.includes(AXMTaskBoardSettings.TimelineLayout)) {
1135
+ const layout = values[AXMTaskBoardSettings.TimelineLayout];
1136
+ if (layout) {
1137
+ patch.timelineLayout = layout;
1138
+ shouldSyncMenuPath = true;
1139
+ }
1140
+ }
1141
+ if (keys.includes(AXMTaskBoardSettings.TableLayout)) {
1142
+ const layout = values[AXMTaskBoardSettings.TableLayout];
1143
+ if (layout) {
1144
+ patch.tableLayout = layout;
1145
+ shouldSyncMenuPath = true;
1146
+ }
1147
+ }
1148
+ if (Object.keys(patch).length > 0) {
1149
+ patchState(store, patch);
1150
+ }
1151
+ if (shouldSyncMenuPath) {
1152
+ syncTaskBoardMenuPath();
1153
+ }
1154
+ };
1155
+ const loadPersistedSettings = async () => {
1156
+ const [savedDaysCount, savedDateString, savedDetailPanel, savedViewMode, savedVisibleCalendar, savedVisibleTimeline, savedVisibleTable, savedVisibleKanban, savedCalendarLayout, savedTimelineLayout, savedTableLayout, savedTaskTypeNames, savedAssigneeIds, savedReporterIds, savedPriorities, savedDueStatusKeys, savedColorBy, savedCalendarEventIds,] = await Promise.all([
1157
+ userScopedSettings().get(AXMTaskBoardSettings.DaysCount),
1158
+ userScopedSettings().get(AXMTaskBoardSettings.CurrentDate),
1159
+ userScopedSettings().get(AXMTaskBoardSettings.DetailPanel),
1160
+ userScopedSettings().get(AXMTaskBoardSettings.ViewMode),
1161
+ userScopedSettings().get(AXMTaskBoardSettings.VisibleCalendar),
1162
+ userScopedSettings().get(AXMTaskBoardSettings.VisibleTimeline),
1163
+ userScopedSettings().get(AXMTaskBoardSettings.VisibleTable),
1164
+ userScopedSettings().get(AXMTaskBoardSettings.VisibleKanban),
1165
+ userScopedSettings().get(AXMTaskBoardSettings.CalendarLayout),
1166
+ userScopedSettings().get(AXMTaskBoardSettings.TimelineLayout),
1167
+ userScopedSettings().get(AXMTaskBoardSettings.TableLayout),
1168
+ userScopedSettings().get(AXMTaskBoardSettings.SelectedTaskTypeNames),
1169
+ userScopedSettings().get(AXMTaskBoardSettings.SelectedAssigneeIds),
1170
+ userScopedSettings().get(AXMTaskBoardSettings.SelectedReporterIds),
1171
+ userScopedSettings().get(AXMTaskBoardSettings.SelectedPriorities),
1172
+ userScopedSettings().get(AXMTaskBoardSettings.SelectedDueStatusKeys),
1173
+ userScopedSettings().get(AXMTaskBoardSettings.ColorBy),
1174
+ settingsService.get(AXPRegionalSetting.CalendarEvents),
1175
+ ]);
1176
+ return {
1177
+ savedDaysCount,
1178
+ savedDateString,
1179
+ savedDetailPanel,
1180
+ savedViewMode,
1181
+ savedVisibleCalendar,
1182
+ savedVisibleTimeline,
1183
+ savedVisibleTable,
1184
+ savedVisibleKanban,
1185
+ savedCalendarLayout,
1186
+ savedTimelineLayout,
1187
+ savedTableLayout,
1188
+ savedTaskTypeNames,
1189
+ savedAssigneeIds,
1190
+ savedReporterIds,
1191
+ savedPriorities,
1192
+ savedDueStatusKeys,
1193
+ savedColorBy,
1194
+ savedCalendarEventIds,
1195
+ };
1196
+ };
1197
+ const buildStateFromPersistedSettings = (settings, taskTypes, taskTypeOptions) => {
1198
+ const { savedDaysCount, savedDateString, savedDetailPanel, savedViewMode, savedVisibleCalendar, savedVisibleTimeline, savedVisibleTable, savedVisibleKanban, savedCalendarLayout, savedTimelineLayout, savedTableLayout, savedTaskTypeNames, savedAssigneeIds, savedReporterIds, savedPriorities, savedDueStatusKeys, savedColorBy, savedCalendarEventIds, } = settings;
1199
+ const visibleViewCategories = normalizeVisibleTaskBoardCategories({
1200
+ calendar: savedVisibleCalendar,
1201
+ timeline: savedVisibleTimeline,
1202
+ table: savedVisibleTable,
1203
+ kanban: savedVisibleKanban,
1204
+ });
1205
+ let currentDate;
1206
+ if (savedDateString) {
1207
+ const parsedDate = new Date(savedDateString);
1208
+ currentDate = isNaN(parsedDate.getTime()) ? new Date() : parsedDate;
1209
+ }
1210
+ else {
1211
+ currentDate = new Date();
1212
+ }
1213
+ const assigneeFilterHasSavedPreference = hasTaskBoardSavedAssigneePreference(savedAssigneeIds);
1214
+ const defaultAssigneeIds = normalizeTaskBoardAssigneeIds(sessionService, resolveTaskBoardSavedAssigneeIds(sessionService, savedAssigneeIds));
1215
+ const calendarLayout = savedCalendarLayout ?? 'monthly';
1216
+ const timelineLayout = savedTimelineLayout ?? 'daily';
1217
+ const tableLayout = savedTableLayout ?? 'simple';
1218
+ let effectiveViewMode = savedViewMode || 'month';
1219
+ const effectiveCategory = resolveTaskBoardViewCategory(effectiveViewMode);
1220
+ if (!visibleViewCategories.includes(effectiveCategory)) {
1221
+ const fallbackCategory = visibleViewCategories[0] ?? 'calendar';
1222
+ effectiveViewMode = resolveTaskBoardLeafViewMode(fallbackCategory, AXM_TASK_BOARD_DEFAULT_LAYOUT_BY_CATEGORY[fallbackCategory]);
1223
+ }
1224
+ if (savedViewMode != null && effectiveViewMode !== savedViewMode) {
1225
+ userScopedSettings()
1226
+ .set(AXMTaskBoardSettings.ViewMode, effectiveViewMode)
1227
+ .catch((error) => console.error('Error aligning task board view mode with visible views:', error));
1228
+ }
1229
+ let selectedTaskTypeNames = normalizeTaskTypeFilterNames(savedTaskTypeNames ?? [], taskTypeOptions);
1230
+ if (selectedTaskTypeNames.length === 0 &&
1231
+ taskTypes.length > 0 &&
1232
+ (effectiveViewMode === 'kanban' || effectiveViewMode === 'status-grouped-grid')) {
1233
+ selectedTaskTypeNames = [taskTypes[0].name];
1234
+ }
1235
+ return {
1236
+ daysCount: savedDaysCount || 7,
1237
+ detailPanel: savedDetailPanel || false,
1238
+ currentViewMode: effectiveViewMode,
1239
+ visibleViewCategories,
1240
+ calendarLayout,
1241
+ timelineLayout,
1242
+ tableLayout,
1243
+ currentDate,
1244
+ selectedTaskTypeNames,
1245
+ selectedAssigneeIds: defaultAssigneeIds,
1246
+ assigneeFilterHasSavedPreference,
1247
+ selectedReporterIds: savedReporterIds || [],
1248
+ selectedPriorities: normalizeTaskBoardPriorityFilterKeys(savedPriorities),
1249
+ selectedDueStatusKeys: normalizeDueStatusFilterKeys(savedDueStatusKeys),
1250
+ colorBy: savedColorBy || 'dueDate',
1251
+ selectedCalendarEventTypeIds: normalizeRegionalCalendarEventIds(savedCalendarEventIds),
1252
+ };
1253
+ };
1254
+ return {
1255
+ async reloadTaskTypes(options) {
1256
+ taskBoardService.clearProviderCache();
1257
+ const taskTypes = await taskBoardService.getTaskTypes(options);
1258
+ patchState(store, {
1259
+ taskTypes,
1260
+ taskTypeOptions: taskBoardService.getTaskTypeOptions(),
1261
+ });
1262
+ },
1263
+ async initialize() {
1264
+ patchState(store, { isLoading: true });
1265
+ try {
1266
+ const persistedSettings = await loadPersistedSettings();
1267
+ let taskTypes = [];
1268
+ let taskTypeOptions = [];
1269
+ try {
1270
+ taskTypes = await taskBoardService.getTaskTypes();
1271
+ taskTypeOptions = taskBoardService.getTaskTypeOptions();
1272
+ }
1273
+ catch (error) {
1274
+ console.error('Error loading task board task types:', error);
1275
+ }
1276
+ patchState(store, {
1277
+ taskTypes,
1278
+ taskTypeOptions,
1279
+ ...buildStateFromPersistedSettings(persistedSettings, taskTypes, taskTypeOptions),
1280
+ isLoading: false,
1281
+ });
1282
+ syncTaskBoardMenuPath();
1283
+ if (!settingsSyncInitialized) {
1284
+ settingsSyncInitialized = true;
1285
+ settingsService.onChanged.subscribe((event) => {
1286
+ if (event.scope !== AXPPlatformScope.User) {
1287
+ return;
1288
+ }
1289
+ syncSettingsFromChangeEvent(event.keys, event.values);
1290
+ });
1291
+ }
1292
+ }
1293
+ catch (error) {
1294
+ console.error('Error initializing TaskBoard ViewModel:', error);
1295
+ let persistedSettingsPatch = {};
1296
+ try {
1297
+ const persistedSettings = await loadPersistedSettings();
1298
+ persistedSettingsPatch = buildStateFromPersistedSettings(persistedSettings, [], []);
1299
+ }
1300
+ catch (settingsError) {
1301
+ console.error('Error loading task board persisted settings:', settingsError);
1302
+ }
1303
+ const fallbackAssigneeIds = normalizeTaskBoardAssigneeIds(sessionService, resolveTaskBoardSavedAssigneeIds(sessionService, null));
1304
+ patchState(store, {
1305
+ detailPanel: false,
1306
+ currentViewMode: 'month',
1307
+ visibleViewCategories: ['calendar', 'timeline', 'table', 'kanban'],
1308
+ calendarLayout: 'monthly',
1309
+ timelineLayout: 'daily',
1310
+ tableLayout: 'simple',
1311
+ currentDate: new Date(),
1312
+ selectedTaskTypeNames: [],
1313
+ selectedAssigneeIds: fallbackAssigneeIds,
1314
+ assigneeFilterHasSavedPreference: false,
1315
+ selectedPriorities: [],
1316
+ selectedDueStatusKeys: [],
1317
+ colorBy: 'dueDate',
1318
+ selectedReporterIds: [],
1319
+ selectedCalendarEventTypeIds: [],
1320
+ taskTypes: store.taskTypes(),
1321
+ taskTypeOptions: store.taskTypeOptions(),
1322
+ isLoading: false,
1323
+ ...persistedSettingsPatch,
1324
+ });
1325
+ }
1326
+ },
1327
+ setViewMode(mode) {
1328
+ let category = resolveTaskBoardViewCategory(mode);
1329
+ const allowed = store.visibleViewCategories();
1330
+ if (!allowed.includes(category)) {
1331
+ const fallbackCategory = allowed[0] ?? category;
1332
+ mode = resolveTaskBoardLeafViewMode(fallbackCategory, this.getLayoutForCategory(fallbackCategory));
1333
+ category = fallbackCategory;
1334
+ }
1335
+ const layout = resolveTaskBoardViewLayout(mode);
1336
+ if (layout) {
1337
+ this.patchLayoutForCategory(category, layout);
1338
+ }
1339
+ this.applyLeafViewMode(mode);
1340
+ },
1341
+ setViewCategory(category) {
1342
+ const allowed = store.visibleViewCategories();
1343
+ const effectiveCategory = allowed.includes(category) ? category : (allowed[0] ?? category);
1344
+ const layout = this.getLayoutForCategory(effectiveCategory);
1345
+ this.applyLeafViewMode(resolveTaskBoardLeafViewMode(effectiveCategory, layout));
1346
+ },
1347
+ setViewLayout(category, layout) {
1348
+ this.patchLayoutForCategory(category, layout);
1349
+ if (resolveTaskBoardViewCategory(store.currentViewMode()) === category) {
1350
+ this.applyLeafViewMode(resolveTaskBoardLeafViewMode(category, layout));
1351
+ }
1352
+ else {
1353
+ this.persistLayout(category, layout);
1354
+ }
1355
+ },
1356
+ getLayoutForCategory(category) {
1357
+ switch (category) {
1358
+ case 'calendar':
1359
+ return store.calendarLayout();
1360
+ case 'timeline':
1361
+ return store.timelineLayout();
1362
+ case 'table':
1363
+ return store.tableLayout();
1364
+ default:
1365
+ return null;
1366
+ }
1367
+ },
1368
+ patchLayoutForCategory(category, layout) {
1369
+ switch (category) {
1370
+ case 'calendar':
1371
+ patchState(store, { calendarLayout: layout });
1372
+ break;
1373
+ case 'timeline':
1374
+ patchState(store, { timelineLayout: layout });
1375
+ break;
1376
+ case 'table':
1377
+ patchState(store, { tableLayout: layout });
1378
+ break;
1379
+ }
1380
+ this.persistLayout(category, layout);
1381
+ },
1382
+ persistLayout(category, layout) {
1383
+ const key = category === 'calendar'
1384
+ ? AXMTaskBoardSettings.CalendarLayout
1385
+ : category === 'timeline'
1386
+ ? AXMTaskBoardSettings.TimelineLayout
1387
+ : AXMTaskBoardSettings.TableLayout;
1388
+ userScopedSettings()
1389
+ .set(key, layout)
1390
+ .catch((error) => console.error('Error saving task board view layout:', error));
1391
+ },
1392
+ applyLeafViewMode(mode) {
1393
+ if ((mode === 'kanban' || mode === 'status-grouped-grid') &&
1394
+ store.selectedTaskTypeNames().length === 0) {
1395
+ const firstTaskType = store.taskTypes()[0];
1396
+ if (firstTaskType) {
1397
+ this.setSelectedTaskTypeNames([firstTaskType.name]);
1398
+ }
1399
+ }
1400
+ if ((mode === 'kanban' || mode === 'status-grouped-grid') &&
1401
+ store.selectedTaskTypeNames().length > 1) {
1402
+ this.setSelectedTaskTypeNames([store.selectedTaskTypeNames()[0]]);
1403
+ }
1404
+ patchState(store, { currentViewMode: mode });
1405
+ userScopedSettings()
1406
+ .set(AXMTaskBoardSettings.ViewMode, mode)
1407
+ .catch((error) => console.error('Error saving task board view mode:', error));
1408
+ syncTaskBoardMenuPath();
1409
+ },
1410
+ setCurrentDate(date) {
1411
+ patchState(store, { currentDate: new Date(date) });
1412
+ userScopedSettings()
1413
+ .set(AXMTaskBoardSettings.CurrentDate, date.toISOString())
1414
+ .catch((error) => console.error('Error saving current date:', error));
1415
+ },
1416
+ async restoreQueryFiltersFromSettings() {
1417
+ const persistedSettings = await loadPersistedSettings();
1418
+ const filterPatch = buildStateFromPersistedSettings(persistedSettings, store.taskTypes(), store.taskTypeOptions());
1419
+ patchState(store, {
1420
+ selectedTaskTypeNames: filterPatch.selectedTaskTypeNames ?? [],
1421
+ selectedAssigneeIds: filterPatch.selectedAssigneeIds ?? [],
1422
+ assigneeFilterHasSavedPreference: filterPatch.assigneeFilterHasSavedPreference ?? false,
1423
+ selectedReporterIds: filterPatch.selectedReporterIds ?? [],
1424
+ });
1425
+ },
1426
+ clearQueryFilters() {
1427
+ const defaultAssigneeIds = normalizeTaskBoardAssigneeIds(sessionService, resolveTaskBoardSavedAssigneeIds(sessionService, null));
1428
+ patchState(store, {
1429
+ selectedTaskTypeNames: [],
1430
+ selectedAssigneeIds: defaultAssigneeIds,
1431
+ assigneeFilterHasSavedPreference: false,
1432
+ selectedReporterIds: [],
1433
+ });
1434
+ },
1435
+ setSelectedTaskTypeNames(taskTypeNames, isQueryParam = false) {
1436
+ patchState(store, { selectedTaskTypeNames: taskTypeNames });
1437
+ if (isQueryParam)
1438
+ return;
1439
+ userScopedSettings()
1440
+ .set(AXMTaskBoardSettings.SelectedTaskTypeNames, taskTypeNames)
1441
+ .catch((error) => console.error('Error saving selected task type filter:', error));
1442
+ },
1443
+ setSelectedAssigneeIds(assigneeIds, isQueryParam = false, persistPreference = !isQueryParam) {
1444
+ const normalizedAssigneeIds = normalizeTaskBoardAssigneeIds(sessionService, assigneeIds);
1445
+ patchState(store, {
1446
+ selectedAssigneeIds: normalizedAssigneeIds,
1447
+ ...(persistPreference ? { assigneeFilterHasSavedPreference: true } : {}),
1448
+ });
1449
+ if (isQueryParam || !persistPreference)
1450
+ return;
1451
+ userScopedSettings()
1452
+ .set(AXMTaskBoardSettings.SelectedAssigneeIds, normalizedAssigneeIds)
1453
+ .catch((error) => console.error('Error saving selected assignee filter:', error));
1454
+ },
1455
+ setSelectedReporterIds(reporterIds, isQueryParam = false) {
1456
+ patchState(store, { selectedReporterIds: reporterIds });
1457
+ if (isQueryParam)
1458
+ return;
1459
+ userScopedSettings()
1460
+ .set(AXMTaskBoardSettings.SelectedReporterIds, reporterIds)
1461
+ .catch((error) => console.error('Error saving selected reporter filter:', error));
1462
+ },
1463
+ setSelectedPriorities(priorities, isQueryParam = false) {
1464
+ patchState(store, { selectedPriorities: priorities });
1465
+ if (isQueryParam)
1466
+ return;
1467
+ userScopedSettings()
1468
+ .set(AXMTaskBoardSettings.SelectedPriorities, priorities)
1469
+ .catch((error) => console.error('Error saving selected priority filter:', error));
1470
+ },
1471
+ setSelectedDueStatusKeys(dueStatusKeys, isQueryParam = false) {
1472
+ patchState(store, { selectedDueStatusKeys: dueStatusKeys });
1473
+ if (isQueryParam)
1474
+ return;
1475
+ userScopedSettings()
1476
+ .set(AXMTaskBoardSettings.SelectedDueStatusKeys, dueStatusKeys)
1477
+ .catch((error) => console.error('Error saving selected due status filter:', error));
1478
+ },
1479
+ setColorBy(colorBy) {
1480
+ patchState(store, { colorBy });
1481
+ userScopedSettings()
1482
+ .set(AXMTaskBoardSettings.ColorBy, colorBy)
1483
+ .catch((error) => console.error('Error saving task board color by:', error));
1484
+ },
1485
+ setSelectedCalendarEventTypeIds(eventTypeIds) {
1486
+ patchState(store, { selectedCalendarEventTypeIds: eventTypeIds });
1487
+ userScopedSettings()
1488
+ .set(AXPRegionalSetting.CalendarEvents, eventTypeIds)
1489
+ .catch((error) => console.error('Error saving selected calendar events:', error));
1490
+ },
1491
+ /**
1492
+ * Sets the currently selected task. Pass `null` to clear the selection.
1493
+ * @param task The task to select, or null.
1494
+ */
1495
+ selectTask(task) {
1496
+ patchState(store, { selectedTask: task });
1497
+ },
1498
+ /**
1499
+ * A convenience method to clear the currently selected task.
1500
+ */
1501
+ clearSelectedTask() {
1502
+ patchState(store, { selectedTask: null });
1503
+ },
1504
+ openDetailPanel() {
1505
+ patchState(store, { detailPanel: true });
1506
+ userScopedSettings()
1507
+ .set(AXMTaskBoardSettings.DetailPanel, true)
1508
+ .catch((error) => console.error('Error saving detail panel:', error));
1509
+ },
1510
+ closeDetailPanel() {
1511
+ patchState(store, { detailPanel: false });
1512
+ userScopedSettings()
1513
+ .set(AXMTaskBoardSettings.DetailPanel, false)
1514
+ .catch((error) => console.error('Error saving detail panel:', error));
1515
+ },
1516
+ toggleDetailPanel() {
1517
+ const detailPanel = !store.detailPanel();
1518
+ patchState(store, { detailPanel: detailPanel });
1519
+ userScopedSettings()
1520
+ .set(AXMTaskBoardSettings.DetailPanel, detailPanel)
1521
+ .catch((error) => console.error('Error saving detail panel:', error));
1522
+ },
1523
+ async getStatusName(key, provider) {
1524
+ return taskBoardService.getStatusName(key, provider);
1525
+ },
1526
+ /**
1527
+ * Sets the start date for a new range selection.
1528
+ * Pass `null` to clear the selection.
1529
+ * @param date The start date of the range.
1530
+ */
1531
+ setRangeStartDate(date) {
1532
+ patchState(store, { rangeStartDate: date });
1533
+ },
1534
+ setDaysCount(days) {
1535
+ const count = Math.max(1, days);
1536
+ patchState(store, { daysCount: count });
1537
+ userScopedSettings()
1538
+ .set(AXMTaskBoardSettings.DaysCount, count)
1539
+ .catch((error) => console.error('Error saving days count:', error));
1540
+ },
1541
+ };
1542
+ }));
1543
+
1544
+ //#region ---- Imports ----
1545
+ const RESET_TASK_BOARD_FILTERS_ASSIGN_TO_CURRENT_USER_COMMAND_KEY = AXPTaskManagementCommandKeys.TaskBoard.ResetFiltersAssignToCurrentUser;
1546
+ //#endregion
1547
+ /**
1548
+ * Clears task-board filters, scopes assignee to the signed-in user, and requests a board refresh.
1549
+ */
1550
+ class ResetTaskBoardFiltersAssignToCurrentUserCommand {
1551
+ constructor() {
1552
+ //#region ---- Services & Dependencies ----
1553
+ this.sessionService = inject(AXPSessionService);
1554
+ this.taskBoardService = inject(AXPTaskBoardService);
1555
+ this.vm = inject(AXMTaskBoardViewModel);
1556
+ }
1557
+ //#endregion
1558
+ //#region ---- Command Implementation ----
1559
+ async execute(_input) {
1560
+ const currentUserId = this.sessionService.user?.id;
1561
+ if (isNil(currentUserId)) {
1562
+ return { success: false, message: { text: 'Current user is not available.' } };
1563
+ }
1564
+ this.vm.setSelectedTaskTypeNames([], false);
1565
+ this.vm.setSelectedReporterIds([], false);
1566
+ this.vm.setSelectedPriorities([], false);
1567
+ this.vm.setSelectedDueStatusKeys([], false);
1568
+ this.vm.setSelectedAssigneeIds([currentUserId], false, true);
1569
+ this.ensureTaskTypeForGroupedViews();
1570
+ this.taskBoardService.requestRefresh();
1571
+ return { success: true, kind: 'object', data: undefined };
1572
+ }
1573
+ //#endregion
1574
+ //#region ---- Helpers ----
1575
+ ensureTaskTypeForGroupedViews() {
1576
+ const viewMode = this.vm.currentViewMode();
1577
+ if (viewMode !== 'kanban' && viewMode !== 'status-grouped-grid') {
1578
+ return;
1579
+ }
1580
+ if (this.vm.selectedTaskTypeNames().length > 0) {
1581
+ return;
1582
+ }
1583
+ const firstTaskType = this.vm.taskTypes()[0];
1584
+ if (firstTaskType) {
1585
+ this.vm.setSelectedTaskTypeNames([firstTaskType.name]);
1586
+ }
1587
+ }
1588
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ResetTaskBoardFiltersAssignToCurrentUserCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1589
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ResetTaskBoardFiltersAssignToCurrentUserCommand, providedIn: 'root' }); }
1590
+ }
1591
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ResetTaskBoardFiltersAssignToCurrentUserCommand, decorators: [{
1592
+ type: Injectable,
1593
+ args: [{ providedIn: 'root' }]
1594
+ }] });
1595
+
1596
+ var resetFiltersAssignToCurrentUser_command = /*#__PURE__*/Object.freeze({
1597
+ __proto__: null,
1598
+ RESET_TASK_BOARD_FILTERS_ASSIGN_TO_CURRENT_USER_COMMAND_KEY: RESET_TASK_BOARD_FILTERS_ASSIGN_TO_CURRENT_USER_COMMAND_KEY,
1599
+ ResetTaskBoardFiltersAssignToCurrentUserCommand: ResetTaskBoardFiltersAssignToCurrentUserCommand
1600
+ });
1601
+
1602
+ class AXPDueStatusProvider {
1603
+ }
1604
+ const AXP_DUE_STATUS_PROVIDERS = new InjectionToken('AXP_DUE_STATUS_PROVIDERS');
1605
+ function getTaskDueStatusKey(task) {
1606
+ const data = task.data;
1607
+ return data?.dueStatusKey;
1608
+ }
1609
+ function matchesTaskDueStatusFilter(task, dueStatusKeys) {
1610
+ if (dueStatusKeys.length === 0) {
1611
+ return true;
1612
+ }
1613
+ const key = getTaskDueStatusKey(task);
1614
+ return key != null && dueStatusKeys.includes(key);
1615
+ }
1616
+
1617
+ function startOfLocalDay(date) {
1618
+ const value = new Date(date);
1619
+ value.setHours(0, 0, 0, 0);
1620
+ return value;
1621
+ }
1622
+ function endOfLocalDay(date) {
1623
+ const value = new Date(date);
1624
+ value.setHours(23, 59, 59, 999);
1625
+ return value;
1626
+ }
1627
+ function isSameLocalDay(a, b) {
1628
+ return (a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate());
1629
+ }
1630
+ function addLocalDays(date, days) {
1631
+ const value = new Date(date);
1632
+ value.setDate(value.getDate() + days);
1633
+ return value;
1634
+ }
1635
+ function getStartOfLocalWeek(date, weekStartsOn) {
1636
+ const day = startOfLocalDay(date);
1637
+ const weekday = day.getDay();
1638
+ const diff = (weekday - weekStartsOn + 7) % 7;
1639
+ day.setDate(day.getDate() - diff);
1640
+ return day;
1641
+ }
1642
+ function getEndOfLocalWeek(date, weekStartsOn) {
1643
+ const start = getStartOfLocalWeek(date, weekStartsOn);
1644
+ return endOfLocalDay(addLocalDays(start, 6));
1645
+ }
1646
+ function matchesDueStatusKey(context, key) {
1647
+ const today = startOfLocalDay(context.now);
1648
+ const tomorrow = startOfLocalDay(addLocalDays(context.now, 1));
1649
+ const weekEnd = getEndOfLocalWeek(context.now, context.weekStartsOn);
1650
+ if (key === 'completed') {
1651
+ return context.bookmarkConsumed;
1652
+ }
1653
+ if (key === 'no-due-date') {
1654
+ return !context.bookmarkConsumed && !context.hasExplicitDueDate;
1655
+ }
1656
+ if (!context.hasExplicitDueDate || !context.dueDate || context.bookmarkConsumed) {
1657
+ return false;
1658
+ }
1659
+ const dueDay = startOfLocalDay(context.dueDate);
1660
+ if (key === 'overdue') {
1661
+ return dueDay.getTime() < today.getTime();
1662
+ }
1663
+ if (key === 'due-today') {
1664
+ return isSameLocalDay(dueDay, today);
1665
+ }
1666
+ if (key === 'due-tomorrow') {
1667
+ return isSameLocalDay(dueDay, tomorrow);
1668
+ }
1669
+ if (key === 'due-this-week') {
1670
+ return dueDay.getTime() > tomorrow.getTime() && dueDay.getTime() <= weekEnd.getTime();
1671
+ }
1672
+ if (key === 'upcoming') {
1673
+ return dueDay.getTime() > weekEnd.getTime();
1674
+ }
1675
+ return false;
1676
+ }
1677
+ function resolveDueStatusFromDefinitions(definitions, context) {
1678
+ const ordered = [...definitions].sort((a, b) => a.order - b.order);
1679
+ for (const definition of ordered) {
1680
+ if (matchesDueStatusKey(context, definition.name)) {
1681
+ return definition;
1682
+ }
1683
+ }
1684
+ return null;
1685
+ }
1686
+
1687
+ const DEFAULT_DUE_STATUS_DEFINITIONS = [
1688
+ {
1689
+ name: 'completed',
1690
+ title: '@task-management:task-board.due-status.completed',
1691
+ color: 'success',
1692
+ icon: 'fa-light fa-circle-check',
1693
+ order: 0,
1694
+ },
1695
+ {
1696
+ name: 'no-due-date',
1697
+ title: '@task-management:task-board.due-status.no-due-date',
1698
+ color: 'neutral',
1699
+ icon: 'fa-light fa-calendar-xmark',
1700
+ order: 1,
1701
+ },
1702
+ {
1703
+ name: 'overdue',
1704
+ title: '@task-management:task-board.due-status.overdue',
1705
+ color: 'danger',
1706
+ icon: 'fa-light fa-calendar-exclamation',
1707
+ order: 2,
1708
+ },
1709
+ {
1710
+ name: 'due-today',
1711
+ title: '@task-management:task-board.due-status.due-today',
1712
+ color: 'warning',
1713
+ icon: 'fa-light fa-calendar-day',
1714
+ order: 3,
1715
+ },
1716
+ {
1717
+ name: 'due-tomorrow',
1718
+ title: '@task-management:task-board.due-status.due-tomorrow',
1719
+ color: 'accent3',
1720
+ icon: 'fa-light fa-calendar-plus',
1721
+ order: 4,
1722
+ },
1723
+ {
1724
+ name: 'due-this-week',
1725
+ title: '@task-management:task-board.due-status.due-this-week',
1726
+ color: 'primary',
1727
+ icon: 'fa-light fa-calendar-week',
1728
+ order: 5,
1729
+ },
1730
+ {
1731
+ name: 'upcoming',
1732
+ title: '@task-management:task-board.due-status.upcoming',
1733
+ color: 'secondary',
1734
+ icon: 'fa-light fa-calendar',
1735
+ order: 6,
1736
+ },
1737
+ ];
1738
+ class AXMDefaultDueStatusProvider extends AXPDueStatusProvider {
1739
+ constructor() {
1740
+ super(...arguments);
1741
+ this.key = 'default';
1742
+ this.priority = 0;
1743
+ }
1744
+ getDefinitions() {
1745
+ return DEFAULT_DUE_STATUS_DEFINITIONS;
1746
+ }
1747
+ resolve(context) {
1748
+ return resolveDueStatusFromDefinitions(DEFAULT_DUE_STATUS_DEFINITIONS, context);
1749
+ }
1750
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDefaultDueStatusProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1751
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDefaultDueStatusProvider, providedIn: 'root' }); }
1752
+ }
1753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDefaultDueStatusProvider, decorators: [{
1754
+ type: Injectable,
1755
+ args: [{ providedIn: 'root' }]
1756
+ }] });
1757
+ function getDefaultDueStatusDefinitions() {
1758
+ return DEFAULT_DUE_STATUS_DEFINITIONS;
1759
+ }
1760
+
1761
+ class AXPDueStatusProviderService {
1762
+ constructor() {
1763
+ this.localeService = inject(AXLocaleService);
1764
+ this.registeredProviders = inject(AXP_DUE_STATUS_PROVIDERS, { optional: true }) ?? [];
1765
+ this.fallbackProvider = inject(AXMDefaultDueStatusProvider);
1766
+ }
1767
+ getDefinitions(options) {
1768
+ return this.selectProvider(options).getDefinitions();
1769
+ }
1770
+ resolveForTask(task, options) {
1771
+ const data = task.data;
1772
+ const context = {
1773
+ now: new Date(),
1774
+ dueDate: task.endDate ? new Date(task.endDate) : null,
1775
+ hasExplicitDueDate: data?.hasExplicitDueDate === true,
1776
+ bookmarkConsumed: data?.bookmarkConsumed === true,
1777
+ weekStartsOn: this.resolveWeekStartsOn(),
1778
+ };
1779
+ const definition = this.selectProvider(options).resolve(context);
1780
+ if (!definition) {
1781
+ return null;
1782
+ }
1783
+ return {
1784
+ name: definition.name,
1785
+ title: definition.title,
1786
+ color: definition.color,
1787
+ icon: definition.icon,
1788
+ };
1789
+ }
1790
+ attachDueStatusToTask(task, options) {
1791
+ const presentation = this.resolveForTask(task, options);
1792
+ if (!presentation) {
1793
+ return task;
1794
+ }
1795
+ return {
1796
+ ...task,
1797
+ data: {
1798
+ ...(task.data ?? {}),
1799
+ dueStatusKey: presentation.name,
1800
+ dueStatus: presentation,
1801
+ },
1802
+ };
1803
+ }
1804
+ selectProvider(options) {
1805
+ const providers = [...this.registeredProviders, this.fallbackProvider].sort((a, b) => b.priority - a.priority);
1806
+ if (!options?.workflowDefinitionId && !options?.activityType) {
1807
+ return providers[providers.length - 1] ?? this.fallbackProvider;
1808
+ }
1809
+ const matched = providers.find((provider) => {
1810
+ if (options.workflowDefinitionId && provider.key === options.workflowDefinitionId) {
1811
+ return true;
1812
+ }
1813
+ return Boolean(options.activityType && provider.key === options.activityType);
1814
+ });
1815
+ return matched ?? providers[providers.length - 1] ?? this.fallbackProvider;
1816
+ }
1817
+ resolveWeekStartsOn() {
1818
+ return this.localeService.activeProfile()?.calendar?.week?.startsOn ?? 0;
1819
+ }
1820
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPDueStatusProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1821
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPDueStatusProviderService, providedIn: 'root' }); }
1822
+ }
1823
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPDueStatusProviderService, decorators: [{
1824
+ type: Injectable,
1825
+ args: [{ providedIn: 'root' }]
1826
+ }] });
1827
+
1828
+ const BADGE_COLORS = new Set([
1829
+ 'primary',
1830
+ 'secondary',
1831
+ 'success',
1832
+ 'warning',
1833
+ 'danger',
1834
+ 'accent1',
1835
+ 'accent2',
1836
+ 'accent3',
1837
+ 'neutral',
1838
+ 'ghost',
1839
+ ]);
1840
+ /** Plain gray/white tokens → soft secondary (`neutral` → secondary-lightest in CSS). */
1841
+ const NON_CHROMATIC_BADGE_COLORS = new Set([
1842
+ 'gray',
1843
+ 'grey',
1844
+ 'white',
1845
+ 'black',
1846
+ 'default',
1847
+ 'light',
1848
+ 'lighter',
1849
+ 'lightest',
1850
+ 'natural',
1851
+ ]);
1852
+ /** Maps entity status semantic colors to badge/chip tokens (`info` → `accent3`). */
1853
+ function mapEntityStatusColorForBadge(color, statusId) {
1854
+ const normalized = color?.trim().toLowerCase();
1855
+ if (normalized === 'info') {
1856
+ return 'accent3';
1857
+ }
1858
+ if (normalized && NON_CHROMATIC_BADGE_COLORS.has(normalized)) {
1859
+ return 'neutral';
1860
+ }
1861
+ if (normalized && BADGE_COLORS.has(normalized)) {
1862
+ return normalized;
1863
+ }
1864
+ return resolveDefaultTaskStatusColor(statusId);
1865
+ }
1866
+ function resolveDefaultTaskStatusColor(statusId) {
1867
+ if (String(statusId) === AXPSystemStatuses.Pending.name) {
1868
+ return 'warning';
1869
+ }
1870
+ return 'primary';
1871
+ }
1872
+ /** Resolves the chip color for a task row, aligned with workflow popover badge layout. */
1873
+ function resolveTaskBoardStatusColor(task) {
1874
+ if (task.status.color) {
1875
+ return task.status.color;
1876
+ }
1877
+ const data = task.data;
1878
+ const rawColor = get(data, 'entityStatusDefinition.color') ?? get(data, 'statusColor');
1879
+ return mapEntityStatusColorForBadge(rawColor, task.status.id);
1880
+ }
1881
+ /** Calendar placement for tasks without an explicit due date (received/created day, all-day). */
1882
+ function resolveTaskBoardCalendarPlacement(task) {
1883
+ const hasExplicitDueDate = get(task.data, 'hasExplicitDueDate') === true;
1884
+ if (hasExplicitDueDate) {
1885
+ const due = new Date(task.endDate);
1886
+ const dayStart = new Date(due);
1887
+ dayStart.setHours(0, 0, 0, 0);
1888
+ const dayEnd = new Date(due);
1889
+ dayEnd.setHours(23, 59, 59, 999);
1890
+ return {
1891
+ startDate: dayStart,
1892
+ endDate: dayEnd,
1893
+ allDay: true,
1894
+ };
1895
+ }
1896
+ const received = task.createdAt ? new Date(task.createdAt) : new Date(task.startDate);
1897
+ const dayStart = new Date(received);
1898
+ dayStart.setHours(0, 0, 0, 0);
1899
+ const dayEnd = new Date(received);
1900
+ dayEnd.setHours(23, 59, 59, 999);
1901
+ return {
1902
+ startDate: dayStart,
1903
+ endDate: dayEnd,
1904
+ allDay: true,
1905
+ };
1906
+ }
1907
+ /**
1908
+ * Returns tasks whose effective scheduling range overlaps the filter range (inclusive).
1909
+ * Open tasks without an explicit due date are always included (widget-style visibility).
1910
+ */
1911
+ function resolveTaskBoardSchedulingRange(task) {
1912
+ const hasExplicitDueDate = get(task.data, 'hasExplicitDueDate') === true;
1913
+ if (hasExplicitDueDate) {
1914
+ const due = new Date(task.endDate);
1915
+ return { from: due, end: due };
1916
+ }
1917
+ const received = task.createdAt ? new Date(task.createdAt) : new Date(task.startDate);
1918
+ return { from: received, end: received };
1919
+ }
1920
+ /** Whether a task should appear for the task board's active date navigator range. */
1921
+ function taskOverlapsFilterRange(task, range) {
1922
+ if (!range?.from || !range?.end) {
1923
+ return true;
1924
+ }
1925
+ const hasExplicitDueDate = get(task.data, 'hasExplicitDueDate') === true;
1926
+ const bookmarkConsumed = get(task.data, 'bookmarkConsumed') === true;
1927
+ if (!hasExplicitDueDate && !bookmarkConsumed) {
1928
+ return true;
1929
+ }
1930
+ const { from, end } = resolveTaskBoardSchedulingRange(task);
1931
+ const rangeStart = range.from.getTime();
1932
+ const rangeEnd = range.end.getTime();
1933
+ const taskStart = from.getTime();
1934
+ const taskEnd = end.getTime();
1935
+ return taskStart <= rangeEnd && taskEnd >= rangeStart;
1936
+ }
1937
+ function filterTasksOverlappingRange(tasks, range) {
1938
+ if (!range?.from || !range?.end) {
1939
+ return tasks;
1940
+ }
1941
+ return tasks.filter((task) => taskOverlapsFilterRange(task, range));
1942
+ }
1943
+
1944
+ /**
1945
+ * Soft secondary fallback class token.
1946
+ * CSS maps `neutral` / `ghost` to `secondary-lightest` / `secondary-lighter` surfaces
1947
+ * (never plain gray/white).
1948
+ */
1949
+ const TASK_BOARD_SOFT_SECONDARY_TOKEN = 'neutral';
1950
+ const SURFACE_CLASS_TOKENS = new Set([
1951
+ 'primary',
1952
+ 'secondary',
1953
+ 'success',
1954
+ 'warning',
1955
+ 'danger',
1956
+ 'accent1',
1957
+ 'accent2',
1958
+ 'accent3',
1959
+ 'neutral',
1960
+ 'ghost',
1961
+ ]);
1962
+ /** Gray / white / natural tokens that blend into the calendar background. */
1963
+ const NON_CHROMATIC_SURFACE_TOKENS = new Set([
1964
+ 'gray',
1965
+ 'grey',
1966
+ 'white',
1967
+ 'black',
1968
+ 'default',
1969
+ 'light',
1970
+ 'lighter',
1971
+ 'lightest',
1972
+ 'natural',
1973
+ ]);
1974
+ function resolveTaskBoardAccentColor(task, colorBy) {
1975
+ switch (colorBy) {
1976
+ case 'dueDate':
1977
+ return resolveDueStatusAccentColor(task);
1978
+ case 'priority':
1979
+ return task.priority
1980
+ ? getTaskPriorityColor(normalizeTaskPriority(task.priority))
1981
+ : TASK_BOARD_SOFT_SECONDARY_TOKEN;
1982
+ case 'status':
1983
+ return resolveTaskBoardStatusColor(task);
1984
+ default:
1985
+ return resolveDueStatusAccentColor(task);
1986
+ }
1987
+ }
1988
+ function resolveDueStatusAccentColor(task) {
1989
+ const dueStatus = get(task.data, 'dueStatus');
1990
+ if (dueStatus?.color) {
1991
+ return dueStatus.color;
1992
+ }
1993
+ if (get(task.data, 'hasExplicitDueDate') !== true) {
1994
+ return TASK_BOARD_SOFT_SECONDARY_TOKEN;
1995
+ }
1996
+ return 'secondary';
1997
+ }
1998
+ function normalizeSurfaceClassToken(colorToken) {
1999
+ const safe = colorToken.replace(/[^a-z0-9-]/gi, '').toLowerCase();
2000
+ if (!safe || NON_CHROMATIC_SURFACE_TOKENS.has(safe)) {
2001
+ return TASK_BOARD_SOFT_SECONDARY_TOKEN;
2002
+ }
2003
+ return SURFACE_CLASS_TOKENS.has(safe) ? safe : TASK_BOARD_SOFT_SECONDARY_TOKEN;
2004
+ }
2005
+ /** Maps a color token to a static BEM class backed by Tailwind @apply rules in task-board-surface.css. */
2006
+ function buildTaskSurfaceCssClass(colorToken) {
2007
+ return `axm-task-board-surface--${normalizeSurfaceClassToken(colorToken)}`;
2008
+ }
2009
+ /** Accent stripe modifier for dashboard widget rows. */
2010
+ function buildTaskAccentSurfaceCssClass(colorToken) {
2011
+ return `axm-task-board-accent--${normalizeSurfaceClassToken(colorToken)}`;
2012
+ }
2013
+ /** @deprecated Use {@link buildTaskSurfaceCssClass}. */
2014
+ function buildTaskAccentCssClass(colorToken) {
2015
+ return buildTaskSurfaceCssClass(colorToken);
2016
+ }
2017
+
2018
+ //#region ---- Imports ----
2019
+ //#endregion
2020
+ //#region ---- Component ----
2021
+ class AXMTaskBoardDefaultTaskPopoverComponent extends AXPTaskPopoverComponent {
2022
+ constructor() {
2023
+ super();
2024
+ //#region ---- Inputs ----
2025
+ this.task = input.required(/* @ts-ignore */
2026
+ ...(ngDevMode ? [{ debugName: "task" }] : /* istanbul ignore next */ []));
2027
+ //#endregion
2028
+ //#region ---- Services & Dependencies ----
2029
+ this.taskBoardService = inject(AXPTaskBoardService);
2030
+ this.calendarService = inject(AXCalendarService);
2031
+ //#endregion
2032
+ //#region ---- Actions ----
2033
+ this.taskActions = signal([], /* @ts-ignore */
2034
+ ...(ngDevMode ? [{ debugName: "taskActions" }] : /* istanbul ignore next */ []));
2035
+ this.primaryActions = computed(() => this.taskActions().filter((action) => (action.priority ?? 'secondary') === 'primary'), /* @ts-ignore */
2036
+ ...(ngDevMode ? [{ debugName: "primaryActions" }] : /* istanbul ignore next */ []));
2037
+ this.secondaryActions = computed(() => this.taskActions().filter((action) => (action.priority ?? 'secondary') === 'secondary'), /* @ts-ignore */
2038
+ ...(ngDevMode ? [{ debugName: "secondaryActions" }] : /* istanbul ignore next */ []));
2039
+ effect(() => {
2040
+ const currentTask = this.task();
2041
+ if (!currentTask?.provider) {
2042
+ this.taskActions.set([]);
2043
+ return;
2044
+ }
2045
+ const provider = this.taskBoardService.getProvider(currentTask.provider);
2046
+ if (!provider) {
2047
+ this.taskActions.set([]);
2048
+ return;
2049
+ }
2050
+ provider
2051
+ .getActions(currentTask)
2052
+ .then((actions) => this.taskActions.set((actions ?? []).map((action) => ({
2053
+ ...action,
2054
+ priority: action.priority ?? 'secondary',
2055
+ }))))
2056
+ .catch((error) => {
2057
+ console.error('[TaskBoardDefaultTaskPopover] Failed to load actions:', error);
2058
+ this.taskActions.set([]);
2059
+ });
2060
+ });
2061
+ }
2062
+ getActionColor(action) {
2063
+ return action.color ?? null;
2064
+ }
2065
+ async executeAction(action) {
2066
+ const currentTask = this.task();
2067
+ if (!currentTask?.provider || !action.command) {
2068
+ return;
2069
+ }
2070
+ try {
2071
+ const result = await this.taskBoardService.executeCommand(action.command, currentTask.provider);
2072
+ if (result?.success === false || result === null) {
2073
+ return;
2074
+ }
2075
+ this.closePopover();
2076
+ this.taskBoardService.requestRefresh(currentTask.provider);
2077
+ }
2078
+ catch (error) {
2079
+ console.error('[TaskBoardDefaultTaskPopover] executeAction failed:', error);
2080
+ }
2081
+ }
2082
+ //#endregion
2083
+ //#region ---- Date Formatting ----
2084
+ formatDateRange(task) {
2085
+ if (task.allDay) {
2086
+ const start = this.calendarService.create(task.startDate);
2087
+ const end = this.calendarService.create(task.endDate);
2088
+ if (start.date.getTime() === end.date.getTime()) {
2089
+ return this.formatDate(task.startDate);
2090
+ }
2091
+ const startFormatted = this.formatDate(task.startDate);
2092
+ const endFormatted = this.formatDate(task.endDate);
2093
+ if (start.date.getMonth() === end.date.getMonth() && start.date.getFullYear() === end.date.getFullYear()) {
2094
+ const startDay = start.date.getDate();
2095
+ const endDay = end.date.getDate();
2096
+ const monthYear = start.format('MMM YYYY');
2097
+ return `${startDay} - ${endDay}, ${monthYear}`;
2098
+ }
2099
+ return `${startFormatted} - ${endFormatted}`;
2100
+ }
2101
+ if (this.isMultiDay(task)) {
2102
+ return `${this.formatDateTime(task.startDate)} - ${this.formatDateTime(task.endDate)}`;
2103
+ }
2104
+ const dateFormatted = this.formatDate(task.startDate);
2105
+ const timeRange = `${this.formatTime(task.startDate)} - ${this.formatTime(task.endDate)}`;
2106
+ return `${dateFormatted} ${timeRange}`;
2107
+ }
2108
+ formatTime(date) {
2109
+ return this.calendarService.create(date).format('HH:mm');
2110
+ }
2111
+ formatDate(date) {
2112
+ return this.calendarService.create(date).format('MMM DD, YYYY');
2113
+ }
2114
+ formatDateTime(date) {
2115
+ return this.calendarService.create(date).format('MMM DD, YYYY HH:mm');
2116
+ }
2117
+ isMultiDay(task) {
2118
+ if (task.allDay) {
2119
+ const start = this.calendarService.create(task.startDate);
2120
+ const end = this.calendarService.create(task.endDate);
2121
+ return start.date.getTime() !== end.date.getTime();
2122
+ }
2123
+ const start = this.calendarService.create(task.startDate);
2124
+ const end = this.calendarService.create(task.endDate);
2125
+ return (start.date.getDate() !== end.date.getDate() ||
2126
+ start.date.getMonth() !== end.date.getMonth() ||
2127
+ start.date.getFullYear() !== end.date.getFullYear());
2128
+ }
2129
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardDefaultTaskPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2130
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardDefaultTaskPopoverComponent, isStandalone: true, selector: "axm-task-board-default-task-popover", inputs: { task: { classPropertyName: "task", publicName: "task", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: `
2131
+ @if (task().description) {
2132
+ <p class="task-popover__description">{{ task().description }}</p>
2133
+ }
2134
+
2135
+ <div class="task-popover__content">
2136
+ <div class="task-popover__info-item">
2137
+ <div class="task-popover__icon">
2138
+ <i class="fa-light fa-clock"></i>
2139
+ </div>
2140
+ <div class="task-popover__info-content">
2141
+ <span class="task-popover__info-text">{{ formatDateRange(task()) }}</span>
2142
+ </div>
2143
+ </div>
2144
+
2145
+ @if (task().status) {
2146
+ <div class="task-popover__info-item">
2147
+ <div class="task-popover__icon">
2148
+ <i class="fa-light fa-flag"></i>
2149
+ </div>
2150
+ <div class="task-popover__info-content">
2151
+ <span class="task-popover__info-text">{{ task().status.title }}</span>
2152
+ </div>
2153
+ </div>
2154
+ }
2155
+
2156
+ @if (task().assignee) {
2157
+ <div class="task-popover__info-item">
2158
+ <div class="task-popover__icon">
2159
+ <i class="fa-light fa-user"></i>
2160
+ </div>
2161
+ <div class="task-popover__info-content">
2162
+ <span class="task-popover__info-text">{{
2163
+ task().assignee?.fullName || task().assignee?.username || 'Unassigned'
2164
+ }}</span>
2165
+ </div>
2166
+ </div>
2167
+ }
2168
+
2169
+ @if (task().priority) {
2170
+ <div class="task-popover__info-item">
2171
+ <div class="task-popover__icon task-popover__icon--priority" [attr.data-priority]="task().priority">
2172
+ <i class="fa-light fa-exclamation-circle"></i>
2173
+ </div>
2174
+ <div class="task-popover__info-content">
2175
+ <span class="task-popover__info-text task-popover__priority" [attr.data-priority]="task().priority">
2176
+ {{ task().priority }}
2177
+ </span>
2178
+ </div>
2179
+ </div>
2180
+ }
2181
+ </div>
2182
+
2183
+ @if (taskActions().length > 0) {
2184
+ <div class="task-popover__actions">
2185
+ <div class="task-popover__actions-group">
2186
+ @if (primaryActions().length > 0) {
2187
+ @for (action of primaryActions(); track action.name || action.title) {
2188
+ <ax-button
2189
+ [look]="'solid'"
2190
+ [color]="getActionColor(action)"
2191
+ (onClick)="executeAction(action)"
2192
+ [disabled]="action.disabled === true"
2193
+ [text]="(action.title | translate | async)!"
2194
+ >
2195
+ @if (action.icon) {
2196
+ <ax-prefix>
2197
+ <ax-icon>
2198
+ <i [class]="action.icon"></i>
2199
+ </ax-icon>
2200
+ </ax-prefix>
2201
+ }
2202
+ </ax-button>
2203
+ }
2204
+ }
2205
+
2206
+ @if (secondaryActions().length > 0) {
2207
+ <ax-button [look]="'blank'">
2208
+ <ax-prefix>
2209
+ <ax-icon icon="fa-light fa-ellipsis-vertical"></ax-icon>
2210
+ </ax-prefix>
2211
+ <ax-dropdown-panel>
2212
+ <ax-button-item-list>
2213
+ @for (action of secondaryActions(); track action.name || action.title) {
2214
+ <ax-button-item
2215
+ [text]="(action.title | translate | async)!"
2216
+ [disabled]="action.disabled === true"
2217
+ (onClick)="executeAction(action)"
2218
+ >
2219
+ @if (action.icon) {
2220
+ <ax-prefix>
2221
+ <ax-icon>
2222
+ <i [class]="action.icon"></i>
2223
+ </ax-icon>
2224
+ </ax-prefix>
2225
+ }
2226
+ </ax-button-item>
2227
+ }
2228
+ </ax-button-item-list>
2229
+ </ax-dropdown-panel>
2230
+ </ax-button>
2231
+ }
2232
+ </div>
2233
+ </div>
2234
+ }
2235
+ `, isInline: true, 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}@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: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.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.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2.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: 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: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2236
+ }
2237
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardDefaultTaskPopoverComponent, decorators: [{
2238
+ type: Component,
2239
+ args: [{ selector: 'axm-task-board-default-task-popover', template: `
2240
+ @if (task().description) {
2241
+ <p class="task-popover__description">{{ task().description }}</p>
2242
+ }
2243
+
2244
+ <div class="task-popover__content">
2245
+ <div class="task-popover__info-item">
2246
+ <div class="task-popover__icon">
2247
+ <i class="fa-light fa-clock"></i>
2248
+ </div>
2249
+ <div class="task-popover__info-content">
2250
+ <span class="task-popover__info-text">{{ formatDateRange(task()) }}</span>
2251
+ </div>
2252
+ </div>
2253
+
2254
+ @if (task().status) {
2255
+ <div class="task-popover__info-item">
2256
+ <div class="task-popover__icon">
2257
+ <i class="fa-light fa-flag"></i>
2258
+ </div>
2259
+ <div class="task-popover__info-content">
2260
+ <span class="task-popover__info-text">{{ task().status.title }}</span>
2261
+ </div>
2262
+ </div>
2263
+ }
2264
+
2265
+ @if (task().assignee) {
2266
+ <div class="task-popover__info-item">
2267
+ <div class="task-popover__icon">
2268
+ <i class="fa-light fa-user"></i>
2269
+ </div>
2270
+ <div class="task-popover__info-content">
2271
+ <span class="task-popover__info-text">{{
2272
+ task().assignee?.fullName || task().assignee?.username || 'Unassigned'
2273
+ }}</span>
2274
+ </div>
2275
+ </div>
2276
+ }
2277
+
2278
+ @if (task().priority) {
2279
+ <div class="task-popover__info-item">
2280
+ <div class="task-popover__icon task-popover__icon--priority" [attr.data-priority]="task().priority">
2281
+ <i class="fa-light fa-exclamation-circle"></i>
2282
+ </div>
2283
+ <div class="task-popover__info-content">
2284
+ <span class="task-popover__info-text task-popover__priority" [attr.data-priority]="task().priority">
2285
+ {{ task().priority }}
2286
+ </span>
2287
+ </div>
2288
+ </div>
2289
+ }
2290
+ </div>
2291
+
2292
+ @if (taskActions().length > 0) {
2293
+ <div class="task-popover__actions">
2294
+ <div class="task-popover__actions-group">
2295
+ @if (primaryActions().length > 0) {
2296
+ @for (action of primaryActions(); track action.name || action.title) {
2297
+ <ax-button
2298
+ [look]="'solid'"
2299
+ [color]="getActionColor(action)"
2300
+ (onClick)="executeAction(action)"
2301
+ [disabled]="action.disabled === true"
2302
+ [text]="(action.title | translate | async)!"
2303
+ >
2304
+ @if (action.icon) {
2305
+ <ax-prefix>
2306
+ <ax-icon>
2307
+ <i [class]="action.icon"></i>
2308
+ </ax-icon>
2309
+ </ax-prefix>
2310
+ }
2311
+ </ax-button>
2312
+ }
2313
+ }
2314
+
2315
+ @if (secondaryActions().length > 0) {
2316
+ <ax-button [look]="'blank'">
2317
+ <ax-prefix>
2318
+ <ax-icon icon="fa-light fa-ellipsis-vertical"></ax-icon>
2319
+ </ax-prefix>
2320
+ <ax-dropdown-panel>
2321
+ <ax-button-item-list>
2322
+ @for (action of secondaryActions(); track action.name || action.title) {
2323
+ <ax-button-item
2324
+ [text]="(action.title | translate | async)!"
2325
+ [disabled]="action.disabled === true"
2326
+ (onClick)="executeAction(action)"
2327
+ >
2328
+ @if (action.icon) {
2329
+ <ax-prefix>
2330
+ <ax-icon>
2331
+ <i [class]="action.icon"></i>
2332
+ </ax-icon>
2333
+ </ax-prefix>
2334
+ }
2335
+ </ax-button-item>
2336
+ }
2337
+ </ax-button-item-list>
2338
+ </ax-dropdown-panel>
2339
+ </ax-button>
2340
+ }
2341
+ </div>
2342
+ </div>
2343
+ }
2344
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule, AXButtonModule, AXDecoratorModule, AXDropdownModule, AXTranslationModule], 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}@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"] }]
2345
+ }], ctorParameters: () => [], propDecorators: { task: [{ type: i0.Input, args: [{ isSignal: true, alias: "task", required: true }] }] } });
2346
+ /**
2347
+ * Resolves the popover component for a task: provider custom component or the default task popover.
2348
+ */
2349
+ async function resolveTaskPopoverComponent(task, taskBoardService) {
2350
+ const defaultType = AXMTaskBoardDefaultTaskPopoverComponent;
2351
+ if (!task.provider) {
2352
+ return defaultType;
2353
+ }
2354
+ const provider = taskBoardService.getProvider(task.provider);
2355
+ if (!provider) {
2356
+ return defaultType;
2357
+ }
2358
+ const componentGetter = provider.getComponent?.();
2359
+ if (!componentGetter) {
2360
+ return defaultType;
2361
+ }
2362
+ try {
2363
+ if (typeof componentGetter !== 'function') {
2364
+ return defaultType;
2365
+ }
2366
+ let result;
2367
+ try {
2368
+ result = componentGetter();
2369
+ }
2370
+ catch {
2371
+ return componentGetter;
2372
+ }
2373
+ if (result instanceof Promise) {
2374
+ return await result;
2375
+ }
2376
+ if (result) {
2377
+ return result;
2378
+ }
2379
+ return componentGetter;
2380
+ }
2381
+ catch (error) {
2382
+ console.error('[TaskPopover] Failed to load custom task popover component:', error);
2383
+ return defaultType;
2384
+ }
2385
+ }
2386
+
2387
+ /** Width for a single icon-only row action column. */
2388
+ const TASK_BOARD_GRID_ROW_ACTION_COLUMN_WIDTH = '70px';
2389
+ /** Icon-only command that opens the task details popover. */
2390
+ const TASK_BOARD_GRID_ROW_VIEW_ACTION = {
2391
+ name: 'view',
2392
+ text: '@task-management:task-board.grid-headers.action-view',
2393
+ icon: 'fa-light fa-ellipsis-vertical',
2394
+ iconOnly: true,
2395
+ look: 'blank',
2396
+ };
2397
+ const TASK_BOARD_GRID_ROW_ACTION_ITEMS = [TASK_BOARD_GRID_ROW_VIEW_ACTION];
2398
+
2399
+ function taskParamToDisplayString(translationService, value) {
2400
+ if (value == null) {
2401
+ return '';
2402
+ }
2403
+ if (typeof value === 'string') {
2404
+ return value.trim();
2405
+ }
2406
+ if (typeof value === 'number' || typeof value === 'boolean') {
2407
+ return String(value);
2408
+ }
2409
+ if (typeof value === 'object') {
2410
+ const resolved = translationService.resolve(value);
2411
+ return resolved?.trim() ?? '';
2412
+ }
2413
+ return String(value).trim();
2414
+ }
2415
+ const TASK_I18N_DATE_KEYS = new Set([
2416
+ 'startDate',
2417
+ 'endDate',
2418
+ 'effectiveDate',
2419
+ 'dueDate',
2420
+ 'occurredDate',
2421
+ ]);
2422
+ function isDateLikeString(value) {
2423
+ return /^\d{4}-\d{2}-\d{2}(?:T|$)/.test(value.trim());
2424
+ }
2425
+ function parseDateOnlyString(value) {
2426
+ const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value.trim());
2427
+ if (!match) {
2428
+ return null;
2429
+ }
2430
+ const year = Number(match[1]);
2431
+ const month = Number(match[2]);
2432
+ const day = Number(match[3]);
2433
+ if (!year || !month || !day) {
2434
+ return null;
2435
+ }
2436
+ return new Date(year, month - 1, day);
2437
+ }
2438
+ /** Resolves MLS and formats dates at display time so task labels react to locale changes. */
2439
+ async function formatTaskI18nParamsForTranslate(raw, translationService, formatService, settingsService) {
2440
+ if (!raw) {
2441
+ return {};
2442
+ }
2443
+ const format = await settingsService.get(AXPRegionalSetting.ShortDate);
2444
+ const params = {};
2445
+ for (const [key, value] of Object.entries(raw)) {
2446
+ if (value == null || value === '') {
2447
+ continue;
2448
+ }
2449
+ if (TASK_I18N_DATE_KEYS.has(key) &&
2450
+ (value instanceof Date || (typeof value === 'string' && isDateLikeString(value)))) {
2451
+ const date = value instanceof Date ? value : parseDateOnlyString(value) ?? new Date(value);
2452
+ if (!Number.isNaN(date.getTime())) {
2453
+ const formatted = formatService.format(date, 'date', { format });
2454
+ const text = typeof formatted === 'string' ? formatted : String(formatted ?? '');
2455
+ if (text) {
2456
+ params[key] = text;
2457
+ }
2458
+ }
2459
+ continue;
2460
+ }
2461
+ const text = taskParamToDisplayString(translationService, value);
2462
+ if (text) {
2463
+ params[key] = text;
2464
+ }
2465
+ }
2466
+ return params;
2467
+ }
2468
+ function getTaskTranslationKey(task, field) {
2469
+ const data = task.data;
2470
+ const key = field === 'title' ? data?.titleKey : data?.descriptionKey;
2471
+ if (key?.trim().startsWith('@')) {
2472
+ return key.trim();
2473
+ }
2474
+ const payloadKey = field === 'title' ? data?.payload?.title : data?.payload?.description;
2475
+ if (payloadKey?.trim().startsWith('@')) {
2476
+ return payloadKey.trim();
2477
+ }
2478
+ const fallback = field === 'title' ? task.title : task.description;
2479
+ return fallback?.trim().startsWith('@') ? fallback.trim() : undefined;
2480
+ }
2481
+ /** Resolves scheduler/kanban labels; popovers should use the translate pipe with {@link getTaskTranslationKey}. */
2482
+ async function resolveTaskBoardSchedulerLabels(tasks, translationService, formatService, settingsService) {
2483
+ return Promise.all(tasks.map(async (task) => {
2484
+ const titleKey = getTaskTranslationKey(task, 'title');
2485
+ const descriptionKey = getTaskTranslationKey(task, 'description');
2486
+ const rawParams = get(task.data, 'taskI18nParams');
2487
+ const params = await formatTaskI18nParamsForTranslate(rawParams, translationService, formatService, settingsService);
2488
+ return {
2489
+ ...task,
2490
+ ...(titleKey ? { title: await translationService.translateAsync(titleKey, { params }) } : {}),
2491
+ ...(descriptionKey
2492
+ ? { description: await translationService.translateAsync(descriptionKey, { params }) }
2493
+ : {}),
2494
+ };
2495
+ }));
2496
+ }
2497
+ function enrichTaskBoardTasks(tasks, colorBy, dueStatusProviderService) {
2498
+ return tasks.map((task) => {
2499
+ const withDueStatus = dueStatusProviderService.attachDueStatusToTask(task);
2500
+ const accentClass = buildTaskSurfaceCssClass(resolveTaskBoardAccentColor(withDueStatus, colorBy));
2501
+ const calendarPlacement = resolveTaskBoardCalendarPlacement(withDueStatus);
2502
+ return {
2503
+ ...withDueStatus,
2504
+ ...calendarPlacement,
2505
+ cssClass: accentClass,
2506
+ };
2507
+ });
2508
+ }
2509
+
2510
+ //#region ---- Imports ----
2511
+ //#endregion
2512
+ //#region ---- Component ----
2513
+ class AXMTaskBoardTaskPopoverShellComponent {
2514
+ constructor() {
2515
+ this.task = input.required(/* @ts-ignore */
2516
+ ...(ngDevMode ? [{ debugName: "task" }] : /* istanbul ignore next */ []));
2517
+ this.bodyComponent = input.required(/* @ts-ignore */
2518
+ ...(ngDevMode ? [{ debugName: "bodyComponent" }] : /* istanbul ignore next */ []));
2519
+ this.requestClose = input.required(/* @ts-ignore */
2520
+ ...(ngDevMode ? [{ debugName: "requestClose" }] : /* istanbul ignore next */ []));
2521
+ this.localeService = inject(AXLocaleService);
2522
+ this.formatService = inject(AXFormatService);
2523
+ this.settingsService = inject(AXPSettingsService);
2524
+ this.translationService = inject(AXTranslationService);
2525
+ this.i18nParams = signal({}, /* @ts-ignore */
2526
+ ...(ngDevMode ? [{ debugName: "i18nParams" }] : /* istanbul ignore next */ []));
2527
+ this.titleKey = computed(() => getTaskTranslationKey(this.task(), 'title'), /* @ts-ignore */
2528
+ ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
2529
+ this.bodyInputs = computed(() => ({ task: this.task() }), /* @ts-ignore */
2530
+ ...(ngDevMode ? [{ debugName: "bodyInputs" }] : /* istanbul ignore next */ []));
2531
+ this.i18nParamsEffect = effect(() => {
2532
+ const task = this.task();
2533
+ void this.localeService.activeProfile().localeInfo?.code;
2534
+ const raw = get(task.data, 'taskI18nParams');
2535
+ void formatTaskI18nParamsForTranslate(raw, this.translationService, this.formatService, this.settingsService).then((params) => this.i18nParams.set(params));
2536
+ }, /* @ts-ignore */
2537
+ ...(ngDevMode ? [{ debugName: "i18nParamsEffect" }] : /* istanbul ignore next */ []));
2538
+ }
2539
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardTaskPopoverShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2540
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMTaskBoardTaskPopoverShellComponent, isStandalone: true, selector: "axm-task-board-task-popover-shell", inputs: { task: { classPropertyName: "task", publicName: "task", isSignal: true, isRequired: true, transformFunction: null }, bodyComponent: { classPropertyName: "bodyComponent", publicName: "bodyComponent", isSignal: true, isRequired: true, transformFunction: null }, requestClose: { classPropertyName: "requestClose", publicName: "requestClose", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2541
+ <div class="task-popover">
2542
+ <div class="task-popover__header">
2543
+ <div class="task-popover__title-section">
2544
+ <div class="task-popover__title-row">
2545
+ <h3 class="task-popover__title">
2546
+ @if (titleKey(); as key) {
2547
+ {{ key | translate: { params: i18nParams() } | async }}
2548
+ } @else {
2549
+ {{ task().title }}
2550
+ }
2551
+ </h3>
2552
+ <button type="button" class="task-popover__close-btn" (click)="requestClose()()" aria-label="Close">
2553
+ <ax-icon class="ax-icon ax-icon-close"></ax-icon>
2554
+ </button>
2555
+ </div>
2556
+ </div>
2557
+ </div>
2558
+ <div class="task-popover__body">
2559
+ @if (bodyComponent()) {
2560
+ <ng-container *ngComponentOutlet="bodyComponent()!; inputs: bodyInputs()" />
2561
+ }
2562
+ </div>
2563
+ </div>
2564
+ `, isInline: true, 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}@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: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2565
+ }
2566
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardTaskPopoverShellComponent, decorators: [{
2567
+ type: Component,
2568
+ args: [{ selector: 'axm-task-board-task-popover-shell', template: `
2569
+ <div class="task-popover">
2570
+ <div class="task-popover__header">
2571
+ <div class="task-popover__title-section">
2572
+ <div class="task-popover__title-row">
2573
+ <h3 class="task-popover__title">
2574
+ @if (titleKey(); as key) {
2575
+ {{ key | translate: { params: i18nParams() } | async }}
2576
+ } @else {
2577
+ {{ task().title }}
2578
+ }
2579
+ </h3>
2580
+ <button type="button" class="task-popover__close-btn" (click)="requestClose()()" aria-label="Close">
2581
+ <ax-icon class="ax-icon ax-icon-close"></ax-icon>
2582
+ </button>
2583
+ </div>
2584
+ </div>
2585
+ </div>
2586
+ <div class="task-popover__body">
2587
+ @if (bodyComponent()) {
2588
+ <ng-container *ngComponentOutlet="bodyComponent()!; inputs: bodyInputs()" />
2589
+ }
2590
+ </div>
2591
+ </div>
2592
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [CommonModule, AXDecoratorModule, AXTranslationModule], 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}@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"] }]
2593
+ }], propDecorators: { task: [{ type: i0.Input, args: [{ isSignal: true, alias: "task", required: true }] }], bodyComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyComponent", required: true }] }], requestClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestClose", required: true }] }] } });
2594
+
2595
+ //#endregion
2596
+ //#region ---- Service ----
2597
+ /**
2598
+ * Opens task-board task popovers via {@link AXOverlayService}, matching {@link AXPopoverComponent} anchor positioning.
2599
+ */
2600
+ class AXMTaskBoardTaskPopoverOverlayService {
2601
+ constructor() {
2602
+ //#region ---- Services & Dependencies ----
2603
+ this.overlayService = inject(AXOverlayService);
2604
+ this.taskBoardService = inject(AXPTaskBoardService);
2605
+ this.platform = inject(AXPlatform);
2606
+ this.document = inject(DOCUMENT);
2607
+ //#endregion
2608
+ //#region ---- Private Properties ----
2609
+ this.overlayRef = null;
2610
+ this.shellRef = null;
2611
+ this.activeTask = null;
2612
+ this.anchorElement = null;
2613
+ this.outsideClickHandler = null;
2614
+ this.keydownHandler = null;
2615
+ }
2616
+ //#endregion
2617
+ //#region ---- Public Methods ----
2618
+ getActiveTask() {
2619
+ return this.activeTask;
2620
+ }
2621
+ isOpen() {
2622
+ return this.overlayRef !== null;
2623
+ }
2624
+ async open(options) {
2625
+ const anchorElement = this.resolveAnchorElement(options.anchor);
2626
+ if (!anchorElement) {
2627
+ return;
2628
+ }
2629
+ this.close();
2630
+ const bodyComponent = await resolveTaskPopoverComponent(options.task, this.taskBoardService);
2631
+ this.activeTask = options.task;
2632
+ this.anchorElement = anchorElement;
2633
+ const panelClasses = ['animate-fadeIn', 'animate-faster'];
2634
+ if (this.platform.isDark()) {
2635
+ panelClasses.push('ax-dark');
2636
+ }
2637
+ const requestClose = () => {
2638
+ this.close();
2639
+ };
2640
+ try {
2641
+ const ref = await this.overlayService.create(AXMTaskBoardTaskPopoverShellComponent, {
2642
+ centered: false,
2643
+ width: 'fit-content',
2644
+ actionSheetStyle: options.adaptivityEnabled ?? true,
2645
+ panelClass: panelClasses,
2646
+ backdrop: {
2647
+ enabled: false,
2648
+ background: false,
2649
+ closeOnClick: false,
2650
+ },
2651
+ anchorOptions: {
2652
+ anchor: anchorElement,
2653
+ placement: options.placement ?? 'bottom-start',
2654
+ offsetX: options.offsetX ?? 8,
2655
+ offsetY: options.offsetY ?? 8,
2656
+ autoFlip: options.autoFlip ?? true,
2657
+ },
2658
+ inputs: {
2659
+ task: options.task,
2660
+ bodyComponent,
2661
+ requestClose,
2662
+ },
2663
+ onDispose: () => {
2664
+ this.handleOverlayDisposed();
2665
+ },
2666
+ });
2667
+ this.overlayRef = ref;
2668
+ this.shellRef = ref.instance;
2669
+ setTimeout(() => {
2670
+ ref.updatePosition();
2671
+ this.bindDismissHandlers(ref.overlayElement, anchorElement);
2672
+ }, 0);
2673
+ }
2674
+ catch (error) {
2675
+ console.error('[TaskBoardTaskPopover] Failed to open overlay:', error);
2676
+ this.handleOverlayDisposed();
2677
+ }
2678
+ }
2679
+ async refreshTask(task) {
2680
+ if (!this.overlayRef || !this.shellRef || !this.activeTask || this.activeTask.id !== task.id) {
2681
+ return;
2682
+ }
2683
+ const bodyComponent = await resolveTaskPopoverComponent(task, this.taskBoardService);
2684
+ this.activeTask = task;
2685
+ this.shellRef.setInput('task', task);
2686
+ this.shellRef.setInput('bodyComponent', bodyComponent);
2687
+ }
2688
+ close() {
2689
+ this.removeDismissHandlers();
2690
+ if (this.overlayRef) {
2691
+ this.overlayRef.dispose();
2692
+ this.overlayRef = null;
2693
+ }
2694
+ this.shellRef = null;
2695
+ this.activeTask = null;
2696
+ this.anchorElement = null;
2697
+ }
2698
+ //#endregion
2699
+ //#region ---- Utility Methods ----
2700
+ /**
2701
+ * Normalizes an anchor to a host {@link HTMLElement} for {@link AXOverlayService}.
2702
+ */
2703
+ resolveAnchorElement(anchor) {
2704
+ if (anchor instanceof ElementRef) {
2705
+ return anchor.nativeElement;
2706
+ }
2707
+ if (anchor instanceof HTMLElement) {
2708
+ return anchor;
2709
+ }
2710
+ const hostCandidate = anchor;
2711
+ if (typeof hostCandidate.getHostElement === 'function') {
2712
+ return hostCandidate.getHostElement();
2713
+ }
2714
+ return anchor;
2715
+ }
2716
+ /**
2717
+ * Resolves an overlay anchor from a grid command-column click.
2718
+ */
2719
+ resolveAnchorFromCommandClick(event) {
2720
+ if (event.htmlElement instanceof HTMLElement) {
2721
+ return event.htmlElement;
2722
+ }
2723
+ const nativeEvent = event.nativeEvent;
2724
+ if (nativeEvent instanceof MouseEvent) {
2725
+ return this.resolveAnchor(nativeEvent);
2726
+ }
2727
+ const nestedClick = nativeEvent;
2728
+ if (nestedClick?.htmlElement instanceof HTMLElement) {
2729
+ return nestedClick.htmlElement;
2730
+ }
2731
+ if (nestedClick?.component && typeof nestedClick.component.getHostElement === 'function') {
2732
+ return nestedClick.component.getHostElement();
2733
+ }
2734
+ return this.resolveAnchor(nestedClick?.nativeEvent);
2735
+ }
2736
+ /**
2737
+ * Resolves an overlay anchor from a native click (e.g. command button in a data table cell).
2738
+ */
2739
+ resolveAnchor(nativeEvent, htmlElement) {
2740
+ if (htmlElement) {
2741
+ return htmlElement;
2742
+ }
2743
+ const target = (nativeEvent?.currentTarget ?? nativeEvent?.target);
2744
+ if (!target) {
2745
+ return undefined;
2746
+ }
2747
+ const cell = target.closest?.('td');
2748
+ const cellTrigger = cell?.querySelector('ax-button, button');
2749
+ if (cellTrigger instanceof HTMLElement) {
2750
+ return cellTrigger;
2751
+ }
2752
+ const buttonHost = target.closest?.('ax-button');
2753
+ if (buttonHost instanceof HTMLElement) {
2754
+ return buttonHost;
2755
+ }
2756
+ let element = target;
2757
+ for (let depth = 0; element && depth < 12; depth++) {
2758
+ if (element.tagName.toLowerCase() === 'ax-button' && element instanceof HTMLElement) {
2759
+ return element;
2760
+ }
2761
+ element = element.parentElement;
2762
+ }
2763
+ return target instanceof HTMLElement ? target : undefined;
2764
+ }
2765
+ bindDismissHandlers(overlayElement, anchor) {
2766
+ this.removeDismissHandlers();
2767
+ this.keydownHandler = (event) => {
2768
+ if (event.key === 'Escape') {
2769
+ this.close();
2770
+ }
2771
+ };
2772
+ this.document.addEventListener('keydown', this.keydownHandler);
2773
+ this.outsideClickHandler = (event) => {
2774
+ const target = event.target;
2775
+ if (!target || !overlayElement) {
2776
+ return;
2777
+ }
2778
+ if (this.isInsideTaskPopoverInteractiveSurface(target, overlayElement, anchor)) {
2779
+ return;
2780
+ }
2781
+ this.close();
2782
+ };
2783
+ this.document.addEventListener('mousedown', this.outsideClickHandler);
2784
+ }
2785
+ /** Dropdown menus render outside the popover overlay; ignore those clicks when dismissing. */
2786
+ isInsideTaskPopoverInteractiveSurface(target, overlayElement, anchor) {
2787
+ if (anchor.contains(target) || overlayElement.contains(target)) {
2788
+ return true;
2789
+ }
2790
+ return Boolean(target.closest('ax-dropdown-panel, ax-button-item-list, ax-button-item, ax-dropdown-button, .ax-dropdown-panel'));
2791
+ }
2792
+ removeDismissHandlers() {
2793
+ if (this.keydownHandler) {
2794
+ this.document.removeEventListener('keydown', this.keydownHandler);
2795
+ this.keydownHandler = null;
2796
+ }
2797
+ if (this.outsideClickHandler) {
2798
+ this.document.removeEventListener('mousedown', this.outsideClickHandler);
2799
+ this.outsideClickHandler = null;
2800
+ }
2801
+ }
2802
+ handleOverlayDisposed() {
2803
+ this.removeDismissHandlers();
2804
+ this.overlayRef = null;
2805
+ this.shellRef = null;
2806
+ this.activeTask = null;
2807
+ this.anchorElement = null;
2808
+ }
2809
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardTaskPopoverOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2810
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardTaskPopoverOverlayService, providedIn: 'root' }); }
2811
+ }
2812
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardTaskPopoverOverlayService, decorators: [{
2813
+ type: Injectable,
2814
+ args: [{ providedIn: 'root' }]
2815
+ }] });
2816
+
2817
+ function createAppRoute() {
2818
+ const config = inject(AXP_ENTITY_CONFIG_TOKEN);
2819
+ return {
2820
+ path: ':app/TaskManagement/task-board',
2821
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
2822
+ data: { rootLayout: true },
2823
+ loadComponent: () => {
2824
+ return config.viewers.root();
2825
+ },
2826
+ children: [
2827
+ {
2828
+ path: '',
2829
+ loadComponent: () => import('./acorex-platform-module-task-management-client-task-board.page-8I8H2pFq.mjs').then((c) => c.AXMTaskBoardPage),
2830
+ },
2831
+ ],
2832
+ };
2833
+ }
2834
+ function routesFactory() {
2835
+ const routes = [createAppRoute()];
2836
+ return routes;
2837
+ }
2838
+ class AXPTaskBoardModule {
2839
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2840
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardModule, imports: [AXPRuntimeModule] }); }
2841
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardModule, providers: [
2842
+ provideCommandSetups([
2843
+ {
2844
+ key: AXPTaskManagementCommandKeys.TaskBoard.ResetFiltersAssignToCurrentUser,
2845
+ command: () => Promise.resolve().then(function () { return resetFiltersAssignToCurrentUser_command; }).then((c) => c.ResetTaskBoardFiltersAssignToCurrentUserCommand),
2846
+ },
2847
+ ]),
2848
+ {
2849
+ provide: AXP_HOME_PAGES,
2850
+ multi: true,
2851
+ useFactory: () => [
2852
+ {
2853
+ key: 'task-board',
2854
+ title: 'Task Board',
2855
+ route: createAppRoute(),
2856
+ },
2857
+ ],
2858
+ },
2859
+ {
2860
+ provide: ROUTES,
2861
+ multi: true,
2862
+ useFactory: routesFactory,
2863
+ },
2864
+ provideQuerySetups([
2865
+ {
2866
+ key: AXMTaskManagementQueryKeys.ListTaskTypes,
2867
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-types.query-CKLUPrBU.mjs').then((c) => c.AXMListTaskTypesQuery),
2868
+ },
2869
+ {
2870
+ key: AXMTaskManagementQueryKeys.TaskBoardAssignees,
2871
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-board-assignees.query-Ci8_D4Vs.mjs').then((c) => c.AXMListTaskBoardAssigneesQuery),
2872
+ },
2873
+ {
2874
+ key: AXMTaskManagementQueryKeys.TaskBoardReporters,
2875
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-board-reporters.query-C3NAYa9b.mjs').then((c) => c.AXMListTaskBoardReportersQuery),
2876
+ },
2877
+ ]),
2878
+ ], imports: [AXPRuntimeModule] }); }
2879
+ }
2880
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardModule, decorators: [{
2881
+ type: NgModule,
2882
+ args: [{
2883
+ imports: [AXPRuntimeModule],
2884
+ providers: [
2885
+ provideCommandSetups([
2886
+ {
2887
+ key: AXPTaskManagementCommandKeys.TaskBoard.ResetFiltersAssignToCurrentUser,
2888
+ command: () => Promise.resolve().then(function () { return resetFiltersAssignToCurrentUser_command; }).then((c) => c.ResetTaskBoardFiltersAssignToCurrentUserCommand),
2889
+ },
2890
+ ]),
2891
+ {
2892
+ provide: AXP_HOME_PAGES,
2893
+ multi: true,
2894
+ useFactory: () => [
2895
+ {
2896
+ key: 'task-board',
2897
+ title: 'Task Board',
2898
+ route: createAppRoute(),
2899
+ },
2900
+ ],
2901
+ },
2902
+ {
2903
+ provide: ROUTES,
2904
+ multi: true,
2905
+ useFactory: routesFactory,
2906
+ },
2907
+ provideQuerySetups([
2908
+ {
2909
+ key: AXMTaskManagementQueryKeys.ListTaskTypes,
2910
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-types.query-CKLUPrBU.mjs').then((c) => c.AXMListTaskTypesQuery),
2911
+ },
2912
+ {
2913
+ key: AXMTaskManagementQueryKeys.TaskBoardAssignees,
2914
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-board-assignees.query-Ci8_D4Vs.mjs').then((c) => c.AXMListTaskBoardAssigneesQuery),
2915
+ },
2916
+ {
2917
+ key: AXMTaskManagementQueryKeys.TaskBoardReporters,
2918
+ loader: () => import('./acorex-platform-module-task-management-client-list-task-board-reporters.query-C3NAYa9b.mjs').then((c) => c.AXMListTaskBoardReportersQuery),
2919
+ },
2920
+ ]),
2921
+ ],
2922
+ }]
2923
+ }] });
2924
+
2925
+ /**
2926
+ * Feeds the platform task board from persisted {@link TaskManagement.Task} rows.
2927
+ */
2928
+ class AXPTaskManagementEntityTaskProvider extends AXPWorkflowTaskProvider {
2929
+ constructor() {
2930
+ super(...arguments);
2931
+ this.entityService = inject(AXPEntityService);
2932
+ this.calendarService = inject(AXCalendarService);
2933
+ this.dueStatusProviderService = inject(AXPDueStatusProviderService);
2934
+ }
2935
+ getTitle() {
2936
+ return Promise.resolve('Tasks');
2937
+ }
2938
+ getIcon() {
2939
+ return Promise.resolve('fa-light fa-clipboard-check');
2940
+ }
2941
+ get name() {
2942
+ return 'task-management.tasks';
2943
+ }
2944
+ async getTasks(options) {
2945
+ const filters = [
2946
+ { field: 'isArchived', operator: { type: 'equal' }, value: false },
2947
+ ];
2948
+ if (options?.subjectRefTypes?.length) {
2949
+ filters.push({
2950
+ field: 'subjectRef.type',
2951
+ operator: { type: 'in' },
2952
+ value: options.subjectRefTypes,
2953
+ });
2954
+ }
2955
+ if (options?.subjectRefIds?.length) {
2956
+ filters.push({
2957
+ field: 'subjectRef.id',
2958
+ operator: { type: 'in' },
2959
+ value: options.subjectRefIds,
2960
+ });
2961
+ }
2962
+ const requiresClientSideFiltering = Boolean(options?.range?.from && options.range.end) ||
2963
+ (options?.priorities?.length ?? 0) > 0 ||
2964
+ (options?.dueStatusKeys?.length ?? 0) > 0;
2965
+ const page = await this.entityService
2966
+ .withEntity(RootConfig.module.name, RootConfig.entities.task.name)
2967
+ .data()
2968
+ .query({
2969
+ skip: requiresClientSideFiltering ? 0 : (options?.skip ?? 0),
2970
+ take: requiresClientSideFiltering ? 1000 : Math.min(options?.take ?? 1000, 1000),
2971
+ filter: { logic: 'and', filters },
2972
+ });
2973
+ const rows = (page.items ?? []);
2974
+ let items = rows.map((row) => this.mapRowToTask(row));
2975
+ if (options?.range?.from && options.range.end) {
2976
+ items = filterTasksOverlappingRange(items, options.range);
2977
+ }
2978
+ if (options?.priorities?.length) {
2979
+ items = items.filter((task) => matchesTaskPriorityFilter(task, options.priorities));
2980
+ }
2981
+ items = items.map((task) => this.dueStatusProviderService.attachDueStatusToTask(task));
2982
+ if (options?.dueStatusKeys?.length) {
2983
+ items = items.filter((task) => matchesTaskDueStatusFilter(task, options.dueStatusKeys));
2984
+ }
2985
+ const total = items.length;
2986
+ const skip = options?.skip ?? 0;
2987
+ const take = options?.take ?? total;
2988
+ return { items: items.slice(skip, skip + take), total };
2989
+ }
2990
+ async updateTasks(tasksToUpdate) {
2991
+ return tasksToUpdate;
2992
+ }
2993
+ async getStatuses() {
2994
+ return [];
2995
+ }
2996
+ async getExtraFields() {
2997
+ return [];
2998
+ }
2999
+ async executeCommand(command) {
3000
+ return command.name === 'Entity:OpenDetails'
3001
+ ? { success: true, kind: 'object', data: undefined }
3002
+ : { success: false };
3003
+ }
3004
+ async getActions(task) {
3005
+ if (!task?.id) {
3006
+ return [];
3007
+ }
3008
+ return [
3009
+ {
3010
+ name: 'Entity:OpenDetails',
3011
+ title: '@general:actions.view.title',
3012
+ icon: 'fa-light fa-eye',
3013
+ command: {
3014
+ name: 'Entity:OpenDetails',
3015
+ options: {
3016
+ entity: `${RootConfig.module.name}.${RootConfig.entities.task.name}`,
3017
+ id: task.id,
3018
+ },
3019
+ },
3020
+ },
3021
+ ];
3022
+ }
3023
+ //#region ---- Mapping ----
3024
+ mapRowToTask(row) {
3025
+ const startDate = row.startDate ?? row.dueDate ?? this.calendarService.create(new Date()).date;
3026
+ const endDate = row.dueDate ?? row.startDate ?? startDate;
3027
+ const statusTitle = defaultTo(get(row, 'status.title'), 'Unknown');
3028
+ const statusId = defaultTo(get(row, 'status.id'), row.statusId);
3029
+ return {
3030
+ id: row.id,
3031
+ index: row.order ?? 0,
3032
+ title: row.title,
3033
+ description: row.description,
3034
+ status: {
3035
+ id: statusId,
3036
+ title: statusTitle,
3037
+ color: get(row, 'status.color'),
3038
+ },
3039
+ reporter: { id: 'system', type: 'user', fullName: 'System' },
3040
+ startDate,
3041
+ endDate,
3042
+ priority: this.mapPriority(row.priority),
3043
+ progress: row.progress,
3044
+ tags: row.tags,
3045
+ provider: this.name,
3046
+ data: {
3047
+ taskTypeId: row.taskTypeId,
3048
+ subjectRef: row.subjectRef,
3049
+ entityId: row.id,
3050
+ },
3051
+ };
3052
+ }
3053
+ mapPriority(priority) {
3054
+ switch (priority) {
3055
+ case 'low':
3056
+ return 'low';
3057
+ case 'medium':
3058
+ return 'normal';
3059
+ case 'high':
3060
+ return 'high';
3061
+ case 'critical':
3062
+ return 'urgent';
3063
+ default:
3064
+ return undefined;
3065
+ }
3066
+ }
3067
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskManagementEntityTaskProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
3068
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskManagementEntityTaskProvider }); }
3069
+ }
3070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskManagementEntityTaskProvider, decorators: [{
3071
+ type: Injectable
3072
+ }] });
3073
+
3074
+ class AXPTaskBoardShortcutViewComponent extends AXPLayoutBaseWidgetComponent {
3075
+ constructor() {
3076
+ super(...arguments);
3077
+ this.taskBoardService = inject(AXPTaskBoardService);
3078
+ this.sessionService = inject(AXPSessionService);
3079
+ this.tasksCount = signal(0, /* @ts-ignore */
3080
+ ...(ngDevMode ? [{ debugName: "tasksCount" }] : /* istanbul ignore next */ []));
3081
+ this.assignee = computed(() => this.options()?.['assignee'] ?? [], /* @ts-ignore */
3082
+ ...(ngDevMode ? [{ debugName: "assignee" }] : /* istanbul ignore next */ []));
3083
+ this.priority = computed(() => this.options()?.['priority'] ?? [], /* @ts-ignore */
3084
+ ...(ngDevMode ? [{ debugName: "priority" }] : /* istanbul ignore next */ []));
3085
+ this.selectedTaskTypes = computed(() => this.options()?.['taskTypes'] ?? [], /* @ts-ignore */
3086
+ ...(ngDevMode ? [{ debugName: "selectedTaskTypes" }] : /* istanbul ignore next */ []));
3087
+ this.dateRange = computed(() => this.options()?.['dateRange'] ?? { title: 'This Month', name: 'this-month' }, /* @ts-ignore */
3088
+ ...(ngDevMode ? [{ debugName: "dateRange" }] : /* istanbul ignore next */ []));
3089
+ this.title = computed(() => {
3090
+ const types = this.selectedTaskTypes()
3091
+ .map((t) => t.title)
3092
+ .join(', ');
3093
+ return types === '' ? 'All Tasks' : types;
3094
+ }, /* @ts-ignore */
3095
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
3096
+ this.queryParams = computed(() => {
3097
+ const dateRange = this.dateRange().name;
3098
+ const assignee = normalizeTaskBoardAssigneeIds(this.sessionService, this.assignee().map((a) => a.id)).map(String);
3099
+ const priority = this.priority().map((p) => p.name);
3100
+ const type = this.selectedTaskTypes().map((t) => t.name);
3101
+ return { type, priority, assignee, dateRange };
3102
+ }, /* @ts-ignore */
3103
+ ...(ngDevMode ? [{ debugName: "queryParams" }] : /* istanbul ignore next */ []));
3104
+ this.#taskCounter = effect(async () => {
3105
+ const params = this.queryParams();
3106
+ const range = this.taskBoardService.rangeCalculator(params.dateRange);
3107
+ const options = {
3108
+ range,
3109
+ types: params.type,
3110
+ priorities: params.priority,
3111
+ assigneeIds: params.assignee,
3112
+ };
3113
+ this.taskBoardService.getTasks(options).then((t) => this.tasksCount.set(t.total));
3114
+ }, /* @ts-ignore */
3115
+ ...(ngDevMode ? [{ debugName: "#taskCounter" }] : /* istanbul ignore next */ []));
3116
+ }
3117
+ #taskCounter;
3118
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardShortcutViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
3119
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXPTaskBoardShortcutViewComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3 px-6 pt-3 pb-6 h-full\">\n <h1 class=\"text-start text-lg font-medium truncate h-9\">{{ title() | translate | async }}</h1>\n <div class=\"w-full h-full relative\">\n <div class=\"flex flex-col gap-2 text-start\">\n <h3 class=\"text-xl text-primary text-nowrap\">{{ dateRange().title | translate | async }}</h3>\n <h1 class=\"text-6xl font-bold\">{{ tasksCount() }}</h1>\n </div>\n <a\n [queryParams]=\"queryParams()\"\n class=\"absolute bottom-0 right-0\"\n [routerLink]=\"['/TaskManagement/task-board']\"\n >\n <ax-button [text]=\"'View all'\" [look]=\"'link'\" class=\"ax-sm\" [color]=\"'primary'\">\n <ax-suffix> <ax-icon class=\"fa-solid fa-external-link\"></ax-icon> </ax-suffix>\n </ax-button>\n </a>\n </div>\n</div>\n", styles: ["/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: 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: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3120
+ }
3121
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardShortcutViewComponent, decorators: [{
3122
+ type: Component,
3123
+ args: [{ imports: [
3124
+ CommonModule,
3125
+ AXButtonComponent,
3126
+ AXDecoratorIconComponent,
3127
+ AXDecoratorGenericComponent,
3128
+ RouterLink,
3129
+ AXTranslationModule,
3130
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-3 px-6 pt-3 pb-6 h-full\">\n <h1 class=\"text-start text-lg font-medium truncate h-9\">{{ title() | translate | async }}</h1>\n <div class=\"w-full h-full relative\">\n <div class=\"flex flex-col gap-2 text-start\">\n <h3 class=\"text-xl text-primary text-nowrap\">{{ dateRange().title | translate | async }}</h3>\n <h1 class=\"text-6xl font-bold\">{{ tasksCount() }}</h1>\n </div>\n <a\n [queryParams]=\"queryParams()\"\n class=\"absolute bottom-0 right-0\"\n [routerLink]=\"['/TaskManagement/task-board']\"\n >\n <ax-button [text]=\"'View all'\" [look]=\"'link'\" class=\"ax-sm\" [color]=\"'primary'\">\n <ax-suffix> <ax-icon class=\"fa-solid fa-external-link\"></ax-icon> </ax-suffix>\n </ax-button>\n </a>\n </div>\n</div>\n", styles: ["/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
3131
+ }] });
3132
+
3133
+ var taskBoardShortcut_component = /*#__PURE__*/Object.freeze({
3134
+ __proto__: null,
3135
+ AXPTaskBoardShortcutViewComponent: AXPTaskBoardShortcutViewComponent
3136
+ });
3137
+
3138
+ /**
3139
+ * Parent tab for task-management dashboard widgets in the widget gallery.
3140
+ */
3141
+ const AXP_TASK_MANAGEMENT_DASHBOARD_WIDGET_CATEGORY = {
3142
+ name: 'tasks',
3143
+ order: 4,
3144
+ title: '@task-management:dashboard-widgets.widget-picker-category.tasks',
3145
+ };
3146
+
3147
+ const SUB = '@task-management:dashboard-widgets.widget-picker-sub-categories';
3148
+ //#region ---- Task dashboard widget picker sub-categories ----
3149
+ const AXP_TASK_MANAGEMENT_DASHBOARD_SUB_SHORTCUT = {
3150
+ name: 'task-dashboard-shortcut',
3151
+ order: 1,
3152
+ title: `${SUB}.shortcut`,
3153
+ };
3154
+ const AXP_TASK_MANAGEMENT_DASHBOARD_SUB_BOARD = {
3155
+ name: 'task-dashboard-board',
3156
+ order: 2,
3157
+ title: `${SUB}.board`,
3158
+ };
3159
+ //#endregion
3160
+
3161
+ //#region ----- Imports -----
3162
+ //#endregion
3163
+ //#region ----- Task dashboard presets -----
3164
+ /** Task board list tile. */
3165
+ const AXP_TASK_BOARD_DIMENSIONS = defineDashboardWidgetDimensions({
3166
+ sm: [[1, 6]],
3167
+ md: [
3168
+ [3, 4],
3169
+ [4, 4],
3170
+ ],
3171
+ lg: [
3172
+ [3, 4],
3173
+ [4, 4],
3174
+ ],
3175
+ xl: [
3176
+ [3, 4],
3177
+ [4, 4],
3178
+ ],
3179
+ '2xl': [
3180
+ [3, 4],
3181
+ [4, 4],
3182
+ ],
3183
+ '3xl': [
3184
+ [3, 4],
3185
+ [4, 4],
3186
+ ],
3187
+ '4xl': [
3188
+ [3, 4],
3189
+ [4, 4],
3190
+ ],
3191
+ });
3192
+ /** Filtered task-board shortcut launcher. */
3193
+ const AXP_TASK_BOARD_SHORTCUT_DIMENSIONS = defineDashboardWidgetDimensions({
3194
+ sm: [[1, 2]],
3195
+ md: [
3196
+ [2, 2],
3197
+ [3, 2],
3198
+ ],
3199
+ lg: [
3200
+ [2, 2],
3201
+ [3, 2],
3202
+ ],
3203
+ xl: [
3204
+ [2, 2],
3205
+ [3, 2],
3206
+ ],
3207
+ '2xl': [
3208
+ [2, 2],
3209
+ [3, 2],
3210
+ ],
3211
+ '3xl': [
3212
+ [2, 2],
3213
+ [3, 2],
3214
+ ],
3215
+ '4xl': [
3216
+ [2, 2],
3217
+ [3, 2],
3218
+ ],
3219
+ });
3220
+ //#endregion
3221
+
3222
+ const VALID_PRIORITIES = [
3223
+ { title: 'Urgent', name: 'urgent' },
3224
+ { title: 'High', name: 'high' },
3225
+ { title: 'Normal', name: 'normal' },
3226
+ { title: 'Low', name: 'low' },
3227
+ { title: 'No Priority', name: 'no_priority' },
3228
+ ];
3229
+ const VALID_DATE_RANGES = [
3230
+ { title: 'Today', name: 'today' },
3231
+ { title: 'This Week', name: 'this-week' },
3232
+ { title: 'This Month', name: 'this-month' },
3233
+ ];
3234
+ const AXPTaskBoardShortcut = {
3235
+ name: RootConfig.widgets.taskBoardShortcut,
3236
+ title: '@dashboard:widgets.task-board-shortcut.widget-title',
3237
+ description: '@dashboard:widgets.task-board-shortcut.widget-description',
3238
+ categories: [AXP_TASK_MANAGEMENT_DASHBOARD_WIDGET_CATEGORY],
3239
+ subCategory: AXP_TASK_MANAGEMENT_DASHBOARD_SUB_SHORTCUT,
3240
+ groups: [AXPWidgetGroupEnum.DashboardWidget],
3241
+ type: 'dashboard',
3242
+ icon: 'fa-light fa-clipboard-list-check',
3243
+ properties: [
3244
+ cloneProperty(AXP_DATA_PATH_PROPERTY, { visible: false }),
3245
+ // ====== Task Type ======
3246
+ {
3247
+ order: 0,
3248
+ name: 'taskTypes',
3249
+ title: '@task-management:task-board.filter.task-type',
3250
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3251
+ schema: {
3252
+ dataType: 'object',
3253
+ interface: {
3254
+ name: 'taskTypes',
3255
+ path: 'options.taskTypes',
3256
+ type: AXPWidgetsCatalog.select,
3257
+ options: {
3258
+ multiple: true,
3259
+ textField: 'title',
3260
+ valueField: 'name',
3261
+ dataSource: AXMTaskManagementQueryKeys.ListTaskTypes,
3262
+ },
3263
+ },
3264
+ },
3265
+ visible: true,
3266
+ },
3267
+ // ====== Asigned To ======
3268
+ {
3269
+ order: 1,
3270
+ name: 'assignee',
3271
+ title: '@task-management:task-board.filter.assignee',
3272
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3273
+ schema: {
3274
+ dataType: 'object',
3275
+ interface: {
3276
+ name: 'assignee',
3277
+ path: 'options.assignee',
3278
+ type: AXPWidgetsCatalog.select,
3279
+ options: {
3280
+ multiple: true,
3281
+ valueField: 'id',
3282
+ allowSearch: true,
3283
+ dataSource: AXMTaskManagementQueryKeys.TaskBoardAssignees,
3284
+ textField: 'displayName',
3285
+ },
3286
+ },
3287
+ },
3288
+ visible: true,
3289
+ },
3290
+ // ====== Priority ======
3291
+ {
3292
+ order: 2,
3293
+ name: 'priority',
3294
+ title: '@task-management:task-board.filter.priority',
3295
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3296
+ schema: {
3297
+ dataType: 'object',
3298
+ interface: {
3299
+ name: 'priority',
3300
+ path: 'options.priority',
3301
+ type: AXPWidgetsCatalog.select,
3302
+ options: {
3303
+ multiple: true,
3304
+ textField: 'title',
3305
+ valueField: 'name',
3306
+ dataSource: VALID_PRIORITIES,
3307
+ },
3308
+ },
3309
+ },
3310
+ visible: true,
3311
+ },
3312
+ // ====== Date Range ======
3313
+ {
3314
+ order: 3,
3315
+ name: 'dateRange',
3316
+ title: '@task-management:task-board.filter.date-range',
3317
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3318
+ schema: {
3319
+ dataType: 'object',
3320
+ defaultValue: { title: 'This Month', name: 'this-month' },
3321
+ interface: {
3322
+ name: 'dateRange',
3323
+ path: 'options.dateRange',
3324
+ type: AXPWidgetsCatalog.select,
3325
+ options: {
3326
+ textField: 'title',
3327
+ valueField: 'name',
3328
+ dataSource: VALID_DATE_RANGES,
3329
+ },
3330
+ },
3331
+ },
3332
+ visible: true,
3333
+ },
3334
+ ],
3335
+ components: {
3336
+ view: {
3337
+ component: () => Promise.resolve().then(function () { return taskBoardShortcut_component; }).then((c) => c.AXPTaskBoardShortcutViewComponent),
3338
+ },
3339
+ },
3340
+ meta: {
3341
+ dimensions: AXP_TASK_BOARD_SHORTCUT_DIMENSIONS,
3342
+ },
3343
+ };
3344
+
3345
+ const NO_PRIORITY_KEY = 'no_priority';
3346
+ const PRIORITY_LABEL_KEYS = {
3347
+ urgent: '@task-management:task-board.priority.urgent',
3348
+ high: '@task-management:task-board.priority.high',
3349
+ normal: '@task-management:task-board.priority.normal',
3350
+ low: '@task-management:task-board.priority.low',
3351
+ no_priority: '@task-management:task-board.priority.no-priority',
3352
+ };
3353
+ class AXPTaskBoardWidgetViewComponent extends AXPLayoutBaseWidgetComponent {
3354
+ constructor() {
3355
+ super();
3356
+ this.taskClick = output();
3357
+ this.taskBoardService = inject(AXPTaskBoardService);
3358
+ this.taskPopoverOverlay = inject(AXMTaskBoardTaskPopoverOverlayService);
3359
+ this.dueStatusProviderService = inject(AXPDueStatusProviderService);
3360
+ this.settingsService = inject(AXPSettingsService);
3361
+ this.formatService = inject(AXFormatService);
3362
+ this.translationService = inject(AXTranslationService);
3363
+ this.localeService = inject(AXLocaleService);
3364
+ this.destroyRef = inject(DestroyRef);
3365
+ this.maxItems = computed(() => this.options()?.['maxItems'] ?? 10, /* @ts-ignore */
3366
+ ...(ngDevMode ? [{ debugName: "maxItems" }] : /* istanbul ignore next */ []));
3367
+ this.assignee = computed(() => this.options()?.['assignee'] ?? [], /* @ts-ignore */
3368
+ ...(ngDevMode ? [{ debugName: "assignee" }] : /* istanbul ignore next */ []));
3369
+ this.selectedTaskTypes = computed(() => {
3370
+ const raw = this.options()?.['taskTypes'] ?? this.options()?.['taskType'];
3371
+ if (!raw) {
3372
+ return [];
3373
+ }
3374
+ return Array.isArray(raw) ? raw : [raw];
3375
+ }, /* @ts-ignore */
3376
+ ...(ngDevMode ? [{ debugName: "selectedTaskTypes" }] : /* istanbul ignore next */ []));
3377
+ this.sortBy = computed(() => this.options()?.['sortBy'] ?? { title: 'Priority', name: 'priority' }, /* @ts-ignore */
3378
+ ...(ngDevMode ? [{ debugName: "sortBy" }] : /* istanbul ignore next */ []));
3379
+ this.widgetColorBy = computed(() => this.options()?.['colorBy'], /* @ts-ignore */
3380
+ ...(ngDevMode ? [{ debugName: "widgetColorBy" }] : /* istanbul ignore next */ []));
3381
+ this.globalColorBy = signal('dueDate', /* @ts-ignore */
3382
+ ...(ngDevMode ? [{ debugName: "globalColorBy" }] : /* istanbul ignore next */ []));
3383
+ this.allTasks = signal([], /* @ts-ignore */
3384
+ ...(ngDevMode ? [{ debugName: "allTasks" }] : /* istanbul ignore next */ []));
3385
+ this.selectedPriorities = signal(new Set([...AXPTASK_PRIORITIES, NO_PRIORITY_KEY]), /* @ts-ignore */
3386
+ ...(ngDevMode ? [{ debugName: "selectedPriorities" }] : /* istanbul ignore next */ []));
3387
+ this.selectedDueStatuses = signal(new Set(getDefaultDueStatusDefinitions().map((item) => item.name)), /* @ts-ignore */
3388
+ ...(ngDevMode ? [{ debugName: "selectedDueStatuses" }] : /* istanbul ignore next */ []));
3389
+ this.effectiveColorBy = computed(() => this.widgetColorBy() ?? this.globalColorBy(), /* @ts-ignore */
3390
+ ...(ngDevMode ? [{ debugName: "effectiveColorBy" }] : /* istanbul ignore next */ []));
3391
+ this.filteredTasks = computed(() => {
3392
+ const tasks = this.allTasks();
3393
+ const limit = this.maxItems();
3394
+ const selectedTypes = this.selectedTaskTypes();
3395
+ const selectedPrioritiesSet = this.selectedPriorities();
3396
+ const selectedDueStatusSet = this.selectedDueStatuses();
3397
+ const selectedTypeNames = new Set(selectedTypes.map((t) => t.name));
3398
+ const typeFiltered = selectedTypes.length > 0
3399
+ ? tasks.filter((task) => task.provider && selectedTypeNames.has(task.provider))
3400
+ : tasks;
3401
+ const priorityFiltered = typeFiltered.filter((task) => {
3402
+ const priorityKey = getTaskPriorityFilterKey(task);
3403
+ return selectedPrioritiesSet.has(priorityKey);
3404
+ });
3405
+ const dueStatusFiltered = priorityFiltered.filter((task) => matchesTaskDueStatusFilter(task, [...selectedDueStatusSet]));
3406
+ const sorted = this.sortTasks([...dueStatusFiltered]);
3407
+ return sorted.slice(0, limit);
3408
+ }, /* @ts-ignore */
3409
+ ...(ngDevMode ? [{ debugName: "filteredTasks" }] : /* istanbul ignore next */ []));
3410
+ this.priorityBadges = computed(() => {
3411
+ const tasks = this.getTypeFilteredTasks();
3412
+ const counts = [...AXPTASK_PRIORITIES, NO_PRIORITY_KEY].reduce((acc, key) => {
3413
+ acc[key] = 0;
3414
+ return acc;
3415
+ }, {});
3416
+ for (const task of tasks) {
3417
+ counts[getTaskPriorityFilterKey(task)]++;
3418
+ }
3419
+ const priorityBadges = AXPTASK_PRIORITIES.map((priority) => ({
3420
+ name: priority,
3421
+ label: PRIORITY_LABEL_KEYS[priority],
3422
+ count: counts[priority],
3423
+ color: getTaskPriorityColor(priority),
3424
+ }));
3425
+ return [
3426
+ ...priorityBadges,
3427
+ {
3428
+ name: NO_PRIORITY_KEY,
3429
+ label: PRIORITY_LABEL_KEYS[NO_PRIORITY_KEY],
3430
+ count: counts[NO_PRIORITY_KEY],
3431
+ color: 'secondary',
3432
+ },
3433
+ ].filter((badge) => badge.count > 0);
3434
+ }, /* @ts-ignore */
3435
+ ...(ngDevMode ? [{ debugName: "priorityBadges" }] : /* istanbul ignore next */ []));
3436
+ this.dueStatusBadges = computed(() => {
3437
+ const tasks = this.getTypeFilteredTasks();
3438
+ const definitions = getDefaultDueStatusDefinitions();
3439
+ const counts = definitions.reduce((acc, definition) => {
3440
+ acc[definition.name] = 0;
3441
+ return acc;
3442
+ }, {});
3443
+ for (const task of tasks) {
3444
+ const key = get(task.data, 'dueStatusKey');
3445
+ if (key && key in counts) {
3446
+ counts[key]++;
3447
+ }
3448
+ }
3449
+ return definitions
3450
+ .map((definition) => ({
3451
+ name: definition.name,
3452
+ label: definition.title,
3453
+ count: counts[definition.name],
3454
+ color: definition.color,
3455
+ }))
3456
+ .filter((badge) => badge.count > 0);
3457
+ }, /* @ts-ignore */
3458
+ ...(ngDevMode ? [{ debugName: "dueStatusBadges" }] : /* istanbul ignore next */ []));
3459
+ this.hasFilterToolbar = computed(() => this.priorityBadges().length > 0 || this.dueStatusBadges().length > 0, /* @ts-ignore */
3460
+ ...(ngDevMode ? [{ debugName: "hasFilterToolbar" }] : /* istanbul ignore next */ []));
3461
+ this.hasNarrowedFilters = computed(() => {
3462
+ const allPriorityCount = AXPTASK_PRIORITIES.length + 1;
3463
+ const allDueStatusCount = getDefaultDueStatusDefinitions().length;
3464
+ return (this.selectedPriorities().size < allPriorityCount ||
3465
+ this.selectedDueStatuses().size < allDueStatusCount);
3466
+ }, /* @ts-ignore */
3467
+ ...(ngDevMode ? [{ debugName: "hasNarrowedFilters" }] : /* istanbul ignore next */ []));
3468
+ this.#loadTasks = effect(() => {
3469
+ this.localeService.activeProfile().localeInfo?.code;
3470
+ this.maxItems();
3471
+ this.assignee();
3472
+ this.selectedTaskTypes();
3473
+ this.sortBy();
3474
+ void this.reloadTasks();
3475
+ }, /* @ts-ignore */
3476
+ ...(ngDevMode ? [{ debugName: "#loadTasks" }] : /* istanbul ignore next */ []));
3477
+ void this.settingsService
3478
+ .scope(AXPPlatformScope.User)
3479
+ .get(AXMTaskBoardSettings.ColorBy)
3480
+ .then((value) => {
3481
+ if (value) {
3482
+ this.globalColorBy.set(value);
3483
+ }
3484
+ });
3485
+ this.taskBoardService.refresh$
3486
+ .pipe(takeUntilDestroyed(this.destroyRef))
3487
+ .subscribe((data) => {
3488
+ void this.handleBoardRefresh(data);
3489
+ });
3490
+ this.taskBoardService.close$
3491
+ .pipe(takeUntilDestroyed(this.destroyRef))
3492
+ .subscribe((data) => {
3493
+ const active = this.taskPopoverOverlay.getActiveTask();
3494
+ if (!active?.provider) {
3495
+ return;
3496
+ }
3497
+ if (data.provider && data.provider !== active.provider) {
3498
+ return;
3499
+ }
3500
+ this.taskPopoverOverlay.close();
3501
+ });
3502
+ }
3503
+ #loadTasks;
3504
+ onPriorityFilterChange(priorityKey) {
3505
+ this.selectedPriorities.update((currentSet) => {
3506
+ const newSet = new Set(currentSet);
3507
+ if (newSet.has(priorityKey)) {
3508
+ newSet.delete(priorityKey);
3509
+ }
3510
+ else {
3511
+ newSet.add(priorityKey);
3512
+ }
3513
+ return newSet;
3514
+ });
3515
+ }
3516
+ onDueStatusFilterChange(dueStatusKey) {
3517
+ this.selectedDueStatuses.update((currentSet) => {
3518
+ const newSet = new Set(currentSet);
3519
+ if (newSet.has(dueStatusKey)) {
3520
+ newSet.delete(dueStatusKey);
3521
+ }
3522
+ else {
3523
+ newSet.add(dueStatusKey);
3524
+ }
3525
+ return newSet;
3526
+ });
3527
+ }
3528
+ isPriorityFilterActive(priorityKey) {
3529
+ return this.selectedPriorities().has(priorityKey);
3530
+ }
3531
+ isDueStatusFilterActive(dueStatusKey) {
3532
+ return this.selectedDueStatuses().has(dueStatusKey);
3533
+ }
3534
+ resetFilters() {
3535
+ this.selectedPriorities.set(new Set([...AXPTASK_PRIORITIES, NO_PRIORITY_KEY]));
3536
+ this.selectedDueStatuses.set(new Set(getDefaultDueStatusDefinitions().map((item) => item.name)));
3537
+ }
3538
+ async onTaskClick(task, event) {
3539
+ const anchor = event.currentTarget;
3540
+ if (anchor) {
3541
+ await this.taskPopoverOverlay.open({
3542
+ task,
3543
+ anchor,
3544
+ offsetX: 12,
3545
+ offsetY: 12,
3546
+ adaptivityEnabled: true,
3547
+ });
3548
+ }
3549
+ this.taskClick.emit(task);
3550
+ }
3551
+ getStatusColor(task) {
3552
+ return resolveTaskBoardStatusColor(task);
3553
+ }
3554
+ getAccentClass(task) {
3555
+ const colorToken = resolveTaskBoardAccentColor(task, this.effectiveColorBy());
3556
+ return buildTaskAccentSurfaceCssClass(colorToken);
3557
+ }
3558
+ hasExplicitDueDate(task) {
3559
+ return get(task.data, 'hasExplicitDueDate') === true;
3560
+ }
3561
+ getProviderTitle(task) {
3562
+ if (!task.provider) {
3563
+ return null;
3564
+ }
3565
+ return (this.taskBoardService.getTaskTypeOption(task.provider)?.title ?? null);
3566
+ }
3567
+ getProviderIcon(task) {
3568
+ if (!task.provider) {
3569
+ return 'fa-light fa-layer-group';
3570
+ }
3571
+ return (this.taskBoardService.getTaskTypeOption(task.provider)?.icon ??
3572
+ 'fa-light fa-layer-group');
3573
+ }
3574
+ getTypeFilteredTasks() {
3575
+ const tasks = this.allTasks();
3576
+ const selectedTypes = this.selectedTaskTypes();
3577
+ const selectedTypeNames = new Set(selectedTypes.map((t) => t.name));
3578
+ return selectedTypes.length > 0
3579
+ ? tasks.filter((task) => task.provider && selectedTypeNames.has(task.provider))
3580
+ : tasks;
3581
+ }
3582
+ async handleBoardRefresh(data) {
3583
+ const active = this.taskPopoverOverlay.getActiveTask();
3584
+ if (data?.provider &&
3585
+ active?.provider &&
3586
+ data.provider !== active.provider) {
3587
+ return;
3588
+ }
3589
+ this.taskBoardService.invalidateTaskCache();
3590
+ await this.reloadTasks();
3591
+ if (!active?.provider) {
3592
+ return;
3593
+ }
3594
+ const updated = this.allTasks().find((task) => task.id === active.id);
3595
+ if (updated) {
3596
+ await this.taskPopoverOverlay.refreshTask({
3597
+ ...updated,
3598
+ provider: active.provider,
3599
+ });
3600
+ return;
3601
+ }
3602
+ this.taskPopoverOverlay.close();
3603
+ }
3604
+ reloadTasks() {
3605
+ const limit = this.maxItems();
3606
+ const assignee = this.assignee();
3607
+ const selectedTypes = this.selectedTaskTypes();
3608
+ const typeNames = selectedTypes.map((t) => t.name);
3609
+ return this.taskBoardService
3610
+ .getTasks({
3611
+ take: limit,
3612
+ assigneeIds: assignee.map((a) => a.id),
3613
+ types: typeNames,
3614
+ })
3615
+ .then(async (tasks) => {
3616
+ const labeled = await resolveTaskBoardSchedulerLabels(tasks.items, this.translationService, this.formatService, this.settingsService);
3617
+ const enriched = labeled.map((task) => this.dueStatusProviderService.attachDueStatusToTask(task));
3618
+ this.allTasks.set(enriched);
3619
+ });
3620
+ }
3621
+ compareByPriority(a, b) {
3622
+ const priorityA = getTaskPriorityFilterKey(a);
3623
+ const priorityB = getTaskPriorityFilterKey(b);
3624
+ return TASK_PRIORITY_ORDER[priorityA] - TASK_PRIORITY_ORDER[priorityB];
3625
+ }
3626
+ compareByDueDate(a, b) {
3627
+ const now = Date.now();
3628
+ const dateA = this.hasExplicitDueDate(a) && a.endDate
3629
+ ? new Date(a.endDate).getTime()
3630
+ : null;
3631
+ const dateB = this.hasExplicitDueDate(b) && b.endDate
3632
+ ? new Date(b.endDate).getTime()
3633
+ : null;
3634
+ if (dateA === null && dateB === null) {
3635
+ return 0;
3636
+ }
3637
+ if (dateA === null) {
3638
+ return 1;
3639
+ }
3640
+ if (dateB === null) {
3641
+ return -1;
3642
+ }
3643
+ return dateA - dateB;
3644
+ }
3645
+ sortTasks(tasks) {
3646
+ const sortBy = this.sortBy().name;
3647
+ return tasks.sort((a, b) => {
3648
+ if (sortBy === 'priority') {
3649
+ const priorityCompare = this.compareByPriority(a, b);
3650
+ if (priorityCompare !== 0) {
3651
+ return priorityCompare;
3652
+ }
3653
+ return this.compareByDueDate(a, b);
3654
+ }
3655
+ const dateCompare = this.compareByDueDate(a, b);
3656
+ if (dateCompare !== 0) {
3657
+ return dateCompare;
3658
+ }
3659
+ return this.compareByPriority(a, b);
3660
+ });
3661
+ }
3662
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardWidgetViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3663
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPTaskBoardWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { taskClick: "taskClick" }, usesInheritance: true, ngImport: i0, template: "<div class=\"size-full p-3 flex flex-col gap-3\">\n @if (hasFilterToolbar()) {\n <section class=\"__filters\">\n @if (priorityBadges().length > 0) {\n <div class=\"__filter-group\">\n <span class=\"__filter-label\">\n {{\n '@task-management:task-board.filter.priority' | translate | async\n }}\n </span>\n <div class=\"__filter-chips\">\n @for (badge of priorityBadges(); track badge.name) {\n <ax-chips\n [color]=\"badge.color\"\n class=\"filter-badge ax-sm\"\n [class.--active]=\"isPriorityFilterActive(badge.name)\"\n [text]=\"\n ((badge.label | translate | async) ?? '') +\n ' (' +\n badge.count +\n ')'\n \"\n (click)=\"onPriorityFilterChange(badge.name)\"\n [look]=\"\n isPriorityFilterActive(badge.name) ? 'solid' : 'outline'\n \"\n ></ax-chips>\n }\n </div>\n </div>\n }\n\n @if (dueStatusBadges().length > 0) {\n <div class=\"__filter-group\">\n <span class=\"__filter-label\">\n {{\n '@task-management:task-board.filter.due-status'\n | translate\n | async\n }}\n </span>\n <div class=\"__filter-chips\">\n @for (badge of dueStatusBadges(); track badge.name) {\n <ax-chips\n [color]=\"badge.color\"\n class=\"filter-badge ax-sm\"\n [class.--active]=\"isDueStatusFilterActive(badge.name)\"\n [text]=\"\n ((badge.label | translate | async) ?? '') +\n ' (' +\n badge.count +\n ')'\n \"\n (click)=\"onDueStatusFilterChange(badge.name)\"\n [look]=\"\n isDueStatusFilterActive(badge.name) ? 'solid' : 'outline'\n \"\n ></ax-chips>\n }\n </div>\n </div>\n }\n </section>\n\n @if (hasNarrowedFilters()) {\n <button type=\"button\" class=\"__filter-reset\" (click)=\"resetFilters()\">\n <i class=\"fa-light fa-arrow-rotate-left\" aria-hidden=\"true\"></i>\n <span>{{ '@acorex:common.general.clear' | translate | async }}</span>\n </button>\n }\n }\n\n <div class=\"overflow-auto overflow-x-hidden grow min-h-0\">\n <div class=\"__list\">\n @for (task of filteredTasks(); track task.id) {\n <div class=\"__item\" (click)=\"onTaskClick(task, $event)\">\n <div\n class=\"__accent\"\n [ngClass]=\"getAccentClass(task)\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"__content\">\n <div class=\"__header\">\n <p class=\"__title\" [title]=\"task.title\">{{ task.title }}</p>\n\n @if (task.status.title) {\n <ax-chips\n class=\"__status-chip ax-sm\"\n [look]=\"'twotone'\"\n [color]=\"getStatusColor(task)\"\n [text]=\"(task.status.title | translate | async)!\"\n ></ax-chips>\n }\n </div>\n\n <div class=\"__meta\">\n @if (getProviderTitle(task); as providerTitle) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.type'\n | translate\n | async\n \"\n >\n <i [class]=\"getProviderIcon(task)\" aria-hidden=\"true\"></i>\n <span>{{ providerTitle | translate | async }}</span>\n </span>\n }\n\n @if (hasExplicitDueDate(task) && task.endDate) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.due-date'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-calendar\" aria-hidden=\"true\"></i>\n <span>{{\n task.endDate\n | format: 'datetime' : { format: 'datetime' }\n | async\n }}</span>\n </span>\n } @else if (task.createdAt) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.received'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-inbox\" aria-hidden=\"true\"></i>\n <span>{{\n task.createdAt\n | format: 'datetime' : { format: 'datetime' }\n | async\n }}</span>\n </span>\n }\n\n @if (task.reporter.fullName) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.reporter'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-user-pen\" aria-hidden=\"true\"></i>\n <span>{{ task.reporter.fullName }}</span>\n </span>\n } @else if (task.assignee; as assignee) {\n @if (assignee.fullName) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.assignee'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-user\" aria-hidden=\"true\"></i>\n <span>{{ assignee.fullName }}</span>\n </span>\n }\n }\n </div>\n </div>\n </div>\n } @empty {\n <axp-state-message\n icon=\"fa-light fa-clipboard-list-check\"\n [title]=\"'@dashboard:task-board.no-tasks-found.title'\"\n [description]=\"'@dashboard:task-board.no-tasks-found.description'\"\n ></axp-state-message>\n }\n </div>\n </div>\n</div>\n", styles: ["@layer properties;:host{display:block;width:100%;height:100%}:host .__accent.axm-task-board-accent--primary{background-color:rgba(var(--ax-sys-color-primary-surface))}:host .__accent.axm-task-board-accent--secondary{background-color:rgba(var(--ax-sys-color-secondary-surface))}:host .__accent.axm-task-board-accent--success{background-color:rgba(var(--ax-sys-color-success-surface))}:host .__accent.axm-task-board-accent--warning{background-color:rgba(var(--ax-sys-color-warning-surface))}:host .__accent.axm-task-board-accent--danger{background-color:rgba(var(--ax-sys-color-danger-surface))}:host .__accent.axm-task-board-accent--accent1{background-color:rgba(var(--ax-sys-color-accent1-surface))}:host .__accent.axm-task-board-accent--accent2{background-color:rgba(var(--ax-sys-color-accent2-surface))}:host .__accent.axm-task-board-accent--accent3{background-color:rgba(var(--ax-sys-color-accent3-surface))}:host .__accent.axm-task-board-accent--neutral{background-color:var(--color-secondary-lightest);color:var(--color-on-secondary-lightest);border-color:var(--color-border-secondary-lightest)}:host .__accent.axm-task-board-accent--ghost{background-color:var(--color-secondary-lighter);color:var(--color-on-secondary-lighter);border-color:var(--color-border-secondary-lighter)}:host .__filters{display:flex;flex-shrink:0;gap:calc(var(--spacing, .25rem) * 2.5);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);border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 2.5)}:host .__filter-group{display:flex;min-width:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1.5)}:host .__filter-label{font-size:.6875rem;--tw-leading: 1;line-height:1;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);text-transform:uppercase;opacity:60%}:host .__filter-chips{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5)}:host .__filter-reset{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);align-self:flex-start;border-radius:var(--radius-md, .375rem);padding-inline:var(--spacing, .25rem);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);opacity:70%;transition-property:opacity;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s))}:host .__filter-reset:hover{text-decoration-line:underline;opacity:100%;text-underline-offset:2px}:host .__filter-reset i{font-size:.625rem}:host .__list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__list>.__item{animation:slideIn .25s ease forwards;opacity:0%}:host .__list>.__item:nth-child(1){animation-delay:.04s}:host .__list>.__item:nth-child(2){animation-delay:.08s}:host .__list>.__item:nth-child(3){animation-delay:.12s}:host .__list>.__item:nth-child(4){animation-delay:.16s}:host .__list>.__item:nth-child(5){animation-delay:.2s}:host .__list>.__item:nth-child(6){animation-delay:.24s}:host .__list>.__item:nth-child(7){animation-delay:.28s}:host .__list>.__item:nth-child(8){animation-delay:.32s}:host .__list>.__item:nth-child(9){animation-delay:.36s}:host .__list>.__item:nth-child(10){animation-delay:.4s}:host .__item{display:flex;cursor:pointer;align-items:stretch;overflow:hidden;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);border-color:rgba(var(--ax-sys-color-lightest-surface));text-align:start;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}:host .__item:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter);border-color:rgba(var(--ax-sys-color-surface));box-shadow:0 1px 4px rgba(var(--ax-sys-color-on-surface),.06)}:host .__item:active{background-color:var(--color-light);color:var(--color-on-light);border-color:var(--color-border-light)}:host .__item .__accent{width:3px;flex-shrink:0;background-color:rgba(var(--ax-sys-color-lighter-surface))}:host .__item .__content{min-width:0px;flex:1;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}:host .__item .__header{display:flex;align-items:flex-start;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 2)}:host .__item .__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__status-chip{max-width:45%;flex-shrink:0;pointer-events:none}:host .__item .__meta{margin-top:calc(var(--spacing, .25rem) * 1.5);display:flex;flex-wrap:wrap;align-items:center;column-gap:calc(var(--spacing, .25rem) * 3);row-gap:calc(var(--spacing, .25rem) * 1.5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);opacity:80%}:host .__item .__meta-item{display:inline-flex;max-width:100%;min-width:0px;align-items:center;gap:var(--spacing, .25rem)}:host .__item .__meta-item i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));opacity:.85}:host .__item .__meta-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .filter-badge{cursor:pointer;transition:transform .15s ease,opacity .15s ease,box-shadow .15s ease}:host .filter-badge:not(.--active){opacity:.78}:host .filter-badge:hover{transform:translateY(-1px);opacity:1}:host .filter-badge.--active{box-shadow:0 1px 2px rgba(var(--ax-sys-color-on-surface),.08)}@keyframes slideIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@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-leading: initial;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: AXChipsComponent, selector: "ax-chips", inputs: ["tabIndex", "color", "look", "text"], outputs: ["textChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: AXFormatPipe, name: "format" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3664
+ }
3665
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPTaskBoardWidgetViewComponent, decorators: [{
3666
+ type: Component,
3667
+ args: [{ imports: [
3668
+ CommonModule,
3669
+ AXFormatPipe,
3670
+ AXChipsComponent,
3671
+ AXTranslationModule,
3672
+ AXPStateMessageComponent,
3673
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"size-full p-3 flex flex-col gap-3\">\n @if (hasFilterToolbar()) {\n <section class=\"__filters\">\n @if (priorityBadges().length > 0) {\n <div class=\"__filter-group\">\n <span class=\"__filter-label\">\n {{\n '@task-management:task-board.filter.priority' | translate | async\n }}\n </span>\n <div class=\"__filter-chips\">\n @for (badge of priorityBadges(); track badge.name) {\n <ax-chips\n [color]=\"badge.color\"\n class=\"filter-badge ax-sm\"\n [class.--active]=\"isPriorityFilterActive(badge.name)\"\n [text]=\"\n ((badge.label | translate | async) ?? '') +\n ' (' +\n badge.count +\n ')'\n \"\n (click)=\"onPriorityFilterChange(badge.name)\"\n [look]=\"\n isPriorityFilterActive(badge.name) ? 'solid' : 'outline'\n \"\n ></ax-chips>\n }\n </div>\n </div>\n }\n\n @if (dueStatusBadges().length > 0) {\n <div class=\"__filter-group\">\n <span class=\"__filter-label\">\n {{\n '@task-management:task-board.filter.due-status'\n | translate\n | async\n }}\n </span>\n <div class=\"__filter-chips\">\n @for (badge of dueStatusBadges(); track badge.name) {\n <ax-chips\n [color]=\"badge.color\"\n class=\"filter-badge ax-sm\"\n [class.--active]=\"isDueStatusFilterActive(badge.name)\"\n [text]=\"\n ((badge.label | translate | async) ?? '') +\n ' (' +\n badge.count +\n ')'\n \"\n (click)=\"onDueStatusFilterChange(badge.name)\"\n [look]=\"\n isDueStatusFilterActive(badge.name) ? 'solid' : 'outline'\n \"\n ></ax-chips>\n }\n </div>\n </div>\n }\n </section>\n\n @if (hasNarrowedFilters()) {\n <button type=\"button\" class=\"__filter-reset\" (click)=\"resetFilters()\">\n <i class=\"fa-light fa-arrow-rotate-left\" aria-hidden=\"true\"></i>\n <span>{{ '@acorex:common.general.clear' | translate | async }}</span>\n </button>\n }\n }\n\n <div class=\"overflow-auto overflow-x-hidden grow min-h-0\">\n <div class=\"__list\">\n @for (task of filteredTasks(); track task.id) {\n <div class=\"__item\" (click)=\"onTaskClick(task, $event)\">\n <div\n class=\"__accent\"\n [ngClass]=\"getAccentClass(task)\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"__content\">\n <div class=\"__header\">\n <p class=\"__title\" [title]=\"task.title\">{{ task.title }}</p>\n\n @if (task.status.title) {\n <ax-chips\n class=\"__status-chip ax-sm\"\n [look]=\"'twotone'\"\n [color]=\"getStatusColor(task)\"\n [text]=\"(task.status.title | translate | async)!\"\n ></ax-chips>\n }\n </div>\n\n <div class=\"__meta\">\n @if (getProviderTitle(task); as providerTitle) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.type'\n | translate\n | async\n \"\n >\n <i [class]=\"getProviderIcon(task)\" aria-hidden=\"true\"></i>\n <span>{{ providerTitle | translate | async }}</span>\n </span>\n }\n\n @if (hasExplicitDueDate(task) && task.endDate) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.due-date'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-calendar\" aria-hidden=\"true\"></i>\n <span>{{\n task.endDate\n | format: 'datetime' : { format: 'datetime' }\n | async\n }}</span>\n </span>\n } @else if (task.createdAt) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.received'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-inbox\" aria-hidden=\"true\"></i>\n <span>{{\n task.createdAt\n | format: 'datetime' : { format: 'datetime' }\n | async\n }}</span>\n </span>\n }\n\n @if (task.reporter.fullName) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.reporter'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-user-pen\" aria-hidden=\"true\"></i>\n <span>{{ task.reporter.fullName }}</span>\n </span>\n } @else if (task.assignee; as assignee) {\n @if (assignee.fullName) {\n <span\n class=\"__meta-item\"\n [title]=\"\n '@task-management:task-board.grid-headers.assignee'\n | translate\n | async\n \"\n >\n <i class=\"fa-light fa-user\" aria-hidden=\"true\"></i>\n <span>{{ assignee.fullName }}</span>\n </span>\n }\n }\n </div>\n </div>\n </div>\n } @empty {\n <axp-state-message\n icon=\"fa-light fa-clipboard-list-check\"\n [title]=\"'@dashboard:task-board.no-tasks-found.title'\"\n [description]=\"'@dashboard:task-board.no-tasks-found.description'\"\n ></axp-state-message>\n }\n </div>\n </div>\n</div>\n", styles: ["@layer properties;:host{display:block;width:100%;height:100%}:host .__accent.axm-task-board-accent--primary{background-color:rgba(var(--ax-sys-color-primary-surface))}:host .__accent.axm-task-board-accent--secondary{background-color:rgba(var(--ax-sys-color-secondary-surface))}:host .__accent.axm-task-board-accent--success{background-color:rgba(var(--ax-sys-color-success-surface))}:host .__accent.axm-task-board-accent--warning{background-color:rgba(var(--ax-sys-color-warning-surface))}:host .__accent.axm-task-board-accent--danger{background-color:rgba(var(--ax-sys-color-danger-surface))}:host .__accent.axm-task-board-accent--accent1{background-color:rgba(var(--ax-sys-color-accent1-surface))}:host .__accent.axm-task-board-accent--accent2{background-color:rgba(var(--ax-sys-color-accent2-surface))}:host .__accent.axm-task-board-accent--accent3{background-color:rgba(var(--ax-sys-color-accent3-surface))}:host .__accent.axm-task-board-accent--neutral{background-color:var(--color-secondary-lightest);color:var(--color-on-secondary-lightest);border-color:var(--color-border-secondary-lightest)}:host .__accent.axm-task-board-accent--ghost{background-color:var(--color-secondary-lighter);color:var(--color-on-secondary-lighter);border-color:var(--color-border-secondary-lighter)}:host .__filters{display:flex;flex-shrink:0;gap:calc(var(--spacing, .25rem) * 2.5);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);border-color:rgba(var(--ax-sys-color-lightest-surface));padding:calc(var(--spacing, .25rem) * 2.5)}:host .__filter-group{display:flex;min-width:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 1.5)}:host .__filter-label{font-size:.6875rem;--tw-leading: 1;line-height:1;--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);text-transform:uppercase;opacity:60%}:host .__filter-chips{display:flex;flex-wrap:wrap;gap:calc(var(--spacing, .25rem) * 1.5)}:host .__filter-reset{display:inline-flex;align-items:center;gap:calc(var(--spacing, .25rem) * 1.5);align-self:flex-start;border-radius:var(--radius-md, .375rem);padding-inline:var(--spacing, .25rem);padding-block:calc(var(--spacing, .25rem) * .5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);opacity:70%;transition-property:opacity;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s))}:host .__filter-reset:hover{text-decoration-line:underline;opacity:100%;text-underline-offset:2px}:host .__filter-reset i{font-size:.625rem}:host .__list{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}:host .__list>.__item{animation:slideIn .25s ease forwards;opacity:0%}:host .__list>.__item:nth-child(1){animation-delay:.04s}:host .__list>.__item:nth-child(2){animation-delay:.08s}:host .__list>.__item:nth-child(3){animation-delay:.12s}:host .__list>.__item:nth-child(4){animation-delay:.16s}:host .__list>.__item:nth-child(5){animation-delay:.2s}:host .__list>.__item:nth-child(6){animation-delay:.24s}:host .__list>.__item:nth-child(7){animation-delay:.28s}:host .__list>.__item:nth-child(8){animation-delay:.32s}:host .__list>.__item:nth-child(9){animation-delay:.36s}:host .__list>.__item:nth-child(10){animation-delay:.4s}:host .__item{display:flex;cursor:pointer;align-items:stretch;overflow:hidden;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);border-color:rgba(var(--ax-sys-color-lightest-surface));text-align:start;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}:host .__item:hover{background-color:var(--color-lighter);color:var(--color-on-lighter);border-color:var(--color-border-lighter);border-color:rgba(var(--ax-sys-color-surface));box-shadow:0 1px 4px rgba(var(--ax-sys-color-on-surface),.06)}:host .__item:active{background-color:var(--color-light);color:var(--color-on-light);border-color:var(--color-border-light)}:host .__item .__accent{width:3px;flex-shrink:0;background-color:rgba(var(--ax-sys-color-lighter-surface))}:host .__item .__content{min-width:0px;flex:1;padding-inline:calc(var(--spacing, .25rem) * 3);padding-block:calc(var(--spacing, .25rem) * 2.5)}:host .__item .__header{display:flex;align-items:flex-start;justify-content:space-between;gap:calc(var(--spacing, .25rem) * 2)}:host .__item .__title{margin:0;min-width:0px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}:host .__item .__status-chip{max-width:45%;flex-shrink:0;pointer-events:none}:host .__item .__meta{margin-top:calc(var(--spacing, .25rem) * 1.5);display:flex;flex-wrap:wrap;align-items:center;column-gap:calc(var(--spacing, .25rem) * 3);row-gap:calc(var(--spacing, .25rem) * 1.5);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-tight, 1.25);line-height:var(--leading-tight, 1.25);opacity:80%}:host .__item .__meta-item{display:inline-flex;max-width:100%;min-width:0px;align-items:center;gap:var(--spacing, .25rem)}:host .__item .__meta-item i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));opacity:.85}:host .__item .__meta-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .filter-badge{cursor:pointer;transition:transform .15s ease,opacity .15s ease,box-shadow .15s ease}:host .filter-badge:not(.--active){opacity:.78}:host .filter-badge:hover{transform:translateY(-1px);opacity:1}:host .filter-badge.--active{box-shadow:0 1px 2px rgba(var(--ax-sys-color-on-surface),.08)}@keyframes slideIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@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-leading: initial;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
3674
+ }], ctorParameters: () => [], propDecorators: { taskClick: [{ type: i0.Output, args: ["taskClick"] }] } });
3675
+
3676
+ var taskBoardWidget_component = /*#__PURE__*/Object.freeze({
3677
+ __proto__: null,
3678
+ AXPTaskBoardWidgetViewComponent: AXPTaskBoardWidgetViewComponent
3679
+ });
3680
+
3681
+ const AXPTaskBoardWidget = {
3682
+ name: RootConfig.widgets.taskBoard,
3683
+ title: '@dashboard:widgets.task-board-widget.widget-title',
3684
+ description: '@dashboard:widgets.task-board-widget.widget-description',
3685
+ categories: [AXP_TASK_MANAGEMENT_DASHBOARD_WIDGET_CATEGORY],
3686
+ subCategory: AXP_TASK_MANAGEMENT_DASHBOARD_SUB_BOARD,
3687
+ groups: [AXPWidgetGroupEnum.DashboardWidget],
3688
+ type: 'dashboard',
3689
+ icon: 'fa-light fa-clipboard-list-check',
3690
+ properties: [
3691
+ cloneProperty(AXP_DATA_PATH_PROPERTY, { visible: false }),
3692
+ // ====== Title ======
3693
+ {
3694
+ order: 1,
3695
+ name: 'title',
3696
+ title: '@dashboard:task-board.task-board-title',
3697
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3698
+ schema: {
3699
+ dataType: 'string',
3700
+ defaultValue: 'Task Board',
3701
+ interface: {
3702
+ name: 'title',
3703
+ path: 'options.title',
3704
+ type: AXPWidgetsCatalog.text,
3705
+ options: {
3706
+ placeholder: '@dashboard:widgets.task-board-widget.title-placeholder',
3707
+ },
3708
+ triggers: [
3709
+ {
3710
+ event: `{{ events.context('options.taskTypes') }}`,
3711
+ action: `{{ widget.setValue(widget.find("taskTypes").output('item').title==='All' ? 'Task Board' : widget.find("taskTypes").output('item').title) }}`,
3712
+ },
3713
+ ],
3714
+ },
3715
+ },
3716
+ visible: true,
3717
+ },
3718
+ // ====== Task Type ======
3719
+ {
3720
+ order: 0,
3721
+ name: 'taskTypes',
3722
+ title: '@task-management:task-board.filter.task-type',
3723
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3724
+ schema: {
3725
+ dataType: 'object',
3726
+ interface: {
3727
+ name: 'taskTypes',
3728
+ path: 'options.taskTypes',
3729
+ type: AXPWidgetsCatalog.select,
3730
+ options: {
3731
+ multiple: true,
3732
+ textField: 'title',
3733
+ valueField: 'name',
3734
+ dataSource: AXMTaskManagementQueryKeys.ListTaskTypes,
3735
+ },
3736
+ },
3737
+ },
3738
+ visible: true,
3739
+ },
3740
+ // ====== Sort By ======
3741
+ {
3742
+ name: 'sortBy',
3743
+ title: '@dashboard:task-board.sort-by',
3744
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3745
+ schema: {
3746
+ dataType: 'object',
3747
+ defaultValue: { title: 'Priority', name: 'priority' },
3748
+ interface: {
3749
+ name: 'sortBy',
3750
+ path: 'options.sortBy',
3751
+ type: AXPWidgetsCatalog.select,
3752
+ options: {
3753
+ valueField: 'name',
3754
+ textField: 'title',
3755
+ dataSource: [
3756
+ { title: 'Priority', name: 'priority' },
3757
+ { title: 'Due Date', name: 'dueDate' },
3758
+ ],
3759
+ multiple: false,
3760
+ direction: 'horizontal',
3761
+ },
3762
+ },
3763
+ },
3764
+ visible: true,
3765
+ },
3766
+ // ====== Color By ======
3767
+ {
3768
+ name: 'colorBy',
3769
+ title: '@dashboard:task-board.color-by',
3770
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3771
+ schema: {
3772
+ dataType: 'string',
3773
+ interface: {
3774
+ name: 'colorBy',
3775
+ path: 'options.colorBy',
3776
+ type: AXPWidgetsCatalog.select,
3777
+ options: {
3778
+ valueField: 'name',
3779
+ textField: 'title',
3780
+ allowClear: true,
3781
+ dataSource: [
3782
+ { title: 'Due Date', name: 'dueDate' },
3783
+ { title: 'Priority', name: 'priority' },
3784
+ { title: 'Status', name: 'status' },
3785
+ ],
3786
+ },
3787
+ },
3788
+ },
3789
+ visible: true,
3790
+ },
3791
+ // ====== Display options ======
3792
+ {
3793
+ name: 'maxItems',
3794
+ title: '@dashboard:task-board.max-items',
3795
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3796
+ schema: {
3797
+ defaultValue: 10,
3798
+ dataType: 'number',
3799
+ interface: {
3800
+ name: 'maxItems',
3801
+ path: 'options.maxItems',
3802
+ type: AXPWidgetsCatalog.number,
3803
+ options: {
3804
+ minValue: 1,
3805
+ maxValue: 50,
3806
+ },
3807
+ },
3808
+ },
3809
+ visible: true,
3810
+ },
3811
+ // ====== Asigned To ======
3812
+ {
3813
+ name: 'assignee',
3814
+ title: '@dashboard:task-board.asigned-to',
3815
+ group: AXP_APPEARANCE_PROPERTY_GROUP,
3816
+ schema: {
3817
+ dataType: 'object',
3818
+ interface: {
3819
+ name: 'assignee',
3820
+ path: 'options.assignee',
3821
+ type: AXPWidgetsCatalog.select,
3822
+ options: {
3823
+ multiple: true,
3824
+ valueField: 'id',
3825
+ allowSearch: true,
3826
+ dataSource: AXMTaskManagementQueryKeys.TaskBoardAssignees,
3827
+ textField: 'displayName',
3828
+ },
3829
+ },
3830
+ },
3831
+ visible: true,
3832
+ },
3833
+ ],
3834
+ components: {
3835
+ view: {
3836
+ component: () => Promise.resolve().then(function () { return taskBoardWidget_component; }).then((c) => c.AXPTaskBoardWidgetViewComponent),
3837
+ },
3838
+ },
3839
+ meta: {
3840
+ dimensions: AXP_TASK_BOARD_DIMENSIONS,
3841
+ },
3842
+ };
3843
+
3844
+ //#endregion
3845
+ //#region ---- Task Management Widgets Provider ----
3846
+ const TASK_WIDGETS = [AXPTaskBoardWidget, AXPTaskBoardShortcut];
3847
+ class AXMTaskManagementWidgetsProvider {
3848
+ getWidgets() {
3849
+ return TASK_WIDGETS;
3850
+ }
3851
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementWidgetsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3852
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementWidgetsProvider }); }
3853
+ }
3854
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementWidgetsProvider, decorators: [{
3855
+ type: Injectable
3856
+ }] });
3857
+
3858
+ //#endregion
3859
+ //#region ---- Permission Definition Provider ----
3860
+ /**
3861
+ * Registers task capabilities into the shared Projects & Work group.
3862
+ */
3863
+ class AXMPermissionDefinitionProvider {
3864
+ async define(context) {
3865
+ const keys = AXPProjectsWorkPermissionKeys;
3866
+ const groups = AXPCommonPermissionGroupKeys;
3867
+ const P = '@common:projects-work.permissions';
3868
+ const G = '@common:permission-groups';
3869
+ context.addGroup(groups.ProjectsWork.Root, `${G}.projects-work.title`, `${G}.projects-work.description`, { order: 60 });
3870
+ context
3871
+ .addGroup(groups.ProjectsWork.Tasks, `${G}.projects-work-tasks.title`, `${G}.projects-work-tasks.description`, { parent: groups.ProjectsWork.Root, order: 20 })
3872
+ .addPermission(keys.MaintainTasks, `${P}.maintain-tasks.title`, `${P}.maintain-tasks.description`)
3873
+ .endPermission()
3874
+ .addPermission(keys.ViewAllTasks, `${P}.view-all-tasks.title`, `${P}.view-all-tasks.description`)
3875
+ .endPermission()
3876
+ .endGroup();
3877
+ }
3878
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3879
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider }); }
3880
+ }
3881
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, decorators: [{
3882
+ type: Injectable
3883
+ }] });
3884
+
3885
+ //#region ----- Imports -----
3886
+ //#endregion
3887
+ //#region ----- Constants -----
3888
+ /** Menu `badgeKey` for Task Board. */
3889
+ const AXM_TASK_BOARD_BADGE_KEY = 'task-management-task-board-badge';
3890
+ const TASK_MANAGEMENT_COMMAND_PREFIX = `${RootConfig.module.name}.`;
3891
+ const WORKFLOW_MANAGEMENT_COMMAND_PREFIX = 'WorkflowManagement.';
3892
+ //#endregion
3893
+ //#region ----- Task badge provider -----
3894
+ /**
3895
+ * Shows the current user's assigned task count on the Task Board menu item.
3896
+ *
3897
+ * On user switch: clears the badge immediately, invalidates task cache, and refetches.
3898
+ * In-flight counts from a previous user are ignored.
3899
+ *
3900
+ * Refreshes after:
3901
+ * - task-management / workflow-management commands
3902
+ * - entity CRUD for Task (`Entity:Insert:Default` etc.)
3903
+ * - realtime `entity.mutation` → layout refresh broadcast for Task, TaskAssignment, WorkflowInstance
3904
+ */
3905
+ class AXMTaskBoardBadgeProvider extends AXPTaskBadgeProvider {
3906
+ //#endregion
3907
+ constructor() {
3908
+ super();
3909
+ //#region ----- Services & Dependencies -----
3910
+ this.taskBoardService = inject(AXPTaskBoardService);
3911
+ this.sessionService = inject(AXPSessionService);
3912
+ this.commandEvents = inject(AXPCommandEventsService);
3913
+ this.entityBroadcast = inject(AXPEntityBroadcastService);
3914
+ this.eventService = inject(AXPBroadcastEventService);
3915
+ this.destroyRef = inject(DestroyRef);
3916
+ //#endregion
3917
+ //#region ----- Signals & Properties -----
3918
+ this.key = AXM_TASK_BOARD_BADGE_KEY;
3919
+ this.count = signal(0, /* @ts-ignore */
3920
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
3921
+ /** Bumped on every refresh so stale async results cannot overwrite a newer user/session. */
3922
+ this.refreshGeneration = 0;
3923
+ // User switch / login: update immediately (no debounce).
3924
+ this.sessionService.user$
3925
+ .pipe(distinctUntilChanged((prev, next) => prev?.id === next?.id), takeUntilDestroyed(this.destroyRef))
3926
+ .subscribe((user) => {
3927
+ // Clear stale badge from previous user right away.
3928
+ this.count.set(0);
3929
+ if (user?.id != null) {
3930
+ void this.refresh({ resetProviders: true });
3931
+ }
3932
+ });
3933
+ // After auth completes (login / user switch), refetch even if user id was already streamed.
3934
+ this.sessionService.status$
3935
+ .pipe(filter((status) => status === AXPSessionStatus.Authenticated), takeUntilDestroyed(this.destroyRef))
3936
+ .subscribe(() => {
3937
+ // Touch getter so storage-synced user switches emit on user$ when needed.
3938
+ const userId = this.sessionService.user?.id;
3939
+ if (userId != null) {
3940
+ void this.refresh({ resetProviders: true });
3941
+ }
3942
+ else {
3943
+ this.count.set(0);
3944
+ }
3945
+ });
3946
+ const unsubscribeCommands = this.commandEvents.listen((event) => {
3947
+ if (this.isRelevantCommandEvent(event)) {
3948
+ void this.handleCommandEvent(event);
3949
+ }
3950
+ });
3951
+ this.eventService
3952
+ .listen('task-board.refresh')
3953
+ .pipe(takeUntilDestroyed(this.destroyRef))
3954
+ .subscribe(() => {
3955
+ void this.refresh();
3956
+ });
3957
+ this.taskBoardService.refresh$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
3958
+ void this.refresh();
3959
+ });
3960
+ const layoutSub = this.entityBroadcast.listenLayoutRefresh().subscribe(({ data }) => {
3961
+ if (isTaskBoardRealtimeEntity(data?.name)) {
3962
+ void this.refresh();
3963
+ }
3964
+ });
3965
+ this.destroyRef.onDestroy(() => {
3966
+ unsubscribeCommands();
3967
+ layoutSub.unsubscribe();
3968
+ });
3969
+ }
3970
+ //#region ----- Badge actions -----
3971
+ async refresh(options) {
3972
+ const generation = ++this.refreshGeneration;
3973
+ const currentUserId = this.sessionService.user?.id;
3974
+ if (currentUserId == null) {
3975
+ if (generation === this.refreshGeneration) {
3976
+ this.count.set(0);
3977
+ }
3978
+ return;
3979
+ }
3980
+ this.taskBoardService.invalidateTaskCache();
3981
+ try {
3982
+ if (options?.resetProviders) {
3983
+ await this.taskBoardService.resolveProviders({ force: true });
3984
+ }
3985
+ // Do not pass a small `take`: multi-provider merge uses client-side length as total.
3986
+ const result = await this.taskBoardService.getTasks({
3987
+ assigneeIds: [currentUserId],
3988
+ });
3989
+ if (generation !== this.refreshGeneration) {
3990
+ return;
3991
+ }
3992
+ if (this.sessionService.user?.id !== currentUserId) {
3993
+ return;
3994
+ }
3995
+ this.count.set(result.total ?? 0);
3996
+ }
3997
+ catch {
3998
+ if (generation === this.refreshGeneration) {
3999
+ this.count.set(0);
4000
+ }
4001
+ }
4002
+ }
4003
+ isRelevantCommandEvent(event) {
4004
+ if (event.key.startsWith(TASK_MANAGEMENT_COMMAND_PREFIX) ||
4005
+ event.key.startsWith(WORKFLOW_MANAGEMENT_COMMAND_PREFIX)) {
4006
+ return true;
4007
+ }
4008
+ const input = event.input;
4009
+ const entity = get(input, 'entity');
4010
+ if (entity === TASK_BOARD_TASK_ENTITY) {
4011
+ return true;
4012
+ }
4013
+ const entityInfo = get(input, 'entityInfo');
4014
+ return (get(entityInfo, 'source') === TASK_BOARD_TASK_ENTITY ||
4015
+ (get(entityInfo, 'module') === RootConfig.module.name &&
4016
+ get(entityInfo, 'name') === RootConfig.entities.task.name));
4017
+ }
4018
+ async handleCommandEvent(event) {
4019
+ if (!event || event.stage !== 'after' || !event.success) {
4020
+ return;
4021
+ }
4022
+ await this.refresh();
4023
+ }
4024
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardBadgeProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4025
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardBadgeProvider, providedIn: 'root' }); }
4026
+ }
4027
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskBoardBadgeProvider, decorators: [{
4028
+ type: Injectable,
4029
+ args: [{ providedIn: 'root' }]
4030
+ }], ctorParameters: () => [] });
4031
+
4032
+ class AXMTaskManagementMenuProvider {
4033
+ constructor() {
4034
+ this.entityService = inject(AXPEntityService);
4035
+ this.sessionService = inject(AXPSessionService);
4036
+ this.settingsService = inject(AXPSettingsService);
4037
+ }
4038
+ async provide(context) {
4039
+ const module = RootConfig.module;
4040
+ const keys = AXPProjectsWorkPermissionKeys;
4041
+ const applicationName = this.sessionService.application?.name ?? '';
4042
+ const taskBoardPath = await this.resolveTaskBoardMenuPath(applicationName, module.name);
4043
+ context.addItems([
4044
+ {
4045
+ name: AXPTaskManagementMenuKeys.TaskBoard,
4046
+ priority: 0,
4047
+ text: '@task-management:task-board.page-title',
4048
+ type: 'menu',
4049
+ icon: 'fa-light fa-list-check',
4050
+ path: taskBoardPath,
4051
+ badgeKey: AXM_TASK_BOARD_BADGE_KEY,
4052
+ policy: {
4053
+ features: [module.name],
4054
+ permissions: [keys.MaintainTasks],
4055
+ },
4056
+ },
4057
+ // {
4058
+ // name: AXPTaskManagementMenuKeys.Cartable,
4059
+ // text: '@task-management:cartable.menu-title',
4060
+ // type: 'menu',
4061
+ // icon: 'fa-light fa-inbox',
4062
+ // path: `${this.sessionService.application?.name}/${RootConfig.module.name}/cartable`,
4063
+ // policy: {
4064
+ // features: [module.name],
4065
+ // },
4066
+ // },
4067
+ // {
4068
+ // name: AXPTaskManagementMenuKeys.Root,
4069
+ // priority: 9003,
4070
+ // text: '@task-management:root-menu',
4071
+ // icon: module.icon,
4072
+ // children: [
4073
+ // {
4074
+ // name: AXPTaskManagementMenuKeys.Tasks,
4075
+ // text: '@task-management:task.plural-title',
4076
+ // path: this.entityService.createPath(module.name, RootConfig.entities.task.name),
4077
+ // icon: RootConfig.entities.task.icon,
4078
+ // priority: 1,
4079
+ // },
4080
+ // {
4081
+ // name: AXPTaskManagementMenuKeys.TaskTemplates,
4082
+ // text: '@task-management:task-template.plural-title',
4083
+ // path: this.entityService.createPath(module.name, RootConfig.entities.taskTemplate.name),
4084
+ // icon: RootConfig.entities.taskTemplate.icon,
4085
+ // priority: 2,
4086
+ // },
4087
+ // {
4088
+ // text: ``,
4089
+ // type: 'break',
4090
+ // priority: 5,
4091
+ // },
4092
+ // {
4093
+ // name: AXPTaskManagementMenuKeys.TaskTypes,
4094
+ // text: '@task-management:task-type.plural-title',
4095
+ // path: this.entityService.createPath(module.name, RootConfig.entities.taskType.name),
4096
+ // icon: RootConfig.entities.taskType.icon,
4097
+ // priority: 6,
4098
+ // },
4099
+ // ],
4100
+ // },
4101
+ ]);
4102
+ context.find(AXPCommonMenuKeys.Projects).addItems([
4103
+ {
4104
+ name: `${AXPTaskManagementMenuKeys.TaskTypes}:divider`,
4105
+ text: '',
4106
+ type: 'break',
4107
+ priority: 5,
4108
+ },
4109
+ {
4110
+ name: AXPTaskManagementMenuKeys.TaskTypes,
4111
+ text: RootConfig.entities.taskType.titlePlural,
4112
+ icon: RootConfig.entities.taskType.icon,
4113
+ priority: 10,
4114
+ path: this.entityService.createPath(module.name, RootConfig.entities.taskType.name),
4115
+ policy: {
4116
+ features: [module.name],
4117
+ permissions: [keys.Setup],
4118
+ },
4119
+ },
4120
+ ]);
4121
+ }
4122
+ async resolveTaskBoardMenuPath(applicationName, moduleName) {
4123
+ const userSettings = this.settingsService.scope(AXPPlatformScope.User);
4124
+ const [viewMode, calendarLayout, timelineLayout, tableLayout] = await Promise.all([
4125
+ userSettings.get(AXMTaskBoardSettings.ViewMode),
4126
+ userSettings.get(AXMTaskBoardSettings.CalendarLayout),
4127
+ userSettings.get(AXMTaskBoardSettings.TimelineLayout),
4128
+ userSettings.get(AXMTaskBoardSettings.TableLayout),
4129
+ ]);
4130
+ return buildTaskBoardMenuPath(applicationName, moduleName, viewMode, {
4131
+ calendar: calendarLayout,
4132
+ timeline: timelineLayout,
4133
+ table: tableLayout,
4134
+ });
4135
+ }
4136
+ }
4137
+
4138
+ //#region ---- Imports ----
4139
+ //#endregion
4140
+ //#region ---- Settings Provider ----
4141
+ const I18N = `@${RootConfig.config.i18n}:settings`;
4142
+ class AXMSettingProvider {
4143
+ async provide(context) {
4144
+ context
4145
+ .addGroup('task-management', `${I18N}.task-management.group.nav-title`, `${I18N}.task-management.group.description`, RootConfig.module.icon, `${I18N}.task-management.group.title`, 90)
4146
+ .addSection('taskboard', `${I18N}.task-management.taskboard.section.title`, `${I18N}.task-management.taskboard.section.description`)
4147
+ .addSetting({
4148
+ key: AXMTaskBoardSettings.VisibleCalendar,
4149
+ title: `${I18N}.task-management.taskboard.parts.calendar.title`,
4150
+ scope: 'U',
4151
+ overridableAt: [],
4152
+ isInherited: true,
4153
+ defaultValue: true,
4154
+ widget: { type: AXPWidgetsList.Editors.ToggleSwitch },
4155
+ description: `${I18N}.task-management.taskboard.parts.calendar.description`,
4156
+ })
4157
+ .addSetting({
4158
+ key: AXMTaskBoardSettings.VisibleTimeline,
4159
+ title: `${I18N}.task-management.taskboard.parts.timeline.title`,
4160
+ scope: 'U',
4161
+ overridableAt: [],
4162
+ isInherited: true,
4163
+ defaultValue: true,
4164
+ widget: { type: AXPWidgetsList.Editors.ToggleSwitch },
4165
+ description: `${I18N}.task-management.taskboard.parts.timeline.description`,
4166
+ })
4167
+ .addSetting({
4168
+ key: AXMTaskBoardSettings.VisibleTable,
4169
+ title: `${I18N}.task-management.taskboard.parts.table.title`,
4170
+ scope: 'U',
4171
+ overridableAt: [],
4172
+ isInherited: true,
4173
+ defaultValue: true,
4174
+ widget: { type: AXPWidgetsList.Editors.ToggleSwitch },
4175
+ description: `${I18N}.task-management.taskboard.parts.table.description`,
4176
+ })
4177
+ .addSetting({
4178
+ key: AXMTaskBoardSettings.VisibleKanban,
4179
+ title: `${I18N}.task-management.taskboard.parts.kanban.title`,
4180
+ scope: 'U',
4181
+ overridableAt: [],
4182
+ isInherited: true,
4183
+ defaultValue: true,
4184
+ widget: { type: AXPWidgetsList.Editors.ToggleSwitch },
4185
+ description: `${I18N}.task-management.taskboard.parts.kanban.description`,
4186
+ })
4187
+ .addSetting({
4188
+ key: AXMTaskBoardSettings.ColorBy,
4189
+ title: `${I18N}.task-management.taskboard.color-by.title`,
4190
+ scope: 'U',
4191
+ overridableAt: [],
4192
+ isInherited: true,
4193
+ defaultValue: 'dueDate',
4194
+ widget: {
4195
+ type: AXPWidgetsList.Editors.SelectBox,
4196
+ options: {
4197
+ valueField: 'name',
4198
+ textField: 'title',
4199
+ dataSource: [
4200
+ { name: 'dueDate', title: `${I18N}.task-management.taskboard.color-by.due-date` },
4201
+ { name: 'priority', title: `${I18N}.task-management.taskboard.color-by.priority` },
4202
+ { name: 'status', title: `${I18N}.task-management.taskboard.color-by.status` },
4203
+ ],
4204
+ },
4205
+ },
4206
+ description: `${I18N}.task-management.taskboard.color-by.description`,
4207
+ })
4208
+ .endSection()
4209
+ .endGroup();
4210
+ }
4211
+ }
4212
+ //#endregion
4213
+
4214
+ class AXMSearchCommandProvider extends AXPSearchCommandProvider {
4215
+ constructor() {
4216
+ super(...arguments);
4217
+ this.commands = [
4218
+ // {
4219
+ // group: 'command',
4220
+ // title: 'New Form Template',
4221
+ // icon: RootConfig.entities.template.icon,
4222
+ // description: 'Create a new form template for designing reusable forms.', // Added description
4223
+ // commands: {
4224
+ // 'create-entity': {
4225
+ // entity: `${RootConfig.module.name}.${RootConfig.entities.template.name}`,
4226
+ // },
4227
+ // },
4228
+ // }
4229
+ ];
4230
+ }
4231
+ }
4232
+
4233
+ //#endregion
4234
+ //#region ---- Constants ----
4235
+ const WORKFLOW_DEFINITION = 'WorkflowManagement.WorkflowDefinition';
4236
+ //#endregion
4237
+ //#region ---- Provider ----
4238
+ class AXMTaskManagementCatalogScopeDefinitionProvider {
4239
+ async items() {
4240
+ return [
4241
+ {
4242
+ name: TASK_MANAGEMENT_CATALOG_SCOPE_TASKS,
4243
+ title: '@task-management:catalog-scopes.tasks.title',
4244
+ description: '@task-management:catalog-scopes.tasks.description',
4245
+ targets: [WORKFLOW_DEFINITION],
4246
+ icon: 'fa-light fa-clipboard-check',
4247
+ cssClass: 'primary-lighter-surface',
4248
+ order: 20,
4249
+ },
4250
+ ];
4251
+ }
4252
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementCatalogScopeDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4253
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementCatalogScopeDefinitionProvider }); }
4254
+ }
4255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementCatalogScopeDefinitionProvider, decorators: [{
4256
+ type: Injectable
4257
+ }] });
4258
+
4259
+ class AXMTaskFeatureModule {
4260
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskFeatureModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
4261
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskFeatureModule, imports: [CommonModule, AXPRuntimeModule] }); }
4262
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskFeatureModule, providers: [
4263
+ provideCommandSetups([
4264
+ {
4265
+ key: AXPTaskManagementCommandKeys.Task.StartFlow,
4266
+ command: () => import('./acorex-platform-module-task-management-client-start-task-flow.command-ChCuTFV-.mjs').then((c) => c.StartTaskFlowCommand),
4267
+ },
4268
+ ]),
4269
+ ], imports: [CommonModule, AXPRuntimeModule] }); }
4270
+ }
4271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskFeatureModule, decorators: [{
4272
+ type: NgModule,
4273
+ args: [{
4274
+ imports: [CommonModule, AXPRuntimeModule],
4275
+ providers: [
4276
+ provideCommandSetups([
4277
+ {
4278
+ key: AXPTaskManagementCommandKeys.Task.StartFlow,
4279
+ command: () => import('./acorex-platform-module-task-management-client-start-task-flow.command-ChCuTFV-.mjs').then((c) => c.StartTaskFlowCommand),
4280
+ },
4281
+ ]),
4282
+ ],
4283
+ }]
4284
+ }] });
4285
+
4286
+ class AXMTaskManagementModule {
4287
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
4288
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementModule, imports: [CommonModule,
4289
+ AXPTaskBoardModule,
4290
+ AXMCalendarManagementModule,
4291
+ AXPWidgetCoreModule,
4292
+ AXMTaskFeatureModule] }); }
4293
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementModule, providers: [
4294
+ {
4295
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
4296
+ useValue: TaskManagementManifest,
4297
+ multi: true,
4298
+ },
4299
+ {
4300
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
4301
+ useClass: AXMPermissionDefinitionProvider,
4302
+ multi: true,
4303
+ },
4304
+ {
4305
+ provide: AXP_MENU_PROVIDER,
4306
+ useClass: AXMTaskManagementMenuProvider,
4307
+ multi: true,
4308
+ },
4309
+ {
4310
+ // useExisting keeps a single root instance (avoids a second copy that never refreshes).
4311
+ provide: AXP_TASK_BADGE_PROVIDERS,
4312
+ useExisting: AXMTaskBoardBadgeProvider,
4313
+ multi: true,
4314
+ },
4315
+ {
4316
+ provide: AXP_SETTING_DEFINITION_PROVIDER,
4317
+ useClass: AXMSettingProvider,
4318
+ multi: true,
4319
+ },
4320
+ {
4321
+ provide: AXP_SEARCH_PROVIDER,
4322
+ useClass: AXMSearchCommandProvider,
4323
+ multi: true,
4324
+ },
4325
+ {
4326
+ provide: AXP_WIDGET_DEFINITION_PROVIDER,
4327
+ useClass: AXMTaskManagementWidgetsProvider,
4328
+ multi: true,
4329
+ },
4330
+ {
4331
+ provide: AXP_CATALOG_SCOPE_DEFINITION_PROVIDER,
4332
+ useClass: AXMTaskManagementCatalogScopeDefinitionProvider,
4333
+ multi: true,
4334
+ },
4335
+ {
4336
+ provide: AXP_WORKFLOW_TASK_PROVIDER,
4337
+ multi: true,
4338
+ useClass: AXPTaskManagementEntityTaskProvider,
4339
+ },
4340
+ ], imports: [CommonModule,
4341
+ AXPTaskBoardModule,
4342
+ AXMCalendarManagementModule,
4343
+ AXPWidgetCoreModule,
4344
+ AXMTaskFeatureModule] }); }
4345
+ }
4346
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMTaskManagementModule, decorators: [{
4347
+ type: NgModule,
4348
+ args: [{
4349
+ imports: [
4350
+ CommonModule,
4351
+ AXPTaskBoardModule,
4352
+ AXMCalendarManagementModule,
4353
+ AXPWidgetCoreModule,
4354
+ AXMTaskFeatureModule,
4355
+ ],
4356
+ providers: [
4357
+ {
4358
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
4359
+ useValue: TaskManagementManifest,
4360
+ multi: true,
4361
+ },
4362
+ {
4363
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
4364
+ useClass: AXMPermissionDefinitionProvider,
4365
+ multi: true,
4366
+ },
4367
+ {
4368
+ provide: AXP_MENU_PROVIDER,
4369
+ useClass: AXMTaskManagementMenuProvider,
4370
+ multi: true,
4371
+ },
4372
+ {
4373
+ // useExisting keeps a single root instance (avoids a second copy that never refreshes).
4374
+ provide: AXP_TASK_BADGE_PROVIDERS,
4375
+ useExisting: AXMTaskBoardBadgeProvider,
4376
+ multi: true,
4377
+ },
4378
+ {
4379
+ provide: AXP_SETTING_DEFINITION_PROVIDER,
4380
+ useClass: AXMSettingProvider,
4381
+ multi: true,
4382
+ },
4383
+ {
4384
+ provide: AXP_SEARCH_PROVIDER,
4385
+ useClass: AXMSearchCommandProvider,
4386
+ multi: true,
4387
+ },
4388
+ {
4389
+ provide: AXP_WIDGET_DEFINITION_PROVIDER,
4390
+ useClass: AXMTaskManagementWidgetsProvider,
4391
+ multi: true,
4392
+ },
4393
+ {
4394
+ provide: AXP_CATALOG_SCOPE_DEFINITION_PROVIDER,
4395
+ useClass: AXMTaskManagementCatalogScopeDefinitionProvider,
4396
+ multi: true,
4397
+ },
4398
+ {
4399
+ provide: AXP_WORKFLOW_TASK_PROVIDER,
4400
+ multi: true,
4401
+ useClass: AXPTaskManagementEntityTaskProvider,
4402
+ },
4403
+ ],
4404
+ }]
4405
+ }] });
4406
+
4407
+ //#region ----- Imports -----
4408
+ //#endregion
4409
+ //#region ----- Builders -----
4410
+ function buildTaskBoardWidget(id, x, y, size = [6, 4], title = 'Tasks') {
4411
+ const { config, meta } = layoutFromDimensions(id, AXP_TASK_BOARD_DIMENSIONS, { x, y, lg: size });
4412
+ return {
4413
+ config: { ...config, title },
4414
+ node: {
4415
+ type: 'TaskManagement:Widget:Dashboard:TaskBoard',
4416
+ options: { title, maxItems: 10 },
4417
+ meta,
4418
+ name: `TaskManagement:Widget:Dashboard:TaskBoard-${id}`,
4419
+ path: `TaskManagement:Widget:Dashboard:TaskBoard-${id}`,
4420
+ },
4421
+ };
4422
+ }
4423
+ function buildTaskBoardShortcutWidget(id, x, y, size = [3, 2]) {
4424
+ const { config, meta } = layoutFromDimensions(id, AXP_TASK_BOARD_SHORTCUT_DIMENSIONS, { x, y, lg: size });
4425
+ return {
4426
+ config,
4427
+ node: {
4428
+ type: 'TaskManagement:Widget:Dashboard:TaskBoardShortcut',
4429
+ options: {
4430
+ dateRange: { title: 'This Month', name: 'this-month' },
4431
+ },
4432
+ meta,
4433
+ name: `TaskManagement:Widget:Dashboard:TaskBoardShortcut-${id}`,
4434
+ path: `TaskManagement:Widget:Dashboard:TaskBoardShortcut-${id}`,
4435
+ },
4436
+ };
4437
+ }
4438
+ //#endregion
4439
+
4440
+ /**
4441
+ * Generated bundle index. Do not edit.
4442
+ */
4443
+
4444
+ export { buildTaskBoardShortcutWidget as $, AXMTaskBoardViewModel as A, AXM_TASK_VIEW_ALL_PERMISSION as B, AXPDueStatusProvider as C, AXPTASK_PRIORITIES as D, AXPTaskBoardModule as E, AXPTaskBoardShortcut as F, AXPTaskBoardShortcutViewComponent as G, AXPTaskBoardWidget as H, AXPTaskBoardWidgetViewComponent as I, AXPTaskManagementEntityTaskProvider as J, AXPTaskPopoverComponent as K, AXPWorkflowTaskProvider as L, AXP_DUE_STATUS_PROVIDERS as M, AXP_TASK_BOARD_DIMENSIONS as N, AXP_TASK_BOARD_SHORTCUT_DIMENSIONS as O, AXP_TASK_MANAGEMENT_DASHBOARD_SUB_BOARD as P, AXP_TASK_MANAGEMENT_DASHBOARD_SUB_SHORTCUT as Q, AXP_TASK_MANAGEMENT_DASHBOARD_WIDGET_CATEGORY as R, AXP_WORKFLOW_TASK_PROVIDER as S, TASK_BOARD_GRID_ROW_ACTION_COLUMN_WIDTH as T, AXP_WORKFLOW_TASK_PROVIDER_RESOLVER as U, RESET_TASK_BOARD_FILTERS_ASSIGN_TO_CURRENT_USER_COMMAND_KEY as V, ResetTaskBoardFiltersAssignToCurrentUserCommand as W, TASK_BOARD_GRID_ROW_VIEW_ACTION as X, TASK_PRIORITY_ORDER as Y, buildTaskAccentCssClass as Z, buildTaskAccentSurfaceCssClass as _, AXPTaskBoardService as a, buildTaskBoardWidget as a0, buildTaskSurfaceCssClass as a1, formatTaskI18nParamsForTranslate as a2, getDefaultDueStatusDefinitions as a3, getTaskDueStatusKey as a4, getTaskPriorityColor as a5, getTaskPriorityFilterKey as a6, getTaskTranslationKey as a7, hasTaskBoardSavedAssigneePreference as a8, mapEntityStatusColorForBadge as a9, matchesTaskBoardAssigneeFilter as aa, matchesTaskBoardAssigneeUserIdsFilter as ab, matchesTaskDueStatusFilter as ac, matchesTaskPriorityFilter as ad, normalizePriorityFilterKey as ae, normalizePriorityFilterKeys as af, normalizeTaskPriority as ag, resolveDefaultTaskStatusColor as ah, resolveDueStatusFromDefinitions as ai, resolveTaskBoardAccentColor as aj, resolveTaskBoardCalendarPlacement as ak, resolveTaskBoardSavedAssigneeIds as al, resolveTaskBoardSchedulingRange as am, resolveTaskBoardStatusColor as an, resolveTaskPopoverComponent as ao, taskOverlapsFilterRange as ap, AXMTaskBoardTaskPopoverOverlayService as b, TASK_BOARD_GRID_ROW_ACTION_ITEMS as c, AXPDueStatusProviderService as d, canViewAllTaskAssignees as e, filterTasksOverlappingRange as f, getTaskBoardLayoutOptions as g, normalizeTaskTypeFilterNames as h, enrichTaskBoardTasks as i, isTaskBoardRangeFilterCategory as j, deriveTaskBoardDateRangeFromNavigator as k, applyTaskBoardDateRangeToViewModel as l, resolveTaskBoardCategoryLayoutFromQuery as m, normalizeTaskBoardAssigneeIds as n, resolveTaskBoardCurrentUserAssigneeItem as o, AXM_TASK_BOARD_UNASSIGNED_ASSIGNEE_ID as p, isTaskBoardUnassignedAssigneeId as q, resolveTaskBoardSchedulerLabels as r, AXMDefaultDueStatusProvider as s, AXMSearchCommandProvider as t, usesTaskBoardToolbarNavigator as u, AXMSettingProvider as v, AXMTaskBoardDefaultTaskPopoverComponent as w, AXMTaskBoardTaskPopoverShellComponent as x, AXMTaskManagementMenuProvider as y, AXMTaskManagementModule as z };
4445
+ //# sourceMappingURL=acorex-platform-module-task-management-client-acorex-platform-module-task-management-client-CIwafWli.mjs.map