"use client"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../auth-context"; import { api } from "../api"; import { sharedStyles } from "../styles/shared"; import { Header } from "../components/Header"; import { useRequireAuth } from "../hooks/useRequireAuth"; import { components } from "../generated/api"; type AppointmentResponse = components["schemas"]["AppointmentResponse"]; // Helper to format datetime function formatDateTime(isoString: string): string { const d = new Date(isoString); return d.toLocaleString("en-US", { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false, }); } // Helper to get status display function getStatusDisplay(status: string): { text: string; color: string } { switch (status) { case "booked": return { text: "Booked", color: "#28a745" }; case "cancelled_by_user": return { text: "Cancelled by you", color: "#dc3545" }; case "cancelled_by_admin": return { text: "Cancelled by admin", color: "#dc3545" }; default: return { text: status, color: "#666" }; } } export default function AppointmentsPage() { const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_OWN_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 fetchAppointments = useCallback(async () => { try { const data = await api.get("/api/appointments"); setAppointments(data); } 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/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 upcomingAppointments = appointments.filter( (apt) => apt.status === "booked" && new Date(apt.slot_start) > new Date() ); const pastOrCancelledAppointments = appointments.filter( (apt) => apt.status !== "booked" || new Date(apt.slot_start) <= new Date() ); return (

My Appointments

View and manage your booked appointments

{error && (
{error}
)} {isLoadingAppointments ? (

Loading appointments...

) : appointments.length === 0 ? (

You don't have any appointments yet.

Book an appointment
) : ( <> {/* Upcoming Appointments */} {upcomingAppointments.length > 0 && (

Upcoming ({upcomingAppointments.length})

{upcomingAppointments.map((apt) => { const status = getStatusDisplay(apt.status); return (
{formatDateTime(apt.slot_start)}
{apt.note && (
{apt.note}
)} {status.text}
{apt.status === "booked" && (
{confirmCancelId === apt.id ? (
) : ( )}
)}
); })}
)} {/* Past/Cancelled Appointments */} {pastOrCancelledAppointments.length > 0 && (

Past & Cancelled ({pastOrCancelledAppointments.length})

{pastOrCancelledAppointments.map((apt) => { const status = getStatusDisplay(apt.status); return (
{formatDateTime(apt.slot_start)}
{apt.note && (
{apt.note}
)} {status.text}
); })}
)} )}
); }