import type {Route} from "../../../.react-router/types/app/pages/dashboard/+types/PatientsManagement"; import {useEffect, useMemo, useState} from "react"; import {DashboardPageType, UserType} from "~/utils/hms_enums.ts"; import {useNavigate, useOutletContext} from "react-router"; import { type DoctorTeamInfo, type OutletContextType, type PatientDataWithWardAndAdmission, SORT_SYMBOLS, type WardInfo } from "~/utils/models.ts"; import {Accordion, ActionIcon, Button, Card, Group, Pagination, Stack, Table, Text, Tooltip} from "@mantine/core"; import {apiGetPatientsList, apiGetTeamList, apiGetWardList} from "~/utils/hms_api.ts"; import {showErrorMessage} from "~/utils/utils.ts"; import {iconMStyle, marginLeftRight, marginRightBottom, marginTopBottom} from "~/styles.ts"; import PencilIcon from "mdi-react/PencilIcon"; import LogoutVariantIcon from "mdi-react/LogoutVariantIcon"; import AddIcon from "mdi-react/AddIcon"; import InfoIcon from "mdi-react/InfoIcon"; import { confirmAdminAddUser, confirmCheckWardPatients, confirmEditPatient, confirmPatientDischarge, confirmResetPassword } from "~/components/subs/confirms.tsx"; import {PatientManagementFilter} from "~/components/subs/PatientManagementFilter.tsx"; import {PatientInfoDisplay} from "~/components/subs/PatientInfoDisplay.tsx"; import {TeamInfoDisplay} from "~/components/subs/TeamInfoDisplay.tsx"; import LockResetIcon from "mdi-react/LockResetIcon"; import {modals} from "@mantine/modals"; import HistoryIcon from "mdi-react/HistoryIcon"; import FilterIcon from "mdi-react/FilterIcon"; import {ResponsiveTableContainer} from "~/components/subs/ResponsiveTableContainer.tsx"; export function meta({}: Route.MetaArgs) { return [ { title: "Patients Management" }, { name: "description", content: "Manage hospital patients" }, ]; } export default function Component() { const navigate = useNavigate(); const [refreshingPatientList, setRefreshingPatientList] = useState(false); const [patientInfo, setPatientInfo] = useState<{patients: PatientDataWithWardAndAdmission[], total_pages: number}>({patients: [], total_pages: 1}); // Lists for filters const [teams, setTeams] = useState([]); const [wards, setWards] = useState([]); const [loadingTeams, setLoadingTeams] = useState(false); const [loadingWards, setLoadingWards] = useState(false); const [sortKey, setSortKey] = useState(""); const [sortDesc, setSortDesc] = useState(true); const [currPage, setCurrPage] = useState(1); // Filter states const [nameSearch, setNameSearch] = useState(""); const [filterGenders, setFilterGenders] = useState([]); const [filterHasTeam, setFilterHasTeam] = useState(-1); const [filterIsAdmitted, setFilterIsAdmitted] = useState(-1); const [filterTeams, setFilterTeams] = useState([]); const [filterWards, setFilterWards] = useState([]); const { changePage } = useOutletContext(); useEffect(() => { refreshPatientList(); loadTeamsList(); loadWardsList(); }, []); useEffect(() => { refreshPatientList(); }, [currPage]); const loadTeamsList = () => { setLoadingTeams(true); apiGetTeamList(-1) // Get all teams .then(res => { if (res.success) { setTeams(res.data.teams); } else { showErrorMessage(res.message, "Failed to load teams"); } }) .catch(err => {}) .finally(() => setLoadingTeams(false)); }; const loadWardsList = () => { setLoadingWards(true); apiGetWardList(-1, true) // Get all wards .then(res => { if (res.success) { setWards(res.data.wards); } else { showErrorMessage(res.message, "Failed to load wards"); } }) .catch(err => {}) .finally(() => setLoadingWards(false)); }; const updateFilter = ( name: string, genders: string[], hasTeam: string, isAdmitted: string, teams: number[], wards: number[] ) => { setNameSearch(name); setFilterGenders(genders); setFilterHasTeam(Number(hasTeam)); setFilterIsAdmitted(Number(isAdmitted)); setFilterTeams(teams); setFilterWards(wards); }; const refreshPatientList = () => { setRefreshingPatientList(true); apiGetPatientsList(currPage).then(res => { if (!res.success) { showErrorMessage(res.message, "Failed to get patient list"); return; } setPatientInfo(res.data); }) .catch(err => {}) .finally(() => { setRefreshingPatientList(false); }); }; const handleSort = (key: string) => { if (sortKey === key) { setSortDesc(!sortDesc); } else { setSortKey(key); setSortDesc(false); // Default ascending } }; const sortedPatients = useMemo(() => { let data = [...patientInfo.patients]; // Apply all filters data = data.filter((p) => { // Name search const matchesName = !nameSearch || ( (p.name?.toLowerCase().includes(nameSearch.toLowerCase()) || p.title?.toLowerCase().includes(nameSearch.toLowerCase())) ); // Gender filter const okGender = filterGenders.length === 0 || filterGenders.includes(p.gender); // Team assignment status filter const okTeam = filterHasTeam === -1 || (filterHasTeam === 1 ? p.admission?.team_id != null : p.admission?.team_id == null); // Admission status filter const okAdmitted = filterIsAdmitted === -1 || (filterIsAdmitted === 1 ? p.admission != null : p.admission == null); // Specific teams filter const okSpecificTeams = filterTeams.length === 0 || (p.admission?.team_id != null && filterTeams.includes(p.admission.team_id)); // Specific wards filter const okSpecificWards = filterWards.length === 0 || (p.ward?.id != null && filterWards.includes(p.ward.id)); return matchesName && okGender && okTeam && okAdmitted && okSpecificTeams && okSpecificWards; }); 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; } return 0; }); return data; }, [ patientInfo.patients, sortKey, sortDesc, nameSearch, filterGenders, filterHasTeam, filterIsAdmitted, filterTeams, filterWards ]); // Convert teams and wards to options format for MultiSelect const teamOptions = useMemo(() => { return teams.map(team => ({ value: team.id.toString(), label: team.department.replace(/_/g, ' ') })); }, [teams]); const wardOptions = useMemo(() => { return wards.map(ward => ({ value: ward.id.toString(), label: `${ward.name} (${ward.current_occupancy}/${ward.total_capacity})` })); }, [wards]); const handleViewPatientInfo = (patient: PatientDataWithWardAndAdmission) => { modals.open({ title: "Patient Information", centered: true, size: "md", children: ( Name: {`${patient.title} ${patient.name}`} Gender: {patient.gender} Birth Date: {patient.birth_date} Email: {patient.email} Phone: {patient.phone} Address: {patient.address} Postcode: {patient.postcode} ) }); }; const handleEditPatient = (patient: PatientDataWithWardAndAdmission) => { confirmEditPatient(patient, refreshPatientList); }; const handleViewWard = (wardId: number) => { confirmCheckWardPatients(wardId, refreshPatientList); }; const handlePatientDischarge = (admissionId: number, patientName: string) => { confirmPatientDischarge(admissionId, patientName, refreshPatientList); }; const rows = sortedPatients.map((patient) => ( {patient.id} {patient.gender} {patient.birth_date} {patient.email} {patient.phone} {patient.admission?.team_id ? ( ) : ( Not Assigned )} {patient.ward ? ( {patient.ward.name} handleViewWard(patient.ward!.id)} title="View ward details" > ) : ( Not Admitted )} { changePage(DashboardPageType.TreatmentRecord, `/dashboard/treatment_record/${patient.id}`) }}> handleViewPatientInfo(patient)} title="View patient info"> handleEditPatient(patient)} title="Edit patient info"> { confirmResetPassword(patient.id, UserType.PATIENT) }}> patient.admission && handlePatientDischarge(patient.admission.id, `${patient.title} ${patient.name}`)} disabled={!patient.admission} title="Discharge patient" > )); return ( Patients Management Filters handleSort("id")}> Patient ID{" "} {sortKey === "id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("name")}> Name{" "} {sortKey === "name" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("gender")}> Gender{" "} {sortKey === "gender" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("birth_date")}> Birth Date{" "} {sortKey === "birth_date" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("email")}> Email{" "} {sortKey === "email" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} handleSort("phone")}> Phone{" "} {sortKey === "phone" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]} Medical Team Ward Operations {rows}
); }