/** * Kanban-specific types with discriminated unions * Ensures type safety for Epic, Story, and Task cards */ import { WorkItemStatus, WorkItemPriority } from './project'; // Base Kanban item interface with common properties interface BaseKanbanItem { id: string; projectId: string; status: WorkItemStatus; priority: WorkItemPriority; description?: string; estimatedHours?: number; actualHours?: number; assigneeId?: string; tenantId: string; createdAt: string; updatedAt: string; } // Epic as Kanban item - discriminated by 'type' field export interface KanbanEpic extends BaseKanbanItem { type: 'Epic'; name: string; // Epic uses 'name' instead of 'title' createdBy: string; childCount?: number; // Number of stories in this epic epicId?: never; // Epic doesn't have epicId storyId?: never; // Epic doesn't have storyId title?: never; // Epic uses 'name', not 'title' } // Story as Kanban item - discriminated by 'type' field export interface KanbanStory extends BaseKanbanItem { type: 'Story'; title: string; // Story uses 'title' epicId: string; // Story always has epicId childCount?: number; // Number of tasks in this story storyId?: never; // Story doesn't have storyId name?: never; // Story uses 'title', not 'name' } // Task as Kanban item - discriminated by 'type' field export interface KanbanTask extends BaseKanbanItem { type: 'Task'; title: string; // Task uses 'title' storyId: string; // Task always has storyId epicId?: never; // Task doesn't have epicId (only through story) childCount?: never; // Task doesn't have children name?: never; // Task uses 'title', not 'name' } // Discriminated union type for Kanban items // TypeScript can narrow the type based on the 'type' field export type KanbanItem = KanbanEpic | KanbanStory | KanbanTask; // Type guards for runtime type checking export function isKanbanEpic(item: KanbanItem): item is KanbanEpic { return item.type === 'Epic'; } export function isKanbanStory(item: KanbanItem): item is KanbanStory { return item.type === 'Story'; } export function isKanbanTask(item: KanbanItem): item is KanbanTask { return item.type === 'Task'; } // Helper to get display title regardless of type export function getKanbanItemTitle(item: KanbanItem): string { if (isKanbanEpic(item)) { return item.name; } return item.title; } // Kanban column type export interface KanbanColumn { id: WorkItemStatus; title: string; items: KanbanItem[]; } // Kanban board type export interface KanbanBoard { projectId: string; projectName: string; columns: KanbanColumn[]; } // ==================== Legacy Types (for backward compatibility) ==================== export interface TaskCard { id: string; title: string; description: string; priority: string; assigneeId?: string; estimatedHours?: number; actualHours?: number; } // Legacy KanbanColumn type for backward compatibility export interface LegacyKanbanColumn { status: string; title: string; tasks: TaskCard[]; } // Legacy KanbanBoard type export interface LegacyKanbanBoard { projectId: string; projectName: string; columns: LegacyKanbanColumn[]; }