Files
scrum-manager/src/Kanban.tsx
tusuii e46d8773ee feat: complete Scrum-manager MVP — dark-themed multi-user task manager
- Login with role-based auth (CTO/Manager/Employee)
- Calendar view (month/week) with task chips and quick-add
- Kanban board with status columns
- Sortable list view with action menus
- Task detail drawer with subtasks, comments, activity
- Add task modal with validation
- Dashboard with stats, workload, priority breakdown
- Team tasks grouped by assignee
- Reports page with recharts (bar, pie, line, horizontal bar)
- Members page with invite modal
- Search and assignee filter across views
- ErrorBoundary for production error handling
- Full dark design system via index.css
2026-02-15 11:36:38 +05:30

74 lines
3.5 KiB
TypeScript

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 }) {
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-row">
<span className="task-card-title">{task.title}</span>
<Avatar userId={task.assignee} size={24} />
</div>
<div className="task-card-badges">
<PriorityBadge level={task.priority} />
<StatusBadge status={task.status} />
{task.tags.slice(0, 2).map(t => <span key={t} className="tag-pill" style={{ fontSize: 9 }}>{t}</span>)}
</div>
{task.subtasks.length > 0 && <ProgressBar subtasks={task.subtasks} />}
<div className="task-card-meta">
<span className={overdue ? 'task-card-overdue' : ''}>📅 {due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</span>
{commCount > 0 && <span>💬 {commCount}</span>}
</div>
</div>
);
}
function KanbanColumn({ status, statusLabel, tasks, color, onTaskClick, onAddTask }: {
status: Status; statusLabel: string; tasks: Task[]; color: string;
onTaskClick: (t: Task) => void; onAddTask: (s: Status) => void;
}) {
return (
<div className="kanban-column">
<div className="kanban-col-header">
<div className="kanban-col-dot" style={{ background: color }} />
<span className="kanban-col-label">{statusLabel}</span>
<span className="kanban-col-count">{tasks.length}</span>
<button className="kanban-col-add" onClick={() => onAddTask(status)}>+</button>
</div>
<div className="kanban-col-body">
{tasks.length === 0 ? (
<div className="kanban-empty">No tasks here · Click + to add one</div>
) : (
tasks.map(t => <TaskCard key={t.id} task={t} onClick={() => onTaskClick(t)} />)
)}
</div>
</div>
);
}
interface KanbanProps {
tasks: Task[]; currentUser: User; onTaskClick: (t: Task) => void;
onAddTask: (s: Status) => void; filterUser: string | null; searchQuery: string;
}
export function KanbanBoard({ tasks, currentUser, onTaskClick, onAddTask, filterUser, searchQuery }: 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);
if (searchQuery) filtered = filtered.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase()));
const statuses: Status[] = ['todo', 'inprogress', 'review', 'done'];
return (
<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} />
))}
</div>
);
}