Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ab826d0
add abstracted tags filter
henry-tp Feb 24, 2026
714ea46
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
d127721
move filter state to redux
henry-tp Feb 24, 2026
61a04f9
add filter status indicator
henry-tp Feb 24, 2026
aa08ffb
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
b894d67
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
93eb37b
fix bug with stale activeFilters
henry-tp Feb 24, 2026
fab884d
persist values to localStorage
henry-tp Feb 24, 2026
0a367a8
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
e44f04c
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
676f729
flip incorrect boolean check
henry-tp Feb 24, 2026
77485f9
un-redux the filters
henry-tp Feb 24, 2026
70fa8fe
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
7494dd8
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
937c410
fix breaking merge changes
henry-tp Feb 24, 2026
475a095
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
46f46cf
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
65d477b
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
5a68d81
move useClinicPatientsFilters into own file
henry-tp Feb 25, 2026
0c8ed7a
add filter reset button
henry-tp Feb 25, 2026
77dc9f2
rename ResetFilters
henry-tp Feb 25, 2026
1c55089
pull ActiveFilterCount into components directory
henry-tp Feb 25, 2026
dcac76c
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
d391a2c
Merge branch 'WEB-4454-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
1bd6794
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 25, 2026
4ec64a4
WEB-4454 pull FilterByTags into components directory
henry-tp Feb 25, 2026
959c0b9
WEB-4454 use abstracted ActiveFilterCount component for ClinicPatients
henry-tp Feb 25, 2026
a011d8d
WEB-4454 revert moving hook into own folder
henry-tp Feb 25, 2026
ec8dfc5
WEB-4454 revert accidental console.log
henry-tp Feb 25, 2026
d08d5a9
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 25, 2026
c6364c1
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 26, 2026
ff17268
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 26, 2026
f08454f
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 26, 2026
627fa67
WEB-4454 isolate persistence of deviceIssues filters from clinicPatie…
henry-tp Feb 26, 2026
f3df7b9
WEB-4454 ClinicPatients uses abstracted form ResetFilters component
henry-tp Feb 26, 2026
095d1ad
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 26, 2026
43b8694
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 27, 2026
1a269fe
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Feb 27, 2026
e71ce0e
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Mar 2, 2026
42f2759
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Mar 3, 2026
104c3f0
WEB-4454 put FilterByTags inside controller
henry-tp Mar 3, 2026
52c6cc9
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Mar 4, 2026
3a2e82c
WEB-4454 rename TagFilterDropdown
henry-tp Mar 4, 2026
c1c56a7
WEB-4454 add empty node state
henry-tp Mar 4, 2026
868a841
WEB-4454 extract build query params fn
henry-tp Mar 5, 2026
8d823e5
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Mar 6, 2026
c26e4ef
WEB-4454 reset offset on filter change
henry-tp Mar 6, 2026
171eea1
WEB-4454 ensure state is preserver per user per clinic
henry-tp Mar 13, 2026
e199e04
WEB-4454 standardize deviceIssuesFilters key
henry-tp Mar 13, 2026
c5fcee5
WEB-4454 prune invalid tags
henry-tp Mar 16, 2026
0fe1122
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Mar 17, 2026
d27e59e
WEB-4454 show tags filter only if entitled
henry-tp Mar 17, 2026
e6e2ec5
WEB-4454 render reset tags only if entitled
henry-tp Mar 17, 2026
17bd2b6
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp May 6, 2026
a35eedc
WEB-4454 add sites filters
henry-tp May 29, 2026
a75fc75
WEB-4454 rename to usePruneInvalidFilters
henry-tp May 29, 2026
c916b5f
WEB-4454 update copy to reflect sites OR filter
henry-tp Jun 2, 2026
7009e01
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Jun 23, 2026
efc4334
Merge branch 'WEB-4454-dashboard' into WEB-4454-tags
henry-tp Jun 29, 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
49 changes: 7 additions & 42 deletions app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import SendEmailIcon from '../../core/icons/SendEmailIcon.svg';
import TabularReportIcon from '../../core/icons/TabularReportIcon.svg';
import utils from '../../core/utils';
import LimitReached from './images/LimitReached.svg';
import ActiveFilterCount from './components/ActiveFilterCount';

