import { Injectable, signal, computed } from '@angular/core'; import { KanbanBoard, KanbanColumn, KanbanTask } from '../models/kanban.types'; /** * KanbanBoardService - Main state management for Kanban boards * Angular 20 + Signals */ @Injectable() export class KanbanBoardService { // Board state private readonly _board = signal(null); readonly board = this._board.asReadonly(); // Computed signals readonly columns = computed(() => this._board()?.columns ?? []); readonly tasks = computed(() => { const cols = this.columns(); return cols.flatMap(col => col.tasks); }); /** * Initialize a new board with default columns */ initializeBoard(blockId: string): void { const now = new Date(); const board: KanbanBoard = { id: blockId, title: 'Board', columns: [ { id: this.generateId(), title: 'Column 1', tasks: [], order: 0, boardId: blockId }, { id: this.generateId(), title: 'Column 2', tasks: [], order: 1, boardId: blockId } ], createdAt: now, updatedAt: now }; this._board.set(board); } /** * Load board from serialized data */ loadBoard(data: KanbanBoard): void { this._board.set(data); } /** * Get current board state for serialization */ serializeBoard(): KanbanBoard | null { return this._board(); } /** * Add a new column */ addColumn(position: 'left' | 'right', targetColumnId?: string): void { const board = this._board(); if (!board) return; const columns = [...board.columns]; let order = columns.length; if (targetColumnId) { const targetIndex = columns.findIndex(c => c.id === targetColumnId); if (targetIndex !== -1) { order = position === 'left' ? targetIndex : targetIndex + 1; // Shift subsequent columns columns.forEach(col => { if (col.order >= order) { col.order++; } }); } } const newColumn: KanbanColumn = { id: this.generateId(), title: `Column ${columns.length + 1}`, tasks: [], order, boardId: board.id }; columns.splice(order, 0, newColumn); this._board.update(b => b ? { ...b, columns, updatedAt: new Date() } : null); } /** * Rename a column */ renameColumn(columnId: string, newTitle: string): void { this._board.update(board => { if (!board) return null; const columns = board.columns.map(col => col.id === columnId ? { ...col, title: newTitle } : col ); return { ...board, columns, updatedAt: new Date() }; }); } /** * Delete a column */ deleteColumn(columnId: string): void { this._board.update(board => { if (!board) return null; const columns = board.columns .filter(col => col.id !== columnId) .map((col, index) => ({ ...col, order: index })); return { ...board, columns, updatedAt: new Date() }; }); } /** * Duplicate a column */ duplicateColumn(columnId: string): void { const board = this._board(); if (!board) return; const sourceColumn = board.columns.find(c => c.id === columnId); if (!sourceColumn) return; const newColumn: KanbanColumn = { ...sourceColumn, id: this.generateId(), title: `${sourceColumn.title} (copy)`, order: sourceColumn.order + 1, tasks: sourceColumn.tasks.map(task => ({ ...task, id: this.generateId(), createdAt: new Date() })) }; const columns = [...board.columns]; columns.splice(newColumn.order, 0, newColumn); // Reorder subsequent columns columns.forEach((col, index) => { col.order = index; }); this._board.update(b => b ? { ...b, columns, updatedAt: new Date() } : null); } /** * Complete all tasks in a column */ completeAllTasks(columnId: string): void { this._board.update(board => { if (!board) return null; const columns = board.columns.map(col => { if (col.id !== columnId) return col; const tasks = col.tasks.map(task => ({ ...task, completed: true })); return { ...col, tasks }; }); return { ...board, columns, updatedAt: new Date() }; }); } /** * Reorder columns (after drag & drop) */ reorderColumns(sourceIndex: number, targetIndex: number): void { this._board.update(board => { if (!board) return null; const columns = [...board.columns]; const [movedColumn] = columns.splice(sourceIndex, 1); columns.splice(targetIndex, 0, movedColumn); // Update orders columns.forEach((col, index) => { col.order = index; }); return { ...board, columns, updatedAt: new Date() }; }); } /** * Generate unique ID */ private generateId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } }