import { CalendarDays, CheckCircle2, Circle, GripVertical, Pencil, Plus, Save, Trash2, X } from "lucide-react"; import { type FormEvent, type ReactNode, useState } from "react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { createTicketTodo, deleteTodo, updateTodo } from "../api"; import { currentDay } from "../format"; import { formatTodoPeriod, isTodoOverdue } from "../todos"; import type { TicketTodo, TodoPriority, TodoStatus } from "../types"; const priorityLabels: Record = { low: "Niedrig", medium: "Normal", high: "Hoch", urgent: "Dringend" }; const priorityClasses: Record = { low: "border-sky-500/30 bg-sky-500/10 text-sky-300", medium: "border-muted-foreground/25 bg-muted/50 text-muted-foreground", high: "border-amber-500/30 bg-amber-500/10 text-amber-300", urgent: "border-red-500/30 bg-red-500/10 text-red-300" }; function notifyTodosChanged() { window.dispatchEvent(new Event("tickettracker:todos-changed")); } type TodoPanelProps = { ticketId?: string; todos: TicketTodo[]; onChanged: () => Promise | void; showCreate?: boolean; showTicketMeta?: boolean; onNavigate?: (to: string) => void; reorderEnabled?: boolean; onReorder?: (draggedTodoId: string, targetTodoId: string) => void; toolbar?: ReactNode; compact?: boolean; }; export function TodoPanel({ ticketId, todos, onChanged, showCreate = true, showTicketMeta = false, onNavigate, reorderEnabled = false, onReorder, toolbar, compact = false }: TodoPanelProps) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [priority, setPriority] = useState("medium"); const [startsOn, setStartsOn] = useState(""); const [endsOn, setEndsOn] = useState(""); const [saving, setSaving] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [editTitle, setEditTitle] = useState(""); const [editDescription, setEditDescription] = useState(""); const [editPriority, setEditPriority] = useState("medium"); const [editStartsOn, setEditStartsOn] = useState(""); const [editEndsOn, setEditEndsOn] = useState(""); const [busyId, setBusyId] = useState(null); const [draggingId, setDraggingId] = useState(null); async function submitTodo(event: FormEvent) { event.preventDefault(); if (!ticketId) { return; } if (startsOn && endsOn && endsOn < startsOn) { toast.error("Zeitraum prüfen", { description: "Das Bis-Datum darf nicht vor dem Von-Datum liegen." }); return; } setSaving(true); try { await createTicketTodo(ticketId, { title, description, priority, startsOn: startsOn || null, endsOn: endsOn || null }); setTitle(""); setDescription(""); setPriority("medium"); setStartsOn(""); setEndsOn(""); setCreateOpen(false); toast.success("ToDo angelegt"); await onChanged(); notifyTodosChanged(); } catch (error) { toast.error("ToDo konnte nicht angelegt werden", { description: error instanceof Error ? error.message : "Unbekannter Fehler" }); } finally { setSaving(false); } } function startEdit(todo: TicketTodo) { setEditingId(todo.id); setEditTitle(todo.title); setEditDescription(todo.description); setEditPriority(todo.priority); setEditStartsOn(todo.starts_on ?? ""); setEditEndsOn(todo.ends_on ?? ""); } async function saveTodo(todo: TicketTodo, status: TodoStatus = todo.status) { if (editingId === todo.id && editStartsOn && editEndsOn && editEndsOn < editStartsOn) { toast.error("Zeitraum prüfen", { description: "Das Bis-Datum darf nicht vor dem Von-Datum liegen." }); return; } setBusyId(todo.id); try { await updateTodo(todo.id, { title: editingId === todo.id ? editTitle : todo.title, description: editingId === todo.id ? editDescription : todo.description, priority: editingId === todo.id ? editPriority : todo.priority, status, startsOn: editingId === todo.id ? editStartsOn || null : todo.starts_on, endsOn: editingId === todo.id ? editEndsOn || null : todo.ends_on }); setEditingId(null); await onChanged(); notifyTodosChanged(); } catch (error) { toast.error("ToDo konnte nicht gespeichert werden", { description: error instanceof Error ? error.message : "Unbekannter Fehler" }); } finally { setBusyId(null); } } async function removeTodo(todo: TicketTodo) { const confirmed = window.confirm(`ToDo "${todo.title}" wirklich löschen?`); if (!confirmed) { return; } setBusyId(todo.id); try { await deleteTodo(todo.id); toast.success("ToDo gelöscht"); await onChanged(); notifyTodosChanged(); } catch (error) { toast.error("ToDo konnte nicht gelöscht werden", { description: error instanceof Error ? error.message : "Unbekannter Fehler" }); } finally { setBusyId(null); } } return (
{toolbar || (showCreate && ticketId) ? (
{toolbar}
{showCreate && ticketId ? ( ToDo anlegen Neue Aufgabe zu diesem Ticket oder Projekt erfassen.
setTitle(event.currentTarget.value)} autoFocus required />
setStartsOn(event.currentTarget.value)} /> {startsOn ? ( ) : null}
setEndsOn(event.currentTarget.value)} /> {endsOn ? ( ) : null}