@flusys/ng-task-manager 9.1.2 → 9.2.1
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.
- package/README.md +15 -0
- package/fesm2022/{flusys-ng-task-manager-board-list-page.component-Dmay3IyF.mjs → flusys-ng-task-manager-board-list-page.component-BAkiTEpy.mjs} +2 -2
- package/fesm2022/{flusys-ng-task-manager-board-list-page.component-Dmay3IyF.mjs.map → flusys-ng-task-manager-board-list-page.component-BAkiTEpy.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-task-manager-board-view-page.component-CkC8-UKF.mjs → flusys-ng-task-manager-board-view-page.component-9ZrU7hON.mjs} +18 -21
- package/fesm2022/flusys-ng-task-manager-board-view-page.component-9ZrU7hON.mjs.map +1 -0
- package/fesm2022/flusys-ng-task-manager.mjs +148 -51
- package/fesm2022/flusys-ng-task-manager.mjs.map +1 -1
- package/package.json +4 -4
- package/types/flusys-ng-task-manager.d.ts +10 -1
- package/fesm2022/flusys-ng-task-manager-board-view-page.component-CkC8-UKF.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -103,6 +103,19 @@ export class MyComponent {
|
|
|
103
103
|
| `list:deleted` | Removed from `lists` |
|
|
104
104
|
| `list:reordered` | Lists re-sorted by new position values |
|
|
105
105
|
|
|
106
|
+
Events whose `boardId` is not the active board (late events after `closeBoard()` or a board switch) and events with malformed payloads are ignored. `task:created` and `list:created` are de-duplicated by id.
|
|
107
|
+
|
|
108
|
+
`openBoard()` discards responses from a superseded call (switching boards quickly never shows the older board), and clears board state when loading fails.
|
|
109
|
+
|
|
110
|
+
### Moving tasks and reordering lists
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
await this.state.moveTask(taskId, targetListId, targetIndex); // same-list reorder or cross-list move
|
|
114
|
+
await this.state.reorderLists(boardId, orderedListIds);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Both apply the change optimistically and roll back (then rethrow) if the API call fails. `moveTask()` clamps `targetIndex` to the target list and sends the midpoint `position` between the neighbouring tasks.
|
|
118
|
+
|
|
106
119
|
---
|
|
107
120
|
|
|
108
121
|
## 4. Socket Service
|
|
@@ -136,6 +149,8 @@ export class MyService {
|
|
|
136
149
|
}
|
|
137
150
|
```
|
|
138
151
|
|
|
152
|
+
The package never calls `connect()` itself — the host app must connect with an access token for real-time sync to work. Joined board rooms are remembered and re-joined automatically on every (re)connect; a join requested before the socket is connected is sent once it connects. Calling `connect()` while a previous socket is still connecting tears that socket down first, and `board:event` messages without a string `type`/`boardId` are dropped.
|
|
153
|
+
|
|
139
154
|
---
|
|
140
155
|
|
|
141
156
|
## 5. API Services
|
|
@@ -26,7 +26,7 @@ var BoardListPageComponent = class BoardListPageComponent {
|
|
|
26
26
|
return boards.filter((board) => board.name.toLowerCase().includes(term) || (board.description ?? "").toLowerCase().includes(term));
|
|
27
27
|
}, ...ngDevMode ? [{ debugName: "filteredBoards" }] : /* istanbul ignore next */ []);
|
|
28
28
|
constructor() {
|
|
29
|
-
this.state.loadBoards();
|
|
29
|
+
this.state.loadBoards().catch(() => void 0);
|
|
30
30
|
}
|
|
31
31
|
onSearchChange(term) {
|
|
32
32
|
this._searchTerm.set(term);
|
|
@@ -781,4 +781,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
781
781
|
});
|
|
782
782
|
export { BoardListPageComponent };
|
|
783
783
|
|
|
784
|
-
//# sourceMappingURL=flusys-ng-task-manager-board-list-page.component-
|
|
784
|
+
//# sourceMappingURL=flusys-ng-task-manager-board-list-page.component-BAkiTEpy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flusys-ng-task-manager-board-list-page.component-Dmay3IyF.mjs","names":[],"sources":["../../../projects/ng-task-manager/pages/board-list-page/board-list-page.component.ts"],"sourcesContent":["import { Component, computed, inject, signal } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TranslatePipe } from '@flusys/ng-shared';\nimport { TaskBoardApiService } from '../../services/task-board-api.service';\nimport { TaskBoardStateService } from '../../services/task-board-state.service';\nimport { BOARD_COLOR_PALETTE } from '../../utils/task-utils';\nimport {\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n SkeletonComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n} from '@flusys/ng-ui';\n\n@Component({\n selector: 'tm-board-list-page',\n imports: [\n DatePipe,\n FormsModule,\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n SkeletonComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n TranslatePipe,\n ],\n template: `\n <div class=\"p-2 sm:p-4\">\n <div\n class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4\"\n >\n <div>\n <h1 class=\"text-xl sm:text-2xl font-bold m-0\">\n {{ 'task.manager.boards.title' | translate }}\n </h1>\n <p class=\"text-muted-color mt-1 text-sm sm:text-base\">\n {{ 'task.manager.boards.subtitle' | translate }}\n </p>\n </div>\n <div\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2 w-full sm:w-auto\"\n >\n <f-iconfield class=\"w-full sm:w-64\">\n <f-inputicon icon=\"search\" />\n <input\n fInputText\n type=\"text\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onSearchChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [placeholder]=\"'shared.search' | translate\"\n class=\"w-full\"\n />\n </f-iconfield>\n <button\n fButton\n class=\"w-full sm:w-auto\"\n (click)=\"showCreateDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{ 'task.manager.board.new' | translate }}</span>\n </button>\n </div>\n </div>\n\n @if (state.isLoading()) {\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <div class=\"card !p-0 overflow-hidden\">\n <f-skeleton height=\"4px\" borderRadius=\"0\" />\n <div class=\"p-5\">\n <f-skeleton height=\"18px\" width=\"60%\" class=\"mb-3\" />\n <f-skeleton height=\"13px\" class=\"mb-1\" />\n <f-skeleton height=\"13px\" width=\"70%\" class=\"mb-5\" />\n <div class=\"flex gap-5 pt-4 border-t border-surface\">\n <f-skeleton height=\"14px\" width=\"55px\" />\n <f-skeleton height=\"14px\" width=\"45px\" />\n <f-skeleton height=\"14px\" width=\"50px\" />\n </div>\n <f-skeleton height=\"11px\" width=\"100px\" class=\"mt-2\" />\n </div>\n </div>\n }\n </div>\n } @else if (filteredBoards().length === 0) {\n <div\n class=\"flex flex-col items-center justify-center py-20 text-muted-color\"\n >\n <span\n class=\"w-16 h-16 rounded-2xl border-2 border-dashed border-surface flex items-center justify-center mb-4\"\n >\n <ui-icon name=\"th-large\" class=\"text-2xl text-muted-color\"></ui-icon>\n </span>\n <p class=\"text-base font-semibold mb-1\">\n {{ 'task.manager.board.empty' | translate }}\n </p>\n <p class=\"text-sm text-muted-color mb-5\">\n {{ 'task.manager.board.empty.hint' | translate }}\n </p>\n <button\n fButton\n size=\"small\"\n (click)=\"showCreateDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{ 'task.manager.board.new' | translate }}</span>\n </button>\n </div>\n } @else {\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n @for (board of filteredBoards(); track board.id) {\n <div\n class=\"card !p-0 cursor-pointer overflow-hidden group hover:shadow-lg transition-all duration-200\"\n (click)=\"openBoard(board.id)\"\n >\n <div class=\"h-1\" [style.background-color]=\"board.color\"></div>\n\n <div class=\"p-5\">\n <div class=\"flex items-start justify-between gap-3 mb-2\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <h3\n class=\"font-semibold text-base m-0 truncate group-hover:text-primary-500 transition-colors duration-150\"\n >\n {{ board.name }}\n </h3>\n </div>\n <ui-icon name=\"arrow-right\" class=\"text-xs text-muted-color group-hover:text-primary-500 group-hover:translate-x-0.5 transition-all duration-150 flex-shrink-0 mt-1\"></ui-icon>\n </div>\n\n <p\n class=\"text-sm text-muted-color line-clamp-2 pl-[18px] mb-0 min-h-[40px]\"\n >\n {{ board.description || '' }}\n </p>\n\n <div\n class=\"border-t border-surface mt-4 pt-4 space-y-2.5\"\n >\n <div class=\"flex items-center gap-4\">\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"users\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalMembers ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.members' | translate\n }}</span>\n </span>\n <span\n class=\"w-px h-3 bg-surface-200 dark:bg-surface-600\"\n ></span>\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"list\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalLists ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.lists' | translate\n }}</span>\n </span>\n <span\n class=\"w-px h-3 bg-surface-200 dark:bg-surface-600\"\n ></span>\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"check-square\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalTasks ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.tasks' | translate\n }}</span>\n </span>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs text-muted-color\"\n >\n <ui-icon name=\"clock\" class=\"text-[10px]\"></ui-icon>\n <span>{{ board.createdAt | date: 'MMM d, yyyy' }}</span>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n <f-dialog\n [header]=\"'task.manager.board.new' | translate\"\n [(visible)]=\"showCreateDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '440px' }\"\n >\n <div class=\"flex flex-col gap-4 py-2\">\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\"\n >{{ 'shared.name' | translate }}\n <span class=\"text-red-500\">*</span></label\n >\n <input\n fInputText\n [(ngModel)]=\"newBoardName\"\n [placeholder]=\"'task.manager.board.name.placeholder' | translate\"\n />\n </div>\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'shared.description' | translate\n }}</label>\n <textarea\n fTextarea\n [(ngModel)]=\"newBoardDescription\"\n rows=\"2\"\n [placeholder]=\"\n 'task.manager.board.description.placeholder' | translate\n \"\n ></textarea>\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.board.color.label' | translate\n }}</label>\n <div class=\"flex flex-wrap gap-2\">\n @for (c of boardColors; track c) {\n <button\n type=\"button\"\n class=\"w-8 h-8 rounded-full border-2 transition-all hover:scale-110 focus:outline-none flex items-center justify-center\"\n [style.background-color]=\"c\"\n [class.ring-2]=\"newBoardColor === c\"\n [class.ring-offset-2]=\"newBoardColor === c\"\n [class.ring-surface-900]=\"newBoardColor === c\"\n [class.dark:ring-surface-0]=\"newBoardColor === c\"\n [fTooltip]=\"c\"\n (click)=\"newBoardColor = c\"\n >\n @if (newBoardColor === c) {\n <ui-icon name=\"check\" class=\"text-white text-xs\"></ui-icon>\n }\n </button>\n }\n </div>\n <div class=\"flex items-center gap-2 mt-1\">\n <span\n class=\"w-8 h-8 rounded-full border border-surface flex-shrink-0\"\n [style.background-color]=\"newBoardColor\"\n ></span>\n <span class=\"text-sm text-muted-color font-mono\">{{\n newBoardColor\n }}</span>\n </div>\n </div>\n </div>\n\n <ng-template #footer>\n <div class=\"flex justify-end gap-2\">\n <button\n fButton\n severity=\"secondary\"\n (click)=\"closeCreateDialog()\"\n >\n <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>\n </button>\n <button\n fButton\n (click)=\"createBoard()\"\n [disabled]=\"isCreating()\"\n >\n @if (isCreating()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n }\n <span pButtonLabel>{{ 'shared.create' | translate }}</span>\n </button>\n </div>\n </ng-template>\n </f-dialog>\n `,\n})\nexport class BoardListPageComponent {\n protected readonly state = inject(TaskBoardStateService);\n private readonly boardApi = inject(TaskBoardApiService);\n private readonly router = inject(Router);\n\n readonly showCreateDialog = signal(false);\n readonly isCreating = signal(false);\n newBoardName = '';\n newBoardDescription = '';\n newBoardColor = BOARD_COLOR_PALETTE[0];\n readonly boardColors = BOARD_COLOR_PALETTE;\n\n private readonly _searchTerm = signal('');\n readonly searchTerm = this._searchTerm.asReadonly();\n\n readonly filteredBoards = computed(() => {\n const term = this.searchTerm().toLowerCase().trim();\n const boards = this.state.boards();\n if (!term) return boards;\n return boards.filter(\n (board) =>\n board.name.toLowerCase().includes(term) ||\n (board.description ?? '').toLowerCase().includes(term),\n );\n });\n\n constructor() {\n this.state.loadBoards();\n }\n\n onSearchChange(term: string): void {\n this._searchTerm.set(term);\n }\n\n openBoard(boardId: string): void {\n this.router.navigate(['/task-manager/board', boardId]);\n }\n\n closeCreateDialog(): void {\n this.showCreateDialog.set(false);\n this.newBoardName = '';\n this.newBoardDescription = '';\n this.newBoardColor = BOARD_COLOR_PALETTE[0];\n }\n\n async createBoard(): Promise<void> {\n if (!this.newBoardName.trim()) return;\n this.isCreating.set(true);\n try {\n await this.boardApi.insert({\n name: this.newBoardName,\n description: this.newBoardDescription || undefined,\n color: this.newBoardColor,\n });\n await this.state.loadBoards();\n this.closeCreateDialog();\n } finally {\n this.isCreating.set(false);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flusys-ng-task-manager-board-list-page.component-BAkiTEpy.mjs","names":[],"sources":["../../../projects/ng-task-manager/pages/board-list-page/board-list-page.component.ts"],"sourcesContent":["import { Component, computed, inject, signal } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TranslatePipe } from '@flusys/ng-shared';\nimport { TaskBoardApiService } from '../../services/task-board-api.service';\nimport { TaskBoardStateService } from '../../services/task-board-state.service';\nimport { BOARD_COLOR_PALETTE } from '../../utils/task-utils';\nimport {\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n SkeletonComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n} from '@flusys/ng-ui';\n\n@Component({\n selector: 'tm-board-list-page',\n imports: [\n DatePipe,\n FormsModule,\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n SkeletonComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n TranslatePipe,\n ],\n template: `\n <div class=\"p-2 sm:p-4\">\n <div\n class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4\"\n >\n <div>\n <h1 class=\"text-xl sm:text-2xl font-bold m-0\">\n {{ 'task.manager.boards.title' | translate }}\n </h1>\n <p class=\"text-muted-color mt-1 text-sm sm:text-base\">\n {{ 'task.manager.boards.subtitle' | translate }}\n </p>\n </div>\n <div\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-2 w-full sm:w-auto\"\n >\n <f-iconfield class=\"w-full sm:w-64\">\n <f-inputicon icon=\"search\" />\n <input\n fInputText\n type=\"text\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onSearchChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [placeholder]=\"'shared.search' | translate\"\n class=\"w-full\"\n />\n </f-iconfield>\n <button\n fButton\n class=\"w-full sm:w-auto\"\n (click)=\"showCreateDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{ 'task.manager.board.new' | translate }}</span>\n </button>\n </div>\n </div>\n\n @if (state.isLoading()) {\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <div class=\"card !p-0 overflow-hidden\">\n <f-skeleton height=\"4px\" borderRadius=\"0\" />\n <div class=\"p-5\">\n <f-skeleton height=\"18px\" width=\"60%\" class=\"mb-3\" />\n <f-skeleton height=\"13px\" class=\"mb-1\" />\n <f-skeleton height=\"13px\" width=\"70%\" class=\"mb-5\" />\n <div class=\"flex gap-5 pt-4 border-t border-surface\">\n <f-skeleton height=\"14px\" width=\"55px\" />\n <f-skeleton height=\"14px\" width=\"45px\" />\n <f-skeleton height=\"14px\" width=\"50px\" />\n </div>\n <f-skeleton height=\"11px\" width=\"100px\" class=\"mt-2\" />\n </div>\n </div>\n }\n </div>\n } @else if (filteredBoards().length === 0) {\n <div\n class=\"flex flex-col items-center justify-center py-20 text-muted-color\"\n >\n <span\n class=\"w-16 h-16 rounded-2xl border-2 border-dashed border-surface flex items-center justify-center mb-4\"\n >\n <ui-icon name=\"th-large\" class=\"text-2xl text-muted-color\"></ui-icon>\n </span>\n <p class=\"text-base font-semibold mb-1\">\n {{ 'task.manager.board.empty' | translate }}\n </p>\n <p class=\"text-sm text-muted-color mb-5\">\n {{ 'task.manager.board.empty.hint' | translate }}\n </p>\n <button\n fButton\n size=\"small\"\n (click)=\"showCreateDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{ 'task.manager.board.new' | translate }}</span>\n </button>\n </div>\n } @else {\n <div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n @for (board of filteredBoards(); track board.id) {\n <div\n class=\"card !p-0 cursor-pointer overflow-hidden group hover:shadow-lg transition-all duration-200\"\n (click)=\"openBoard(board.id)\"\n >\n <div class=\"h-1\" [style.background-color]=\"board.color\"></div>\n\n <div class=\"p-5\">\n <div class=\"flex items-start justify-between gap-3 mb-2\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <h3\n class=\"font-semibold text-base m-0 truncate group-hover:text-primary-500 transition-colors duration-150\"\n >\n {{ board.name }}\n </h3>\n </div>\n <ui-icon name=\"arrow-right\" class=\"text-xs text-muted-color group-hover:text-primary-500 group-hover:translate-x-0.5 transition-all duration-150 flex-shrink-0 mt-1\"></ui-icon>\n </div>\n\n <p\n class=\"text-sm text-muted-color line-clamp-2 pl-[18px] mb-0 min-h-[40px]\"\n >\n {{ board.description || '' }}\n </p>\n\n <div\n class=\"border-t border-surface mt-4 pt-4 space-y-2.5\"\n >\n <div class=\"flex items-center gap-4\">\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"users\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalMembers ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.members' | translate\n }}</span>\n </span>\n <span\n class=\"w-px h-3 bg-surface-200 dark:bg-surface-600\"\n ></span>\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"list\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalLists ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.lists' | translate\n }}</span>\n </span>\n <span\n class=\"w-px h-3 bg-surface-200 dark:bg-surface-600\"\n ></span>\n <span class=\"flex items-center gap-1.5\">\n <ui-icon name=\"check-square\" class=\"text-[11px] text-muted-color\"></ui-icon>\n <span class=\"text-sm font-semibold\">{{\n board.totalTasks ?? 0\n }}</span>\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.tasks' | translate\n }}</span>\n </span>\n </div>\n <div\n class=\"flex items-center gap-1.5 text-xs text-muted-color\"\n >\n <ui-icon name=\"clock\" class=\"text-[10px]\"></ui-icon>\n <span>{{ board.createdAt | date: 'MMM d, yyyy' }}</span>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n <f-dialog\n [header]=\"'task.manager.board.new' | translate\"\n [(visible)]=\"showCreateDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '440px' }\"\n >\n <div class=\"flex flex-col gap-4 py-2\">\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\"\n >{{ 'shared.name' | translate }}\n <span class=\"text-red-500\">*</span></label\n >\n <input\n fInputText\n [(ngModel)]=\"newBoardName\"\n [placeholder]=\"'task.manager.board.name.placeholder' | translate\"\n />\n </div>\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'shared.description' | translate\n }}</label>\n <textarea\n fTextarea\n [(ngModel)]=\"newBoardDescription\"\n rows=\"2\"\n [placeholder]=\"\n 'task.manager.board.description.placeholder' | translate\n \"\n ></textarea>\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.board.color.label' | translate\n }}</label>\n <div class=\"flex flex-wrap gap-2\">\n @for (c of boardColors; track c) {\n <button\n type=\"button\"\n class=\"w-8 h-8 rounded-full border-2 transition-all hover:scale-110 focus:outline-none flex items-center justify-center\"\n [style.background-color]=\"c\"\n [class.ring-2]=\"newBoardColor === c\"\n [class.ring-offset-2]=\"newBoardColor === c\"\n [class.ring-surface-900]=\"newBoardColor === c\"\n [class.dark:ring-surface-0]=\"newBoardColor === c\"\n [fTooltip]=\"c\"\n (click)=\"newBoardColor = c\"\n >\n @if (newBoardColor === c) {\n <ui-icon name=\"check\" class=\"text-white text-xs\"></ui-icon>\n }\n </button>\n }\n </div>\n <div class=\"flex items-center gap-2 mt-1\">\n <span\n class=\"w-8 h-8 rounded-full border border-surface flex-shrink-0\"\n [style.background-color]=\"newBoardColor\"\n ></span>\n <span class=\"text-sm text-muted-color font-mono\">{{\n newBoardColor\n }}</span>\n </div>\n </div>\n </div>\n\n <ng-template #footer>\n <div class=\"flex justify-end gap-2\">\n <button\n fButton\n severity=\"secondary\"\n (click)=\"closeCreateDialog()\"\n >\n <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>\n </button>\n <button\n fButton\n (click)=\"createBoard()\"\n [disabled]=\"isCreating()\"\n >\n @if (isCreating()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n }\n <span pButtonLabel>{{ 'shared.create' | translate }}</span>\n </button>\n </div>\n </ng-template>\n </f-dialog>\n `,\n})\nexport class BoardListPageComponent {\n protected readonly state = inject(TaskBoardStateService);\n private readonly boardApi = inject(TaskBoardApiService);\n private readonly router = inject(Router);\n\n readonly showCreateDialog = signal(false);\n readonly isCreating = signal(false);\n newBoardName = '';\n newBoardDescription = '';\n newBoardColor = BOARD_COLOR_PALETTE[0];\n readonly boardColors = BOARD_COLOR_PALETTE;\n\n private readonly _searchTerm = signal('');\n readonly searchTerm = this._searchTerm.asReadonly();\n\n readonly filteredBoards = computed(() => {\n const term = this.searchTerm().toLowerCase().trim();\n const boards = this.state.boards();\n if (!term) return boards;\n return boards.filter(\n (board) =>\n board.name.toLowerCase().includes(term) ||\n (board.description ?? '').toLowerCase().includes(term),\n );\n });\n\n constructor() {\n void this.state.loadBoards().catch(() => undefined);\n }\n\n onSearchChange(term: string): void {\n this._searchTerm.set(term);\n }\n\n openBoard(boardId: string): void {\n this.router.navigate(['/task-manager/board', boardId]);\n }\n\n closeCreateDialog(): void {\n this.showCreateDialog.set(false);\n this.newBoardName = '';\n this.newBoardDescription = '';\n this.newBoardColor = BOARD_COLOR_PALETTE[0];\n }\n\n async createBoard(): Promise<void> {\n if (!this.newBoardName.trim()) return;\n this.isCreating.set(true);\n try {\n await this.boardApi.insert({\n name: this.newBoardName,\n description: this.newBoardDescription || undefined,\n color: this.newBoardColor,\n });\n await this.state.loadBoards();\n this.closeCreateDialog();\n } finally {\n this.isCreating.set(false);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BOARD_COLOR_PALETTE,
|
|
1
|
+
import { BOARD_COLOR_PALETTE, TaskBoardStateService, TaskCardComponent, TaskDetailPanelComponent, TaskFormDialogComponent, TaskListApiService, isTaskOverdue, memberColorFromId, memberInitials, taskPrioritySeverity } from "./flusys-ng-task-manager.mjs";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
import { Component, DestroyRef, computed, effect, inject, input, signal, untracked } from "@angular/core";
|
|
4
4
|
import { TranslatePipe, TranslateService, UserSelectComponent } from "@flusys/ng-shared";
|
|
@@ -7,10 +7,9 @@ import { AvatarComponent, ConfirmationService, DialogComponent, FButtonDirective
|
|
|
7
7
|
import * as i2 from "@angular/forms";
|
|
8
8
|
import { FormsModule } from "@angular/forms";
|
|
9
9
|
import * as i1 from "@angular/cdk/drag-drop";
|
|
10
|
-
import { DragDropModule, moveItemInArray
|
|
10
|
+
import { DragDropModule, moveItemInArray } from "@angular/cdk/drag-drop";
|
|
11
11
|
var BoardViewPageComponent = class BoardViewPageComponent {
|
|
12
12
|
state = inject(TaskBoardStateService);
|
|
13
|
-
taskApi = inject(TaskApiService);
|
|
14
13
|
listApi = inject(TaskListApiService);
|
|
15
14
|
confirmationService = inject(ConfirmationService);
|
|
16
15
|
messageService = inject(MessageService);
|
|
@@ -66,7 +65,7 @@ var BoardViewPageComponent = class BoardViewPageComponent {
|
|
|
66
65
|
constructor() {
|
|
67
66
|
effect(() => {
|
|
68
67
|
const id = this.boardId();
|
|
69
|
-
untracked(() => this.state.openBoard(id));
|
|
68
|
+
untracked(() => void this.state.openBoard(id).catch(() => void 0));
|
|
70
69
|
});
|
|
71
70
|
this.destroyRef.onDestroy(() => this.state.closeBoard());
|
|
72
71
|
}
|
|
@@ -129,23 +128,21 @@ var BoardViewPageComponent = class BoardViewPageComponent {
|
|
|
129
128
|
prioritySeverity = taskPrioritySeverity;
|
|
130
129
|
isOverdue = isTaskOverdue;
|
|
131
130
|
async onTaskDrop(event) {
|
|
132
|
-
if (event.previousContainer === event.container
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await this.
|
|
137
|
-
|
|
138
|
-
targetListId: event.container.id,
|
|
139
|
-
position: event.currentIndex * 100
|
|
140
|
-
});
|
|
141
|
-
}
|
|
131
|
+
if (event.previousContainer === event.container && event.previousIndex === event.currentIndex) return;
|
|
132
|
+
const task = event.item?.data ?? event.previousContainer.data[event.previousIndex];
|
|
133
|
+
if (!task) return;
|
|
134
|
+
try {
|
|
135
|
+
await this.state.moveTask(task.id, event.container.id, event.currentIndex);
|
|
136
|
+
} catch {}
|
|
142
137
|
}
|
|
143
138
|
async onListDrop(event) {
|
|
144
139
|
if (event.previousIndex === event.currentIndex) return;
|
|
145
140
|
const lists = this.state.listsWithTasks().slice();
|
|
146
141
|
moveItemInArray(lists, event.previousIndex, event.currentIndex);
|
|
147
142
|
const orderedIds = lists.map((l) => l.id);
|
|
148
|
-
|
|
143
|
+
try {
|
|
144
|
+
await this.state.reorderLists(this.boardId(), orderedIds);
|
|
145
|
+
} catch {}
|
|
149
146
|
}
|
|
150
147
|
startRenameList(list) {
|
|
151
148
|
this.editingListId.set(list.id);
|
|
@@ -232,7 +229,7 @@ var BoardViewPageComponent = class BoardViewPageComponent {
|
|
|
232
229
|
const position = this.state.lists().length * 100;
|
|
233
230
|
await this.listApi.insert({
|
|
234
231
|
boardId: this.boardId(),
|
|
235
|
-
name: this.newListName,
|
|
232
|
+
name: this.newListName.trim(),
|
|
236
233
|
position
|
|
237
234
|
});
|
|
238
235
|
await this.state.openBoard(this.boardId());
|
|
@@ -532,7 +529,7 @@ var BoardViewPageComponent = class BoardViewPageComponent {
|
|
|
532
529
|
class="flex flex-col gap-2 p-2 overflow-y-auto flex-1 min-h-[60px]"
|
|
533
530
|
>
|
|
534
531
|
@for (task of list.tasks; track task.id) {
|
|
535
|
-
<div cdkDrag>
|
|
532
|
+
<div cdkDrag [cdkDragData]="task">
|
|
536
533
|
<tm-task-card
|
|
537
534
|
[task]="task"
|
|
538
535
|
(cardClick)="state.selectTask($event)"
|
|
@@ -658,7 +655,7 @@ var BoardViewPageComponent = class BoardViewPageComponent {
|
|
|
658
655
|
</td>
|
|
659
656
|
<td>
|
|
660
657
|
<f-tag
|
|
661
|
-
[value]="task.priority"
|
|
658
|
+
[value]="('task.priority.' + task.priority) | translate"
|
|
662
659
|
[severity]="prioritySeverity(task.priority)"
|
|
663
660
|
/>
|
|
664
661
|
</td>
|
|
@@ -1792,7 +1789,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1792
1789
|
class="flex flex-col gap-2 p-2 overflow-y-auto flex-1 min-h-[60px]"
|
|
1793
1790
|
>
|
|
1794
1791
|
@for (task of list.tasks; track task.id) {
|
|
1795
|
-
<div cdkDrag>
|
|
1792
|
+
<div cdkDrag [cdkDragData]="task">
|
|
1796
1793
|
<tm-task-card
|
|
1797
1794
|
[task]="task"
|
|
1798
1795
|
(cardClick)="state.selectTask($event)"
|
|
@@ -1918,7 +1915,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1918
1915
|
</td>
|
|
1919
1916
|
<td>
|
|
1920
1917
|
<f-tag
|
|
1921
|
-
[value]="task.priority"
|
|
1918
|
+
[value]="('task.priority.' + task.priority) | translate"
|
|
1922
1919
|
[severity]="prioritySeverity(task.priority)"
|
|
1923
1920
|
/>
|
|
1924
1921
|
</td>
|
|
@@ -2342,4 +2339,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
2342
2339
|
});
|
|
2343
2340
|
export { BoardViewPageComponent };
|
|
2344
2341
|
|
|
2345
|
-
//# sourceMappingURL=flusys-ng-task-manager-board-view-page.component-
|
|
2342
|
+
//# sourceMappingURL=flusys-ng-task-manager-board-view-page.component-9ZrU7hON.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flusys-ng-task-manager-board-view-page.component-9ZrU7hON.mjs","names":[],"sources":["../../../projects/ng-task-manager/pages/board-view-page/board-view-page.component.ts"],"sourcesContent":["import {\n CdkDragDrop,\n DragDropModule,\n moveItemInArray,\n} from '@angular/cdk/drag-drop';\nimport {\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport {\n TranslatePipe,\n TranslateService,\n UserSelectComponent,\n} from '@flusys/ng-shared';\nimport {\n AvatarComponent,\n ConfirmationService,\n DialogComponent,\n FButtonDirective,\n InputTextDirective,\n MessageService,\n SelectComponent,\n SkeletonComponent,\n SortableColumnDirective,\n SortIconComponent,\n TableComponent,\n TagComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n} from '@flusys/ng-ui';\nimport { TaskCardComponent } from '../../components/task-card/task-card.component';\nimport { TaskDetailPanelComponent } from '../../components/task-detail-panel/task-detail-panel.component';\nimport { TaskFormDialogComponent } from '../../components/task-form-dialog/task-form-dialog.component';\nimport {\n ITask,\n ITaskBoard,\n ITaskBoardMember,\n ITaskList,\n} from '../../interfaces/task.interface';\nimport { TaskListApiService } from '../../services/task-api.service';\nimport { TaskBoardStateService } from '../../services/task-board-state.service';\nimport {\n BOARD_COLOR_PALETTE,\n isTaskOverdue,\n memberColorFromId,\n memberInitials,\n taskPrioritySeverity,\n} from '../../utils/task-utils';\n\ntype ViewMode = 'board' | 'list';\ntype ITaskListWithTasks = ITaskList & { tasks: ITask[] };\n\ninterface ITaskRow extends ITask {\n listName: string;\n listColor: string;\n}\n\n@Component({\n selector: 'tm-board-view-page',\n imports: [\n AvatarComponent,\n DialogComponent,\n FButtonDirective,\n InputTextDirective,\n SelectComponent,\n SkeletonComponent,\n SortableColumnDirective,\n SortIconComponent,\n TableComponent,\n TagComponent,\n TextareaDirective,\n TooltipDirective,\n UiIconComponent,\n DragDropModule,\n DatePipe,\n FormsModule,\n TaskCardComponent,\n TaskFormDialogComponent,\n TaskDetailPanelComponent,\n TranslatePipe,\n UserSelectComponent,\n ],\n template: `\n <div class=\"flex flex-col h-full\">\n <div class=\"flex flex-col\">\n <div class=\"flex items-center justify-between px-5 py-2.5 gap-4\">\n @if (state.activeBoard(); as board) {\n <div class=\"flex items-center gap-2.5 min-w-0 flex-1 group\">\n <div class=\"flex flex-col min-w-0\">\n <div class=\"flex items-center gap-1.5 min-w-0\">\n <h1\n class=\"text-base font-bold text-surface-900 dark:text-surface-0 truncate leading-snug\"\n >\n {{ board.name }}\n </h1>\n <button\n fButton\n [text]=\"true\"\n size=\"small\"\n severity=\"secondary\"\n class=\"!w-6 !h-6 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity\"\n [fTooltip]=\"'task.manager.board.edit' | translate\"\n tooltipPosition=\"bottom\"\n (click)=\"openEditBoardDialog(board)\"\n >\n <ui-icon pButtonIcon name=\"pencil\"></ui-icon>\n </button>\n </div>\n @if (board.description) {\n <span\n class=\"text-xs text-muted-color truncate max-w-sm hidden md:block leading-snug\"\n >\n {{ board.description }}\n </span>\n }\n </div>\n </div>\n }\n\n <div class=\"flex items-center gap-1.5 flex-shrink-0\">\n @if (state.members().length > 0) {\n <div\n class=\"flex items-center -space-x-2 cursor-pointer mr-1\"\n (click)=\"showMembersDialog.set(true)\"\n [fTooltip]=\"'task.manager.board.manage.members' | translate\"\n tooltipPosition=\"bottom\"\n >\n @for (member of state.members().slice(0, 4); track member.id) {\n <f-avatar\n shape=\"circle\"\n size=\"small\"\n [label]=\"memberInitials(member)\"\n [color]=\"memberColor(member)\"\n styleClass=\"!border-2 !border-surface-card ring-1 ring-surface flex-shrink-0\"\n />\n }\n @if (state.members().length > 4) {\n <f-avatar\n shape=\"circle\"\n size=\"small\"\n [label]=\"'+' + (state.members().length - 4)\"\n styleClass=\"!border-2 !border-surface-card !bg-surface-200 dark:!bg-surface-700 !text-surface-600 dark:!text-surface-300 flex-shrink-0\"\n />\n }\n </div>\n }\n\n <div\n class=\"h-5 w-px bg-surface-300 dark:bg-surface-600 mx-0.5\"\n ></div>\n\n <div\n class=\"flex rounded-border overflow-hidden border border-surface\"\n >\n <button\n fButton\n [fTooltip]=\"'task.manager.view.board' | translate\"\n tooltipPosition=\"bottom\"\n size=\"small\"\n [severity]=\"viewMode() === 'board' ? 'primary' : 'secondary'\"\n [outlined]=\"viewMode() !== 'board'\"\n class=\"!rounded-none !border-0\"\n (click)=\"viewMode.set('board')\"\n >\n <ui-icon pButtonIcon name=\"th-large\"></ui-icon>\n </button>\n <button\n fButton\n [fTooltip]=\"'task.manager.view.list' | translate\"\n tooltipPosition=\"bottom\"\n size=\"small\"\n [severity]=\"viewMode() === 'list' ? 'primary' : 'secondary'\"\n [outlined]=\"viewMode() !== 'list'\"\n class=\"!rounded-none !border-0\"\n (click)=\"viewMode.set('list')\"\n >\n <ui-icon pButtonIcon name=\"list\"></ui-icon>\n </button>\n </div>\n\n <div\n class=\"h-5 w-px bg-surface-300 dark:bg-surface-600 mx-0.5\"\n ></div>\n\n <button\n fButton\n [text]=\"true\"\n severity=\"secondary\"\n size=\"small\"\n class=\"!w-8 !h-8\"\n [fTooltip]=\"'task.manager.board.manage.members' | translate\"\n tooltipPosition=\"bottom\"\n (click)=\"showMembersDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"users\"></ui-icon>\n </button>\n <button\n fButton\n [text]=\"true\"\n severity=\"secondary\"\n size=\"small\"\n class=\"!w-8 !h-8\"\n [fTooltip]=\"'task.manager.board.manage.labels' | translate\"\n tooltipPosition=\"bottom\"\n (click)=\"showLabelsDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"tag\"></ui-icon>\n </button>\n\n @if (viewMode() === 'board') {\n <div\n class=\"h-5 w-px bg-surface-300 dark:bg-surface-600 mx-0.5\"\n ></div>\n <button\n fButton\n size=\"small\"\n (click)=\"showAddListDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{\n 'task.manager.board.add.list' | translate\n }}</span>\n </button>\n }\n </div>\n </div>\n\n @if (state.activeBoard(); as board) {\n <div\n class=\"h-0.5 w-full flex-shrink-0\"\n [style.background-color]=\"board.color\"\n ></div>\n }\n </div>\n\n @if (state.isBoardLoading()) {\n @if (viewMode() === 'board') {\n <div class=\"flex gap-4 p-6 overflow-x-auto\">\n @for (i of [1, 2, 3]; track i) {\n <div class=\"flex-shrink-0 w-72\">\n <f-skeleton height=\"36px\" class=\"mb-3\" />\n @for (j of [1, 2, 3]; track j) {\n <f-skeleton height=\"90px\" class=\"mb-2\" />\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"p-6\">\n @for (i of [1, 2, 3, 4, 5]; track i) {\n <f-skeleton height=\"48px\" class=\"mb-2\" />\n }\n </div>\n }\n } @else if (viewMode() === 'board') {\n <div\n class=\"flex gap-4 p-5 overflow-x-auto flex-1\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"state.listsWithTasks()\"\n (cdkDropListDropped)=\"onListDrop($event)\"\n >\n @for (list of state.listsWithTasks(); track list.id) {\n <div\n cdkDrag\n class=\"flex-shrink-0 w-72 flex flex-col max-h-[calc(100vh-140px)] self-start rounded-border border border-surface shadow-sm overflow-hidden\"\n >\n <div\n class=\"flex items-center gap-1 px-2.5 py-2.5 border-b border-surface\"\n >\n <ui-icon\n cdkDragHandle\n name=\"bars\"\n class=\"text-muted-color text-xs cursor-grab flex-shrink-0 px-1\"\n ></ui-icon>\n\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n @if (editingListId() === list.id) {\n <input\n fInputText\n class=\"text-sm font-semibold !h-6 !py-0 !px-1 w-full\"\n [ngModel]=\"editingListNameValue()\"\n (ngModelChange)=\"editingListNameValue.set($event)\"\n (keydown.enter)=\"saveRenameList()\"\n (keydown.escape)=\"cancelRenameList()\"\n (blur)=\"cancelRenameList()\"\n (click)=\"$event.stopPropagation()\"\n autofocus\n />\n } @else {\n <span class=\"font-semibold text-sm truncate\">{{\n list.name\n }}</span>\n <span\n class=\"text-[11px] font-semibold px-1.5 py-0.5 rounded-full text-white flex-shrink-0\"\n [style.background-color]=\"list.color\"\n >\n {{ list.tasks.length }}\n </span>\n }\n </div>\n\n @if (editingListId() === list.id) {\n <button\n fButton\n [text]=\"true\"\n size=\"small\"\n severity=\"success\"\n class=\"!w-6 !h-6 flex-shrink-0\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"saveRenameList(); $event.stopPropagation()\"\n >\n <ui-icon pButtonIcon name=\"check\"></ui-icon>\n </button>\n <button\n fButton\n [text]=\"true\"\n size=\"small\"\n severity=\"secondary\"\n class=\"!w-6 !h-6 flex-shrink-0\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"cancelRenameList(); $event.stopPropagation()\"\n >\n <ui-icon pButtonIcon name=\"times\"></ui-icon>\n </button>\n } @else {\n <button\n fButton\n [text]=\"true\"\n size=\"small\"\n severity=\"secondary\"\n class=\"!w-6 !h-6 flex-shrink-0\"\n [fTooltip]=\"'task.manager.list.rename' | translate\"\n tooltipPosition=\"bottom\"\n (click)=\"startRenameList(list); $event.stopPropagation()\"\n >\n <ui-icon pButtonIcon name=\"pencil\"></ui-icon>\n </button>\n }\n </div>\n\n <div\n cdkDropList\n [id]=\"list.id\"\n [cdkDropListData]=\"list.tasks\"\n [cdkDropListConnectedTo]=\"listDropIds()\"\n (cdkDropListDropped)=\"onTaskDrop($event)\"\n class=\"flex flex-col gap-2 p-2 overflow-y-auto flex-1 min-h-[60px]\"\n >\n @for (task of list.tasks; track task.id) {\n <div cdkDrag [cdkDragData]=\"task\">\n <tm-task-card\n [task]=\"task\"\n (cardClick)=\"state.selectTask($event)\"\n (editClick)=\"onEditTask($event)\"\n (deleteClick)=\"onDeleteTask($event)\"\n />\n </div>\n }\n @if (list.tasks.length === 0) {\n <div\n class=\"flex items-center justify-center h-14 border-2 border-dashed border-surface rounded-border\"\n >\n <span class=\"text-xs text-muted-color\">{{\n 'task.manager.board.drop.tasks.here' | translate\n }}</span>\n </div>\n }\n </div>\n\n <div class=\"border-t border-surface p-1.5\">\n <button\n fButton\n severity=\"secondary\"\n [text]=\"true\"\n size=\"small\"\n class=\"w-full !justify-start\"\n (click)=\"openAddTask(list)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{\n 'task.manager.board.add.task' | translate\n }}</span>\n </button>\n </div>\n </div>\n }\n\n @if (state.listsWithTasks().length === 0) {\n <div class=\"flex-1 flex items-center justify-center\">\n <div class=\"text-center text-muted-color\">\n <div\n class=\"w-16 h-16 rounded-2xl border-2 border-dashed border-surface flex items-center justify-center mx-auto mb-4\"\n >\n <ui-icon\n name=\"list\"\n class=\"text-3xl text-muted-color\"\n ></ui-icon>\n </div>\n <p class=\"font-medium text-muted-color\">\n {{ 'task.manager.board.list.empty' | translate }}\n </p>\n <button\n fButton\n severity=\"secondary\"\n size=\"small\"\n class=\"mt-3\"\n (click)=\"showAddListDialog.set(true)\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{\n 'task.manager.board.add.list' | translate\n }}</span>\n </button>\n </div>\n </div>\n }\n </div>\n } @else {\n <div class=\"p-4 sm:p-6 flex-1 overflow-auto\">\n <div class=\"card\">\n <div class=\"overflow-x-auto -mx-4 sm:mx-0\">\n <f-table\n [value]=\"allTasksFlat()\"\n [paginator]=\"allTasksFlat().length > 20\"\n [rows]=\"20\"\n [rowsPerPageOptions]=\"[10, 20, 50]\"\n [scrollable]=\"true\"\n [tableStyle]=\"{ 'min-width': '700px' }\"\n >\n <ng-template #header>\n <tr>\n <th fSortableColumn=\"title\" class=\"min-w-[240px]\">\n {{ 'task.manager.table.title' | translate }}\n <f-sorticon field=\"title\" />\n </th>\n <th fSortableColumn=\"listName\" class=\"w-40\">\n {{ 'task.manager.table.list' | translate }}\n <f-sorticon field=\"listName\" />\n </th>\n <th fSortableColumn=\"priority\" class=\"w-32\">\n {{ 'task.manager.table.priority' | translate }}\n <f-sorticon field=\"priority\" />\n </th>\n <th class=\"w-36\">\n {{ 'task.manager.table.assignee' | translate }}\n </th>\n <th fSortableColumn=\"dueDate\" class=\"w-32\">\n {{ 'task.manager.table.due.date' | translate }}\n <f-sorticon field=\"dueDate\" />\n </th>\n <th class=\"w-[100px]\">\n {{ 'shared.actions' | translate }}\n </th>\n </tr>\n </ng-template>\n <ng-template #body let-task>\n <tr>\n <td>\n <span\n class=\"font-medium text-sm cursor-pointer hover:text-primary-500 transition-colors\"\n (click)=\"state.selectTask(task)\"\n >{{ task.title }}</span\n >\n </td>\n <td>\n <span class=\"inline-flex items-center gap-1.5 text-sm\">\n <span\n class=\"w-2 h-2 rounded-full flex-shrink-0\"\n [style.background-color]=\"task.listColor\"\n ></span>\n {{ task.listName }}\n </span>\n </td>\n <td>\n <f-tag\n [value]=\"('task.priority.' + task.priority) | translate\"\n [severity]=\"prioritySeverity(task.priority)\"\n />\n </td>\n <td>\n @if (task.assignees?.length) {\n <div class=\"flex items-center -space-x-1.5\">\n @for (\n a of task.assignees!.slice(0, 3);\n track a.userId\n ) {\n <f-avatar\n shape=\"circle\"\n size=\"small\"\n [label]=\"getMemberInitials(a.userId)\"\n [color]=\"memberColorById(a.userId)\"\n styleClass=\"!ring-surface-card flex-shrink-0\"\n [fTooltip]=\"a.userName ?? ''\"\n />\n }\n @if (task.assignees!.length > 3) {\n <f-avatar\n shape=\"circle\"\n size=\"small\"\n [label]=\"'+' + (task.assignees!.length - 3)\"\n styleClass=\"!bg-surface-200 dark:!bg-surface-700 !text-surface-600 !ring-surface-card flex-shrink-0\"\n />\n }\n </div>\n } @else {\n <span class=\"text-muted-color text-sm\">—</span>\n }\n </td>\n <td>\n @if (task.dueDate) {\n <span\n class=\"text-sm\"\n [class.text-red-500]=\"isOverdue(task.dueDate)\"\n >\n {{ task.dueDate | date: 'MMM d, y' }}\n </span>\n } @else {\n <span class=\"text-muted-color text-sm\">—</span>\n }\n </td>\n <td>\n <div class=\"flex gap-1\">\n <button\n fButton\n [text]=\"true\"\n severity=\"secondary\"\n size=\"small\"\n [fTooltip]=\"'shared.edit' | translate\"\n (click)=\"onEditTask(task)\"\n >\n <ui-icon pButtonIcon name=\"pencil\"></ui-icon>\n </button>\n <button\n fButton\n [text]=\"true\"\n severity=\"danger\"\n size=\"small\"\n [fTooltip]=\"'shared.delete' | translate\"\n (click)=\"onDeleteTask(task)\"\n >\n <ui-icon pButtonIcon name=\"trash\"></ui-icon>\n </button>\n </div>\n </td>\n </tr>\n </ng-template>\n <ng-template #emptymessage>\n <tr>\n <td colspan=\"6\" class=\"text-center text-muted-color py-8\">\n <ui-icon\n name=\"inbox\"\n class=\"text-4xl block mb-3\"\n ></ui-icon>\n {{ 'task.manager.table.empty' | translate }}\n </td>\n </tr>\n </ng-template>\n </f-table>\n </div>\n </div>\n </div>\n }\n </div>\n\n <tm-task-form-dialog\n [(visible)]=\"showTaskForm\"\n [task]=\"editingTask()\"\n [boardId]=\"boardId()\"\n [listId]=\"activeListId()\"\n [members]=\"state.members()\"\n [labels]=\"state.labels()\"\n (saved)=\"onTaskSaved($event)\"\n />\n\n <tm-task-detail-panel\n [task]=\"state.selectedTask()\"\n (closePanel)=\"state.selectTask(null)\"\n (editTask)=\"onEditTask($event)\"\n />\n\n <f-dialog\n [header]=\"'task.manager.label.manage.title' | translate\"\n [(visible)]=\"showLabelsDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '420px' }\"\n (onHide)=\"newLabelName = ''; newLabelColor = boardColorPalette[0]\"\n >\n <div class=\"flex flex-col gap-4 py-2\">\n <div class=\"flex gap-2 items-end\">\n <div class=\"flex flex-col gap-1 flex-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.label.name.label' | translate\n }}</label>\n <input\n fInputText\n [(ngModel)]=\"newLabelName\"\n [placeholder]=\"'task.manager.label.name.placeholder' | translate\"\n />\n </div>\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.label.color.label' | translate\n }}</label>\n <input\n type=\"color\"\n [(ngModel)]=\"newLabelColor\"\n class=\"w-10 h-10 rounded cursor-pointer border border-surface\"\n />\n </div>\n <button\n fButton\n size=\"small\"\n (click)=\"addLabel()\"\n [disabled]=\"isAddingLabel()\"\n >\n @if (isAddingLabel()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n } @else {\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n }\n <span pButtonLabel>{{ 'task.manager.label.add' | translate }}</span>\n </button>\n </div>\n\n <div class=\"flex flex-col gap-2 max-h-64 overflow-y-auto\">\n @if (state.labels().length === 0) {\n <p class=\"text-sm text-muted-color text-center py-4\">\n {{ 'task.manager.label.no.labels' | translate }}\n </p>\n }\n @for (label of state.labels(); track label.id) {\n <div\n class=\"flex items-center justify-between px-3 py-2 rounded-border border border-surface\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n class=\"w-4 h-4 rounded-full flex-shrink-0\"\n [style.background-color]=\"label.color\"\n ></span>\n <span class=\"text-sm font-medium\">{{ label.name }}</span>\n </div>\n <button\n fButton\n severity=\"danger\"\n size=\"small\"\n [text]=\"true\"\n (click)=\"removeLabel(label.id)\"\n >\n <ui-icon pButtonIcon name=\"trash\"></ui-icon>\n </button>\n </div>\n }\n </div>\n </div>\n </f-dialog>\n\n <f-dialog\n [header]=\"'task.manager.member.manage.title' | translate\"\n [(visible)]=\"showMembersDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '480px' }\"\n (onHide)=\"resetMemberForm()\"\n >\n <div class=\"flex flex-col gap-4 py-2\">\n <div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.member.user.label' | translate\n }}</label>\n <lib-user-select\n [(value)]=\"selectedMemberUser\"\n [isEditMode]=\"true\"\n [placeHolder]=\"'task.manager.member.user.placeholder' | translate\"\n />\n </div>\n\n <div class=\"flex gap-2 items-end\">\n <div class=\"flex flex-col gap-1 flex-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.member.role.label' | translate\n }}</label>\n <f-select\n [(ngModel)]=\"newMemberRole\"\n [options]=\"memberRoleOptions\"\n optionValue=\"value\"\n [placeholder]=\"\n 'task.manager.member.role.placeholder' | translate\n \"\n class=\"w-full\"\n >\n <ng-template #selectedItem let-opt>\n {{ opt.labelKey | translate }}\n </ng-template>\n <ng-template #item let-opt>\n {{ opt.labelKey | translate }}\n </ng-template>\n </f-select>\n </div>\n <button\n fButton\n size=\"small\"\n [disabled]=\"!selectedMemberUser || isAddingMember()\"\n (click)=\"addMember()\"\n >\n @if (isAddingMember()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n } @else {\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n }\n <span pButtonLabel>{{\n 'task.manager.member.add' | translate\n }}</span>\n </button>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-2 max-h-72 overflow-y-auto\">\n @if (state.members().length === 0) {\n <p class=\"text-sm text-muted-color text-center py-4\">\n {{ 'task.manager.member.no.members' | translate }}\n </p>\n }\n @for (member of state.members(); track member.id) {\n <div\n class=\"flex items-center justify-between px-3 py-2.5 rounded-border border border-surface\"\n >\n <div class=\"flex items-center gap-3\">\n <f-avatar\n shape=\"circle\"\n [label]=\"memberInitials(member)\"\n [color]=\"memberColor(member)\"\n styleClass=\"!text-[11px] flex-shrink-0\"\n />\n <div>\n <div class=\"text-sm font-medium \">\n {{ member.userName ?? member.userId }}\n </div>\n @if (member.userEmail) {\n <div class=\"text-xs text-muted-color\">\n {{ member.userEmail }}\n </div>\n }\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <span\n class=\"text-[10px] font-semibold px-2 py-0.5 rounded-full\"\n [class]=\"roleBadgeClass(member.role)\"\n >\n {{\n 'task.manager.member.role.' + member.role.toLowerCase()\n | translate\n }}\n </span>\n @if (member.role !== 'OWNER') {\n <button\n fButton\n severity=\"danger\"\n size=\"small\"\n [text]=\"true\"\n (click)=\"removeMember(member.userId)\"\n >\n <ui-icon pButtonIcon name=\"trash\"></ui-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </f-dialog>\n\n <f-dialog\n [header]=\"'task.manager.list.new' | translate\"\n [(visible)]=\"showAddListDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '360px' }\"\n (onHide)=\"newListName = ''\"\n >\n <div class=\"flex flex-col gap-2 py-2\">\n <label class=\"text-sm font-medium\">\n {{ 'task.manager.list.name.label' | translate }}\n <span class=\"text-red-500\">*</span>\n </label>\n <input\n fInputText\n [(ngModel)]=\"newListName\"\n [placeholder]=\"'task.manager.list.name.placeholder' | translate\"\n />\n </div>\n <ng-template #footer>\n <div class=\"flex justify-end gap-2\">\n <button\n fButton\n severity=\"secondary\"\n (click)=\"showAddListDialog.set(false)\"\n >\n <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>\n </button>\n <button fButton (click)=\"addList()\" [disabled]=\"isAddingList()\">\n @if (isAddingList()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n }\n <span pButtonLabel>{{ 'shared.create' | translate }}</span>\n </button>\n </div>\n </ng-template>\n </f-dialog>\n\n <f-dialog\n [header]=\"'task.manager.board.edit' | translate\"\n [(visible)]=\"showEditBoardDialog\"\n [modal]=\"true\"\n [style]=\"{ width: '420px' }\"\n >\n <div class=\"flex flex-col gap-4 py-2\">\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">\n {{ 'task.manager.board.name.label' | translate }}\n <span class=\"text-red-500\">*</span>\n </label>\n <input fInputText [(ngModel)]=\"editBoardName\" />\n </div>\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.board.description.label' | translate\n }}</label>\n <textarea\n fTextarea\n [(ngModel)]=\"editBoardDescription\"\n [rows]=\"3\"\n [placeholder]=\"\n 'task.manager.board.description.placeholder' | translate\n \"\n ></textarea>\n </div>\n <div class=\"flex flex-col gap-1\">\n <label class=\"text-sm font-medium\">{{\n 'task.manager.board.color.label' | translate\n }}</label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"color\"\n [(ngModel)]=\"editBoardColor\"\n class=\"w-10 h-10 rounded cursor-pointer border border-surface\"\n />\n <div class=\"flex gap-2 flex-wrap\">\n @for (color of boardColorPalette; track color) {\n <button\n type=\"button\"\n class=\"w-6 h-6 rounded-full border-2 transition-transform hover:scale-110\"\n [style.background-color]=\"color\"\n [class.border-surface-900]=\"editBoardColor === color\"\n [class.dark:border-surface-0]=\"editBoardColor === color\"\n [class.border-transparent]=\"editBoardColor !== color\"\n (click)=\"editBoardColor = color\"\n ></button>\n }\n </div>\n </div>\n </div>\n </div>\n <ng-template #footer>\n <div class=\"flex justify-end gap-2\">\n <button\n fButton\n severity=\"secondary\"\n (click)=\"showEditBoardDialog.set(false)\"\n >\n <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>\n </button>\n <button\n fButton\n [disabled]=\"!editBoardName.trim() || isSavingBoard()\"\n (click)=\"saveBoard()\"\n >\n @if (isSavingBoard()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n }\n <span pButtonLabel>{{ 'shared.save' | translate }}</span>\n </button>\n </div>\n </ng-template>\n </f-dialog>\n `,\n})\nexport class BoardViewPageComponent {\n protected readonly state = inject(TaskBoardStateService);\n private readonly listApi = inject(TaskListApiService);\n private readonly confirmationService = inject(ConfirmationService);\n private readonly messageService = inject(MessageService);\n private readonly translateAdapter = inject(TranslateService, {\n optional: true,\n });\n private readonly destroyRef = inject(DestroyRef);\n\n private translate(\n key: string,\n vars?: Record<string, string | number>,\n ): string {\n return this.translateAdapter?.translate(key, vars) ?? key;\n }\n\n readonly boardId = input.required<string>();\n\n readonly viewMode = signal<ViewMode>('board');\n readonly showTaskForm = signal(false);\n readonly editingTask = signal<ITask | null>(null);\n readonly activeListId = signal('');\n readonly showAddListDialog = signal(false);\n readonly isAddingList = signal(false);\n readonly showLabelsDialog = signal(false);\n readonly isAddingLabel = signal(false);\n readonly showMembersDialog = signal(false);\n readonly isAddingMember = signal(false);\n\n readonly editingListId = signal<string | null>(null);\n readonly editingListNameValue = signal('');\n readonly showEditBoardDialog = signal(false);\n readonly isSavingBoard = signal(false);\n\n newListName = '';\n newLabelName = '';\n newLabelColor = BOARD_COLOR_PALETTE[0];\n selectedMemberUser: string | null = null;\n newMemberRole = 'MEMBER';\n editBoardName = '';\n editBoardDescription = '';\n editBoardColor = BOARD_COLOR_PALETTE[0];\n\n readonly boardColorPalette = BOARD_COLOR_PALETTE;\n\n readonly memberRoleOptions = [\n { labelKey: 'task.manager.member.role.admin', value: 'ADMIN' },\n { labelKey: 'task.manager.member.role.member', value: 'MEMBER' },\n { labelKey: 'task.manager.member.role.viewer', value: 'VIEWER' },\n ];\n\n readonly allTasksFlat = computed<ITaskRow[]>(() =>\n this.state.listsWithTasks().flatMap((list) =>\n list.tasks.map((task) => ({\n ...task,\n listName: list.name,\n listColor: list.color,\n })),\n ),\n );\n\n readonly listDropIds = computed(() =>\n this.state.listsWithTasks().map((l) => l.id),\n );\n\n constructor() {\n effect(() => {\n const id = this.boardId();\n untracked(() => void this.state.openBoard(id).catch(() => undefined));\n });\n this.destroyRef.onDestroy(() => this.state.closeBoard());\n }\n\n openAddTask(list: ITaskList): void {\n this.editingTask.set(null);\n this.activeListId.set(list.id);\n this.showTaskForm.set(true);\n }\n\n onTaskSaved(task: ITask): void {\n this.state.addTaskOptimistic(task);\n }\n\n onDeleteTask(task: ITask): void {\n this.confirmationService.confirm({\n message: this.translate('shared.confirm.delete.item', {\n name: task.title,\n }),\n header: this.translate('task.detail.delete'),\n icon: 'exclamation-triangle',\n acceptButtonStyleClass: 'p-button-danger',\n accept: async () => {\n try {\n await this.state.deleteTask(task.id);\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: this.translate('task.delete.success'),\n });\n } catch {}\n },\n });\n }\n\n onEditTask(task: ITask): void {\n this.editingTask.set(task);\n this.activeListId.set(task.listId);\n this.showTaskForm.set(true);\n this.state.selectTask(null);\n }\n\n getMemberName(assigneeId: string): string | null {\n return (\n this.state.members().find((m) => m.userId === assigneeId)?.userName ??\n null\n );\n }\n\n getMemberInitials(assigneeId: string): string {\n return memberInitials(this.getMemberName(assigneeId));\n }\n\n memberInitials(member: ITaskBoardMember): string {\n return memberInitials(member.userName);\n }\n\n memberColor(member: ITaskBoardMember): string {\n return memberColorFromId(member.userId);\n }\n\n memberColorById(userId: string): string {\n return memberColorFromId(userId);\n }\n\n roleBadgeClass(role: string): string {\n const map: Record<string, string> = {\n OWNER: 'text-primary-600 dark:text-primary-400 font-semibold',\n ADMIN: 'text-orange-600 dark:text-orange-400 font-semibold',\n MEMBER: 'text-surface-500 dark:text-surface-400',\n VIEWER: 'text-surface-400 dark:text-surface-500',\n };\n return map[role] ?? map['MEMBER'];\n }\n\n protected readonly prioritySeverity = taskPrioritySeverity;\n protected readonly isOverdue = isTaskOverdue;\n\n async onTaskDrop(event: CdkDragDrop<ITask[]>): Promise<void> {\n const sameList = event.previousContainer === event.container;\n if (sameList && event.previousIndex === event.currentIndex) return;\n const task = event.item?.data ?? event.previousContainer.data[event.previousIndex];\n if (!task) return;\n try {\n await this.state.moveTask(task.id, event.container.id, event.currentIndex);\n } catch {}\n }\n\n async onListDrop(event: CdkDragDrop<ITaskListWithTasks[]>): Promise<void> {\n if (event.previousIndex === event.currentIndex) return;\n const lists = this.state.listsWithTasks().slice();\n moveItemInArray(lists, event.previousIndex, event.currentIndex);\n const orderedIds = lists.map((l) => l.id);\n try {\n await this.state.reorderLists(this.boardId(), orderedIds);\n } catch {}\n }\n\n startRenameList(list: ITaskList): void {\n this.editingListId.set(list.id);\n this.editingListNameValue.set(list.name);\n }\n\n cancelRenameList(): void {\n this.editingListId.set(null);\n this.editingListNameValue.set('');\n }\n\n async saveRenameList(): Promise<void> {\n const id = this.editingListId();\n const name = this.editingListNameValue().trim();\n this.cancelRenameList();\n if (!id || !name) return;\n await this.state.renameList(id, name);\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: this.translate('task.list.update.success'),\n });\n }\n\n openEditBoardDialog(board: ITaskBoard): void {\n this.editBoardName = board.name;\n this.editBoardDescription = board.description ?? '';\n this.editBoardColor = board.color;\n this.showEditBoardDialog.set(true);\n }\n\n async saveBoard(): Promise<void> {\n const board = this.state.activeBoard();\n if (!board || !this.editBoardName.trim()) return;\n this.isSavingBoard.set(true);\n try {\n await this.state.updateBoard({\n id: board.id,\n name: this.editBoardName.trim(),\n description: this.editBoardDescription.trim() || undefined,\n color: this.editBoardColor,\n });\n this.showEditBoardDialog.set(false);\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: this.translate('task.board.update.success'),\n });\n } finally {\n this.isSavingBoard.set(false);\n }\n }\n\n async addLabel(): Promise<void> {\n if (!this.newLabelName.trim()) return;\n this.isAddingLabel.set(true);\n try {\n await this.state.addLabel(\n this.boardId(),\n this.newLabelName.trim(),\n this.newLabelColor,\n );\n this.newLabelName = '';\n this.newLabelColor = BOARD_COLOR_PALETTE[0];\n } finally {\n this.isAddingLabel.set(false);\n }\n }\n\n async removeLabel(labelId: string): Promise<void> {\n await this.state.deleteLabel(labelId);\n }\n\n async addMember(): Promise<void> {\n if (!this.selectedMemberUser) return;\n this.isAddingMember.set(true);\n try {\n await this.state.addMember(this.selectedMemberUser, this.newMemberRole);\n this.resetMemberForm();\n } finally {\n this.isAddingMember.set(false);\n }\n }\n\n resetMemberForm(): void {\n this.selectedMemberUser = null;\n this.newMemberRole = 'MEMBER';\n }\n\n async removeMember(userId: string): Promise<void> {\n await this.state.removeMember(userId);\n }\n\n async addList(): Promise<void> {\n if (!this.newListName.trim()) return;\n this.isAddingList.set(true);\n try {\n const position = this.state.lists().length * 100;\n await this.listApi.insert({\n boardId: this.boardId(),\n name: this.newListName.trim(),\n position,\n });\n await this.state.openBoard(this.boardId());\n this.showAddListDialog.set(false);\n this.newListName = '';\n } finally {\n this.isAddingList.set(false);\n }\n }\n}\n"],"mappings":"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"}
|