import { For, Show, createResource, createSignal } from "solid-js"; import { apiClient } from "../../lib/api-client"; import { Input } from "../../components/ui/input"; import { Select } from "../../components/ui/select"; import { getInitials } from "../../components/dashboard/types"; import { MapPinIcon, PlusIcon, BanIcon, ClockIcon, SparklesIcon, Trash2Icon, UsersIcon, PencilIcon, ChevronDownIcon, CalendarDaysIcon, CheckIcon, XIcon } from "../../components/dashboard/icons"; import { DashboardLayout, useDashboardData } from "./layout"; function ZonesPage() { const data = useDashboardData(); const cs = () => data.i18n.locale() === "cs"; const [activeTab, setActiveTab] = createSignal<"zones" | "blocked" | "hours" | "classes">("zones"); const [showAddZone, setShowAddZone] = createSignal(false); const [newZoneName, setNewZoneName] = createSignal(""); const [newZoneType, setNewZoneType] = createSignal("room"); const [newZoneCapacity, setNewZoneCapacity] = createSignal(10); const [showUpgradePrompt, setShowUpgradePrompt] = createSignal(false); const [newBlockedDate, setNewBlockedDate] = createSignal(""); const [newBlockedReason, setNewBlockedReason] = createSignal(""); const [newBlockedStaffId, setNewBlockedStaffId] = createSignal(""); const [blockedSaving, setBlockedSaving] = createSignal(false); const [hoursSaving, setHoursSaving] = createSignal(null); const [hoursScope, setHoursScope] = createSignal(""); const [newTemplateTitle, setNewTemplateTitle] = createSignal(""); const [newTemplateDuration, setNewTemplateDuration] = createSignal(60); const [newTemplateCapacity, setNewTemplateCapacity] = createSignal(8); const [templateSaving, setTemplateSaving] = createSignal(false); const [editingTemplateId, setEditingTemplateId] = createSignal(null); const [newSessionTemplateId, setNewSessionTemplateId] = createSignal(""); const [newSessionLocationId, setNewSessionLocationId] = createSignal(""); const [newSessionStartsAt, setNewSessionStartsAt] = createSignal(""); const [sessionSaving, setSessionSaving] = createSignal(false); const [newSchedTemplateId, setNewSchedTemplateId] = createSignal(""); const [newSchedDayOfWeek, setNewSchedDayOfWeek] = createSignal("3"); const [newSchedStartsLocal, setNewSchedStartsLocal] = createSignal(""); const [newSchedLocationId, setNewSchedLocationId] = createSignal(""); const [schedSaving, setSchedSaving] = createSignal(false); const [editingSchedId, setEditingSchedId] = createSignal(null); const [editSchedDayOfWeek, setEditSchedDayOfWeek] = createSignal("1"); const [editSchedStartsLocal, setEditSchedStartsLocal] = createSignal(""); const [editSchedLocationId, setEditSchedLocationId] = createSignal(""); const [editSchedApplyFuture, setEditSchedApplyFuture] = createSignal(true); const [editSchedActive, setEditSchedActive] = createSignal(true); const [schedEditSaving, setSchedEditSaving] = createSignal(false); const [editingCapSessionId, setEditingCapSessionId] = createSignal(null); const [editCapValue, setEditCapValue] = createSignal(0); const [capSaving, setCapSaving] = createSignal(false); // Session ID whose capacity shrink hit the confirmed-seat floor — the row // switches to an explicit "set anyway" confirm before resubmitting. const [capOverbookConfirm, setCapOverbookConfirm] = createSignal(null); const [expandedSessionId, setExpandedSessionId] = createSignal(null); const [promoteBusy, setPromoteBusy] = createSignal(null); // Booking IDs whose promote attempt hit a full session — awaiting the // explicit "confirm anyway" over-capacity resubmit. const [promoteArmed, setPromoteArmed] = createSignal>(new Set()); const authHeaders = () => ({ headers: { Authorization: `Bearer ${data.token()}` } }); const [locations, { refetch: refetchLocations }] = createResource( () => data.token(), async (bearer) => { if (!bearer) return []; if (bearer.startsWith("demo.")) { return [ { id: "z1", name: cs() ? "Hlavní sál" : "Main Hall", type: "hall", capacity: 20 }, { id: "z2", name: cs() ? "Soukromá místnost A" : "Private Room A", type: "private", capacity: 4 }, { id: "z3", name: cs() ? "Ošetřovna" : "Treatment Room", type: "room", capacity: 2 }, ]; } const response = await (apiClient as any).GET("/v1/catalog/locations", authHeaders()); return (response.data as any[]) ?? []; }, ); const [blockedDays, { refetch: refetchBlocked }] = createResource( () => [data.token(), activeTab()] as const, async ([bearer, tab]) => { if (!bearer || tab !== "blocked") return []; if (bearer.startsWith("demo.")) return []; const response = await (apiClient as any).GET("/v1/catalog/blocked-days", authHeaders()); return (response.data as any[]) ?? []; }, ); const [staffMembers] = createResource( () => [data.token(), activeTab()] as const, async ([bearer, tab]) => { if (!bearer || (tab !== "blocked" && tab !== "hours")) return []; if (bearer.startsWith("demo.")) { return [ { id: "st1", displayName: "Petra M.", active: true }, { id: "st2", displayName: "Lucie K.", active: true }, ]; } try { const response = await apiClient.GET("/v1/catalog/staff", authHeaders()); return ((response.data as any[]) ?? []).filter((m) => m.active); } catch { return []; } }, ); const staffName = (id: string | null | undefined) => (staffMembers() ?? []).find((m: any) => m.id === id)?.displayName ?? ""; const [workingHours, { refetch: refetchHours, mutate: mutateHours }] = createResource( () => [data.token(), activeTab(), hoursScope()] as const, async ([bearer, tab, scope]) => { if (!bearer || tab !== "hours") return []; if (bearer.startsWith("demo.")) { if (scope) { return [0, 1, 2, 3, 4, 5, 6].map((d) => ({ dayOfWeek: d, open: d >= 1 && d <= 5 ? "08:00" : "", close: d >= 1 && d <= 5 ? "14:00" : "", isOpen: d >= 1 && d <= 5 })); } return [0, 1, 2, 3, 4, 5, 6].map((d) => ({ dayOfWeek: d, open: d === 0 ? "" : "09:00", close: d === 0 ? "" : "18:00", isOpen: d !== 0 })); } const url = scope ? `/v1/catalog/staff/${scope}/working-hours` : "/v1/catalog/working-hours"; const response = await (apiClient as any).GET(url, authHeaders()); const rows = (response.data as any[]) ?? []; return [0, 1, 2, 3, 4, 5, 6].map((d) => { const row = rows.find((r) => r.dayOfWeek === d); return { dayOfWeek: d, open: (row?.open ?? "").slice(0, 5), close: (row?.close ?? "").slice(0, 5), isOpen: row?.isOpen ?? false }; }); }, ); const [classTemplates, { refetch: refetchTemplates }] = createResource( () => [data.token(), activeTab()] as const, async ([bearer, tab]) => { if (!bearer || tab !== "classes") return []; if (bearer.startsWith("demo.")) { return [{ id: "ct1", title: cs() ? "Jóga pro začátečníky" : "Beginner yoga", durationMinutes: 60, capacity: 8 }]; } const response = await (apiClient as any).GET("/v1/catalog/class-templates", authHeaders()); return (response.data as any[]) ?? []; }, ); const [classSessions, { refetch: refetchSessions }] = createResource( () => [data.token(), activeTab()] as const, async ([bearer, tab]) => { if (!bearer || tab !== "classes") return []; if (bearer.startsWith("demo.")) { const start = new Date(Date.now() + 3 * 86400000); return [{ id: "cs1", templateId: "ct1", templateTitle: cs() ? "Jóga pro začátečníky" : "Beginner yoga", locationName: cs() ? "Hlavní sál" : "Main Hall", startsAt: start.toISOString(), endsAt: new Date(start.getTime() + 3600000).toISOString(), capacity: 8, bookedCount: 3, waitlistCount: 2 }]; } const response = await (apiClient as any).GET("/v1/catalog/class-sessions", authHeaders()); return (response.data as any[]) ?? []; }, ); const [classSchedules, { refetch: refetchSchedules }] = createResource( () => [data.token(), activeTab()] as const, async ([bearer, tab]) => { if (!bearer || tab !== "classes") return []; if (bearer.startsWith("demo.")) { return [ { id: "sch1", templateId: "ct1", templateTitle: cs() ? "Jóga pro začátečníky" : "Beginner yoga", locationId: "z1", locationName: cs() ? "Hlavní sál" : "Main Hall", dayOfWeek: 3, startsLocal: "10:00:00", active: true }, { id: "sch2", templateId: "ct1", templateTitle: cs() ? "Jóga pro začátečníky" : "Beginner yoga", locationId: "z1", locationName: cs() ? "Hlavní sál" : "Main Hall", dayOfWeek: 6, startsLocal: "09:30:00", active: true }, ]; } const response = await (apiClient as any).GET("/v1/catalog/class-schedules", authHeaders()); return (response.data as any[]) ?? []; }, ); const weekdayName = (dow: number) => data.i18n.t(`dashboard.class.dow.${dow}`) as string; const [roster, { refetch: refetchRoster }] = createResource( () => [data.token(), expandedSessionId()] as const, async ([bearer, sessionId]) => { if (!bearer || !sessionId) return null; if (bearer.startsWith("demo.")) { const start = new Date(Date.now() + 3 * 86400000); const mk = (id: string, name: string, email: string, status: string, pos = 0) => ({ bookingId: id, reference: `BOK-${id.slice(-3).toUpperCase()}`, customerName: name, customerEmail: email, status, waitlistPosition: pos, createdAt: start.toISOString(), }); return { sessionId, templateTitle: cs() ? "Jóga pro začátečníky" : "Beginner yoga", locationName: cs() ? "Hlavní sál" : "Main Hall", startsAt: start.toISOString(), endsAt: new Date(start.getTime() + 3600000).toISOString(), capacity: 8, confirmed: [ mk("roster-001", cs() ? "Martina Nováková" : "Martina Novakova", "martina@example.com", "confirmed"), mk("roster-002", "David Svoboda", "david@example.com", "confirmed"), mk("roster-003", "Alice Johnson", "alice@example.com", "confirmed"), ], waitlist: [ mk("roster-004", cs() ? "Jana Kovářová" : "Jana Kovarova", "jana@example.com", "waitlisted", 1), mk("roster-005", "Bob Smith", "bob@example.com", "waitlisted", 2), ], }; } const response = await (apiClient as any).GET(`/v1/catalog/class-sessions/${sessionId}/roster`, authHeaders()); return response.data ?? null; }, ); const handlePromote = async (bookingId: string, allowOverCapacity: boolean) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noModify")); return; } setPromoteBusy(bookingId); try { const response = await (apiClient as any).POST(`/v1/catalog/bookings/${bookingId}/promote`, { ...authHeaders(), body: { allowOverCapacity }, }); if (response.error) { if (response.response?.status === 409 && !allowOverCapacity) { setPromoteArmed((prev) => new Set(prev).add(bookingId)); return; } throw new Error("promote failed"); } setPromoteArmed((prev) => { const next = new Set(prev); next.delete(bookingId); return next; }); void refetchRoster(); void refetchSessions(); void data.refetchSummary(); } catch { data.setDemoNotice(data.i18n.t("dashboard.class.promoteFailed")); } finally { setPromoteBusy(null); } }; const resolvedLocations = () => locations() ?? []; const locationLimit = () => data.resolvedBilling()?.entitlements?.maxLocations ?? 3; const canAddLocation = () => locationLimit() < 0 || resolvedLocations().length < locationLimit(); const handleAddZone = async () => { const bearer = data.token(); if (!newZoneName().trim()) return; if (!canAddLocation()) { setShowUpgradePrompt(true); setShowAddZone(false); return; } if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } try { await (apiClient as any).POST("/v1/catalog/locations", { ...authHeaders(), body: { name: newZoneName().trim(), type: newZoneType(), capacity: newZoneCapacity() }, }); setNewZoneName(""); setNewZoneType("room"); setNewZoneCapacity(10); setShowAddZone(false); void refetchLocations(); } catch { data.setDemoNotice(data.i18n.t("dashboard.zone.saveFailed")); } }; const handleDeleteZone = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/locations/${id}`, authHeaders()); void refetchLocations(); } catch { data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddBlockedDay = async () => { const bearer = data.token(); if (!newBlockedDate()) return; if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setBlockedSaving(true); try { await (apiClient as any).POST("/v1/catalog/blocked-days", { ...authHeaders(), body: { date: new Date(`${newBlockedDate()}T00:00:00`).toISOString(), type: "closure", reason: newBlockedReason().trim() || (data.i18n.t("dashboard.day.closed")), ...(newBlockedStaffId() ? { staffId: newBlockedStaffId() } : {}), }, }); setNewBlockedDate(""); setNewBlockedReason(""); setNewBlockedStaffId(""); void refetchBlocked(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setBlockedSaving(false); } }; const handleDeleteBlockedDay = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) return; try { await (apiClient as any).DELETE(`/v1/catalog/blocked-days/${id}`, authHeaders()); void refetchBlocked(); } catch { /* noop */ } }; const handleSaveHours = async (day: { dayOfWeek: number; open: string; close: string; isOpen: boolean }) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setHoursSaving(day.dayOfWeek); try { const scope = hoursScope(); const url = scope ? `/v1/catalog/staff/${scope}/working-hours/${day.dayOfWeek}` : `/v1/catalog/working-hours/${day.dayOfWeek}`; await (apiClient as any).PUT(url, { ...authHeaders(), body: { open: day.open, close: day.close, isOpen: day.isOpen }, }); void refetchHours(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setHoursSaving(null); } }; const resetTemplateForm = () => { setNewTemplateTitle(""); setNewTemplateDuration(60); setNewTemplateCapacity(8); setEditingTemplateId(null); }; const handleSaveTemplate = async () => { const bearer = data.token(); if (!newTemplateTitle().trim()) return; if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setTemplateSaving(true); try { const body = { title: newTemplateTitle().trim(), durationMinutes: newTemplateDuration(), capacity: newTemplateCapacity() }; const editing = editingTemplateId(); if (editing) { await (apiClient as any).PUT(`/v1/catalog/class-templates/${editing}`, { ...authHeaders(), body }); } else { await (apiClient as any).POST("/v1/catalog/class-templates", { ...authHeaders(), body }); } resetTemplateForm(); void refetchTemplates(); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setTemplateSaving(false); } }; const handleEditTemplate = (tpl: any) => { setNewTemplateTitle(tpl.title); setNewTemplateDuration(tpl.durationMinutes); setNewTemplateCapacity(tpl.capacity); setEditingTemplateId(tpl.id); }; const handleDeleteTemplate = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-templates/${id}`, authHeaders()); void refetchTemplates(); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddSession = async () => { const bearer = data.token(); if (!newSessionTemplateId() || !newSessionStartsAt()) return; if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSessionSaving(true); try { await (apiClient as any).POST("/v1/catalog/class-sessions", { ...authHeaders(), body: { templateId: newSessionTemplateId(), startsAt: new Date(newSessionStartsAt()).toISOString(), ...(newSessionLocationId() ? { locationId: newSessionLocationId() } : {}), }, }); setNewSessionTemplateId(""); setNewSessionLocationId(""); setNewSessionStartsAt(""); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSessionSaving(false); } }; const handleDeleteSession = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-sessions/${id}`, authHeaders()); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const handleAddSchedule = async () => { const bearer = data.token(); if (!newSchedTemplateId() || !newSchedStartsLocal()) return; if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSchedSaving(true); try { await (apiClient as any).POST("/v1/catalog/class-schedules", { ...authHeaders(), body: { templateId: newSchedTemplateId(), dayOfWeek: parseInt(newSchedDayOfWeek(), 10), startsLocal: newSchedStartsLocal(), ...(newSchedLocationId() ? { locationId: newSchedLocationId() } : {}), }, }); setNewSchedTemplateId(""); setNewSchedStartsLocal(""); setNewSchedLocationId(""); void refetchSchedules(); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSchedSaving(false); } }; const startEditSchedule = (sch: any) => { setEditingSchedId(sch.id); setEditSchedDayOfWeek(String(sch.dayOfWeek)); setEditSchedStartsLocal(String(sch.startsLocal).slice(0, 5)); setEditSchedLocationId(sch.locationId ?? ""); setEditSchedApplyFuture(true); setEditSchedActive(sch.active !== false); }; const handleSaveSchedule = async () => { const bearer = data.token(); const id = editingSchedId(); if (!id) return; if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setSchedEditSaving(true); try { await (apiClient as any).PATCH(`/v1/catalog/class-schedules/${id}`, { ...authHeaders(), body: { dayOfWeek: parseInt(editSchedDayOfWeek(), 10), startsLocal: editSchedStartsLocal(), locationId: editSchedLocationId(), active: editSchedActive(), applyToFuture: editSchedApplyFuture(), }, }); setEditingSchedId(null); void refetchSchedules(); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setSchedEditSaving(false); } }; const startEditCapacity = (session: any) => { setEditingCapSessionId(session.id); setEditCapValue(session.capacity); setCapOverbookConfirm(null); }; const handleSaveCapacity = async (session: any, allowOverCapacity: boolean) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noSave")); return; } setCapSaving(true); try { const response = await (apiClient as any).PATCH(`/v1/catalog/class-sessions/${session.id}`, { ...authHeaders(), body: { capacity: editCapValue(), allowOverCapacity }, }); if (response.response?.status === 409 && !allowOverCapacity) { setCapOverbookConfirm(session.id); return; } setEditingCapSessionId(null); setCapOverbookConfirm(null); void refetchSessions(); } catch { data.setDemoNotice(data.i18n.t("dashboard.saveFailed")); } finally { setCapSaving(false); } }; const handleDeleteSchedule = async (id: string) => { const bearer = data.token(); if (!bearer || bearer.startsWith("demo.")) { data.setDemoNotice(data.i18n.t("dashboard.demo.noDelete")); return; } try { await (apiClient as any).DELETE(`/v1/catalog/class-schedules/${id}`, authHeaders()); void refetchSchedules(); } catch { data.setDemoNotice(data.i18n.t("dashboard.deleteFailed")); } }; const typeLabel = (type: string) => { const map: Record = { room: data.i18n.t("dashboard.zone.rooms"), private: data.i18n.t("dashboard.zone.private"), hall: data.i18n.t("dashboard.zone.hall") }; return map[type] || type; }; const dayLabel = (d: number) => { const labels = cs() ? ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota"] : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; return labels[d]; }; return (

{data.i18n.t("dashboard.zones")}

{resolvedLocations().length} {cs() ? "zón" : "locations"}

{[ { id: "zones", label: data.i18n.t("dashboard.zones"), icon: MapPinIcon }, { id: "blocked", label: data.i18n.t("dashboard.zone.blockedDays"), icon: BanIcon }, { id: "hours", label: data.i18n.t("dashboard.zone.workingHours"), icon: ClockIcon }, { id: "classes", label: data.i18n.t("dashboard.classes"), icon: UsersIcon }, ].map((tab) => ( ))}
{activeTab() === "zones" && (

{data.i18n.t("dashboard.zones")}

setShowUpgradePrompt(true)} class="flex items-center gap-2 px-4 py-2 bg-canvas-subtle text-ink-muted border border-border rounded-lg text-sm opacity-60 cursor-not-allowed"> {data.i18n.t("dashboard.zone.limitReached")} }>

{data.i18n.t("dashboard.zone.add")}

setNewZoneName(e.currentTarget.value)} placeholder={cs() ? "např. Hlavní sál" : "e.g. Main Hall"} /> setNewZoneCapacity(parseInt(e.currentTarget.value) || 10)} />
0} fallback={

{data.i18n.t("dashboard.empty.title")}

{data.i18n.t("dashboard.zone.addFirst")}

}>
{resolvedLocations().map((zone: any) => (

{zone.name}

{typeLabel(zone.type)} • {data.i18n.t("dashboard.zone.capacity")}: {zone.capacity}

))}
)} {activeTab() === "blocked" && (

{data.i18n.t("dashboard.zone.blockedDays")}

{data.i18n.t("dashboard.blockedDay.hint")}

setNewBlockedDate(e.currentTarget.value)} /> setNewBlockedReason(e.currentTarget.value)} placeholder={data.i18n.t("dashboard.blockedDay.placeholder")} /> setHoursScope(v)} options={[ { value: "", label: data.i18n.t("dashboard.zone.businessHours") }, ...(staffMembers() ?? []).map((m: any) => ({ value: m.id, label: m.displayName })), ]} />
{(day) => { const isOpen = () => day.isOpen; const setField = (patch: Partial) => { const updated = { ...day, ...patch }; mutateHours((workingHours() ?? []).map((d) => (d.dayOfWeek === day.dayOfWeek ? updated : d))); }; return (
{dayLabel(day.dayOfWeek)}
setField({ open: e.currentTarget.value })} class="px-3 py-2 bg-canvas-subtle border border-border rounded-lg text-sm text-ink" /> – setField({ close: e.currentTarget.value })} class="px-3 py-2 bg-canvas-subtle border border-border rounded-lg text-sm text-ink" />
); }}
)} {activeTab() === "classes" && (

{data.i18n.t("dashboard.class.templates")}

{data.i18n.t("dashboard.class.sessionHint")}

setNewTemplateTitle(e.currentTarget.value)} placeholder={data.i18n.t("dashboard.class.titlePlaceholder")} /> setNewTemplateDuration(parseInt(e.currentTarget.value) || 60)} /> setNewTemplateCapacity(parseInt(e.currentTarget.value) || 8)} />
0} fallback={

{data.i18n.t("dashboard.class.noTemplates")}

}>
{(tpl: any) => (

{tpl.title}

{tpl.durationMinutes} {data.i18n.t("dashboard.service.min")} • {data.i18n.t("dashboard.zone.capacity")}: {tpl.capacity}

)}

