feat(#17): implement Kanban board view

- Drag-and-drop with @dnd-kit
- Four status columns (Not Started, In Progress, Paused, Completed)
- Task cards with priority badges and due dates
- PDA-friendly design (calm colors, gentle language)
- 70 tests (87% coverage)
- Demo page at /demo/kanban
This commit is contained in:
Jason Woltje
2026-01-29 17:55:33 -06:00
parent 5ce3bb0e28
commit 0b330464ba
9 changed files with 1700 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
"use client";
import { useState } from "react";
import { KanbanBoard } from "@/components/kanban";
import type { Task } from "@mosaic/shared";
import { TaskStatus, TaskPriority } from "@mosaic/shared";
const initialTasks: Task[] = [
{
id: "task-1",
title: "Design homepage wireframes",
description: "Create wireframes for the new homepage design",
status: TaskStatus.NOT_STARTED,
priority: TaskPriority.HIGH,
dueDate: new Date("2026-02-01"),
assigneeId: "user-1",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 0,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "task-2",
title: "Implement authentication flow",
description: "Add OAuth support with Google and GitHub",
status: TaskStatus.IN_PROGRESS,
priority: TaskPriority.HIGH,
dueDate: new Date("2026-01-30"),
assigneeId: "user-2",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 0,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "task-3",
title: "Write comprehensive unit tests",
description: "Achieve 85% test coverage for all components",
status: TaskStatus.IN_PROGRESS,
priority: TaskPriority.MEDIUM,
dueDate: new Date("2026-02-05"),
assigneeId: "user-3",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 1,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "task-4",
title: "Research state management libraries",
description: "Evaluate Zustand vs Redux Toolkit",
status: TaskStatus.PAUSED,
priority: TaskPriority.LOW,
dueDate: new Date("2026-02-10"),
assigneeId: "user-1",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 0,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "task-5",
title: "Deploy to production",
description: "Set up CI/CD pipeline with GitHub Actions",
status: TaskStatus.COMPLETED,
priority: TaskPriority.HIGH,
dueDate: new Date("2026-01-25"),
assigneeId: "user-1",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 0,
metadata: {},
completedAt: new Date("2026-01-25"),
createdAt: new Date("2026-01-20"),
updatedAt: new Date("2026-01-25"),
},
{
id: "task-6",
title: "Update API documentation",
description: "Document all REST endpoints with OpenAPI",
status: TaskStatus.COMPLETED,
priority: TaskPriority.MEDIUM,
dueDate: new Date("2026-01-27"),
assigneeId: "user-2",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 1,
metadata: {},
completedAt: new Date("2026-01-27"),
createdAt: new Date("2026-01-25"),
updatedAt: new Date("2026-01-27"),
},
{
id: "task-7",
title: "Setup database migrations",
description: "Configure Prisma migrations for production",
status: TaskStatus.NOT_STARTED,
priority: TaskPriority.MEDIUM,
dueDate: new Date("2026-02-03"),
assigneeId: "user-3",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 1,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
{
id: "task-8",
title: "Performance optimization",
description: "Improve page load time by 30%",
status: TaskStatus.PAUSED,
priority: TaskPriority.LOW,
dueDate: null,
assigneeId: "user-2",
creatorId: "user-1",
workspaceId: "workspace-1",
projectId: null,
parentId: null,
sortOrder: 1,
metadata: {},
completedAt: null,
createdAt: new Date("2026-01-28"),
updatedAt: new Date("2026-01-28"),
},
];
export default function KanbanDemoPage() {
const [tasks, setTasks] = useState<Task[]>(initialTasks);
const handleStatusChange = (taskId: string, newStatus: TaskStatus) => {
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === taskId
? {
...task,
status: newStatus,
updatedAt: new Date(),
completedAt:
newStatus === TaskStatus.COMPLETED ? new Date() : null,
}
: task
)
);
};
return (
<div className="min-h-screen bg-gray-100 dark:bg-gray-950 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 p-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Kanban Board Demo
</h1>
<p className="mt-2 text-gray-600 dark:text-gray-400">
Drag and drop tasks between columns to update their status.
</p>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-500">
{tasks.length} total tasks {tasks.filter((t) => t.status === TaskStatus.COMPLETED).length} completed
</p>
</div>
{/* Kanban Board */}
<KanbanBoard tasks={tasks} onStatusChange={handleStatusChange} />
</div>
</div>
);
}