Skip to content
Open
130 changes: 10 additions & 120 deletions app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ import colorPalette from '../../themes/colorPalette';
import noop from 'lodash/noop';
import { getGlycemicRangesPreset } from '../../core/glycemicRangesUtils';
import ClinicPatientsPrintModal from './ClinicPatientsPrintModal';
import AddPatientDialog from './clinicPatientsDialogs/AddPatientDialog';
import EditPatientDialog from './clinicPatientsDialogs/EditPatientDialog';
Comment thread
henry-tp marked this conversation as resolved.

const { Loader } = vizComponents;
const { reshapeBgClassesToBgBounds, generateBgRangeLabels, formatBgValue } = vizUtils.bg;
Expand Down Expand Up @@ -736,7 +738,6 @@ export const ClinicPatients = (props) => {
const [selectedClinicSite, setSelectedClinicSite] = useState(null);
const [selectedPatientTag, setSelectedPatientTag] = useState(null);
const [loading, setLoading] = useState(false);
const [patientFormContext, setPatientFormContext] = useState();
const [rpmReportFormContext, setRpmReportFormContext] = useState();
const [tideDashboardFormContext, setTideDashboardFormContext] = useState();
const [clinicSiteFormContext, setClinicSiteFormContext] = useState();
Expand Down Expand Up @@ -952,7 +953,6 @@ export const ClinicPatients = (props) => {
}

setTimeout(() => {
setPatientFormContext(null);
setSelectedPatient(null);
});
}, [
Expand Down Expand Up @@ -1399,22 +1399,6 @@ export const ClinicPatients = (props) => {
setShowAddPatientDialog(true);
}

const handleAddPatientConfirm = useCallback(() => {
trackMetric('Clinic - Add patient confirmed', { clinicId: selectedClinicId });
patientFormContext?.handleSubmit();
}, [patientFormContext, selectedClinicId, trackMetric]);

const handleEditPatientConfirm = useCallback(() => {
trackMetric('Clinic - Edit patient confirmed', { clinicId: selectedClinicId });
const updatedTags = [...(patientFormContext?.values?.tags || [])];
const existingTags = [...(selectedPatient?.tags || [])];

if (!isEqual(updatedTags.sort(), existingTags.sort())) {
trackMetric(prefixPopHealthMetric('Edit patient tags confirm'), { clinicId: selectedClinicId });
}
patientFormContext?.handleSubmit();
}, [patientFormContext, selectedClinicId, trackMetric, selectedPatient?.tags, prefixPopHealthMetric]);

function handleConfigureTideDashboard() {
if (validateTideConfig(tideDashboardConfig[localConfigKey], patientTags)) {
trackMetric('Clinic - Navigate to Tide Dashboard', { clinicId: selectedClinicId, source: 'Patients list' });
Expand Down Expand Up @@ -1504,10 +1488,6 @@ export const ClinicPatients = (props) => {
dispatch(actions.async.sendPatientUploadReminder(api, selectedClinicId, selectedPatient?.id));
}, [api, dispatch, prefixPopHealthMetric, selectedClinicId, selectedPatient?.id, trackMetric]);

function handlePatientFormChange(formikContext) {
setPatientFormContext({ ...formikContext });
}

function handleTideDashboardConfigFormChange(formikContext) {
setTideDashboardFormContext({ ...formikContext });
}
Expand Down Expand Up @@ -3053,110 +3033,20 @@ export const ClinicPatients = (props) => {
}, [handleDeleteClinicPatientTagConfirm, handleCloseClinicPatientTagUpdateDialog, selectedPatientTag?.name, showDeleteClinicPatientTagDialog, t]);

const renderAddPatientDialog = useCallback(() => {
return (
<Dialog
id="addPatient"
aria-labelledby="dialog-title"
open={showAddPatientDialog}
onClose={handleCloseOverlays}
>
<DialogTitle onClose={handleCloseOverlays}>
<MediumTitle id="dialog-title">{t('Add New Patient Account')}</MediumTitle>
</DialogTitle>
if (!showAddPatientDialog) return null;

<DialogContent>
<PatientForm api={api} trackMetric={trackMetric} onFormChange={handlePatientFormChange} searchDebounceMs={searchDebounceMs} action="create" />
</DialogContent>

<DialogActions>
<Button id="addPatientCancel" variant="secondary" onClick={handleCloseOverlays}>
{t('Cancel')}
</Button>
<Button
id="addPatientConfirm"
variant="primary"
onClick={handleAddPatientConfirm}
processing={creatingClinicCustodialAccount.inProgress}
disabled={!fieldsAreValid(keys(patientFormContext?.values), validationSchema({mrnSettings, existingMRNs}), patientFormContext?.values)}
>
{t('Add Patient')}
</Button>
</DialogActions>
</Dialog>
return (
<AddPatientDialog api={api} onClose={handleCloseOverlays} />
);
}, [
api,
creatingClinicCustodialAccount.inProgress,
handleAddPatientConfirm,
mrnSettings,
existingMRNs,
patientFormContext?.values,
showAddPatientDialog,
t,
trackMetric
]);
}, [api, showAddPatientDialog, handleCloseOverlays]);

