"use client"; import { useMemo, useState, useTransition } from "react"; import { EventParticipationStatus, RsvpMode, TerminStatus, TerminType, } from "@prisma/client"; import { format } from "date-fns"; import { de } from "date-fns/locale"; import { CalendarPlus, Check, Clock, HelpCircle, Pencil, Users, X, } from "lucide-react"; import { toast } from "sonner"; import { suggestedRsvpMode } from "@/lib/rsvp"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Textarea } from "@/components/ui/textarea"; import { createTerminAdmin, updateTerminAdmin } from "../../kalender/actions"; type AdminTerminDto = { id: string; title: string; description: string | null; type: TerminType; status: TerminStatus; startDate: string; endDate: string; allDay: boolean; mitbringselListEnabled: boolean; requiresRsvp: boolean; rsvpMode: RsvpMode; familyParticipations: { familyId: string; status: EventParticipationStatus; adultCount: number; }[]; rsvpDeadline: string | null; participantInfo: string | null; participations: { childId: string; status: EventParticipationStatus; }[]; }; type ChildDto = { id: string; name: string; familyName: string; }; export function AdminCalendarManager({ termine, childRows, familyRows, }: { termine: AdminTerminDto[]; childRows: ChildDto[]; familyRows: { id: string; name: string }[]; }) { const rsvpCount = termine.filter((termin) => termin.requiresRsvp).length; const pendingCount = termine.filter( (termin) => termin.status === TerminStatus.PENDING, ).length; return (

ADMIN · EVENT-ANMELDUNGEN

Termine{" "} koordinieren

Termine verwalten, RSVP aktivieren und den Rücklauf pro Familie oder Kind verfolgen.

{termine.length} Termine {rsvpCount} RSVP {pendingCount > 0 && ( {pendingCount} ausstehend )}
{termine.length === 0 ? (

Noch keine Termine

Lege den ersten Termin an, um RSVP-Rückmeldungen einzusammeln.

) : (
{termine.map((termin) => ( ))}
)}
); } function AdminTerminCard({ termin, childRows, familyRows, }: { termin: AdminTerminDto; childRows: ChildDto[]; familyRows: { id: string; name: string }[]; }) { const isFamily = termin.rsvpMode === RsvpMode.FAMILY; const rows = useMemo( () => isFamily ? familyRows.map((family) => ({ id: family.id, name: family.name, familyName: family.name, })) : childRows, [isFamily, familyRows, childRows], ); const stats = useMemo(() => getStats(termin, rows), [termin, rows]); return (
{getTypeLabel(termin.type)} {getStatusLabel(termin.status)} {termin.requiresRsvp && ( Antwort pro {isFamily ? "Familie" : "Kind"} )}
{termin.title} {format(new Date(termin.startDate), "dd.MM.yyyy", { locale: de, })} {!termin.allDay && ( <> · {format(new Date(termin.startDate), "HH:mm", { locale: de, })}{" "} bis{" "} {format(new Date(termin.endDate), "HH:mm", { locale: de, })} )}
{termin.description && (

{termin.description}

)} {termin.requiresRsvp ? (

{stats.attending} Zugesagt / {stats.notAttending} Abgesagt /{" "} {stats.pending} Ausstehend{" "} {isFamily ? `· ${stats.adultCount} Erwachsene` : ""}

Frist:{" "} {termin.rsvpDeadline ? format( new Date(termin.rsvpDeadline), "dd.MM.yyyy HH:mm", { locale: de, }, ) : "bis Terminbeginn"}

{termin.participantInfo && (
Hinweis für Teilnehmer: {termin.participantInfo}
)} {isFamily ? "Familie" : "Kind"} {isFamily ? "Erwachsene" : "Familie"} Status {rows.map((child) => { const status = stats.statusByChildId.get(child.id) ?? null; return ( {child.name} {isFamily ? (stats.adultCounts.get(child.id) ?? "–") : child.familyName} ); })}
) : (
Für diesen Termin ist keine Teilnahme-Abfrage aktiv.
)}
); } function TerminFormDialog({ mode, termin, }: { mode: "create" | "edit"; termin?: AdminTerminDto; }) { const [open, setOpen] = useState(false); const [requiresRsvp, setRequiresRsvp] = useState( termin?.requiresRsvp ?? false, ); const [rsvpMode, setRsvpMode] = useState( termin?.rsvpMode ?? suggestedRsvpMode(TerminType.KITA_FEST), ); const [modeTouched, setModeTouched] = useState(false); const modeLocked = !!termin && termin.participations.length + termin.familyParticipations.length > 0; const [isPending, startTransition] = useTransition(); function handleAction(formData: FormData) { const startDate = new Date(String(formData.get("startDate"))); const endDate = new Date(String(formData.get("endDate"))); const deadlineRaw = String(formData.get("rsvpDeadline") ?? ""); const hasRsvp = formData.get("requiresRsvp") === "on"; const payload = { title: String(formData.get("title") ?? ""), description: String(formData.get("description") ?? ""), type: formData.get("type") as TerminType, startDate: startDate.toISOString(), endDate: endDate.toISOString(), allDay: formData.get("allDay") === "on", mitbringselListEnabled: formData.get("mitbringselListEnabled") === "on", requiresRsvp: hasRsvp, rsvpMode, rsvpDeadline: hasRsvp && deadlineRaw ? new Date(deadlineRaw).toISOString() : null, participantInfo: String(formData.get("participantInfo") ?? ""), }; startTransition(async () => { const result = mode === "edit" && termin ? await updateTerminAdmin(termin.id, payload) : await createTerminAdmin(payload); if (result.error) { toast.error(result.error); return; } toast.success( mode === "edit" ? "Termin aktualisiert." : "Termin angelegt.", ); setOpen(false); }); } return ( { setOpen(nextOpen); if (nextOpen) { setRequiresRsvp(termin?.requiresRsvp ?? false); setRsvpMode( termin?.rsvpMode ?? suggestedRsvpMode(TerminType.KITA_FEST), ); setModeTouched(false); } }} >
{mode === "edit" ? "Termin bearbeiten" : "Termin anlegen"} RSVP kann für Ausflüge und besondere Events aktiviert werden.