import {
Dialog,
Expand Down Expand Up @@ -118,6 +119,7 @@ import Banner from '../../components/elements/Banner';
import colorPalette from '../../themes/colorPalette';
import noop from 'lodash/noop';
import { getGlycemicRangesPreset } from '../../core/glycemicRangesUtils';
import ResetFilters from './components/ResetFilters';

const { Loader } = vizComponents;
const { reshapeBgClassesToBgBounds, generateBgRangeLabels, formatBgValue } = vizUtils.bg;
Expand Down Expand Up @@ -1769,37 +1771,7 @@ export const ClinicPatients = (props) => {
sx={{ alignItems: 'center', gap: 2, justifyContent: 'flex-start', flexWrap: 'wrap' }}
id='summary-dashboard-filters'
>
<Flex
pl={[0, 0, 2]}
py={1}
sx={{
color: activeFiltersCount > 0 ? 'purpleMedium' : 'grays.4',
alignItems: 'center',
gap: 1,
borderLeft: ['none', null, borders.divider],
flexShrink: 0
}}
>
{activeFiltersCount > 0 ? (
<Pill
id="filter-count"
label="filter count"
round
sx={{ width: '14px', lineHeight: '15px', fontSize: '9px', display: 'flex', justifyContent: 'center' }}
colorPalette={['purpleMedium', 'white']}
text={`${activeFiltersCount}`}
/>
) : (
<Icon
id="filter-icon"
variant="static"
iconSrc={FilterIcon}
label={t('Filter')}
sx={{ fontSize: 1, width: '14px', color: 'grays.4' }}
/>
)}
<Text sx={{ fontSize: 0 }}>{t('Filter By')}</Text>
</Flex>
<ActiveFilterCount count={activeFiltersCount} />

<Flex sx={{ flexShrink: 0, gap: 2 }}>
<Box
Expand Down Expand Up @@ -2561,17 +2533,10 @@ export const ClinicPatients = (props) => {
</Popover>
</Flex>

{activeFiltersCount > 0 && (
<Button
id="reset-all-active-filters"
variant="textSecondary"
onClick={handleResetFilters}
sx={{ fontSize: 0, color: 'grays.4', flexShrink: 0 }}
px={0}
>
{t('Reset Filters')}
</Button>
)}
<ResetFilters
hidden={activeFiltersCount <= 0}
onClick={handleResetFilters}
/>
</Flex>
)}

Expand Down
50 changes: 41 additions & 9 deletions app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useTranslation, Trans } from 'react-i18next';
import { colors as vizColors } from '@tidepool/viz';
import Table from '../../../components/elements/Table';
import { Box, Flex, Grid, Text } from 'theme-ui';

import ActiveFilterCount from '../components/ActiveFilterCount';
import FilterByTags from './FilterByTags';
import FilterBySites from './FilterBySites';
import FilterByCategory from './FilterByCategory';
import ResetFilters from '../components/ResetFilters';
import PaginationControls from '../components/PaginationControls';

import { resetDeviceIssuesFilters } from './deviceIssuesFiltersSlice';
import { setOffset, resetDeviceIssuesState } from './deviceIssuesSlice';
import { useGetDeviceIssuesPatientsQuery } from './deviceIssuesApi';
import useActiveFiltersCount from './useActiveFiltersCount';
import EmptyContentNode from './EmptyContentNode';
import usePruneInvalidFilters from './usePruneInvalidFilters';
import useTableColumns from './useTableColumns';
import PatientCount from '../components/PatientCount';

