"use client"; import { useState, useEffect, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { ChevronLeft, ChevronRight, Plus, Trash2, RefreshCw, Ban, Check } from "lucide-react"; import { toast } from "sonner"; import { adminGetSchedule, adminCreateSchedule, adminDeleteSchedule, adminListSlots, adminGenerateSlots, adminUpdateSlot, adminDeleteSlot, adminGetBlockedDates, adminAddBlockedDate, adminRemoveBlockedDate, WeeklySchedule, BlockedDate, TimeSlotApi, } from "@/lib/api/bookings"; import { useLanguage } from "@/contexts/LanguageContext"; const DURATIONS = [30, 45, 60, 90, 120]; function toDateStr(d: Date): string { return d.toISOString().slice(0, 10); } function monthStart(year: number, month: number) { return new Date(year, month, 1); } function daysInMonth(year: number, month: number) { return new Date(year, month + 1, 0).getDate(); } // Monday-first weekday index (0=Mon, 6=Sun) function weekdayMon(date: Date) { return (date.getDay() + 6) % 7; } // ── Schedule Tab ────────────────────────────────────────────────────────────── function ScheduleTab() { const { t } = useLanguage(); const DAY_NAMES = Array.from({ length: 7 }, (_, i) => t(`admin.day.${i}`)); const [schedule, setSchedule] = useState([]); const [loading, setLoading] = useState(true); const [form, setForm] = useState({ day_of_week: 0, start_time: "09:00", end_time: "18:00", slot_duration_minutes: 60, }); const [saving, setSaving] = useState(false); const [genFrom, setGenFrom] = useState(toDateStr(new Date())); const [genTo, setGenTo] = useState(() => { const d = new Date(); d.setDate(d.getDate() + 30); return toDateStr(d); }); const [generating, setGenerating] = useState(false); useEffect(() => { adminGetSchedule() .then(setSchedule) .catch(() => toast.error(t("admin.error"))) .finally(() => setLoading(false)); }, []); const addEntry = async () => { setSaving(true); try { const entry = await adminCreateSchedule({ day_of_week: Number(form.day_of_week), start_time: form.start_time + ":00", end_time: form.end_time + ":00", slot_duration_minutes: Number(form.slot_duration_minutes), }); setSchedule((prev) => [...prev, entry].sort((a, b) => a.day_of_week - b.day_of_week)); toast.success(t("admin.planning.schedule_added")); } catch { toast.error(t("admin.error")); } finally { setSaving(false); } }; const removeEntry = async (id: string) => { try { await adminDeleteSchedule(id); setSchedule((prev) => prev.filter((e) => e.id !== id)); toast.success(t("admin.planning.schedule_deleted")); } catch { toast.error(t("admin.error")); } }; const generate = async () => { setGenerating(true); try { const res = await adminGenerateSlots(genFrom, genTo); toast.success(t("admin.planning.generated", { n: res.created })); } catch { toast.error(t("admin.error")); } finally { setGenerating(false); } }; const grouped = DAY_NAMES.map((name, idx) => ({ name, entries: schedule.filter((e) => e.day_of_week === idx), })); return (
{t("admin.planning.weekly_title")} {loading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : (
{grouped.map(({ name, entries }) => (
{name}
{entries.length === 0 ? ( {t("admin.planning.not_available")} ) : ( entries.map((e) => (
{e.start_time.slice(0, 5)} – {e.end_time.slice(0, 5)} ({e.slot_duration_minutes} min)
)) )}
))}
)} {t("admin.planning.add_title")}
setForm((f) => ({ ...f, start_time: e.target.value }))} />
setForm((f) => ({ ...f, end_time: e.target.value }))} />
{t("admin.planning.generate_title")}

{t("admin.planning.generate_desc")}

