← Components/Project Management

KanbanBoard

kanbanboard-1779379195813.tsx
'use client'
import { useState } from 'react'

type Priority = 'high' | 'medium' | 'low'
type Status = 'todo' | 'in-progress' | 'review' | 'done'

interface Card {
  id: number
  title: string
  priority: Priority
  assignee: string
  tags: string[]
}

const PRIORITY_COLORS = { high: '#ef4444', medium: '#f59e0b', low: '#22c55e' }

const INITIAL: Record<Status, Card[]> = {
  'todo': [
    { id: 1, title: 'Design pricing page', priority: 'high', assignee: 'SC', tags: ['design', 'frontend'] },
    { id: 2, title: 'Setup pgvector index', priority: 'medium', assignee: 'MW', tags: ['backend', 'db'] },
    { id: 3, title: 'Write MCP docs', priority: 'low', assignee: 'PP', tags: ['docs'] },
  ],
  'in-progress': [
    { id: 4, title: 'User auth system', priority: 'high', assignee: 'AK', tags: ['backend', 'auth'] },
    { id: 5, title: 'Component catalogue UI', priority: 'medium', assignee: 'SC', tags: ['frontend'] },
  ],
  'review': [
    { id: 6, title: 'Admin panel CRUD', priority: 'medium', assignee: 'MW', tags: ['frontend', 'admin'] },
  ],
  'done': [
    { id: 7, title: 'Wave 1 generation (20 components)', priority: 'high', assignee: 'AI', tags: ['generation'] },
    { id: 8, title: 'Docker deployment setup', priority: 'high', assignee: 'AK', tags: ['devops'] },
  ],
}

const COLS: { id: Status; label: string; color: string }[] = [
  { id: 'todo', label: 'To Do', color: '#64748b' },
  { id: 'in-progress', label: 'In Progress', color: '#C9A84C' },
  { id: 'review', label: 'Review', color: '#6366f1' },
  { id: 'done', label: 'Done', color: '#22c55e' },
]

const AVATAR_COLORS: Record<string, string> = { SC: '#C9A84C', MW: '#6366f1', PP: '#22c55e', AK: '#ef4444', AI: '#f59e0b' }

export default function KanbanBoard() {
  const [board, setBoard] = useState(INITIAL)
  const [dragging, setDragging] = useState<{ card: Card; from: Status } | null>(null)

  function onDrop(to: Status) {
    if (!dragging || dragging.from === to) return
    setBoard(b => {
      const next = { ...b }
      next[dragging.from] = next[dragging.from].filter(c => c.id !== dragging.card.id)
      next[to] = [...next[to], dragging.card]
      return next
    })
    setDragging(null)
  }

  return (
    <div style={{ background: '#080808', padding: 20, overflowX: 'auto' }}>
      <div style={{ display: 'flex', gap: 12, minWidth: 680 }}>
        {COLS.map(col => (
          <div key={col.id} onDragOver={e => e.preventDefault()} onDrop={() => onDrop(col.id)}
            style={{ flex: 1, background: '#0F0F0F', border: `1px solid ${dragging ? 'rgba(201,168,76,0.2)' : 'rgba(255,255,255,0.06)'}`, borderRadius: 14, overflow: 'hidden', transition: 'border-color 0.2s', minWidth: 160 }}>
            <div style={{ padding: '12px 14px', borderBottom: '1px solid rgba(255,255,255,0.06)', display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{ width: 8, height: 8, borderRadius: '50%', background: col.color }} />
              <span style={{ color: '#F5F5F0', fontSize: 13, fontWeight: 600 }}>{col.label}</span>
              <span style={{ marginLeft: 'auto', background: 'rgba(255,255,255,0.08)', color: 'rgba(255,255,255,0.4)', borderRadius: 20, padding: '1px 7px', fontSize: 11 }}>{board[col.id].length}</span>
            </div>

            <div style={{ padding: 10, display: 'flex', flexDirection: 'column', gap: 8, minHeight: 200 }}>
              {board[col.id].map(card => (
                <div key={card.id} draggable onDragStart={() => setDragging({ card, from: col.id })} onDragEnd={() => setDragging(null)}
                  style={{ background: '#1a1a1a', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 10, padding: 12, cursor: 'grab', opacity: dragging?.card.id === card.id ? 0.5 : 1, transition: 'opacity 0.2s' }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8 }}>
                    <span style={{ color: '#F5F5F0', fontSize: 13, fontWeight: 500, lineHeight: 1.4 }}>{card.title}</span>
                    <div style={{ width: 6, height: 6, borderRadius: '50%', background: PRIORITY_COLORS[card.priority], flexShrink: 0, marginTop: 4, marginLeft: 6 }} />
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                      {card.tags.map(tag => (
                        <span key={tag} style={{ background: 'rgba(255,255,255,0.06)', color: 'rgba(255,255,255,0.4)', padding: '1px 6px', borderRadius: 4, fontSize: 10 }}>{tag}</span>
                      ))}
                    </div>
                    <div style={{ width: 22, height: 22, borderRadius: '50%', background: AVATAR_COLORS[card.assignee] || '#555', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#0A0A0A', fontSize: 9, fontWeight: 800, flexShrink: 0 }}>{card.assignee}</div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

Component info

CategoryProject Management
Frameworkreact
TierFREE
Views0
Copies0

About

Drag-and-drop Kanban board with swimlanes, card priorities, assignees, and task counts