Expand All @@ -20,24 +28,37 @@ const DeviceIssues = () => {
const { t } = useTranslation();
const dispatch = useDispatch();

usePruneInvalidFilters();

const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
const category = useSelector(state => state.blip.deviceIssues.category);
const offset = useSelector(state => state.blip.deviceIssues.offset);
const { patientTags, clinicSites } = useSelector(state => state.blip.deviceIssuesFilters);

const showFilters = clinic?.entitlements?.patientTags && clinic?.entitlements?.clinicSites;

const columns = useTableColumns();

const { data } = useGetDeviceIssuesPatientsQuery(
{ clinicId: selectedClinicId, offset, category, limit: LIMIT },
{ clinicId: selectedClinicId, offset, category, tags: patientTags, sites: clinicSites, limit: LIMIT },
{ skip: !selectedClinicId }
);

const activeFiltersCount = useActiveFiltersCount();

// reset state on dismount
useEffect(() => {
return () => dispatch(resetDeviceIssuesState());
}, []);

const handleChangeOffset = (newOffset) => dispatch(setOffset(newOffset));

const handleResetFilters = () => {
dispatch(resetDeviceIssuesFilters());
dispatch(setOffset(0));
};

if (!data) return null;

const tableData = data?.data || [];
Expand All @@ -46,14 +67,21 @@ const DeviceIssues = () => {

return (
<>
<Flex mb={2}>
<Flex mb={3} sx={{ fontSize: 0, color: vizColors.blueGray50, fontStyle: 'italic' }}>
<Trans>
<Text sx={{ fontSize: 0, color: vizColors.blueGray50, fontStyle: 'italic' }}>
Only patients with active device issues or delayed data from a <Text sx={{ fontWeight: 'bold' }}>cloud-connected device</Text> will be displayed.
</Text>
Only patients with active device issues or delayed data from a <Text sx={{ fontWeight: 'bold' }}>cloud-connected device</Text> will be displayed.
</Trans>
</Flex>

{ showFilters &&
<Flex mb={3} sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
<ActiveFilterCount count={activeFiltersCount} />
<FilterByTags />
<FilterBySites />
<ResetFilters hidden={activeFiltersCount <= 0} onClick={handleResetFilters} />
</Flex>
}

<Flex mb={3} sx={{ justifyContent: 'center' }}>
<FilterByCategory />
</Flex>
Expand All @@ -64,12 +92,16 @@ const DeviceIssues = () => {
label="deviceIssuesPatientsTable"
columns={columns}
data={tableData}
// sx={tableStyle}
emptyContentNode={<EmptyContentNode />}
sx={{
'&.MuiTable-root': {
display: tableData?.length > 0 ? 'table' : 'none',
},
}}
// onSort={handleSortChange}
// order={sort?.substring(0, 1) === '+' ? 'asc' : 'desc'}
// orderBy={sort?.substring(1)}
// onClickRow={handleClickPatient}
// emptyContentNode={}
/>

<Grid sx={{ gridTemplateColumns: '1fr 2fr 1fr' }}>
Expand Down
66 changes: 66 additions & 0 deletions app/pages/clinicworkspace/DeviceIssues/EmptyContentNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import colorPalette from '../../../themes/colorPalette';
import { Flex, Text, Box } from 'theme-ui';
import styled from '@emotion/styled';
import { colors as vizColors } from '@tidepool/viz';
import useActiveFiltersCount from './useActiveFiltersCount';
import { resetDeviceIssuesFilters } from './deviceIssuesFiltersSlice';

const ClearButton = styled.button`
background: none;
color: ${vizColors.indigo30};
border: none;
padding: 0;
font: inherit;
cursor: pointer;
text-underline-offset: 4px;
text-decoration: underline;
`;

const EmptyContentNode = () => {
const { t } = useTranslation();
const dispatch = useDispatch();

const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);

const showTags = clinic?.entitlements?.patientTags;

const activeFiltersCount = useActiveFiltersCount();
const hasActiveFilters = activeFiltersCount > 0;

const emptyContentCopy = hasActiveFilters
? t('There are no device issues with the current filter(s)')
: t('There are no results to show');

const handleResetFilters = () => dispatch(resetDeviceIssuesFilters());

const showResetButton = showTags && hasActiveFilters;

return (
<Flex sx={{
backgroundColor: colorPalette.primary.bluePrimary00,
justifyContent: 'center',
alignItems: 'center',
minHeight: '90px',
flexDirection: 'column',
gap: 2,
marginBottom: 4,
borderBottom: '1px solid #D1D6E1',
}}>
<Text className="table-empty-text" sx={{ fontWeight: 'medium' }}>
{emptyContentCopy}
</Text>

{ showResetButton && (
<ClearButton className='reset-filters-button' onClick={handleResetFilters}>
{t('Reset Filter')}
</ClearButton>
)}
</Flex>
);
};

export default EmptyContentNode;
19 changes: 19 additions & 0 deletions app/pages/clinicworkspace/DeviceIssues/FilterBySites.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setOffset } from './deviceIssuesSlice';
import { setClinicSitesFilter } from './deviceIssuesFiltersSlice';
import SitesFilterDropdown from '../components/SitesFilterDropdown';

const FilterBySites = () => {
const dispatch = useDispatch();
const { clinicSites } = useSelector(state => state.blip.deviceIssuesFilters);

const handleChange = (clinicSites) => {
dispatch(setClinicSitesFilter(clinicSites));
dispatch(setOffset(0));
};

return <SitesFilterDropdown onChange={handleChange} clinicSites={clinicSites} />;
};

export default FilterBySites;
19 changes: 19 additions & 0 deletions app/pages/clinicworkspace/DeviceIssues/FilterByTags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setOffset } from './deviceIssuesSlice';
import { setPatientTagsFilter } from './deviceIssuesFiltersSlice';
import TagFilterDropdown from '../components/TagFilterDropdown';

const FilterByTags = () => {
const dispatch = useDispatch();
const { patientTags } = useSelector(state => state.blip.deviceIssuesFilters);

const handleChange = (tags) => {
dispatch(setPatientTagsFilter(tags));
dispatch(setOffset(0));
};

return <TagFilterDropdown onChange={handleChange} patientTags={patientTags} />;
};

export default FilterByTags;
29 changes: 20 additions & 9 deletions app/pages/clinicworkspace/DeviceIssues/deviceIssuesApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,32 @@ const getDeviceIssuesParam = (category) => {
}
};