{data.i18n.t("dashboard.class.recurring")}

{data.i18n.t("dashboard.class.recurringHint")}

setNewSchedDayOfWeek(v)} options={[1, 2, 3, 4, 5, 6, 0].map((d) => ({ value: String(d), label: weekdayName(d) }))} /> setNewSchedStartsLocal(e.currentTarget.value)} /> setEditSchedDayOfWeek(v)} options={[1, 2, 3, 4, 5, 6, 0].map((d) => ({ value: String(d), label: weekdayName(d) }))} /> setEditSchedStartsLocal(e.currentTarget.value)} /> setEditSchedActive(e.currentTarget.checked)} class="mt-0.5 accent-accent" /> {data.i18n.t("dashboard.class.ruleActive")}
)}

{data.i18n.t("dashboard.class.sessions")}

setNewSessionLocationId(v)} options={[ { value: "", label: data.i18n.t("dashboard.class.noLocation") }, ...resolvedLocations().map((l: any) => ({ value: l.id, label: l.name })), ]} /> setNewSessionStartsAt(e.currentTarget.value)} />
0} fallback={

{data.i18n.t("dashboard.class.noSessions")}

}>
{(session: any) => { const expanded = () => expandedSessionId() === session.id; return (

{session.templateTitle}

0}> {session.waitlistCount} {data.i18n.t("dashboard.class.waiting")}

{new Date(session.startsAt).toLocaleString(cs() ? "cs-CZ" : "en-US", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })} {session.locationName ? ` • ${session.locationName}` : ""} {` • ${session.bookedCount}/${session.capacity} ${data.i18n.t("dashboard.class.booked")}`}

setEditCapValue(parseInt(e.currentTarget.value, 10) || 0)} />

