import { useState, useEffect } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { queryClient } from "@/client/rpc-client"; export function AdminAvailability() { const today = new Date().toISOString().split("T")[0]; const [selectedDate, setSelectedDate] = useState(today); const [time, setTime] = useState("09:00"); const [duration, setDuration] = useState(30); const [selectedTreatmentId, setSelectedTreatmentId] = useState(""); const [slotType, setSlotType] = useState<"treatment" | "manual">("treatment"); // Neue State-Variablen für Tab-Navigation const [activeSubTab, setActiveSubTab] = useState<"slots" | "recurring" | "timeoff">("slots"); // States für Recurring Rules const [selectedDayOfWeek, setSelectedDayOfWeek] = useState(1); // 1=Montag const [ruleStartTime, setRuleStartTime] = useState("13:00"); const [ruleEndTime, setRuleEndTime] = useState("18:00"); const [editingRuleId, setEditingRuleId] = useState(""); // States für Time-Off const [timeOffStartDate, setTimeOffStartDate] = useState(""); const [timeOffEndDate, setTimeOffEndDate] = useState(""); const [timeOffReason, setTimeOffReason] = useState(""); const [editingTimeOffId, setEditingTimeOffId] = useState(""); const { data: allSlots, refetch: refetchSlots } = useQuery( queryClient.availability.live.list.experimental_liveOptions() ); const { data: treatments } = useQuery( queryClient.treatments.live.list.experimental_liveOptions() ); // Neue Queries für wiederkehrende Verfügbarkeiten (mit Authentifizierung) const { data: recurringRules } = useQuery( queryClient.recurringAvailability.live.adminListRules.experimental_liveOptions({ input: { sessionId: localStorage.getItem("sessionId") || "" } }) ); const { data: timeOffPeriods } = useQuery( queryClient.recurringAvailability.live.adminListTimeOff.experimental_liveOptions({ input: { sessionId: localStorage.getItem("sessionId") || "" } }) ); const [errorMsg, setErrorMsg] = useState(""); const [successMsg, setSuccessMsg] = useState(""); // Auto-clear messages after 5 seconds useEffect(() => { if (errorMsg) { const timer = setTimeout(() => setErrorMsg(""), 5000); return () => clearTimeout(timer); } }, [errorMsg]); useEffect(() => { if (successMsg) { const timer = setTimeout(() => setSuccessMsg(""), 5000); return () => clearTimeout(timer); } }, [successMsg]); const { mutate: createSlot, isPending: isCreating } = useMutation( queryClient.availability.create.mutationOptions() ); const { mutate: removeSlot } = useMutation( queryClient.availability.remove.mutationOptions() ); const { mutate: cleanupPastSlots } = useMutation( queryClient.availability.cleanupPastSlots.mutationOptions() ); // Neue Mutations für wiederkehrende Verfügbarkeiten const { mutate: createRule } = useMutation( queryClient.recurringAvailability.createRule.mutationOptions() ); const { mutate: updateRule } = useMutation( queryClient.recurringAvailability.updateRule.mutationOptions() ); const { mutate: deleteRule } = useMutation( queryClient.recurringAvailability.deleteRule.mutationOptions() ); const { mutate: toggleRuleActive } = useMutation( queryClient.recurringAvailability.toggleRuleActive.mutationOptions() ); const { mutate: createTimeOff } = useMutation( queryClient.recurringAvailability.createTimeOff.mutationOptions() ); const { mutate: updateTimeOff } = useMutation( queryClient.recurringAvailability.updateTimeOff.mutationOptions() ); const { mutate: deleteTimeOff } = useMutation( queryClient.recurringAvailability.deleteTimeOff.mutationOptions() ); // Auto-update duration when treatment is selected useEffect(() => { if (selectedTreatmentId && treatments) { const treatment = treatments.find(t => t.id === selectedTreatmentId); if (treatment) { setDuration(treatment.duration); } } }, [selectedTreatmentId, treatments]); // Get selected treatment details const selectedTreatment = treatments?.find(t => t.id === selectedTreatmentId); // Get treatment name for display const getTreatmentName = (treatmentId: string) => { return treatments?.find(t => t.id === treatmentId)?.name || "Unbekannte Behandlung"; }; // Helper-Funktion für Wochentag-Namen const getDayName = (dayOfWeek: number): string => { const days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"]; return days[dayOfWeek]; }; const addSlot = () => { setErrorMsg(""); setSuccessMsg(""); // Validation based on slot type if (slotType === "treatment" && !selectedTreatmentId) { setErrorMsg("Bitte eine Behandlung auswählen."); return; } if (!selectedDate || !time || !duration) { setErrorMsg("Bitte Datum, Uhrzeit und Dauer angeben."); return; } const sessionId = localStorage.getItem("sessionId") || ""; if (!sessionId) { setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden."); return; } createSlot( { sessionId, date: selectedDate, time, durationMinutes: duration }, { onSuccess: () => { const slotDescription = slotType === "treatment" ? `${getTreatmentName(selectedTreatmentId)} (${duration} Min)` : `Manueller Slot (${duration} Min)`; setSuccessMsg(`${slotDescription} angelegt.`); // Manually refetch slots to ensure live updates refetchSlots(); // advance time by the duration of the slot const [hStr, mStr] = time.split(":"); let h = parseInt(hStr, 10); let m = parseInt(mStr, 10); m += duration; if (m >= 60) { h += Math.floor(m / 60); m = m % 60; } if (h >= 24) { h = h % 24; } const next = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`; setTime(next); }, onError: (err: any) => { const msg = (err && (err.message || (err as any).toString())) || "Fehler beim Anlegen."; setErrorMsg(msg); }, } ); }; return (
{/* Tab-Navigation */}
{/* Tab Inhalt */} {activeSubTab === "slots" && ( <> {/* Slot Type Selection */}

Neuen Slot anlegen

setSelectedDate(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
setTime(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
{slotType === "treatment" ? (
) : (
setDuration(Number(e.target.value))} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
)}
{/* Treatment Info Display */} {slotType === "treatment" && selectedTreatment && (

{selectedTreatment.name}

{selectedTreatment.description}

{(selectedTreatment.price / 100).toFixed(2)} €
{selectedTreatment.duration} Minuten
)}
{(errorMsg || successMsg) && (
{errorMsg && (
Fehler: {errorMsg}
)} {successMsg && (
Erfolg: {successMsg}
)}
)} {/* Quick Stats */}
{allSlots?.filter(s => s.status === "free").length || 0}
Freie Slots
{allSlots?.filter(s => s.status === "reserved").length || 0}
Reservierte Slots
{allSlots?.length || 0}
Slots gesamt
{/* Cleanup Button */}

Bereinigung

Vergangene Slots automatisch löschen

{/* All Slots List */}

Alle Slots

{allSlots ?.sort((a, b) => (a.date === b.date ? a.time.localeCompare(b.time) : a.date.localeCompare(b.date))) .map((slot) => { // Try to find matching treatment based on duration const matchingTreatments = treatments?.filter(t => t.duration === slot.durationMinutes) || []; return (
Datum
{new Date(slot.date).toLocaleDateString('de-DE')}
Zeit
{slot.time}
Dauer
{slot.durationMinutes} Min
{matchingTreatments.length > 0 && (
Passende Behandlungen
{matchingTreatments.length === 1 ? matchingTreatments[0].name : `${matchingTreatments.length} Behandlungen` }
)} {slot.status === "free" ? "Frei" : "Reserviert"}
{/* Show matching treatments if multiple */} {matchingTreatments.length > 1 && (
Passende Behandlungen:
{matchingTreatments.map(treatment => ( {treatment.name} ))}
)}
); })} {allSlots?.length === 0 && (
Keine Slots vorhanden
Legen Sie den ersten Slot an, um zu beginnen.
)}
)} {/* Tab "Wiederkehrende Zeiten" */} {activeSubTab === "recurring" && (
{/* Neue Regel erstellen */}

Neue wiederkehrende Regel erstellen

setRuleStartTime(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
setRuleEndTime(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
{/* Bestehende Regeln */}

Bestehende Regeln

{recurringRules?.length === 0 && (
Noch keine wiederkehrenden Regeln definiert
Erstellen Sie Ihre erste Regel, um automatisch Slots zu generieren.
)} {recurringRules?.map((rule) => (
{getDayName(rule.dayOfWeek)}
{rule.startTime} - {rule.endTime} Uhr
{rule.isActive ? "Aktiv" : "Inaktiv"}
))}
)} {/* Tab "Urlaubszeiten" */} {activeSubTab === "timeoff" && (
{/* Neue Urlaubszeit erstellen */}

Neue Urlaubszeit erstellen

setTimeOffStartDate(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
setTimeOffEndDate(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
setTimeOffReason(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-pink-500" />
{/* Bestehende Urlaubszeiten */}

Bestehende Urlaubszeiten

{timeOffPeriods?.length === 0 && (
Keine Urlaubszeiten eingetragen
Fügen Sie Urlaubszeiten hinzu, um automatisch Slots zu blockieren.
)} {timeOffPeriods?.map((period) => { const today = new Date().toISOString().split("T")[0]; const isPast = period.endDate < today; const isCurrent = period.startDate <= today && period.endDate >= today; const isFuture = period.startDate > today; return (
{new Date(period.startDate).toLocaleDateString('de-DE')} - {new Date(period.endDate).toLocaleDateString('de-DE')}
{period.reason}
{isPast ? "Vergangen" : isCurrent ? "Aktuell" : "Geplant"}
); })}
)}
); }