export const buildGetDeviceIssuesPatientsParams = (offset, limit, category, tags = [], sites = []) => {
const formattedTags = tags.length > 0 ? tags.join(',') : undefined;
const formattedSites = sites.length > 0 ? sites.join(',') : undefined;

const deviceIssues = getDeviceIssuesParam(category);
const omitHiddenDevicesIssues = category !== CATEGORY.HIDDEN;

return {
offset,
limit,
tags: formattedTags,
sites: formattedSites,
deviceIssues,
omitHiddenDevicesIssues,
};
};

const deviceIssuesApi = RTKQueryApi.injectEndpoints({
endpoints: (builder) => ({
getDeviceIssuesPatients: builder.query({
query: ({ clinicId, offset, category, limit }) => {
const deviceIssues = getDeviceIssuesParam(category);
const omitHiddenDevicesIssues = category !== CATEGORY.HIDDEN;
query: ({ clinicId, offset, limit, category, tags, sites }) => {
const params = buildGetDeviceIssuesPatientsParams(offset, limit, category, tags, sites);

return {
url: `/clinics/${clinicId}/patients`,
params: {
offset,
limit,
deviceIssues,
omitHiddenDevicesIssues,
},
params,
};
},
}),
Expand Down
30 changes: 30 additions & 0 deletions app/pages/clinicworkspace/DeviceIssues/deviceIssuesFiltersSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
patientTags: [],
clinicSites: [],
};

const deviceIssuesFiltersSlice = createSlice({
name: 'deviceIssuesFilters',
initialState,
reducers: {
setDeviceIssuesFilters: (_state, action) => action.payload || initialState,
setPatientTagsFilter: (state, action) => {
state.patientTags = action.payload;
},
setClinicSitesFilter: (state, action) => {
state.clinicSites = action.payload;
},
resetDeviceIssuesFilters: () => initialState,
},
});

export const {
setDeviceIssuesFilters,
setClinicSitesFilter,
setPatientTagsFilter,
resetDeviceIssuesFilters
} = deviceIssuesFiltersSlice.actions;

export default deviceIssuesFiltersSlice.reducer;
16 changes: 16 additions & 0 deletions app/pages/clinicworkspace/DeviceIssues/useActiveFiltersCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { useSelector } from 'react-redux';
import without from 'lodash/without';

const useActiveFiltersCount = () => {
const { patientTags, clinicSites } = useSelector(state => state.blip.deviceIssuesFilters);

const count = without([
patientTags?.length,
clinicSites?.length,
], null, 0, undefined).length;

return count;
};

export default useActiveFiltersCount;
Loading