feat: Kanban board with five-column view, drag-and-drop, detail drawer, anomaly detection (Phase 7)

Backend: new kanban API with mock data, five-column task aggregation, task detail, drag-to-move
with block reason validation, and server-side anomaly detection (blocked_timeout, unclaimed,
retry_failure).

Frontend: full kanban page with summary bar + filters, five-column board with BLOCKED orange
border, task cards with color-coded left bar and anomaly highlighting, HTML5 native drag-and-drop
(desktop only), block reason modal, done confirmation, 480px detail drawer with meta/description/
dependencies/activity timeline, floating batch operations bar, and optimistic updates with rollback.

Drawer component: title prop changed from string to ReactNode to support rich title content.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
enzo
2026-07-22 17:31:03 +08:00
co-authored by Claude
parent 3d905b606a
commit 5c0352c768
5 changed files with 1300 additions and 4 deletions
+55
View File
@@ -0,0 +1,55 @@
import client from './client'
export interface KanbanTask {
id: string
title: string
board: string
worker_profile: string
worker_type: string
priority: number
column: string
created_at: string
updated_at: string
goal_mode: boolean
retries: number
failures: number
description: string
block_reason: string | null
dependencies: string[]
dependents: string[]
activities: { time: string; user: string; action: string }[]
anomalies: string[]
dependency_tasks?: { id: string; title: string; column: string }[]
dependent_tasks?: { id: string; title: string; column: string }[]
}
export interface KanbanOverview {
total_tasks: number
profile_count: number
board_count: number
running_count: number
blocked_count: number
}
export interface KanbanResponse {
todo: KanbanTask[]
ready: KanbanTask[]
running: KanbanTask[]
blocked: KanbanTask[]
done: KanbanTask[]
overview: KanbanOverview
}
export interface MoveResponse {
success: boolean
task: KanbanTask
}
export const getKanbanTasks = (params?: { worker?: string; board?: string; anomaly?: string }) =>
client.get<KanbanResponse>('/kanban/tasks', { params })
export const getKanbanTaskDetail = (taskId: string) =>
client.get<KanbanTask>(`/kanban/tasks/${taskId}`)
export const moveKanbanTask = (taskId: string, column: string, blockReason?: string) =>
client.patch<MoveResponse>(`/kanban/tasks/${taskId}/move`, { column, block_reason: blockReason })