Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export function AnaestheticLogApp() {
if (!open) cancelPatientSelect();
}}
title="Switch patient?"
message={`You have unsaved changes in your current testing session.${selectedPatient ? ` Current: ${selectedPatient.lastName ? `${selectedPatient.lastName.toUpperCase()}, ${selectedPatient.firstName}` : selectedPatient.firstName} (MRN: ${selectedPatient.mrn || '—'}, DOB: ${selectedPatient.dob || 'not recorded'}).` : ''}${pendingPatientSelection ? ` Target: ${pendingPatientSelection.patient.lastName ? `${pendingPatientSelection.patient.lastName.toUpperCase()}, ${pendingPatientSelection.patient.firstName}` : pendingPatientSelection.patient.firstName} (MRN: ${pendingPatientSelection.patient.mrn || '—'}, DOB: ${pendingPatientSelection.patient.dob || 'not recorded'}).` : ''} Switching patients will discard these changes. This cannot be undone.`}
message={`You have unsaved changes in your current testing session.${selectedPatient ? ` Current: ${selectedPatient.lastName ? `${selectedPatient.lastName.toUpperCase()}, ${selectedPatient.firstName}` : selectedPatient.firstName} (REDCap ID: ${selectedPatient.mrn || '—'}, DOB: ${selectedPatient.dob || 'not recorded'}).` : ''}${pendingPatientSelection ? ` Target: ${pendingPatientSelection.patient.lastName ? `${pendingPatientSelection.patient.lastName.toUpperCase()}, ${pendingPatientSelection.patient.firstName}` : pendingPatientSelection.patient.firstName} (REDCap ID: ${pendingPatientSelection.patient.mrn || '—'}, DOB: ${pendingPatientSelection.patient.dob || 'not recorded'}).` : ''} Switching patients will discard these changes. This cannot be undone.`}
confirmLabel="Switch patient"
cancelLabel="Cancel"
variant="danger"
Expand Down
6 changes: 3 additions & 3 deletions e2e/direct-entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.describe('Home quick-start entry points', () => {
await expect(page).toHaveURL(/\/testing$/);
await expect(page.getByRole('heading', { name: 'Allergy Testing', exact: true })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Patient Identity', exact: true })).toBeVisible();
await expect(page.getByLabel(/MRN/i)).toBeEditable();
await expect(page.getByLabel(/REDCap ID/i)).toBeEditable();
await expect(page.getByLabel(/First Name/i)).toBeEditable();
await expect(page.getByLabel(/Last Name/i)).toBeEditable();
await expect(page.getByLabel(/Date of Birth/i)).toBeEditable();
Expand All @@ -35,7 +35,7 @@ test.describe('Home quick-start entry points', () => {
// section 0, where each field shows its own inline error message (not a link).
await page.getByRole('button', { name: /7\.\s*Review and save/i }).click();
await page.getByRole('button', { name: 'Save Clinical Record', exact: true }).first().click();
await expect(page.getByText('MRN is required')).toBeVisible();
await expect(page.getByText('REDCap ID is required')).toBeVisible();
await expect(page.getByText('First name is required')).toBeVisible();
await expect(page.getByText('Last name is required')).toBeVisible();
});
Expand All @@ -48,7 +48,7 @@ test.describe('Home quick-start entry points', () => {
await expect(page).toHaveURL(/\/testing$/);
await expect(page.getByRole('heading', { name: 'Allergy Testing', exact: true })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Patient Identity', exact: true })).toBeVisible();
await expect(page.getByLabel(/MRN/i)).toBeEditable();
await expect(page.getByLabel(/REDCap ID/i)).toBeEditable();
await expect(page.getByLabel(/Patient identity/i)).toHaveCount(0);
});

Expand Down
4 changes: 2 additions & 2 deletions e2e/testing-day.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ test.describe('Testing Day Flow', () => {
await page.waitForLoadState('networkidle');
await dismissHelpModal(page);

await page.getByLabel(/^mrn/i).fill('1');
await page.getByLabel(/^REDCap ID/i).fill('1');
await page.getByLabel(/first name/i).fill('Wei');
await page.getByLabel(/last name/i).fill('Chen');

Expand All @@ -138,7 +138,7 @@ test.describe('Testing Day Flow', () => {
await expect(page.getByText(/Draft saved/)).toBeVisible({ timeout: 5000 });
await expect(page.getByLabel(/first name/i)).toHaveValue('Wei');
await expect(page.getByLabel(/last name/i)).toHaveValue('Chen');
await expect(page.getByLabel(/^mrn/i)).toHaveValue('1');
await expect(page.getByLabel(/^REDCap ID/i)).toHaveValue('1');

// Section 1's field values are also preserved in the draft.
await page.getByRole('button', { name: 'Next Section', exact: true }).click();
Expand Down
12 changes: 6 additions & 6 deletions src/core/screens/LogScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function LogScreen({
const errors = {
firstName: selectedPatient.firstName.trim() ? '' : 'First name is required.',
lastName: selectedPatient.lastName.trim() ? '' : 'Last name is required.',
mrn: selectedPatient.mrn.trim() ? '' : 'MRN is required.',
mrn: selectedPatient.mrn.trim() ? '' : 'REDCap ID is required.',
};
setManualPatientErrors(errors);
if (Object.values(errors).some(Boolean)) return;
Expand Down Expand Up @@ -430,7 +430,7 @@ export function LogScreen({
if (!open) setPendingPatientToSelect(null);
}}
title="Switch patient?"
message={`You have unsaved changes in your current testing session.${selectedPatient ? ` Current: ${selectedPatient.lastName ? `${selectedPatient.lastName.toUpperCase()}, ${selectedPatient.firstName}` : selectedPatient.firstName} (MRN: ${selectedPatient.mrn || '—'}, DOB: ${selectedPatient.dob || 'not recorded'}).` : ''}${pendingPatientToSelect ? ` Target: ${pendingPatientToSelect.lastName ? `${pendingPatientToSelect.lastName.toUpperCase()}, ${pendingPatientToSelect.firstName}` : pendingPatientToSelect.firstName} (MRN: ${pendingPatientToSelect.mrn || '—'}, DOB: ${pendingPatientToSelect.dob || 'not recorded'}).` : ''} Switching patients will discard these changes. This cannot be undone.`}
message={`You have unsaved changes in your current testing session.${selectedPatient ? ` Current: ${selectedPatient.lastName ? `${selectedPatient.lastName.toUpperCase()}, ${selectedPatient.firstName}` : selectedPatient.firstName} (REDCap ID: ${selectedPatient.mrn || '—'}, DOB: ${selectedPatient.dob || 'not recorded'}).` : ''}${pendingPatientToSelect ? ` Target: ${pendingPatientToSelect.lastName ? `${pendingPatientToSelect.lastName.toUpperCase()}, ${pendingPatientToSelect.firstName}` : pendingPatientToSelect.firstName} (REDCap ID: ${pendingPatientToSelect.mrn || '—'}, DOB: ${pendingPatientToSelect.dob || 'not recorded'}).` : ''} Switching patients will discard these changes. This cannot be undone.`}
confirmLabel="Switch patient"
cancelLabel="Cancel"
variant="danger"
Expand Down Expand Up @@ -501,7 +501,7 @@ export function LogScreen({
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label htmlFor="manual-mrn" className="section-label mb-1.5 block">
MRN<span className="text-destructive ml-0.5" aria-hidden="true">*</span>
REDCap ID<span className="text-destructive ml-0.5" aria-hidden="true">*</span>
</Label>
<Input
id="manual-mrn"
Expand All @@ -511,7 +511,7 @@ export function LogScreen({
onManualDetailChange('mrn', e.target.value);
setManualPatientErrors(prev => ({ ...prev, mrn: '' }));
}}
placeholder="Medical Record Number..."
placeholder="REDCap ID..."
aria-invalid={!!manualPatientErrors.mrn}
aria-describedby={manualPatientErrors.mrn ? 'manual-mrn-error' : undefined}
/>
Expand All @@ -523,14 +523,14 @@ export function LogScreen({
</div>
<div>
<Label htmlFor="manual-redcap-id" className="section-label mb-1.5 block">
REDCap Record ID
REDCap Record ID (secondary)
</Label>
<Input
id="manual-redcap-id"
className="rounded-none font-mono"
value={selectedPatient.redcapId || ''}
onChange={(e) => onManualDetailChange('redcapId', e.target.value)}
placeholder="REDCap ID..."
placeholder="Secondary ID..."
/>
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/features/dashboard/components/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('Dashboard', () => {
it('filters patients by search term', async () => {
render(<Dashboard {...mockProps} />);

const searchInput = screen.getByPlaceholderText(/Search by Name, MRN/i);
const searchInput = screen.getByPlaceholderText(/Search by Name, REDCap ID/i);
fireEvent.change(searchInput, { target: { value: 'John' } });

await waitFor(() => {
Expand All @@ -205,7 +205,7 @@ describe('Dashboard', () => {
it('filters patients by MRN', async () => {
render(<Dashboard {...mockProps} />);

const searchInput = screen.getByPlaceholderText(/Search by Name, MRN/i);
const searchInput = screen.getByPlaceholderText(/Search by Name, REDCap ID/i);
fireEvent.change(searchInput, { target: { value: 'MRN001' } });

await waitFor(() => {
Expand All @@ -217,7 +217,7 @@ describe('Dashboard', () => {
it('clears search when input is cleared', async () => {
render(<Dashboard {...mockProps} />);

const searchInput = screen.getByPlaceholderText(/Search by Name, MRN/i);
const searchInput = screen.getByPlaceholderText(/Search by Name, REDCap ID/i);
fireEvent.change(searchInput, { target: { value: 'John' } });

await waitFor(() => {
Expand Down Expand Up @@ -583,7 +583,7 @@ describe('Dashboard', () => {
it('has proper ARIA labels on search input', () => {
render(<Dashboard {...mockProps} />);

const searchInput = screen.getByPlaceholderText(/Search by Name, MRN/i);
const searchInput = screen.getByPlaceholderText(/Search by Name, REDCap ID/i);
expect(searchInput).toHaveAttribute('aria-label');
});

Expand All @@ -597,7 +597,7 @@ describe('Dashboard', () => {
it('is keyboard navigable', () => {
render(<Dashboard {...mockProps} />);

const searchInput = screen.getByPlaceholderText(/Search by Name, MRN/i);
const searchInput = screen.getByPlaceholderText(/Search by Name, REDCap ID/i);
searchInput.focus();
expect(searchInput).toHaveFocus();

Expand Down
4 changes: 2 additions & 2 deletions src/features/dashboard/components/PatientTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ const PatientTable: React.FC<PatientTableProps> = ({
<div className="relative flex-1 sm:flex-none sm:w-64">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" aria-hidden="true" />
<Input
placeholder="Search by Name, MRN..."
aria-label="Search patients by name, medical record number (MRN), or city"
placeholder="Search by Name, REDCap ID..."
aria-label="Search patients by name, REDCap ID, or city"
className="pl-9 h-9 bg-muted"
value={filters.textQuery}
onChange={(e) => updateFilter('textQuery', e.target.value)}
Expand Down
4 changes: 2 additions & 2 deletions src/features/info-pages/components/FAQPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const FAQPage: React.FC<FAQPageProps> = ({ setScreen }) => {
items: [
{
question: "What are the ways to start an allergy testing session?",
answer: "DREAM supports two entry paths: (1) Patient-Linked Testing: Select a patient from the database on Home, review their reaction history, build and preview a testing plan, and proceed with prefilled drug protocols. (2) Direct Allergy Testing: Click 'Open Allergy Testing' on Home (or navigate directly to /testing) to start a fresh testing session at bedside with editable patient identity fields (MRN, first name, last name, DOB) without pre-selecting a patient or creating a prior plan."
answer: "DREAM supports two entry paths: (1) Patient-Linked Testing: Select a patient from the database on Home, review their reaction history, build and preview a testing plan, and proceed with prefilled drug protocols. (2) Direct Allergy Testing: Click 'Open Allergy Testing' on Home (or navigate directly to /testing) to start a fresh testing session at bedside with editable patient identity fields (REDCap ID, first name, last name, DOB) without pre-selecting a patient or creating a prior plan."
},
{
question: "How do I upload patient data?",
Expand All @@ -37,7 +37,7 @@ const FAQPage: React.FC<FAQPageProps> = ({ setScreen }) => {
items: [
{
question: "How do I search for a patient?",
answer: "Use the search box at the top of the dashboard to find patients by name, MRN, or suspected agent. You can also use the Filters button for advanced filtering."
answer: "Use the search box at the top of the dashboard to find patients by name, REDCap ID, or suspected agent. You can also use the Filters button for advanced filtering."
},
{
question: "What do the reaction grades mean?",
Expand Down
2 changes: 1 addition & 1 deletion src/features/info-pages/components/PrivacyPolicyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const PrivacyPolicyPage: React.FC<PrivacyPolicyPageProps> = ({ setScreen }) => {
<ul className="space-y-2 ml-4">
<li className="flex items-start gap-2">
<span className="text-primary mt-1 select-none">•</span>
<span><strong className="text-foreground">Personal Information:</strong> Name, date of birth, MRN/hospital ID, city or suburb</span>
<span><strong className="text-foreground">Personal Information:</strong> Name, date of birth, REDCap ID/hospital ID, city or suburb</span>
</li>
<li className="flex items-start gap-2">
<span className="text-primary mt-1 select-none">•</span>
Expand Down
4 changes: 2 additions & 2 deletions src/features/patients/components/ClinicalContextBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('ClinicalContextBar', () => {
expect(screen.getByText('Visit 18/03/2026')).toBeInTheDocument();
});

it('renders direct-entry badge for direct source and never displays REDCap ID', () => {
it('renders direct-entry badge for direct source and displays REDCap ID label', () => {
const context = createClinicalWorkContext({
source: 'direct',
firstName: 'John',
Expand All @@ -99,7 +99,7 @@ describe('ClinicalContextBar', () => {

render(<ClinicalContextBar context={context} />);
expect(screen.getAllByText('Direct Entry').length).toBeGreaterThanOrEqual(1);
expect(screen.queryByText(/redcap/i)).not.toBeInTheDocument();
expect(screen.getAllByText(/REDCap ID/i).length).toBeGreaterThanOrEqual(1);
});

it('renders manual-entry badge for manual source', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/features/patients/components/ClinicalContextBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function ClinicalContextBar({
<span className="font-semibold truncate">{formattedName}</span>
<span aria-hidden="true" className="text-muted-foreground shrink-0">·</span>
<span className="shrink-0">
MRN <span className="font-mono font-semibold">{mrn ? redact(mrn) : '—'}</span>
REDCap ID <span className="font-mono font-semibold">{mrn ? redact(mrn) : '—'}</span>
</span>
</div>

Expand Down Expand Up @@ -106,7 +106,7 @@ export function ClinicalContextBar({
<dt className="text-muted-foreground font-medium">Name</dt>
<dd className="font-semibold text-foreground break-words m-0">{formattedName}</dd>

<dt className="text-muted-foreground font-medium">MRN</dt>
<dt className="text-muted-foreground font-medium">REDCap ID</dt>
<dd className="font-mono font-semibold text-foreground m-0">{mrn ? redact(mrn) : '—'}</dd>

<dt className="text-muted-foreground font-medium">DOB</dt>
Expand Down Expand Up @@ -146,7 +146,7 @@ export function ClinicalContextBar({

<span aria-hidden="true" className="text-muted-foreground">·</span>
<span>
MRN <span className="font-mono font-semibold">{mrn ? redact(mrn) : '—'}</span>
REDCap ID <span className="font-mono font-semibold">{mrn ? redact(mrn) : '—'}</span>
</span>

<span aria-hidden="true" className="text-muted-foreground">·</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('PatientIdentityBar', () => {
render(<PatientIdentityBar {...baseProps} />);

const identityBar = screen.getByLabelText('Patient identity');
expect(identityBar).toHaveTextContent('DOE, Jane·MRN MrN00aB1·DOB not recorded');
expect(identityBar).toHaveTextContent('DOE, Jane·REDCap ID MrN00aB1·DOB not recorded');
expect(screen.queryByText(/^Reaction/)).not.toBeInTheDocument();
});

Expand Down
2 changes: 1 addition & 1 deletion src/features/reports/components/ClinicalReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const ClinicalReport: React.FC<ClinicalReportProps> = ({ data, activeReportSaved
<p className="text-xl md:text-2xl font-semibold tracking-tight text-primary print:text-base print:text-black">{patientName}</p>
</div>
<div>
<label className="text-xs uppercase tracking-wider text-muted-foreground font-semibold block mb-1 print:mb-0.5">MRN</label>
<label className="text-xs uppercase tracking-wider text-muted-foreground font-semibold block mb-1 print:mb-0.5">REDCap ID</label>
<p className="text-lg font-mono font-medium text-foreground print:text-sm">{redact(data.mrn)}</p>
</div>
<div>
Expand Down
4 changes: 2 additions & 2 deletions src/features/reports/components/OutboundActionDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ describe('OutboundActionDialog', () => {

expect(screen.getByText(/Transmitted Field Categories:/i)).toBeInTheDocument();
expect(screen.getByText(/Tested drug names and skin\/intradermal test/i)).toBeInTheDocument();
expect(screen.getAllByText(/Patient direct identifiers \(name, MRN, DOB, contacts\) are omitted/i).length).toBeGreaterThanOrEqual(1);
expect(screen.getByText(/De-identified \(patient direct identifiers: name, MRN, DOB, contacts omitted\)/i)).toBeInTheDocument();
expect(screen.getAllByText(/Patient direct identifiers \(name, REDCap ID, DOB, contacts\) are omitted/i).length).toBeGreaterThanOrEqual(1);
expect(screen.getByText(/De-identified \(patient direct identifiers: name, REDCap ID, DOB, contacts omitted\)/i)).toBeInTheDocument();
expect(screen.queryByText(/Fully De-identified/i)).not.toBeInTheDocument();
expect(screen.queryByText(/anonymised/i)).not.toBeInTheDocument();
expect(screen.getByText(/This record has already been submitted/i)).toBeInTheDocument();
Expand Down
8 changes: 4 additions & 4 deletions src/features/reports/components/OutboundActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ export function OutboundActionDialog({
icon: <Database className="w-5 h-5 text-primary" />,
confirmLabel: researchAlreadySubmitted ? 'Already Submitted' : 'Confirm & Submit to Research Registry',
destination: propDestination || 'ANZTADC Secure Research Database',
disclosure: propDisclosureMode || 'De-identified (patient direct identifiers: name, MRN, DOB, contacts omitted)',
disclosure: propDisclosureMode || 'De-identified (patient direct identifiers: name, REDCap ID, DOB, contacts omitted)',
description:
'Confirm transmission of de-identified clinical testing results to the anaesthetic reaction registry. Patient direct identifiers (name, MRN, DOB, contacts) are omitted prior to transmission.',
'Confirm transmission of de-identified clinical testing results to the anaesthetic reaction registry. Patient direct identifiers (name, REDCap ID, DOB, contacts) are omitted prior to transmission.',
};
}
};
Expand Down Expand Up @@ -167,7 +167,7 @@ export function OutboundActionDialog({
<span className="font-semibold text-foreground">{patientName}</span>

<span className="font-semibold text-muted-foreground uppercase tracking-wider text-xs">
MRN:
REDCap ID:
</span>
<span className="font-mono text-foreground">{mrn}</span>

Expand Down Expand Up @@ -206,7 +206,7 @@ export function OutboundActionDialog({
))}
</ul>
<p className="text-xs text-muted-foreground pt-1 border-t border-primary/10 italic">
Note: Patient direct identifiers (name, MRN, DOB, contacts) are omitted.
Note: Patient direct identifiers (name, REDCap ID, DOB, contacts) are omitted.
</p>
</div>
)}
Expand Down
Loading