feat: MySQL integration, Docker setup, drag-and-drop kanban
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import type { Task, User } from './data';
|
||||
import { USERS, PRIORITY_COLORS } from './data';
|
||||
import { PRIORITY_COLORS } from './data';
|
||||
import { Avatar } from './Shared';
|
||||
|
||||
interface CalendarProps {
|
||||
tasks: Task[]; currentUser: User; calMonth: { year: number; month: number }; calView: string;
|
||||
onMonthChange: (m: { year: number; month: number }) => void; onViewChange: (v: string) => void;
|
||||
onTaskClick: (t: Task) => void; onDayClick: (date: string, el: HTMLElement) => void;
|
||||
filterUser: string | null; searchQuery: string;
|
||||
filterUser: string | null; searchQuery: string; users: User[];
|
||||
}
|
||||
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
@@ -47,29 +47,29 @@ function getWeekDays(_year: number, _month: number) {
|
||||
function dateStr(d: Date) { return d.toISOString().split('T')[0]; }
|
||||
function isToday(d: Date) { const t = new Date(); return d.getDate() === t.getDate() && d.getMonth() === t.getMonth() && d.getFullYear() === t.getFullYear(); }
|
||||
|
||||
function TaskChip({ task, onClick }: { task: Task; onClick: () => void }) {
|
||||
function TaskChip({ task, onClick, users }: { task: Task; onClick: () => void; users: User[] }) {
|
||||
const p = PRIORITY_COLORS[task.priority];
|
||||
return (
|
||||
<div className="task-chip" style={{ background: p.bg, borderLeftColor: p.color }} onClick={e => { e.stopPropagation(); onClick(); }}>
|
||||
<span className="task-chip-dot" style={{ background: p.color }} />
|
||||
<span className="task-chip-title">{task.title}</span>
|
||||
<Avatar userId={task.assignee} size={14} />
|
||||
<Avatar userId={task.assignee} size={14} users={users} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MorePopover({ tasks, onTaskClick, onClose }: { tasks: Task[]; onTaskClick: (t: Task) => void; onClose: () => void }) {
|
||||
function MorePopover({ tasks, onTaskClick, onClose, users }: { tasks: Task[]; onTaskClick: (t: Task) => void; onClose: () => void; users: User[] }) {
|
||||
return (
|
||||
<div className="more-popover" onClick={e => e.stopPropagation()}>
|
||||
<div className="more-popover-title">{tasks.length} tasks</div>
|
||||
{tasks.map(t => <TaskChip key={t.id} task={t} onClick={() => { onTaskClick(t); onClose(); }} />)}
|
||||
{tasks.map(t => <TaskChip key={t.id} task={t} onClick={() => { onTaskClick(t); onClose(); }} users={users} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function QuickAddPanel({ date, onAdd, onOpenFull, onClose }: { date: string; onAdd: (t: Partial<Task>) => void; onOpenFull: () => void; onClose: () => void }) {
|
||||
export function QuickAddPanel({ date, onAdd, onOpenFull, onClose, users }: { date: string; onAdd: (t: Partial<Task>) => void; onOpenFull: () => void; onClose: () => void; users: User[] }) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [assignee, setAssignee] = useState('u1');
|
||||
const [assignee, setAssignee] = useState(users[0]?.id || '');
|
||||
const [priority, setPriority] = useState<'medium' | 'low' | 'high' | 'critical'>('medium');
|
||||
|
||||
const submit = () => {
|
||||
@@ -86,7 +86,7 @@ export function QuickAddPanel({ date, onAdd, onOpenFull, onClose }: { date: stri
|
||||
onChange={e => setTitle(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} />
|
||||
<div className="quick-add-row">
|
||||
<select className="quick-add-select" value={assignee} onChange={e => setAssignee(e.target.value)}>
|
||||
{USERS.map(u => <option key={u.id} value={u.id}>{u.avatar} {u.name}</option>)}
|
||||
{users.map(u => <option key={u.id} value={u.id}>{u.avatar} {u.name}</option>)}
|
||||
</select>
|
||||
<select className="quick-add-select" value={priority} onChange={e => setPriority(e.target.value as any)}>
|
||||
{['critical', 'high', 'medium', 'low'].map(p => <option key={p} value={p}>{p}</option>)}
|
||||
@@ -100,7 +100,7 @@ export function QuickAddPanel({ date, onAdd, onOpenFull, onClose }: { date: stri
|
||||
);
|
||||
}
|
||||
|
||||
export function CalendarView({ tasks, currentUser, calMonth, calView, onMonthChange, onViewChange, onTaskClick, onDayClick, filterUser, searchQuery }: CalendarProps) {
|
||||
export function CalendarView({ tasks, currentUser, calMonth, calView, onMonthChange, onViewChange, onTaskClick, onDayClick, filterUser, searchQuery, users }: CalendarProps) {
|
||||
const [morePopover, setMorePopover] = useState<{ date: string; tasks: Task[] } | null>(null);
|
||||
const filtered = filterTasks(tasks, currentUser, filterUser, searchQuery);
|
||||
|
||||
@@ -147,14 +147,14 @@ export function CalendarView({ tasks, currentUser, calMonth, calView, onMonthCha
|
||||
{cell.date.getDate()}
|
||||
</div>
|
||||
<div className="day-tasks">
|
||||
{show.map(t => <TaskChip key={t.id} task={t} onClick={() => onTaskClick(t)} />)}
|
||||
{show.map(t => <TaskChip key={t.id} task={t} onClick={() => onTaskClick(t)} users={users} />)}
|
||||
{extra > 0 && (
|
||||
<span className="more-tasks-link" onClick={e => { e.stopPropagation(); setMorePopover({ date: ds, tasks: dayTasks }); }}>
|
||||
+{extra} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{morePopover?.date === ds && <MorePopover tasks={morePopover.tasks} onTaskClick={onTaskClick} onClose={() => setMorePopover(null)} />}
|
||||
{morePopover?.date === ds && <MorePopover tasks={morePopover.tasks} onTaskClick={onTaskClick} onClose={() => setMorePopover(null)} users={users} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user