"use client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "@/lib/api"; import type { Appointment, Case } from "@/lib/types"; import { format, parseISO, isToday, isTomorrow, isThisWeek, isPast } from "date-fns"; import { de } from "date-fns/locale"; import { Calendar, Filter, MapPin, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { useState, useMemo } from "react"; const TYPE_LABELS: Record = { hearing: "Verhandlung", meeting: "Besprechung", consultation: "Beratung", deadline_hearing: "Fristanhorung", other: "Sonstiges", }; const TYPE_COLORS: Record = { hearing: "bg-blue-100 text-blue-700", meeting: "bg-violet-100 text-violet-700", consultation: "bg-emerald-100 text-emerald-700", deadline_hearing: "bg-amber-100 text-amber-700", other: "bg-neutral-100 text-neutral-600", }; interface AppointmentListProps { onEdit: (appointment: Appointment) => void; } function groupByDate(appointments: Appointment[]): Map { const groups = new Map(); for (const a of appointments) { const key = a.start_at.slice(0, 10); const group = groups.get(key) || []; group.push(a); groups.set(key, group); } return groups; } function formatDateLabel(dateStr: string): string { const d = parseISO(dateStr); if (isToday(d)) return "Heute"; if (isTomorrow(d)) return "Morgen"; return format(d, "EEEE, d. MMMM yyyy", { locale: de }); } export function AppointmentList({ onEdit }: AppointmentListProps) { const queryClient = useQueryClient(); const [caseFilter, setCaseFilter] = useState("all"); const [typeFilter, setTypeFilter] = useState("all"); const { data: appointments, isLoading } = useQuery({ queryKey: ["appointments"], queryFn: () => api.get("/appointments"), }); const { data: cases } = useQuery({ queryKey: ["cases"], queryFn: () => api.get<{ cases: Case[]; total: number }>("/cases"), }); const deleteMutation = useMutation({ mutationFn: (id: string) => api.delete(`/appointments/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["appointments"] }); toast.success("Termin geloscht"); }, onError: () => toast.error("Fehler beim Loschen"), }); const caseMap = useMemo(() => { const map = new Map(); const arr = Array.isArray(cases?.cases) ? cases.cases : []; arr.forEach((c) => map.set(c.id, c)); return map; }, [cases]); const filtered = useMemo(() => { if (!Array.isArray(appointments)) return []; return appointments .filter((a) => { if (caseFilter !== "all" && a.case_id !== caseFilter) return false; if (typeFilter !== "all" && a.appointment_type !== typeFilter) return false; return true; }) .sort((a, b) => a.start_at.localeCompare(b.start_at)); }, [appointments, caseFilter, typeFilter]); const grouped = useMemo(() => groupByDate(filtered), [filtered]); const counts = useMemo(() => { if (!Array.isArray(appointments)) return { today: 0, thisWeek: 0, total: 0 }; let today = 0; let thisWeek = 0; for (const a of appointments) { const d = parseISO(a.start_at); if (isToday(d)) today++; if (isThisWeek(d, { weekStartsOn: 1 })) thisWeek++; } return { today, thisWeek, total: appointments.length }; }, [appointments]); if (isLoading) { return (
{[1, 2, 3, 4].map((i) => (
))}
); } return (
{/* Summary cards */}
{counts.today}
Heute
{counts.thisWeek}
Diese Woche
{counts.total}
Gesamt
{/* Filters */}
Filter:
{Array.isArray(cases?.cases) && cases.cases.length > 0 && ( )}
{/* Grouped list */} {filtered.length === 0 ? (

Keine Termine gefunden

) : (
{Array.from(grouped.entries()).map(([dateKey, dayAppointments]) => { const dateIsPast = isPast(parseISO(dateKey + "T23:59:59")); return (
{formatDateLabel(dateKey)}
{dayAppointments.map((appt) => { const caseInfo = appt.case_id ? caseMap.get(appt.case_id) : null; const typeBadge = appt.appointment_type ? TYPE_COLORS[appt.appointment_type] ?? TYPE_COLORS.other : null; const typeLabel = appt.appointment_type ? TYPE_LABELS[appt.appointment_type] ?? appt.appointment_type : null; return (
onEdit(appt)} className={`flex cursor-pointer items-start gap-3 rounded-lg border px-4 py-3 transition-colors hover:bg-neutral-50 ${ dateIsPast ? "border-neutral-150 bg-neutral-50/50" : "border-neutral-200 bg-white" }`} >
{format(parseISO(appt.start_at), "HH:mm")}
{appt.end_at && (
{format(parseISO(appt.end_at), "HH:mm")}
)}
{appt.title} {typeBadge && typeLabel && ( {typeLabel} )}
{appt.location && ( {appt.location} )} {appt.location && caseInfo && ·} {caseInfo && ( {caseInfo.case_number} — {caseInfo.title} )}
{appt.description && (

{appt.description}

)}
); })}
); })}
)}
); }