Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d5aeef1
WEB-4654 abstract out CGM Use filter
henry-tp Jul 15, 2026
3911e1f
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 15, 2026
75e0462
WEB-4654 change directory for FilterByCGMUse
henry-tp Jul 15, 2026
7257c07
WEB-4654 clear unneeded props
henry-tp Jul 16, 2026
e0d8a32
WEB-4654 add tests for dropdown
henry-tp Jul 17, 2026
53c73a7
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 17, 2026
ef670d2
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 17, 2026
a3dc964
WEB-4654 fix metrics tracking
henry-tp Jul 17, 2026
0901ffe
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 17, 2026
65123c3
WEB-4654 fix tests
henry-tp Jul 17, 2026
d4d3821
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 17, 2026
fc2614e
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 19, 2026
e0edb0c
WEB-4654 use MemoryRouter instead of mocking out location hook
henry-tp Jul 19, 2026
17ef2fb
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 20, 2026
33961fe
WEB-4654 remove unused vars
henry-tp Jul 20, 2026
ff50288
WEB-4654 remove pendingFilters dead state
henry-tp Jul 21, 2026
279c4f0
Merge branch 'WEB-4654-filter-bar' into WEB-4654-cgm-use
henry-tp Jul 21, 2026
552162d
Merge branch 'WEB-4654-tags' into WEB-4654-cgm-use
henry-tp Jul 21, 2026
8582105
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 24, 2026
6ac7ce4
WEB-4654 fix missing metric update
henry-tp Jul 26, 2026
94c7db8
WEB-4654 update pageName
henry-tp Jul 26, 2026
10c09bf
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 26, 2026
79841f6
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 26, 2026
5235683
Merge branch 'WEB-4654-time-in-range' into WEB-4654-cgm-use
henry-tp Jul 26, 2026
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
101 changes: 5 additions & 96 deletions app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import compact from 'lodash/compact';
import debounce from 'lodash/debounce';
import difference from 'lodash/difference';
import forEach from 'lodash/forEach';
import find from 'lodash/find';
import get from 'lodash/get';
import includes from 'lodash/includes';
import indexOf from 'lodash/indexOf';
Expand Down Expand Up @@ -126,6 +125,7 @@ import FilterByTags from './FilterByTags';
import FilterBySites from './FilterBySites';
import FilterByDataRecency from './FilterByDataRecency';
import FilterByTimeInRange from './FilterByTimeInRange';
import FilterByCGMUse from './FilterByCGMUse';
import ClinicPatientsPrintModal from './ClinicPatientsPrintModal';

