Skip to content

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 →

Powered by CodyMaster × VitePress