-
-
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 11 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 |
|---|---|---|
|
|
@@ -71,7 +71,6 @@ import { TagList } from '../../components/elements/Tag'; | |
| import Pagination from '../../components/elements/Pagination'; | ||
| import TextInput from '../../components/elements/TextInput'; | ||
| import BgSummaryCell from '../../components/clinic/BgSummaryCell'; | ||
| import PatientForm from '../../components/clinic/PatientForm'; | ||
| import PatientLastReviewed from '../../components/clinic/PatientLastReviewed'; | ||
| import TideDashboardConfigForm, { validateTideConfig } from '../../components/clinic/TideDashboardConfigForm'; | ||
| import RpmReportConfigForm, { exportRpmReport } from '../../components/clinic/RpmReportConfigForm'; | ||
|
|
@@ -101,7 +100,6 @@ import { useIsFirstRender, useLocalStorage, usePrevious, useScrollToTop } from ' | |
| import { fieldsAreValid, getCommonFormikFieldProps } from '../../core/forms'; | ||
|
|
||
| import { | ||
| patientSchema as validationSchema, | ||
| clinicSiteSchema, | ||
| clinicPatientTagSchema, | ||
| lastDataFilterOptions, | ||
|
|
@@ -120,6 +118,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; | ||
|
|
@@ -713,7 +713,6 @@ export const ClinicPatients = (props) => { | |
| const loggedInUserId = useSelector((state) => state.blip.loggedInUserId); | ||
| const pdf = useSelector((state) => state.blip.pdf); | ||
| const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); | ||
| const mrnSettings = useMemo(() => clinic?.mrnSettings ?? {}, [clinic?.mrnSettings]); | ||
| const timePrefs = useSelector((state) => state.blip.timePrefs); | ||
| const rpmReportPatients = useSelector(state => state.blip.rpmReportPatients); | ||
| const isClinicAdmin = includes(get(clinic, ['clinicians', loggedInUserId, 'roles'], []), 'CLINIC_ADMIN'); | ||
|
|
@@ -736,7 +735,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(); | ||
|
|
@@ -759,8 +757,6 @@ export const ClinicPatients = (props) => { | |
| const ldClient = useLDClient(); | ||
| const ldContext = ldClient.getContext(); | ||
|
|
||
| const existingMRNs = useSelector(state => state.blip.clinicMRNsForPatientFormValidation)?.filter(mrn => mrn !== selectedPatient?.mrn) || []; | ||
|
|
||
| const defaultPatientFetchOptions = useMemo( | ||
| () => { | ||
| const options = { | ||
|
|
@@ -892,22 +888,20 @@ export const ClinicPatients = (props) => { | |
| }); | ||
| }, searchDebounceMs), [patientFetchOptions, searchDebounceMs]); | ||
|
|
||
| const { | ||
| fetchingPatientFromClinic, | ||
| fetchingPatientsForClinic, | ||
| deletingPatientFromClinic, | ||
| updatingClinicPatient, | ||
| creatingClinicCustodialAccount, | ||
| sendingPatientUploadReminder, | ||
| creatingClinicSite, | ||
| updatingClinicSite, | ||
| creatingClinicPatientTag, | ||
| updatingClinicPatientTag, | ||
| deletingClinicSite, | ||
| deletingClinicPatientTag, | ||
| fetchingTideDashboardPatients, | ||
| fetchingRpmReportPatients, | ||
| } = useSelector((state) => state.blip.working); | ||
| const fetchingPatientFromClinic = useSelector((state) => state.blip.working.fetchingPatientFromClinic); | ||
| const fetchingPatientsForClinic = useSelector((state) => state.blip.working.fetchingPatientsForClinic); | ||
| const deletingPatientFromClinic = useSelector((state) => state.blip.working.deletingPatientFromClinic); | ||
| const updatingClinicPatient = useSelector((state) => state.blip.working.updatingClinicPatient); | ||
| const creatingClinicCustodialAccount = useSelector((state) => state.blip.working.creatingClinicCustodialAccount); | ||
| const sendingPatientUploadReminder = useSelector((state) => state.blip.working.sendingPatientUploadReminder); | ||
| const creatingClinicSite = useSelector((state) => state.blip.working.creatingClinicSite); | ||
| const updatingClinicSite = useSelector((state) => state.blip.working.updatingClinicSite); | ||
| const creatingClinicPatientTag = useSelector((state) => state.blip.working.creatingClinicPatientTag); | ||
| const updatingClinicPatientTag = useSelector((state) => state.blip.working.updatingClinicPatientTag); | ||
| const deletingClinicSite = useSelector((state) => state.blip.working.deletingClinicSite); | ||
| const deletingClinicPatientTag = useSelector((state) => state.blip.working.deletingClinicPatientTag); | ||
| const fetchingTideDashboardPatients = useSelector((state) => state.blip.working.fetchingTideDashboardPatients); | ||
| const fetchingRpmReportPatients = useSelector((state) => state.blip.working.fetchingRpmReportPatients); | ||
|
Contributor
Author
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. By calling the non-specific |
||
|
|
||
| const { patientListSearchTextInput, isPatientListVisible } = useSelector(({ blip }) => blip.patientListFilters); | ||
|
|
||
|
|
@@ -952,7 +946,6 @@ export const ClinicPatients = (props) => { | |
| } | ||
|
|
||
| setTimeout(() => { | ||
| setPatientFormContext(null); | ||
| setSelectedPatient(null); | ||
| }); | ||
| }, [ | ||
|
|
@@ -1399,22 +1392,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 +1481,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 }); | ||
| } | ||
|
|
@@ -1568,11 +1541,11 @@ export const ClinicPatients = (props) => { | |
| setActiveSort, | ||
| ]); | ||
|
|
||
| function handleClearSearch() { | ||
| const handleClearSearch = useCallback(() => { | ||
| dispatch(actions.sync.setPatientListSearchTextInput('')); | ||
| setLoading(true); | ||
| debounceSearch(''); | ||
| } | ||
| }, [debounceSearch, dispatch]); | ||
|
|
||
| const handlePageChange = useCallback((event, page) => { | ||
| setPatientFetchOptions({ | ||
|
|
@@ -1581,11 +1554,11 @@ export const ClinicPatients = (props) => { | |
| }); | ||
| }, [patientFetchOptions]); | ||
|
|
||
| function handleResetFilters() { | ||
| const handleResetFilters = useCallback(() => { | ||
| trackMetric(prefixPopHealthMetric('Clear all filters'), { clinicId: selectedClinicId }); | ||
| setActiveFilters(defaultFilterState); | ||
| setPendingFilters(defaultFilterState); | ||
| } | ||
| }, [prefixPopHealthMetric, selectedClinicId, setActiveFilters, trackMetric]); | ||
|
|
||
| const handleFilterTimeInRange = useCallback(() => { | ||
| trackMetric(prefixPopHealthMetric('Time in range apply filter'), { | ||
|
|
@@ -3053,110 +3026,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> | ||
| if (!showEditPatientDialog || !selectedPatient) return null; | ||
|
|
||
| <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> | ||
|
|
||
| <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, showEditPatientDialog, selectedPatient, handleCloseOverlays]); | ||
|
|
||
| const renderTideDashboardConfigDialog = useCallback(() => { | ||
| return ( | ||
|
|
@@ -4241,13 +4124,26 @@ export const ClinicPatients = (props) => { | |
| }, | ||
| }), [data?.length, showSummaryData]); | ||
|
|
||
| const patientListQueryState = useMemo( | ||
| () => getPatientListQueryState(activeFilters, patientListSearchTextInput), | ||
| [activeFilters, patientListSearchTextInput] | ||
| ); | ||
|
|
||
| const emptyContentNode = useMemo(() => ( | ||
| <EmptyContentNode patientListQueryState={patientListQueryState}> | ||
| <ClearFilterButtons | ||
| patientListQueryState={patientListQueryState} | ||
| onClearSearch={handleClearSearch} | ||
| onResetFilters={handleResetFilters} | ||
| /> | ||
| </EmptyContentNode> | ||
| ), [patientListQueryState, handleClearSearch, handleResetFilters]); | ||
|
|
||
| const renderPeopleTable = useCallback(() => { | ||
| const pageCount = Math.ceil(clinic?.fetchedPatientCount / patientFetchOptions.limit); | ||
| const page = Math.ceil(patientFetchOptions.offset / patientFetchOptions.limit) + 1; | ||
| const sort = patientFetchOptions.sort || defaultPatientFetchOptions.sort; | ||
|
|
||
| const patientListQueryState = getPatientListQueryState(activeFilters, patientListSearchTextInput); | ||
|
|
||
| // Show the Filter Reset Bar only if data exists and any filters/search are applied | ||
| const showFilterResetBar = (data?.length > 0) && patientListQueryState !== PATIENT_LIST_QUERY_STATE.NONE; | ||
|
|
||
|
|
@@ -4279,15 +4175,7 @@ export const ClinicPatients = (props) => { | |
| order={sort?.substring(0, 1) === '+' ? 'asc' : 'desc'} | ||
| orderBy={sort?.substring(1)} | ||
| onClickRow={handleClickPatient} | ||
| emptyContentNode={ | ||
| <EmptyContentNode patientListQueryState={patientListQueryState}> | ||
| <ClearFilterButtons | ||
| patientListQueryState={patientListQueryState} | ||
| onClearSearch={handleClearSearch} | ||
| onResetFilters={handleResetFilters} | ||
| /> | ||
| </EmptyContentNode> | ||
| } | ||
| emptyContentNode={emptyContentNode} | ||
| /> | ||
|
|
||
| {pageCount > 1 && ( | ||
|
|
@@ -4310,10 +4198,15 @@ export const ClinicPatients = (props) => { | |
| columns, | ||
| data, | ||
| defaultPatientFetchOptions.sort, | ||
| emptyContentNode, | ||
| handleClearSearch, | ||
| handleClickPatient, | ||
| handlePageChange, | ||
| handleResetFilters, | ||
| handleSortChange, | ||
| loading, | ||
| patientFetchOptions, | ||
| patientListQueryState, | ||
| showSummaryData, | ||
| tableStyle, | ||
| ]); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.