Chương 8: Kanban Board — Code Thành Sản Phẩm

"Lan nhìn Kanban board lần đầu và nói: 'Đẹp! Nhưng drag-drop chưa smooth.' Đức cười: 'Chưa có drag-drop — mới chỉ có layout!' Tuấn: 'Để em thêm, 15 phút là xong với AI.'"
🎯 Mục tiêu
- Build Kanban board UI (dark glassmorphism theme)
- CSS Grid + Drag & Drop (SortableJS)
- Connect frontend → API
- Design system tokens
Phần 1: Design System (20 phút)
CSS Design Tokens
css
/* public/static/css/style.css */
:root {
/* Colors */
--bg-primary: #0f0f23;
--bg-secondary: #1a1a3e;
--bg-card: rgba(255, 255, 255, 0.05);
--bg-card-hover: rgba(255, 255, 255, 0.1);
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--accent: #6366f1;
--accent-hover: #818cf8;
--success: #22c55e;
--warning: #f59e0b;
--danger: #ef4444;
--urgent: #dc2626;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
/* Effects */
--glass: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.1);
--shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
--radius: 12px;
--radius-sm: 8px;
}Phần 2: Kanban Layout (30 phút)
Lab: Build Board
bash
antigravity "@cm-design-system Build Kanban board for TeamFlow:
File: public/index.html + public/static/css/style.css + public/static/js/app.js
Layout:
- Sidebar (240px): project list, user avatar, settings
- Main: 4 columns (To Do, In Progress, Review, Done)
- Each column: header with count + task cards
- Task card: title, priority badge, assignee avatar, due date
- Add task button per column
Design:
- Dark mode with glassmorphism
- Google Font Inter
- Smooth hover animations
- Priority colors: low=green, medium=yellow, high=orange, urgent=red
- Drag & drop between columns (SortableJS CDN)
Responsive:
- Desktop: 4 columns side by side
- Tablet: 2 columns, scroll
- Mobile: 1 column, horizontal swipe"Board Structure
html
<div class="app-layout">
<aside class="sidebar">
<div class="logo">TeamFlow</div>
<nav class="project-list"><!-- projects --></nav>
<div class="user-menu"><!-- avatar + name --></div>
</aside>
<main class="board">
<header class="board-header">
<h1>Sprint 1 — Foundation</h1>
<div class="board-stats"><!-- counts --></div>
</header>
<div class="columns">
<div class="column" data-status="todo">
<div class="column-header">
<span>📋 To Do</span>
<span class="count">3</span>
</div>
<div class="task-list" id="todo">
<!-- Task cards rendered here -->
</div>
<button class="add-task">+ Add Task</button>
</div>
<!-- Repeat for in_progress, review, done -->
</div>
</main>
</div>Phần 3: Drag & Drop (25 phút)
SortableJS Integration
javascript
// public/static/js/app.js
import Sortable from 'https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/+esm'
document.querySelectorAll('.task-list').forEach(column => {
new Sortable(column, {
group: 'kanban',
animation: 200,
ghostClass: 'task-ghost',
dragClass: 'task-drag',
onEnd: async (evt) => {
const taskId = evt.item.dataset.id
const newStatus = evt.to.parentElement.dataset.status
// Update via API
await fetch(`/api/tasks/${taskId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify({ status: newStatus })
})
updateColumnCounts()
}
})
})Phần 4: API Connection (15 phút)
javascript
async function loadBoard(projectId) {
const res = await fetch(`/api/tasks?project_id=${projectId}`, {
headers: { 'Authorization': `Bearer ${getToken()}` }
})
const tasks = await res.json()
// Clear columns
document.querySelectorAll('.task-list').forEach(el => el.innerHTML = '')
// Distribute tasks to columns
tasks.forEach(task => {
const column = document.getElementById(task.status)
column.appendChild(createTaskCard(task))
})
updateColumnCounts()
}Homework
- [ ] Kanban board rendering
- [ ] Drag & drop working
- [ ] API connected
- [ ] Mobile responsive
Chương tiếp: Testing Như Thở — TDD & Test Gate →