const { Loader } = vizComponents;
Expand Down Expand Up @@ -663,11 +663,6 @@ export const ClinicPatients = (props) => {
const [pendingFilters, setPendingFilters] = useState({ ...defaultFilterState, ...activeFilters });
const previousActiveFilters = usePrevious(activeFilters);

const cgmUseFilterOptions = [
{ value: '<0.7', label: t('Less than 70%') },
{ value: '>=0.7', label: t('70% or more') },
];

const lastDataTypeFilterOptions = [
{ value: 'cgm', label: t('CGM') },
{ value: 'bgm', label: t('BGM') },
Expand All @@ -694,11 +689,6 @@ export const ClinicPatients = (props) => {
const [activeSummaryPeriod, setActiveSummaryPeriod] = useLocalStorage('activePatientSummaryPeriod', defaultSummaryPeriod);
const previousSummaryPeriod = usePrevious(activeSummaryPeriod);

const cgmUsePopupFilterState = usePopupState({
variant: 'popover',
popupId: 'cgmUseFilters',
});

const debounceSearch = useCallback(debounce(search => {
// Prevent a premature update to patientFetchOptions, which would trigger an initial patient
// fetch with potentially incorrect sorting.
Expand Down Expand Up @@ -1634,91 +1624,10 @@ export const ClinicPatients = (props) => {
setActiveFilters={setActiveFilters}
/>

<Box
onClick={() => {
if (!cgmUsePopupFilterState.isOpen) trackMetric(prefixPopHealthMetric('CGM Use filter open'), { clinicId: selectedClinicId });
}}
sx={{ flexShrink: 0 }}
>
<Button
variant="filter"
id="cgm-use-filter-trigger"
selected={!!activeFilters.timeCGMUsePercent}
{...bindTrigger(cgmUsePopupFilterState)}
icon={KeyboardArrowDownRoundedIcon}
iconLabel="Filter by cgm use"
sx={{ fontSize: 0, lineHeight: 1.3 }}
>
{activeFilters.timeCGMUsePercent ? find(cgmUseFilterOptions, { value: activeFilters.timeCGMUsePercent })?.label : t('% CGM Use')}
</Button>
</Box>

<Popover
minWidth="11em"
closeIcon
{...bindPopover(cgmUsePopupFilterState)}
onClickCloseIcon={() => {
trackMetric(prefixPopHealthMetric('CGM Use filter close'), { clinicId: selectedClinicId });
}}
onClose={() => {
cgmUsePopupFilterState.close();
setPendingFilters(activeFilters);
}}
>
<DialogContent px={2} py={3} dividers>
<Box sx={{ alignItems: 'center' }} mb={2}>
<Text sx={{ color: 'grays.4', fontWeight: 'medium', fontSize: 0, whiteSpace: 'nowrap' }}>
{t('% CGM Use')}
</Text>
</Box>

<RadioGroup
id="cgm-use"
name="cgm-use"
options={cgmUseFilterOptions}
variant="vertical"
sx={{ fontSize: 0 }}
value={pendingFilters.timeCGMUsePercent || activeFilters.timeCGMUsePercent}
onChange={event => {
setPendingFilters({ ...pendingFilters, timeCGMUsePercent: event.target.value || null });
}}
/>
</DialogContent>

<DialogActions sx={{ justifyContent: 'space-between' }} p={1}>
<Button
id="clear-cgm-use-filter"
sx={{ fontSize: 1 }}
variant="textSecondary"
onClick={() => {
trackMetric(prefixPopHealthMetric('CGM use clear filter'), { clinicId: selectedClinicId });
setPendingFilters({ ...activeFilters, timeCGMUsePercent: defaultFilterState.timeCGMUsePercent });
setActiveFilters({ ...activeFilters, timeCGMUsePercent: defaultFilterState.timeCGMUsePercent });
cgmUsePopupFilterState.close();
}}
>
{t('Clear')}
</Button>

<Button
id="apply-cgm-use-filter"
disabled={!pendingFilters.timeCGMUsePercent}
sx={{ fontSize: 1 }}
variant="textPrimary"
onClick={() => {
trackMetric(prefixPopHealthMetric('CGM use apply filter'), {
clinicId: selectedClinicId,
filter: pendingFilters.timeCGMUsePercent,
});

setActiveFilters(pendingFilters);
cgmUsePopupFilterState.close();
}}
>
{t('Apply')}
</Button>
</DialogActions>
</Popover>
<FilterByCGMUse
activeFilters={activeFilters}
setActiveFilters={setActiveFilters}
/>

<FilterByTimeInRange
activeFilters={activeFilters}
Expand Down
24 changes: 24 additions & 0 deletions app/pages/clinicworkspace/FilterByCGMUse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import noop from 'lodash/noop';

import CGMUseFilterDropdown from './components/CGMUseFilterDropdown';

const FilterByCGMUse = ({
activeFilters = {},
setActiveFilters = noop,
}) => {
const handleChange = (timeCGMUsePercent) => {
setActiveFilters({ ...activeFilters, timeCGMUsePercent });
};

const { timeCGMUsePercent } = activeFilters;

return (
<CGMUseFilterDropdown
onChange={handleChange}
timeCGMUsePercent={timeCGMUsePercent}
/>
);
};

export default FilterByCGMUse;
160 changes: 160 additions & 0 deletions app/pages/clinicworkspace/components/CGMUseFilterDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { trackMetric } from '../../../core/metricUtils';
import { colors as vizColors } from '@tidepool/viz';

import { Box, Grid, Text } from 'theme-ui';
import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';

import find from 'lodash/find';
import noop from 'lodash/noop';

import { bindPopover, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';

import Button from '../../../components/elements/Button';
import Popover from '../../../components/elements/Popover';
import RadioGroup from '../../../components/elements/RadioGroup';

const prefixPopHealthMetric = () => noop; // TODO: FIX

const getCgmUseFilterOptions = (t) => [
{ value: '<0.7', label: t('Less than 70%') },
{ value: '>=0.7', label: t('70% or more') },
];

const DropdownContent = ({
onClose = noop,
onChange = noop,
timeCGMUsePercent = null,
}) => {
const { t } = useTranslation();
const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);

const [pendingTimeCGMUsePercent, setPendingTimeCGMUsePercent] = useState(timeCGMUsePercent);

const cgmUseFilterOptions = getCgmUseFilterOptions(t);

const handleChange = timeCGMUsePercent => onChange(timeCGMUsePercent);

return (
<Box mt={5} mx={2} sx={{ width: 300 }}>
<Box>
<Box sx={{ alignItems: 'center' }} mb={2}>
<Text sx={{ color: 'grays.4', fontWeight: 'medium', fontSize: 1, whiteSpace: 'nowrap' }}>
{t('% CGM Use')}
</Text>
</Box>

<Box sx={{ border: `1px solid ${vizColors.gray10}`, borderRadius: 6, padding: 2 }}>
<RadioGroup
id="cgm-use"
name="cgm-use"
options={cgmUseFilterOptions}
variant="vertical"
sx={{ fontSize: 0 }}
value={pendingTimeCGMUsePercent}
onChange={event => setPendingTimeCGMUsePercent(event.target.value || null)}
/>
</Box>
</Box>

<Grid sx={{ gridTemplateColumns: '1fr 1fr' }} mt={3} mb={2}>
<Button
id="clear-cgm-use-filter"
sx={{ fontSize: 1 }}
variant="secondary"
onClick={() => {
trackMetric(prefixPopHealthMetric('CGM use clear filter'), { clinicId: selectedClinicId });
setPendingTimeCGMUsePercent(null);
handleChange(null);
onClose();
}}
>
{t('Clear')}
</Button>

<Button
id="apply-cgm-use-filter"
disabled={!pendingTimeCGMUsePercent}
sx={{ fontSize: 1 }}
variant="primary"
onClick={() => {
trackMetric(prefixPopHealthMetric('CGM use apply filter'), {
clinicId: selectedClinicId,
filter: pendingTimeCGMUsePercent,
});

handleChange(pendingTimeCGMUsePercent);
onClose();
}}
>
{t('Apply')}
</Button>
</Grid>
</Box>
);
};

const CGMUseFilterDropdown = ({
onChange = noop,
timeCGMUsePercent = null,
}) => {
const { t } = useTranslation();

const cgmUsePopupFilterState = usePopupState({
variant: 'popover',
popupId: 'cgmUseFilters',
});

const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);

const handleCloseDropdown = () => {
cgmUsePopupFilterState.close();
};

return (
<>
<Box
onClick={() => {
if (!cgmUsePopupFilterState.isOpen) trackMetric(prefixPopHealthMetric('CGM Use filter open'), { clinicId: selectedClinicId });
}}
sx={{ flexShrink: 0 }}
>
<Button
variant="filter"
id="cgm-use-filter-trigger"
selected={!!timeCGMUsePercent}
{...bindTrigger(cgmUsePopupFilterState)}
icon={KeyboardArrowDownRoundedIcon}
iconLabel="Filter by cgm use"
sx={{ fontSize: 0, lineHeight: 1.3 }}
>
{t('% CGM Use')}
</Button>
</Box>

<Popover
minWidth="11em"
closeIcon
{...bindPopover(cgmUsePopupFilterState)}
onClickCloseIcon={() => {
trackMetric(prefixPopHealthMetric('CGM Use filter close'), { clinicId: selectedClinicId });
}}
onClose={() => {
cgmUsePopupFilterState.close();
}}
>
{ cgmUsePopupFilterState.isOpen &&
<DropdownContent
timeCGMUsePercent={timeCGMUsePercent}
onClose={handleCloseDropdown}
onChange={onChange}
/>
}
</Popover>
</>
);
};

export default CGMUseFilterDropdown;