"use client"; import React from "react"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../../auth-context"; import { api } from "../../api"; import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; import { components } from "../../generated/api"; import { formatDateTime } from "../../utils/date"; import { getStatusDisplay } from "../../utils/appointment"; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; type PaginatedAppointments = components["schemas"]["PaginatedResponse_AppointmentResponse_"]; const styles: Record = { main: { minHeight: "100vh", background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)", display: "flex", flexDirection: "column", }, loader: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.5)", }, content: { flex: 1, padding: "2rem", maxWidth: "900px", margin: "0 auto", width: "100%", }, pageTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1.75rem", fontWeight: 600, color: "#fff", marginBottom: "0.5rem", }, pageSubtitle: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.5)", fontSize: "0.9rem", marginBottom: "1.5rem", }, errorBanner: { background: "rgba(239, 68, 68, 0.15)", border: "1px solid rgba(239, 68, 68, 0.3)", color: "#f87171", padding: "1rem", borderRadius: "8px", marginBottom: "1rem", fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", }, filterRow: { display: "flex", alignItems: "center", gap: "0.75rem", marginBottom: "1.5rem", }, filterLabel: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.6)", fontSize: "0.875rem", }, filterSelect: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.5rem 1rem", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "6px", color: "#fff", fontSize: "0.875rem", }, appointmentList: { display: "flex", flexDirection: "column", gap: "0.75rem", }, appointmentCard: { background: "rgba(255, 255, 255, 0.03)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "12px", padding: "1.25rem", transition: "all 0.2s", }, appointmentCardPast: { opacity: 0.6, }, appointmentHeader: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "1rem", }, appointmentTime: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1rem", fontWeight: 500, color: "#fff", marginBottom: "0.25rem", }, appointmentUser: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.5)", marginBottom: "0.25rem", }, appointmentNote: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.4)", fontStyle: "italic", marginBottom: "0.5rem", }, statusBadge: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.75rem", fontWeight: 500, padding: "0.25rem 0.75rem", borderRadius: "9999px", display: "inline-block", }, buttonGroup: { display: "flex", gap: "0.5rem", }, cancelButton: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.35rem 0.75rem", fontSize: "0.75rem", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "6px", color: "rgba(255, 255, 255, 0.7)", cursor: "pointer", transition: "all 0.2s", }, confirmButton: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.35rem 0.75rem", fontSize: "0.75rem", background: "rgba(239, 68, 68, 0.2)", border: "1px solid rgba(239, 68, 68, 0.3)", borderRadius: "6px", color: "#f87171", cursor: "pointer", transition: "all 0.2s", }, emptyState: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.4)", textAlign: "center", padding: "3rem", }, }; export default function AdminAppointmentsPage() { const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_ALL_APPOINTMENTS, fallbackRedirect: "/", }); const [appointments, setAppointments] = useState([]); const [isLoadingAppointments, setIsLoadingAppointments] = useState(true); const [cancellingId, setCancellingId] = useState(null); const [confirmCancelId, setConfirmCancelId] = useState(null); const [error, setError] = useState(null); const [statusFilter, setStatusFilter] = useState("all"); const fetchAppointments = useCallback(async () => { try { // Fetch with large per_page to get all appointments for now const data = await api.get("/api/admin/appointments?per_page=100"); setAppointments(data.records); } catch (err) { console.error("Failed to fetch appointments:", err); setError("Failed to load appointments"); } finally { setIsLoadingAppointments(false); } }, []); useEffect(() => { if (user && isAuthorized) { fetchAppointments(); } }, [user, isAuthorized, fetchAppointments]); const handleCancel = async (appointmentId: number) => { setCancellingId(appointmentId); setError(null); try { await api.post(`/api/admin/appointments/${appointmentId}/cancel`, {}); await fetchAppointments(); setConfirmCancelId(null); } catch (err) { if (err instanceof Error) { setError(err.message); } else { setError("Failed to cancel appointment"); } } finally { setCancellingId(null); } }; if (isLoading) { return (
Loading...
); } if (!isAuthorized) { return null; } const filteredAppointments = appointments.filter((apt) => { if (statusFilter === "all") return true; return apt.status === statusFilter; }); const bookedCount = appointments.filter((a) => a.status === "booked").length; return (

All Appointments

View and manage all user appointments

{error && (
{error}
)} {/* Status Filter */}
Filter:
{isLoadingAppointments ? (
Loading appointments...
) : appointments.length === 0 ? (
No appointments yet.
) : filteredAppointments.length === 0 ? (
No appointments match the filter.
) : (
{filteredAppointments.map((apt) => { const status = getStatusDisplay(apt.status); const isPast = new Date(apt.slot_start) <= new Date(); return (
{formatDateTime(apt.slot_start)}
{apt.user_email}
{apt.note && (
"{apt.note}"
)} {status.text}
{apt.status === "booked" && (
{confirmCancelId === apt.id ? ( <> ) : ( )}
)}
); })}
)}
); }