"use client"; import { useState, useTransition } from "react"; import { format } from "date-fns"; import { de } from "date-fns/locale"; import { NotdienstPlanStatus } from "@prisma/client"; import { ArrowRight, CheckSquare, Download, Loader2, RotateCcw, Send, Wand2, } from "lucide-react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { generatePlanAction, publishPlanAction, updateAssignmentAction, } from "./actions"; interface PlanViewProps { planId?: string; status?: NotdienstPlanStatus; days: { date: string; assignmentId: string | null; childId: string | null; }[]; allChildren: { id: string; name: string; parentName: string; }[]; familyStatuses: { id: string; name: string; childCount: number; selectedDays: { iso: string; label: string; }[]; }[]; requiredDays: number; respondedFamilies: number; openFamilies: number; totalFamilies: number; totalAvailabilitySlots: number; coverageFactor: number; closureCount: number; monthName: string; } export function PlanView({ planId, status, days, allChildren, familyStatuses, requiredDays, respondedFamilies, openFamilies, totalFamilies, totalAvailabilitySlots, coverageFactor, closureCount, monthName, }: PlanViewProps) { const [isPending, startTransition] = useTransition(); const [activeTab, setActiveTab] = useState<"preview" | "manual">("preview"); const responsePercent = totalFamilies > 0 ? Math.round((respondedFamilies / totalFamilies) * 100) : 0; const handleGenerate = () => { startTransition(async () => { const result = await generatePlanAction(); if ("error" in result && result.error) { toast.error(result.error as string); } else { toast.success("Plan erfolgreich generiert."); } }); }; const handlePublish = () => { if (!planId) return; startTransition(async () => { const result = await publishPlanAction(planId); if ("error" in result && result.error) { toast.error(result.error as string); } else { toast.success("Plan veröffentlicht! Er ist nun fix."); } }); }; const handleAssignmentChange = (date: string, newChildId: string) => { if (!planId || status !== "DRAFT") return; const valueToSet = newChildId === "empty" ? null : newChildId; startTransition(async () => { const result = await updateAssignmentAction(planId, date, valueToSet); if ("error" in result && result.error) { toast.error(result.error as string); } else { toast.success("Zuteilung aktualisiert."); } }); }; if (!planId && respondedFamilies === 0) { return (

Noch keine Verfügbarkeiten

Für {monthName} hat noch keine Familie geantwortet. Verschicke zuerst Erinnerungen, damit die automatische Planung belastbar ist.

); } if (!planId) { return (

Verfügbarkeiten Eltern

{respondedFamilies} von {totalFamilies} Familien haben geantwortet · noch {openFamilies} offen

{responsePercent}%
{familyStatuses.map((family) => (
{family.name.slice(0, 1)}

{family.name}

{family.childCount} Kind {family.childCount === 1 ? "" : "er"}

{family.selectedDays.length > 0 ? family.selectedDays.map((day) => day.label).join(" · ") : "Noch keine Tage markiert"}
))}

Bedarfs-Übersicht

Planungsgrundlage für {monthName}.

{coverageFactor >= 1 ? "Du hast genug Verfügbarkeiten für einen Plan." : "Die Verfügbarkeiten reichen noch nicht für einen belastbaren Plan."}

Plan automatisch generieren

Verteilt {requiredDays} Notdiensttage fair auf{" "} {respondedFamilies} Familien.

Faire Verteilung berücksichtigt
☑ Quote pro Familie ☑ Bevorzugte Tage ☐ Geschwister-Familien zusammen
); } const isDraft = status === "DRAFT"; return (
setActiveTab("preview")} > Plan-Vorschau setActiveTab("manual")} withDivider > Manuell zuweisen
{isDraft && ( )}
{isDraft ? "Entwurf" : "Veröffentlicht"} {isDraft ? "Du kannst die Liste jetzt manuell anpassen." : "Dieser Plan ist verbindlich."}
{activeTab === "preview" ? ( ) : ( )}
); } function FamilyStatusPill({ selectedDays }: { selectedDays: number }) { if (selectedDays === 0) { return ( Noch nicht ); } return ( {selectedDays} Tage ); } function PlanMetric({ label, value, badge, }: { label: string; value: number | string; badge?: "good"; }) { return (
{label} {badge ? ( {value} ) : ( {value} )}
); } function TabButton({ active, withDivider, onClick, children, }: { active: boolean; withDivider?: boolean; onClick: () => void; children: React.ReactNode; }) { return ( ); } function PlanPreviewTable({ days, allChildren, }: Pick) { return (
Datum
Wochentag
Zugewiesen
Backup
Status
{days.map((day) => { const dateObj = new Date(day.date); const assignedChild = allChildren.find( (child) => child.id === day.childId, ); return (
{format(dateObj, "dd.MM.", { locale: de })}
{format(dateObj, "EEEE", { locale: de })}
{assignedChild ? assignedChild.parentName : "Unbesetzt"} {assignedChild && ( ({assignedChild.name}) )}
Noch offen
{assignedChild ? "Besetzt" : "Offen"}
); })}
); } function ManualAssignmentTable({ days, allChildren, isDraft, isPending, onAssignmentChange, }: Pick & { isDraft: boolean; isPending: boolean; onAssignmentChange: (date: string, newChildId: string) => void; }) { return (
Datum
Eingeteilte Familie
{days.map((day) => { const dateObj = new Date(day.date); const assignedChild = allChildren.find( (child) => child.id === day.childId, ); return (
{format(dateObj, "EE, dd.MM.", { locale: de })}
{isDraft ? ( ) : assignedChild ? ( {assignedChild.parentName}{" "} ({assignedChild.name}) ) : ( Unbesetzt! )}
); })}
); }