docs: remove outdated implementation documentation files - Deleted AI_TOOLS_IMPLEMENTATION.md (296 lines) - outdated AI tools integration guide - Deleted ALIGN_INDENT_COLUMNS_FIX.md (557 lines) - obsolete column alignment fix documentation - Deleted BLOCK_COMMENTS_IMPLEMENTATION.md (400 lines) - superseded block comments implementation notes - Deleted DRAG_DROP_COLUMNS_IMPLEMENTATION.md (500 lines) - outdated drag-and-drop columns guide - Deleted INLINE_TOOLBAR_IMPLEMENTATION.md (350 lines) - obsol
210 lines
5.1 KiB
TypeScript
210 lines
5.1 KiB
TypeScript
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<KanbanBoard | null>(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)}`;
|
|
}
|
|
}
|