const renderEditPatientDialog = useCallback(() => {
return (
<Dialog
id="editPatient"
aria-labelledby="dialog-title"
open={showEditPatientDialog}
onClose={handleCloseOverlays}
>
<DialogTitle onClose={() => {
trackMetric('Clinic - Edit patient close', { clinicId: selectedClinicId });
handleCloseOverlays()
}}>
<MediumTitle id="dialog-title">{t('Edit Patient Details')}</MediumTitle>
</DialogTitle>

<DialogContent>
<PatientForm
api={api}
trackMetric={trackMetric}
onFormChange={handlePatientFormChange}
patient={selectedPatient}
searchDebounceMs={searchDebounceMs}
action="edit"
/>
</DialogContent>

<DialogActions>
<Button id="editPatientCancel" variant="secondary" onClick={() => {
trackMetric('Clinic - Edit patient cancel', { clinicId: selectedClinicId, source: 'Patients list' });
handleCloseOverlays()
}}>
{t('Cancel')}
</Button>
if (!showEditPatientDialog || !selectedPatient) return null;

<Button
id="editPatientConfirm"
variant="primary"
onClick={handleEditPatientConfirm}
processing={updatingClinicPatient.inProgress}
disabled={!fieldsAreValid(keys(patientFormContext?.values), validationSchema({mrnSettings, existingMRNs}), patientFormContext?.values)}
>
{t('Save Changes')}
</Button>
</DialogActions>
</Dialog>
return (
<EditPatientDialog api={api} patient={selectedPatient} onClose={handleCloseOverlays} />
);
}, [
api,
handleEditPatientConfirm,
mrnSettings,
existingMRNs,
handleCloseOverlays,
patientFormContext,
searchDebounceMs,
selectedClinicId,
selectedPatient,
showEditPatientDialog,
t,
trackMetric,
updatingClinicPatient.inProgress
]);
}, [api, selectedPatient, handleCloseOverlays]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Forward searchDebounceMs through the extracted dialogs.

The inline dialogs forwarded searchDebounceMs to PatientForm, and PatientForm uses it to debounce MRN lookups. The extracted dialogs replace that input with fixed 1000, so a caller-provided non-default value has no effect. (raw.githubusercontent.com)

  • app/pages/clinicworkspace/ClinicPatients.js#L3036-L3049: Pass searchDebounceMs to both dialog components and add it to both useCallback dependency arrays.
  • app/pages/clinicworkspace/clinicPatientsDialogs/addPatientDialog.js#L18-L21: Accept searchDebounceMs, with SEARCH_DEBOUNCE_MS as its default.
  • app/pages/clinicworkspace/clinicPatientsDialogs/addPatientDialog.js#L60-L60: Pass the received value to PatientForm.
  • app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js#L19-L23: Accept searchDebounceMs, with SEARCH_DEBOUNCE_MS as its default.
  • app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js#L76-L76: Pass the received value to PatientForm.
Proposed fix
-<AddPatientDialog api={api} onClose={handleCloseOverlays} />
+<AddPatientDialog
+  api={api}
+  onClose={handleCloseOverlays}
+  searchDebounceMs={searchDebounceMs}
+/>