setGenFrom(e.target.value)} />
setGenTo(e.target.value)} />
); } // ── Calendar Tab ────────────────────────────────────────────────────────────── function CalendarTab() { const { t, locale } = useLanguage(); const DAY_NAMES = Array.from({ length: 7 }, (_, i) => t(`admin.day.${i}`)); const MONTH_NAMES = Array.from({ length: 12 }, (_, i) => t(`admin.month.${i}`)); const today = new Date(); const [year, setYear] = useState(today.getFullYear()); const [month, setMonth] = useState(today.getMonth()); const [slots, setSlots] = useState([]); const [loadingSlots, setLoadingSlots] = useState(false); const [selectedDay, setSelectedDay] = useState(null); const loadSlots = useCallback(() => { setLoadingSlots(true); const from = toDateStr(monthStart(year, month)); const last = new Date(year, month + 1, 0); const to = toDateStr(last); adminListSlots(from, to) .then(setSlots) .catch(() => toast.error(t("admin.error"))) .finally(() => setLoadingSlots(false)); }, [year, month]); useEffect(() => { loadSlots(); }, [loadSlots]); const prevMonth = () => { if (month === 0) { setYear((y) => y - 1); setMonth(11); } else setMonth((m) => m - 1); setSelectedDay(null); }; const nextMonth = () => { if (month === 11) { setYear((y) => y + 1); setMonth(0); } else setMonth((m) => m + 1); setSelectedDay(null); }; const blockSlot = async (slot: TimeSlotApi) => { try { const updated = await adminUpdateSlot(slot.id, !slot.is_blocked); setSlots((prev) => prev.map((s) => s.id === slot.id ? updated : s)); toast.success(slot.is_blocked ? t("admin.planning.slot_unblocked") : t("admin.planning.slot_blocked")); } catch { toast.error(t("admin.error")); } }; const deleteSlot = async (id: string) => { try { await adminDeleteSlot(id); setSlots((prev) => prev.filter((s) => s.id !== id)); toast.success(t("admin.planning.slot_deleted")); } catch { toast.error(t("admin.error")); } }; const firstDay = monthStart(year, month); const totalDays = daysInMonth(year, month); const startOffset = weekdayMon(firstDay); const cells: (number | null)[] = [ ...Array(startOffset).fill(null), ...Array.from({ length: totalDays }, (_, i) => i + 1), ]; while (cells.length % 7 !== 0) cells.push(null); const slotsByDay: Record = {}; slots.forEach((s) => { (slotsByDay[s.date] ||= []).push(s); }); const selectedSlots = selectedDay ? (slotsByDay[selectedDay] ?? []) : []; return (

{MONTH_NAMES[month]} {year}

{DAY_NAMES.map((d) => (
{d.slice(0, 3)}
))} {cells.map((day, i) => { if (!day) return
; const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; const daySlots = slotsByDay[dateStr] ?? []; const available = daySlots.filter((s) => !s.is_blocked && !s.is_booked).length; const booked = daySlots.filter((s) => s.is_booked).length; const blocked = daySlots.filter((s) => s.is_blocked).length; const isToday = dateStr === toDateStr(today); const isSelected = dateStr === selectedDay; return ( ); })}
{selectedDay && ( {t("admin.planning.slots_for")} {new Date(selectedDay + "T00:00:00").toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long" })} {loadingSlots ? (
{t("admin.loading")}
) : selectedSlots.length === 0 ? (

{t("admin.planning.no_slots")}

) : (
{selectedSlots .sort((a, b) => a.start_time.localeCompare(b.start_time)) .map((slot) => (
{slot.start_time.slice(0, 5)} – {slot.end_time.slice(0, 5)} {slot.is_booked && {t("admin.status.booked")}} {slot.is_blocked && {t("admin.status.blocked")}} {!slot.is_booked && !slot.is_blocked && {t("admin.status.free")}}
{!slot.is_booked && (
)}
))}
)}
)}
); } // ── Blocked Dates Tab ───────────────────────────────────────────────────────── function BlockedDatesTab() { const { t, locale } = useLanguage(); const [dates, setDates] = useState([]); const [loading, setLoading] = useState(true); const [newDate, setNewDate] = useState(toDateStr(new Date())); const [newReason, setNewReason] = useState(""); const [adding, setAdding] = useState(false); useEffect(() => { adminGetBlockedDates() .then(setDates) .catch(() => toast.error(t("admin.error"))) .finally(() => setLoading(false)); }, []); const add = async () => { setAdding(true); try { const entry = await adminAddBlockedDate(newDate, newReason || undefined); setDates((prev) => [...prev, entry].sort((a, b) => a.date.localeCompare(b.date))); setNewReason(""); toast.success(t("admin.planning.date_blocked")); } catch { toast.error(t("admin.error")); } finally { setAdding(false); } }; const remove = async (id: string) => { try { await adminRemoveBlockedDate(id); setDates((prev) => prev.filter((d) => d.id !== id)); toast.success(t("admin.planning.date_unblocked")); } catch { toast.error(t("admin.error")); } }; return (
{t("admin.planning.block_title")}

{t("admin.planning.block_desc")}

setNewDate(e.target.value)} />
setNewReason(e.target.value)} />
{t("admin.planning.blocked_list")} {loading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : dates.length === 0 ? (

{t("admin.planning.no_blocked")}

) : (
{dates.map((d) => (
{new Date(d.date + "T00:00:00").toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric", })} {d.reason && — {d.reason}}
))}
)}
); } // ── Main Page ───────────────────────────────────────────────────────────────── type Tab = "schedule" | "calendar" | "blocked"; export default function PlanningPage() { const { t } = useLanguage(); const [tab, setTab] = useState("schedule"); const tabs: { id: Tab; label: string }[] = [ { id: "schedule", label: t("admin.planning.tab_schedule") }, { id: "calendar", label: t("admin.planning.tab_calendar") }, { id: "blocked", label: t("admin.planning.tab_blocked") }, ]; return (

{t("admin.planning.title")}

{t("admin.planning.subtitle")}

{tabs.map((item) => ( ))}
{tab === "schedule" && } {tab === "calendar" && } {tab === "blocked" && }
); }