import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Patient } from 'fhir/r4'; import { Paginator, FhirPatientDisplay } from '../../../components'; import PresenceDot from '../../../components/PresenceDot/PresenceDot'; import { FhirHumanNameDisplay, formatFhirHumanName } from '../../../utils/fhirHumanName.tsx'; import { Plus, Zap } from 'lucide-react'; import { useAuthServiceContext, usePatientServiceContext, useSurfaceExpectedPatient } from '../../../services'; import FrontDeskPatientQuickView from '../ReceptionistHomePage/FrontDeskPatientQuickView.tsx'; import ReceptionistRegisterPatientDialog from '../ReceptionistHomePage/ReceptionistRegisterPatientDialog.tsx'; import { useCapabilityServiceContext } from '../../../services/CapabilityService.tsx'; import { frontDeskActionGrants } from '../../../components/PortalFrameHeader/frontDeskHeaderAction'; import { PatientMark } from '../ReceptionistHomePage/PatientMark.tsx'; import { patientRowSummary } from './patientRowSummary'; import './PatientsPage.css'; const PatientsPage = () => { const navigate = useNavigate(); const { userRole, USER_ROLES } = useAuthServiceContext(); const { fetchPatientsMatching, setPatientState } = usePatientServiceContext(); // - declares AND releases on unmount; see the hook. const declareExpectedPatient = useSurfaceExpectedPatient(); const [selectedPatient, setSelectedPatient] = useState(null); const [showDisplayModal, setShowDisplayModal] = useState(false); const isReceptionist = userRole === USER_ROLES.RECEPTIONIST; const { mayCall } = useCapabilityServiceContext(); // Add is the header's Register, offered where Register is. const mayRegister = frontDeskActionGrants({ isPatientViewer: false, closedToStaff: false, isReceptionist, deskWorkspace: true, mayCall }).register; const [registerOpen, setRegisterOpen] = useState(false); // The receptionist's quick view is keyed by the patient it was opened for. const [quickViewId, setQuickViewId] = useState(); useEffect(() => { if (userRole && ![USER_ROLES.PRACTITIONER, USER_ROLES.RECEPTIONIST].includes(userRole as string)) { navigate('/'); } }, [userRole]); // Receptionist: row-click opens the read-only-default workspace modal in place. Practitioner: keep the full // patient chart route - they need the clinical chart, so their path is deliberately left unchanged. const viewPatient = (patient: Patient) => { if (isReceptionist) { setQuickViewId(patient.id); return; } // - seeded here so the chart has a patient before its own effect runs; `PatientPage` // re-declares the same id from the route on arrival. Both declare the SAME id, so the two agree // by construction rather than by ordering. declareExpectedPatient(patient.id); setPatientState(patient); navigate(patient.id as string, { relative: 'route' }); }; // Receptionist: "Quick View" also opens the workspace (retires the legacy FhirPatientDisplay for them). // Practitioner: unchanged legacy read-only display modal. const quickViewPatient = (event: React.MouseEvent, patient: Patient) => { event.stopPropagation(); if (isReceptionist) { setQuickViewId(patient.id); return; } setSelectedPatient(patient); setShowDisplayModal(true); }; // Registration opens in place: a chart address named "new" is read by the desk as a patient id. const addPatient = () => setRegisterOpen(true); const renderList = (patients: Patient[]) => (
    {patients.map((patient: Patient) => { const birthDate = patient.birthDate ? new Date(patient.birthDate) : null; const age = birthDate ? Math.floor((Date.now() - birthDate.getTime()) / (365.25 * 24 * 60 * 60 * 1000)) : null; const phone = patient.telecom?.find(contact => contact.system === 'phone')?.value; const email = patient.telecom?.find(contact => contact.system === 'email')?.value; const gender = patient.gender ? patient.gender.charAt(0).toUpperCase() + patient.gender.slice(1) : ''; return ( // The row and its Quick View are sibling buttons: a button inside a button is invalid // and leaves the inner one unreachable by keyboard.
  • {/* Holds its width so the name block wraps instead; icon only on a phone. */}
  • ); })}
); return (

Patients

{/* Card 329 — the way in for a practice arriving with an existing patient list. ⭐ Ghost, not blue: adding one patient is the everyday action and importing is the once-per-practice one, so the colour has to say which is which. */} {mayRegister && ( )}
{/* Name, phone or date of birth, as the header search reads them. */} You do not have access to this practice's patient list.{' '} Your role does not include reading patient records, so the list cannot be shown. That is a refusal rather than a loading problem, so trying again will not change it. Ask an administrator if you need it. )} > {(patients) => renderList(patients)} {selectedPatient && ( { setShowDisplayModal(false); setSelectedPatient(null); }}/> )} {registerOpen && ( { setRegisterOpen(false); if (patient.id) viewPatient(patient); }} /> )} {isReceptionist && quickViewId && ( setQuickViewId(undefined)} backLabel="Back to patients" /> )}
); }; export default PatientsPage;