{data.i18n.t("dashboard.class.capacityOverbookHint")}

…
}>

{data.i18n.t("dashboard.class.attendees")} ({(roster()?.confirmed ?? []).length}/{roster()?.capacity ?? session.capacity})

0} fallback={

{data.i18n.t("dashboard.class.noAttendees")}

}>
{(entry: any) => (
{getInitials(entry.customerName)}

{entry.customerName}

{entry.customerEmail} • {entry.reference}

)}

{data.i18n.t("dashboard.class.waitlist")} ({(roster()?.waitlist ?? []).length})

0} fallback={

{data.i18n.t("dashboard.class.noWaitlist")}

}>
{(entry: any) => (
#{entry.waitlistPosition}

{entry.customerName}

{entry.customerEmail} • {entry.reference}

void handlePromote(entry.bookingId, true)} disabled={promoteBusy() === entry.bookingId} title={data.i18n.t("dashboard.class.promoteFull")} class="shrink-0 px-2.5 py-1.5 text-xs font-medium rounded-lg bg-warning text-canvas hover:opacity-90 transition-opacity disabled:opacity-60" > {promoteBusy() === entry.bookingId ? "…" : data.i18n.t("dashboard.class.promoteConfirm")} } >
)}
); }}
)}
setShowUpgradePrompt(false)} />

{data.i18n.t("dashboard.locationLimitReached")}

{data.i18n.t("dashboard.zone.limitHint")}

); } export default function ZonesRoute() { return ( ); }