/** * Calendar Kanban page — three task columns with drag & drop status updates. */ import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { KanbanBoard, type KanbanStatus } from '@/components/calendar/KanbanBoard'; import { TaskDetailPanel } from '@/components/calendar/TaskDetailPanel'; import { fetchKanbanBoard, updateEntry, type CalendarEntry, type KanbanBoard as KanbanBoardData, } from '@/api/calendar'; const EMPTY_BOARD: KanbanBoardData = { open: [], in_progress: [], done: [], cancelled: [], }; export function CalendarKanbanPage() { const { t } = useTranslation(); const [board, setBoard] = useState(EMPTY_BOARD); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const data = await fetchKanbanBoard(); setBoard(data); } catch (e) { setError((e as Error).message ?? t('calendar.errorLoad')); } finally { setLoading(false); } }, [t]); useEffect(() => { void load(); }, [load]); const handleMove = async (entry: CalendarEntry, target: KanbanStatus) => { // Optimistic update const previous = board; const next: KanbanBoardData = { open: board.open.filter((e) => e.id !== entry.id), in_progress: board.in_progress.filter((e) => e.id !== entry.id), done: board.done.filter((e) => e.id !== entry.id), cancelled: board.cancelled.filter((e) => e.id !== entry.id), }; next[target] = [...next[target], { ...entry, status: target }]; setBoard(next); try { const updated = await updateEntry(entry.id, { status: target }); setBoard((cur) => { const list = cur[target].map((e) => (e.id === updated.id ? updated : e)); return { ...cur, [target]: list }; }); } catch (e) { setError((e as Error).message ?? t('calendar.errorSave')); setBoard(previous); } }; const handleTaskChanged = (updated: CalendarEntry) => { setSelectedTask(updated); setBoard((cur) => { const mapStatus = (s: string): KanbanStatus => s === 'open' || s === 'in_progress' || s === 'done' || s === 'cancelled' ? s : 'open'; const target = mapStatus(updated.status); const strip = (col: CalendarEntry[]) => col.filter((e) => e.id !== updated.id); return { open: target === 'open' ? [...strip(cur.open), updated] : strip(cur.open), in_progress: target === 'in_progress' ? [...strip(cur.in_progress), updated] : strip(cur.in_progress), done: target === 'done' ? [...strip(cur.done), updated] : strip(cur.done), cancelled: target === 'cancelled' ? [...strip(cur.cancelled), updated] : strip(cur.cancelled), }; }); }; return (

{t('calendar.kanbanTitle')}

{error && (
{error}
)} setSelectedTask(entry)} /> {selectedTask && ( setSelectedTask(null)} onChanged={handleTaskChanged} /> )}
); } export default CalendarKanbanPage;