-
-
Notifications
You must be signed in to change notification settings - Fork 55
WEB-4677 - Improve Pop Health Add/Edit Patient Modal Performance #1995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
dd19b61
9829870
4e1aaa9
99783f4
2c8194a
39b73d2
651efa4
dd70d46
bb0e996
420f591
8a83a08
3a292e7
8c9d577
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
|
||
| const { Loader } = vizComponents; | ||
| const { reshapeBgClassesToBgBounds, generateBgRangeLabels, formatBgValue } = vizUtils.bg; | ||
|
|
@@ -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(); | ||
|
|
@@ -952,7 +953,6 @@ export const ClinicPatients = (props) => { | |
| } | ||
|
|
||
| setTimeout(() => { | ||
| setPatientFormContext(null); | ||
| setSelectedPatient(null); | ||
| }); | ||
| }, [ | ||
|
|
@@ -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' }); | ||
|
|
@@ -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 }); | ||
| } | ||
|
|
@@ -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]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Forward The inline dialogs forwarded
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
🤖 Prompt for AI Agents |
||
|
|
||
| const renderTideDashboardConfigDialog = useCallback(() => { | ||
| return ( | ||
|
|
||
| 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; |
| 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; |
Uh oh!
There was an error while loading. Please reload this page.