feat: MySQL integration, Docker setup, drag-and-drop kanban

This commit is contained in:
tusuii
2026-02-16 10:20:27 +05:30
parent 5d8af1f173
commit 892a2ceba1
24 changed files with 919 additions and 196 deletions

View File

@@ -1,17 +1,27 @@
import { useState } from 'react';
import type { Task, User, Status } from './data';
import { PRIORITY_COLORS, STATUS_COLORS, STATUS_LABELS } from './data';
import { Avatar, PriorityBadge, StatusBadge, ProgressBar } from './Shared';
function TaskCard({ task, onClick }: { task: Task; onClick: () => void }) {
function TaskCard({ task, onClick, users }: { task: Task; onClick: () => void; users: User[] }) {
const p = PRIORITY_COLORS[task.priority];
const due = new Date(task.dueDate + 'T00:00:00');
const overdue = due < new Date() && task.status !== 'done';
const commCount = task.comments.length;
return (
<div className="task-card" style={{ borderLeftColor: p.color }} onClick={onClick}>
<div className="task-card" style={{ borderLeftColor: p.color }}
draggable
onDragStart={e => {
e.dataTransfer.setData('text/plain', task.id);
e.dataTransfer.effectAllowed = 'move';
(e.currentTarget as HTMLElement).classList.add('dragging');
}}
onDragEnd={e => (e.currentTarget as HTMLElement).classList.remove('dragging')}
onClick={onClick}
>
<div className="task-card-row">
<span className="task-card-title">{task.title}</span>
<Avatar userId={task.assignee} size={24} />
<Avatar userId={task.assignee} size={24} users={users} />
</div>
<div className="task-card-badges">
<PriorityBadge level={task.priority} />
@@ -27,12 +37,29 @@ function TaskCard({ task, onClick }: { task: Task; onClick: () => void }) {
);
}
function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTask }: {
function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTask, onMoveTask, users }: {
status: Status; statusLabel: string; tasks: Task[]; color: string;
onTaskClick: (t: Task) => void; onAddTask: (s: Status) => void;
onMoveTask: (taskId: string, newStatus: Status) => void; users: User[];
}) {
const [dragOver, setDragOver] = useState(false);
return (
<div className="kanban-column">
<div
className={`kanban-column ${dragOver ? 'kanban-column-dragover' : ''}`}
onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOver(true); }}
onDragEnter={e => { e.preventDefault(); setDragOver(true); }}
onDragLeave={e => {
// Only set false if leaving the column (not entering a child)
if (!e.currentTarget.contains(e.relatedTarget as Node)) setDragOver(false);
}}
onDrop={e => {
e.preventDefault();
setDragOver(false);
const taskId = e.dataTransfer.getData('text/plain');
if (taskId) onMoveTask(taskId, status);
}}
>
<div className="kanban-col-header">
<div className="kanban-col-dot" style={{ background: color }} />
<span className="kanban-col-label">{statusLabel}</span>
@@ -41,9 +68,11 @@ function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTas
</div>
<div className="kanban-col-body">
{tasks.length === 0 ? (
<div className="kanban-empty">No tasks here · Click + to add one</div>
<div className="kanban-empty">
{dragOver ? '⬇ Drop here' : 'No tasks here · Click + to add one'}
</div>
) : (
tasks.map(t => <TaskCard key={t.id} task={t} onClick={() => onTaskClick(t)} />)
tasks.map(t => <TaskCard key={t.id} task={t} onClick={() => onTaskClick(t)} users={users} />)
)}
</div>
</div>
@@ -52,10 +81,11 @@ function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTas
interface KanbanProps {
tasks: Task[]; currentUser: User; onTaskClick: (t: Task) => void;
onAddTask: (s: Status) => void; filterUser: string | null; searchQuery: string;
onAddTask: (s: Status) => void; onMoveTask: (taskId: string, newStatus: Status) => void;
filterUser: string | null; searchQuery: string; users: User[];
}
export function KanbanBoard({ tasks, currentUser, onTaskClick, onAddTask, filterUser, searchQuery }: KanbanProps) {
export function KanbanBoard({ tasks, currentUser, onTaskClick, onAddTask, onMoveTask, filterUser, searchQuery, users }: KanbanProps) {
let filtered = tasks;
if (currentUser.role === 'employee') filtered = filtered.filter(t => t.assignee === currentUser.id);
if (filterUser) filtered = filtered.filter(t => t.assignee === filterUser);
@@ -66,7 +96,8 @@ export function KanbanBoard({ tasks, currentUser, onTaskClick, onAddTask, filter
<div className="kanban-board">
{statuses.map(s => (
<KanbanColumn key={s} status={s} statusLabel={STATUS_LABELS[s]} color={STATUS_COLORS[s]}
tasks={filtered.filter(t => t.status === s)} onTaskClick={onTaskClick} onAddTask={onAddTask} />
tasks={filtered.filter(t => t.status === s)} onTaskClick={onTaskClick}
onAddTask={onAddTask} onMoveTask={onMoveTask} users={users} />
))}
</div>
);