import type {Route} from "../../../.react-router/types/app/pages/dashboard/+types/AppointmentManagement"; import {useDisclosure} from "@mantine/hooks"; import {useEffect, useMemo, useState} from "react"; import {BookingCategory, DashboardPageType} from "~/utils/hms_enums.ts"; import {useNavigate, useOutletContext} from "react-router"; import { type OutletContextType, type PatientBookingInfo, type DoctorTeamInfo, SORT_SYMBOLS } from "~/utils/models.ts"; import {Accordion, ActionIcon, Badge, Button, Card, Group, Pagination, Stack, Table, Text} from "@mantine/core"; import {apiGetAllAppointments, get_team_info} from "~/utils/hms_api.ts"; import {showErrorMessage, timestampToDate} from "~/utils/utils.ts"; import {iconMStyle, marginLeftRight, marginRightBottom, marginRound, marginTopBottom, textCenter} from "~/styles.ts"; import CheckCircleIcon from "mdi-react/CheckCircleIcon"; import CancelIcon from "mdi-react/CancelIcon"; import HospitalIcon from "mdi-react/HospitalIcon"; import { confirmApproveAppointment, confirmRejectAppointment, confirmPatientAdmission, confirmViewTeamMembers } from "~/components/subs/confirms.tsx"; import {AppointmentManageFilter} from "~/components/subs/AppointmentManageFilter.tsx"; import HomePlusIcon from "mdi-react/HomePlusIcon"; import {TruncatedText} from "~/components/subs/TruncatedText.tsx"; import InfoIcon from "mdi-react/InfoIcon"; import {PatientInfoDisplay} from "~/components/subs/PatientInfoDisplay.tsx"; import {ResponsiveTableContainer} from "~/components/subs/ResponsiveTableContainer.tsx"; export function meta({}: Route.MetaArgs) { return [ { title: "Appointment Management" }, { name: "description", content: "Appointment Management" }, ]; } export default function Component() { const navigate = useNavigate(); const [refreshingAppointmentList, setRefreshingAppointmentList] = useState(false); const [appointmentInfo, setAppointmentInfo] = useState<{appointments: PatientBookingInfo[], total_pages: number}>({appointments: [], total_pages: 1}); const [teamInfoMap, setTeamInfoMap] = useState>(new Map()); const [loadingTeams, setLoadingTeams] = useState>(new Set()); const [sortKey, setSortKey] = useState("id"); const [sortDesc, setSortDesc] = useState(true); const [currPage, setCurrPage] = useState(1); const [filterCategories, setFilterCategories] = useState([]); const [filterStatus, setFilterStatus] = useState(-1); const [includeDischarged, setIncludeDischarged] = useState(false); const { loginUserInfo, refreshMyInfo } = useOutletContext(); useEffect(() => { refreshAppointmentList(); }, []); useEffect(() => { refreshAppointmentList(); }, [currPage, includeDischarged]); const updateFilter = (categories: string[], status: number, discharged: boolean) => { setFilterCategories(categories); setFilterStatus(status); setIncludeDischarged(discharged); }; const refreshAppointmentList = () => { setRefreshingAppointmentList(true); apiGetAllAppointments(currPage, includeDischarged).then(res => { if (!res.success) { showErrorMessage(res.message, "Failed to get appointments list"); return; } setAppointmentInfo(res.data); // Fetch team info for all appointments with assigned teams const teamsToFetch = res.data.appointments .filter(app => app.assigned_team !== null) .map(app => app.assigned_team as number); // Remove duplicates const uniqueTeams = [...new Set(teamsToFetch)]; // Fetch team info for each unique team ID uniqueTeams.forEach(teamId => { fetchTeamInfo(teamId); }); }) .catch(err => {}) .finally(() => { setRefreshingAppointmentList(false); }); }; const fetchTeamInfo = (teamId: number) => { if (teamId === null || teamInfoMap.has(teamId) || loadingTeams.has(teamId)) { return; } setLoadingTeams(prev => new Set([...prev, teamId])); get_team_info(teamId).then(res => { if (res.success) { setTeamInfoMap(prev => { const newMap = new Map(prev); newMap.set(teamId, res.data.team); return newMap; }); } }) .catch(err => {}) .finally(() => { setLoadingTeams(prev => { const newSet = new Set(prev); newSet.delete(teamId); return newSet; }); }); }; const handleViewTeam = (teamId: number) => { const teamInfo = teamInfoMap.get(teamId); if (teamInfo) { confirmViewTeamMembers(teamInfo); } else { // If team info is not fetched yet, fetch it and then show get_team_info(teamId).then(res => { if (res.success) { setTeamInfoMap(prev => { const newMap = new Map(prev); newMap.set(teamId, res.data.team); return newMap; }); confirmViewTeamMembers(res.data.team); } else { showErrorMessage(res.message, "Failed to fetch team information"); } }).catch(err => { showErrorMessage("Failed to fetch team information", "Error"); }); } }; const handleSort = (key: string) => { if (sortKey === key) { setSortDesc(!sortDesc); } else { setSortKey(key); setSortDesc(false); // Default ascending } }; const sortedAppointments = useMemo(() => { let data = [...appointmentInfo.appointments]; // Apply filters data = data.filter((appointment) => { // Filter by category const categoryMatch = filterCategories.length === 0 || filterCategories.includes(appointment.category); // Filter by status let statusMatch = true; if (filterStatus !== -1) { if (filterStatus === 0) { // Pending statusMatch = !appointment.approved; } else if (filterStatus === 1) { // Approved statusMatch = appointment.approved && appointment.assigned_team !== null; } else if (filterStatus === 2) { // Rejected statusMatch = appointment.approved && appointment.assigned_team === null; } } return categoryMatch && statusMatch; }); // Apply sorting if (!sortKey) return data; data.sort((a, b) => { const valA = a[sortKey]; const valB = b[sortKey]; if (typeof valA === 'string' && typeof valB === 'string') { const cmp = valA.localeCompare(valB); return sortDesc ? -cmp : cmp; } if (typeof valA === 'number' && typeof valB === 'number') { return sortDesc ? valB - valA : valA - valB; } if (typeof valA === 'boolean' && typeof valB === 'boolean') { return sortDesc ? (valB ? 1 : -1) : (valA ? 1 : -1); } return 0; }); return data; }, [appointmentInfo.appointments, sortKey, sortDesc, filterCategories, filterStatus]); const handleApproveAppointment = (appointmentId: number) => { confirmApproveAppointment(appointmentId, refreshAppointmentList); }; const handleRejectAppointment = (appointmentId: number) => { confirmRejectAppointment(appointmentId, refreshAppointmentList); }; const handlePatientAdmission = (appointmentId: number) => { confirmPatientAdmission(appointmentId, refreshAppointmentList); }; const getCategoryDisplay = (category: string) => { const key = Object.entries(BookingCategory).find(([_, value]) => value === category)?.[0]; return key ? key.replace(/_/g, ' ') : category; }; const getStatusBadge = (appointment: PatientBookingInfo) => { if (appointment.discharged) { return Discharged; } else if (appointment.admitted) { return Admitted; } else if (appointment.approved) { if (appointment.assigned_team === null) { return Rejected; } else { return Approved; } } else { return Pending; } }; const getTeamDisplay = (appointment: PatientBookingInfo) => { if (!appointment.approved) { return Pending approval; } if (appointment.assigned_team === null) { return Not assigned; } const teamInfo = teamInfoMap.get(appointment.assigned_team); if (!teamInfo) { return ( ID: {appointment.assigned_team} handleViewTeam(appointment.assigned_team as number)} title="View team details" > ); } return ( {teamInfo.department.replace(/_/g, ' ')} handleViewTeam(appointment.assigned_team as number)} title="View team details" > ); }; const rows = sortedAppointments.map((appointment) => ( {appointment.id} {getCategoryDisplay(appointment.category)} {new Date(appointment.appointment_time * 1000).toLocaleString('en-GB').replace(',', '')} {getStatusBadge(appointment)} {appointment.feedback ? : '-' } {getTeamDisplay(appointment)} handleApproveAppointment(appointment.id)} title="Approve" disabled={appointment.admitted || appointment.discharged} > handleRejectAppointment(appointment.id)} title="Reject" disabled={appointment.admitted || appointment.discharged} > { if (appointment.approved && appointment.assigned_team !== null && !appointment.admitted && !appointment.discharged) { handlePatientAdmission(appointment.id); } }} disabled={!appointment.approved || appointment.assigned_team === null || appointment.admitted || appointment.discharged} title={ appointment.admitted ? "Already admitted" : appointment.discharged ? "Already discharged" : !appointment.approved ? "Not approved yet" : appointment.assigned_team === null ? "No team assigned" : "Admit Patient" } > )); return ( Appointment Management Filter handleSort("id")}> ID{" "} {sortKey === "id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("patient_id")}> Patient ID{" "} {sortKey === "patient_id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("category")}> Category{" "} {sortKey === "category" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("appointment_time")}> Appointment Time{" "} {sortKey === "appointment_time" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} Description handleSort("approved")}> Status{" "} {sortKey === "approved" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} Feedback handleSort("assigned_team")}> Team{" "} {sortKey === "assigned_team" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} Actions {rows}
); }