import { useEffect, useMemo, useState } from 'react'; import { faMagnifyingGlass } from '@fortawesome/pro-duotone-svg-icons'; import { faIcon } from '@/lib/icon-wrapper'; const SearchLg = faIcon(faMagnifyingGlass); import { Badge } from '@/components/base/badges/badges'; import { Input } from '@/components/base/input/input'; import { Table } from '@/components/application/table/table'; import { PaginationCardDefault } from '@/components/application/pagination/pagination'; import { Tabs, TabList, Tab } from '@/components/application/tabs/tabs'; import { TopBar } from '@/components/layout/top-bar'; import { PhoneActionCell } from '@/components/call-desk/phone-action-cell'; import { formatPhone, formatDateOnly, formatTimeOnly } from '@/lib/format'; import { apiClient } from '@/lib/api-client'; type AppointmentRecord = { id: string; scheduledAt: string | null; durationMin: number | null; appointmentType: string | null; status: string | null; doctorName: string | null; department: string | null; reasonForVisit: string | null; patient: { id: string; fullName: { firstName: string; lastName: string } | null; phones: { primaryPhoneNumber: string } | null; } | null; doctor: { clinic: { clinicName: string } | null; } | null; }; type StatusTab = 'all' | 'SCHEDULED' | 'COMPLETED' | 'CANCELLED' | 'RESCHEDULED'; const STATUS_COLORS: Record = { SCHEDULED: 'brand', CONFIRMED: 'brand', COMPLETED: 'success', CANCELLED: 'error', NO_SHOW: 'warning', RESCHEDULED: 'warning', }; const STATUS_LABELS: Record = { SCHEDULED: 'Booked', CONFIRMED: 'Confirmed', COMPLETED: 'Completed', CANCELLED: 'Cancelled', NO_SHOW: 'No Show', RESCHEDULED: 'Rescheduled', FOLLOW_UP: 'Follow-up', CONSULTATION: 'Consultation', }; const QUERY = `{ appointments(first: 100, orderBy: [{ scheduledAt: DescNullsLast }]) { edges { node { id scheduledAt durationMin appointmentType status doctorName department reasonForVisit patient { id fullName { firstName lastName } phones { primaryPhoneNumber } } doctor { id } } } } }`; const formatDate = (iso: string): string => formatDateOnly(iso); const formatTime = (iso: string): string => formatTimeOnly(iso); export const AppointmentsPage = () => { const [appointments, setAppointments] = useState([]); const [loading, setLoading] = useState(true); const [tab, setTab] = useState('all'); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const PAGE_SIZE = 20; useEffect(() => { apiClient.graphql<{ appointments: { edges: Array<{ node: AppointmentRecord }> } }>(QUERY, undefined, { silent: true }) .then(data => setAppointments(data.appointments.edges.map(e => e.node))) .catch(() => {}) .finally(() => setLoading(false)); }, []); const statusCounts = useMemo(() => { const counts: Record = {}; for (const a of appointments) { const s = a.status ?? 'UNKNOWN'; counts[s] = (counts[s] ?? 0) + 1; } return counts; }, [appointments]); const filtered = useMemo(() => { let rows = appointments; if (tab !== 'all') { rows = rows.filter(a => a.status === tab); } if (search.trim()) { const q = search.toLowerCase(); rows = rows.filter(a => { const patientName = `${a.patient?.fullName?.firstName ?? ''} ${a.patient?.fullName?.lastName ?? ''}`.toLowerCase(); const phone = a.patient?.phones?.primaryPhoneNumber ?? ''; const doctor = (a.doctorName ?? '').toLowerCase(); const dept = (a.department ?? '').toLowerCase(); const branch = (a.department ?? '').toLowerCase(); return patientName.includes(q) || phone.includes(q) || doctor.includes(q) || dept.includes(q) || branch.includes(q); }); } return rows; }, [appointments, tab, search]); const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const pagedRows = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); // Reset page on filter/search change useEffect(() => { setPage(1); }, [tab, search]); const tabItems = [ { id: 'all' as const, label: 'All', badge: appointments.length > 0 ? String(appointments.length) : undefined }, { id: 'SCHEDULED' as const, label: 'Booked', badge: statusCounts.SCHEDULED ? String(statusCounts.SCHEDULED) : undefined }, { id: 'COMPLETED' as const, label: 'Completed', badge: statusCounts.COMPLETED ? String(statusCounts.COMPLETED) : undefined }, { id: 'CANCELLED' as const, label: 'Cancelled', badge: statusCounts.CANCELLED ? String(statusCounts.CANCELLED) : undefined }, { id: 'RESCHEDULED' as const, label: 'Rescheduled', badge: statusCounts.RESCHEDULED ? String(statusCounts.RESCHEDULED) : undefined }, ]; return ( <>
{/* Tabs + search */}
setTab(key as StatusTab)}> {(item) => }
{/* Table */}
{loading ? (

Loading appointments...

) : filtered.length === 0 ? (

{search ? 'No matching appointments' : 'No appointments found'}

) : ( {(appt) => { const patientName = appt.patient ? `${appt.patient.fullName?.firstName ?? ''} ${appt.patient.fullName?.lastName ?? ''}`.trim() || 'Unknown' : 'Unknown'; const phone = appt.patient?.phones?.primaryPhoneNumber ?? ''; const branch = appt.department ?? '—'; const statusLabel = STATUS_LABELS[appt.status ?? ''] ?? appt.status ?? '—'; const statusColor = STATUS_COLORS[appt.status ?? ''] ?? 'gray'; return (
{patientName} {phone && ( )}
{appt.scheduledAt ? formatDate(appt.scheduledAt) : '—'} {appt.scheduledAt ? formatTime(appt.scheduledAt) : '—'} {appt.doctorName ?? '—'} {appt.department ?? '—'} {branch} {statusLabel} {appt.reasonForVisit ?? '—'}
); }}
)}
); };