-<EditPatientDialog api={api} patient={selectedPatient} onClose={handleCloseOverlays} />
+<EditPatientDialog
+  api={api}
+  patient={selectedPatient}
+  onClose={handleCloseOverlays}
+  searchDebounceMs={searchDebounceMs}
+/>

 const AddPatientDialog = ({
   api,
   onClose = noop,
+  searchDebounceMs = SEARCH_DEBOUNCE_MS,
 }) => {
-  ...
-  searchDebounceMs={SEARCH_DEBOUNCE_MS}
+  ...
+  searchDebounceMs={searchDebounceMs}

 const EditPatientDialog = ({
   api,
   patient,
   onClose = noop,
+  searchDebounceMs = SEARCH_DEBOUNCE_MS,
 }) => {
-  ...
-  searchDebounceMs={SEARCH_DEBOUNCE_MS}
+  ...
+  searchDebounceMs={searchDebounceMs}
📍 Affects 3 files
  • app/pages/clinicworkspace/ClinicPatients.js#L3036-L3049 (this comment)
  • app/pages/clinicworkspace/clinicPatientsDialogs/addPatientDialog.js#L18-L21
  • app/pages/clinicworkspace/clinicPatientsDialogs/addPatientDialog.js#L60-L60
  • app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js#L19-L23
  • app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js#L76-L76
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/pages/clinicworkspace/ClinicPatients.js` around lines 3036 - 3049,
Forward searchDebounceMs through both extracted patient dialogs so
caller-provided values reach PatientForm instead of being replaced by the fixed
default. In app/pages/clinicworkspace/ClinicPatients.js:3036-3049, pass
searchDebounceMs to AddPatientDialog and EditPatientDialog and include it in
both useCallback dependency arrays. In
app/pages/clinicworkspace/clinicPatientsDialogs/addPatientDialog.js:18-21 and
:60, accept it with SEARCH_DEBOUNCE_MS as the default and pass it to
PatientForm. Apply the same changes in
app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js:19-23 and
:76.


const renderTideDashboardConfigDialog = useCallback(() => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { trackMetric } from '../../../core/metricUtils';

import keys from 'lodash/keys';
import noop from 'lodash/noop';
import { fieldsAreValid } from '../../../core/forms';
import { patientSchema as validationSchema } from '../../../core/clinicUtils';

import Button from '../../../components/elements/Button';
import { Dialog, DialogContent, DialogTitle, DialogActions } from '../../../components/elements/Dialog';
import { MediumTitle } from '../../../components/elements/FontStyles';
import PatientForm from '../../../components/clinic/PatientForm';

const SEARCH_DEBOUNCE_MS = 1000;

const AddPatientDialog = ({
api,
onClose = noop,
}) => {
const { t } = useTranslation();
const creatingClinicCustodialAccount = useSelector(state => state.blip.working.creatingClinicCustodialAccount);
const selectedClinicId = useSelector(state => state.blip.selectedClinicId);

const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
const mrnSettings = useMemo(() => clinic?.mrnSettings ?? {}, [clinic?.mrnSettings]);

const clinicMRNsForPatientFormValidation = useSelector(state => state.blip.clinicMRNsForPatientFormValidation);
const existingMRNs = useMemo(() => clinicMRNsForPatientFormValidation || [], [clinicMRNsForPatientFormValidation]);
const schema = useMemo(() => validationSchema({ mrnSettings, existingMRNs }), [mrnSettings, existingMRNs]);

const [formContext, setFormContext] = useState(null);

const handleFormChange = (formikContext) => setFormContext({ ...formikContext });

const handleClose = () => onClose();

const handleConfirm = () => {
trackMetric('Clinic - Add patient confirmed', { clinicId: selectedClinicId });
formContext?.handleSubmit();
};

return (
<Dialog
id="addPatient"
aria-labelledby="dialog-title"
open={true}
onClose={handleClose}
>
<DialogTitle onClose={handleClose}>
<MediumTitle id="dialog-title">{t('Add New Patient Account')}</MediumTitle>
</DialogTitle>

<DialogContent>
<PatientForm
api={api}
trackMetric={trackMetric}
onFormChange={handleFormChange}
searchDebounceMs={SEARCH_DEBOUNCE_MS}
action="create"
/>
</DialogContent>

<DialogActions>
<Button id="addPatientCancel" variant="secondary" onClick={handleClose}>
{t('Cancel')}
</Button>
<Button
id="addPatientConfirm"
variant="primary"
onClick={handleConfirm}
processing={creatingClinicCustodialAccount.inProgress}
disabled={!fieldsAreValid(keys(formContext?.values), schema, formContext?.values)}
>
{t('Add Patient')}
</Button>
</DialogActions>
</Dialog>
);
}

export default AddPatientDialog;
103 changes: 103 additions & 0 deletions app/pages/clinicworkspace/clinicPatientsDialogs/editPatientDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { trackMetric } from '../../../core/metricUtils';

import keys from 'lodash/keys';
import noop from 'lodash/noop';
import isEqual from 'lodash/isEqual';
import { fieldsAreValid } from '../../../core/forms';
import { patientSchema as validationSchema } from '../../../core/clinicUtils';

import Button from '../../../components/elements/Button';
import { Dialog, DialogContent, DialogTitle, DialogActions } from '../../../components/elements/Dialog';
import { MediumTitle } from '../../../components/elements/FontStyles';
import PatientForm from '../../../components/clinic/PatientForm';

const SEARCH_DEBOUNCE_MS = 1000;

const EditPatientDialog = ({
api,
patient,
onClose = noop,
}) => {
const { t } = useTranslation();
const updatingClinicPatient = useSelector(state => state.blip.working.updatingClinicPatient);
const selectedClinicId = useSelector(state => state.blip.selectedClinicId);

const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
const mrnSettings = useMemo(() => clinic?.mrnSettings ?? {}, [clinic?.mrnSettings]);

const clinicMRNsForPatientFormValidation = useSelector(state => state.blip.clinicMRNsForPatientFormValidation);
// Patient's pre-existing MRN will already exist in clinic, so it needs to not trip the validator
const existingMRNs = useMemo(() => (
clinicMRNsForPatientFormValidation?.filter(mrn => mrn !== patient?.mrn) || []
), [clinicMRNsForPatientFormValidation, patient?.mrn]);
const schema = useMemo(() => validationSchema({ mrnSettings, existingMRNs }), [mrnSettings, existingMRNs]);

const [formContext, setFormContext] = useState(null);

const handleFormChange = (formikContext) => setFormContext({ ...formikContext });

const handleClose = () => onClose();

const handleConfirm = () => {
trackMetric('Clinic - Edit patient confirmed', { clinicId: selectedClinicId });
const updatedTags = [...(formContext?.values?.tags || [])];
const existingTags = [...(patient?.tags || [])];

if (!isEqual(updatedTags.sort(), existingTags.sort())) {
trackMetric('Clinic - Population Health - Edit patient tags confirm', { clinicId: selectedClinicId });
}

formContext?.handleSubmit();
};

return (
<Dialog
id="editPatient"
aria-labelledby="dialog-title"
open={true}
onClose={handleClose}
>
<DialogTitle onClose={() => {
trackMetric('Clinic - Edit patient close', { clinicId: selectedClinicId });
handleClose()
}}>
<MediumTitle id="dialog-title">{t('Edit Patient Details')}</MediumTitle>
</DialogTitle>

<DialogContent>
<PatientForm
api={api}
trackMetric={trackMetric}
onFormChange={handleFormChange}
patient={patient}
searchDebounceMs={SEARCH_DEBOUNCE_MS}
action="edit"
/>
</DialogContent>

<DialogActions>
<Button id="editPatientCancel" variant="secondary" onClick={() => {
trackMetric('Clinic - Edit patient cancel', { clinicId: selectedClinicId, source: 'Patients list' });
handleClose()
}}>
{t('Cancel')}
</Button>

<Button
id="editPatientConfirm"
variant="primary"
onClick={handleConfirm}
processing={updatingClinicPatient.inProgress}
disabled={!fieldsAreValid(keys(formContext?.values), schema, formContext?.values)}
>
{t('Save Changes')}
</Button>
</DialogActions>
</Dialog>
);
}

export default EditPatientDialog;