"use client" import * as React from "react" import { CalendarPlusIcon, Trash2Icon } from "lucide-react" import { toast } from "sonner" import { cancelFacilitySession, createFacilitySession, getFacilities, getFacilitySessions, } from "@/lib/api/facilities" import type { Facility, FacilitySession } from "@/lib/models/facility" import { useFetch } from "@/lib/use-fetch" import { useMutation } from "@/lib/use-mutation" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Spinner } from "@/components/ui/spinner" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { DataTable, type DataTableColumn } from "@/components/hexa/data-table" import { PageHeader } from "@/components/hexa/page-header" import { PageSkeleton } from "@/components/hexa/page-skeleton" import { StatusBadge } from "@/components/hexa/status-badge" import { AdhocBookingTab } from "@/components/hexa/views/adhoc-booking-tab" import { BookingDashboardTab } from "@/components/hexa/views/booking-dashboard-tab" import { BookingInvoicesTab } from "@/components/hexa/views/booking-invoices-tab" import { BookingRosterTab } from "@/components/hexa/views/booking-roster-tab" import { RfidTagsTab } from "@/components/hexa/views/rfid-tags-tab" import { TimeBankCardsTab } from "@/components/hexa/views/time-bank-cards-tab" function formatDateTime(iso: string) { return new Date(iso).toLocaleString(undefined, { day: "numeric", month: "short", hour: "numeric", minute: "2-digit", }) } /** `datetime-local` input value in local time, e.g. "2026-09-20T18:30". */ function toLocalInputValue(date: Date) { const pad = (n: number) => String(n).padStart(2, "0") return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}` } function BookingsView() { const facilitiesFetch = useFetch(() => getFacilities(true)) const facilities = facilitiesFetch.data ?? [] if (facilitiesFetch.loading) { return } return (
Dashboard Sessions Book Now Roster & Check-in Invoices RFID Tags Time Bank Cards
) } // ---------- Sessions ---------- interface SessionsTabProps { facilities: Facility[] } function SessionsTab({ facilities }: SessionsTabProps) { const scheduledFacilities = facilities.filter((f) => f.bookingMode === "Scheduled") const [facilityFilter, setFacilityFilter] = React.useState("all") const sessionsFetch = useFetch(() => getFacilitySessions(facilityFilter === "all" ? {} : { facilityId: facilityFilter }) ) const sessions = sessionsFetch.data ?? [] const [dialogOpen, setDialogOpen] = React.useState(false) const [cancelTarget, setCancelTarget] = React.useState(null) const { busy: cancelling, run: runCancel } = useMutation() const filterOptions = [ { label: "All facilities", value: "all" }, ...scheduledFacilities.map((f) => ({ label: f.name, value: f.facilityId })), ] async function confirmCancel() { if (!cancelTarget) return const target = cancelTarget setCancelTarget(null) try { await runCancel(() => cancelFacilitySession(target.facilitySessionId)) toast.success("Session cancelled") sessionsFetch.reload() } catch { // run() already toasted the real error. } } const columns: DataTableColumn[] = [ { key: "facility", header: "Facility", cell: (row) => row.facilityName ?? "-" }, { key: "start", header: "Start", cell: (row) => formatDateTime(row.startTime) }, { key: "end", header: "End", cell: (row) => formatDateTime(row.endTime) }, { key: "capacity", header: "Booked / Capacity", cell: (row) => `${row.bookedCount} / ${row.capacity}`, }, { key: "status", header: "Status", cell: (row) => row.isCancelled ? ( Cancelled ) : ( Scheduled ), }, { key: "actions", header: "Actions", className: "text-center", cell: (row) => row.isCancelled ? null : (
setCancelTarget(row)} > } /> Cancel session
), }, ] return (
{sessionsFetch.loading ? (
) : ( row.facilitySessionId} emptyMessage="No sessions scheduled yet." renderMobileCard={(row) => (

{row.facilityName ?? "-"}

{formatDateTime(row.startTime)} - {formatDateTime(row.endTime)}

{row.isCancelled ? ( Cancelled ) : ( Scheduled )}
{row.bookedCount} / {row.capacity} booked {!row.isCancelled && ( )}
)} /> )} !open && setCancelTarget(null)}> Cancel this session? Existing bookings for this session will need to be handled separately. This cannot be undone. Keep Session Cancel Session
) } interface ScheduleSessionDialogProps { open: boolean onOpenChange: (open: boolean) => void facilities: Facility[] onScheduled: () => void } function ScheduleSessionDialog({ open, onOpenChange, facilities, onScheduled }: ScheduleSessionDialogProps) { const [facilityId, setFacilityId] = React.useState("") const [startTime, setStartTime] = React.useState("") const [endTime, setEndTime] = React.useState("") const [capacity, setCapacity] = React.useState("") const { busy, run } = useMutation() const [prevOpen, setPrevOpen] = React.useState(open) if (open !== prevOpen) { setPrevOpen(open) if (open) { const now = new Date() now.setMinutes(0, 0, 0) now.setHours(now.getHours() + 1) const later = new Date(now.getTime() + 60 * 60 * 1000) setFacilityId(facilities[0]?.facilityId ?? "") setStartTime(toLocalInputValue(now)) setEndTime(toLocalInputValue(later)) setCapacity("") } } const facilityOptions = facilities.map((f) => ({ label: f.name, value: f.facilityId })) const canSubmit = !!facilityId && !!startTime && !!endTime && endTime > startTime && !busy async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!canSubmit) return try { await run(() => createFacilitySession({ facilityId, startTime: new Date(startTime).toISOString(), endTime: new Date(endTime).toISOString(), capacity: capacity ? Number(capacity) : undefined, }) ) toast.success("Session scheduled") onOpenChange(false) onScheduled() } catch { // run() already toasted the real error; keep the dialog open for retry. } } return (
Schedule Session Open a bookable time slot for a Scheduled-mode facility.
setStartTime(e.target.value)} required />
setEndTime(e.target.value)} required />
setCapacity(e.target.value)} />
) } export { BookingsView }