diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js b/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js
new file mode 100644
index 0000000000..7c66d54f23
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js
@@ -0,0 +1,156 @@
+import React, { useState} from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+import { bindPopover, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
+import { setLastDataFilter } from './tideDashboardFiltersSlice';
+import { Box } from 'theme-ui';
+import Button from '../../../components/elements/Button';
+import Popover from '../../../components/elements/Popover';
+import RadioGroup from '../../../components/elements/RadioGroup';
+
+import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';
+
+import noop from 'lodash/noop';
+import reject from 'lodash/reject';
+import find from 'lodash/find';
+
+import { lastDataFilterOptions } from '../../../core/clinicUtils';
+import { borders } from '../../../themes/baseTheme';
+import { DialogActions, DialogContent } from '../../../components/elements/Dialog';
+import { Body0 } from '../../../components/elements/FontStyles';
+import { setOffset } from './tideDashboardSlice';
+
+const trackMetric = noop;
+const prefixPopHealthMetric = noop;
+
+const customLastDataFilterOptions = lastDataFilterOptions.filter(opt => [1, 2, 7].includes(opt.value));
+
+const DropdownContent = ({
+ onClose = noop,
+}) => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const lastData = useSelector(state => state.blip.tideDashboardFilters.lastData);
+ const [pendingLastData, setPendingLastData] = useState(lastData);
+
+ return (
+ <>
+
+
+ {t('Data Recency')}
+ {t('Tidepool will only show patients who have data within the selected number of days.')}
+
+
+ setPendingLastData(parseInt(evt.target.value) || null)}
+ />
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const FilterByDataRecency = () => {
+ const { t } = useTranslation();
+ const lastDataPopupFilterState = usePopupState({
+ variant: 'popover',
+ popupId: 'lastDataFilters',
+ });
+
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const lastData = useSelector(state => state.blip.tideDashboardFilters.lastData);
+
+ return (
+ <>
+ {
+ if (!lastDataPopupFilterState.isOpen) {
+ trackMetric(prefixPopHealthMetric('Last data filter open'), { clinicId: selectedClinicId });
+ }
+ }}
+ sx={{ flexShrink: 0 }}
+ >
+
+
+
+ {
+ trackMetric(prefixPopHealthMetric('Last upload filter close'), { clinicId: selectedClinicId });
+ }}
+ onClose={() => lastDataPopupFilterState.close()}
+ >
+ { lastDataPopupFilterState.isOpen &&
+ lastDataPopupFilterState.close()}
+ />
+ }
+
+ >
+ );
+};
+
+export default FilterByDataRecency;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js b/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js
new file mode 100644
index 0000000000..c759c720a4
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js
@@ -0,0 +1,149 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+import { bindPopover, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
+import { setSummaryPeriodFilter } from './tideDashboardFiltersSlice';
+import { Box } from 'theme-ui';
+import Button from '../../../components/elements/Button';
+import Popover from '../../../components/elements/Popover';
+import RadioGroup from '../../../components/elements/RadioGroup';
+
+import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';
+
+import noop from 'lodash/noop';
+import find from 'lodash/find';
+
+import { DialogActions, DialogContent } from '../../../components/elements/Dialog';
+import { Body0 } from '../../../components/elements/FontStyles';
+import { setOffset } from './tideDashboardSlice';
+
+const trackMetric = noop;
+const prefixPopHealthMetric = noop;
+
+const summaryPeriodOptions = [
+ { value: '1d', label: '24 hours' },
+ { value: '7d', label: '7 days' },
+ { value: '14d', label: '14 days' },
+ { value: '30d', label: '30 days' },
+];
+
+const DropdownContent = ({
+ onClose = noop,
+}) => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const [pendingSummaryPeriod, setPendingSummaryPeriod] = useState(summaryPeriod);
+
+ return (
+ <>
+
+
+ {t('Summary Period')}
+ {t('Tidepool will generate health summaries for the selected number of days.')}
+
+
+ setPendingSummaryPeriod(event.target.value)}
+ />
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const FilterBySummaryPeriod = () => {
+ const { t } = useTranslation();
+ const summaryPeriodPopupFilterState = usePopupState({
+ variant: 'popover',
+ popupId: 'summaryPeriodFilters',
+ });
+
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+
+ return (
+ <>
+ {
+ if (!summaryPeriodPopupFilterState.isOpen) {
+ trackMetric(prefixPopHealthMetric('Summary period filter open'), { clinicId: selectedClinicId });
+ }
+ }}
+ sx={{ flexShrink: 0 }}
+ >
+
+
+
+ {
+ trackMetric(prefixPopHealthMetric('Summary period filter close'), { clinicId: selectedClinicId });
+ }}
+ onClose={() => summaryPeriodPopupFilterState.close()}
+ >
+ { summaryPeriodPopupFilterState.isOpen &&
+ summaryPeriodPopupFilterState.close()}
+ />
+ }
+
+ >
+ );
+};
+
+export default FilterBySummaryPeriod;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
index ea6527c1c1..4a641a2c6f 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
@@ -4,10 +4,13 @@ import { useDispatch, useSelector } from 'react-redux';
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 { Flex, Text, Box, Grid } from 'theme-ui';
import FilterByCategory from './FilterByCategory';
import FilterByTags from './FilterByTags';
+import FilterByDataRecency from './FilterByDataRecency';
+import FilterBySummaryPeriod from './FilterBySummaryPeriod';
+
import TableCategoryHeader from './TableCategoryHeader';
import PaginationControls from '../components/PaginationControls';
import ActiveFilterCount from '../components/ActiveFilterCount';
@@ -18,6 +21,7 @@ import { resetTideDashboardState, setOffset } from './tideDashboardSlice';
import { useGetTideDashboardPatientsQuery } from './tideDashboardApi';
import ResetFilters from '../components/ResetFilters';
import useActiveFiltersCount from './useActiveFiltersCount';
+import useDerivedDataRecencyEndpoints from './useDerivedDataRecencyEndpoints';
import usePruneInvalidFilters from './usePruneInvalidFilters';
import { resetTideDashboardFilters } from './tideDashboardFiltersSlice';
import EmptyContentNode from './EmptyContentNode';
@@ -26,6 +30,10 @@ import PatientCount from '../components/PatientCount';
const LIMIT = 12;
+const Divider = () => ;
+
+const Gap = () => ;
+
const TideDashboard = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -37,8 +45,10 @@ const TideDashboard = () => {
const offset = useSelector(state => state.blip.tideDashboard.offset);
const { patientTags, clinicSites } = useSelector(state => state.blip.tideDashboardFilters);
+ const [lastDataFrom, lastDataTo] = useDerivedDataRecencyEndpoints();
+
const { data } = useGetTideDashboardPatientsQuery(
- { clinicId: selectedClinicId, offset, category, tags: patientTags, sites: clinicSites, limit: LIMIT },
+ { clinicId: selectedClinicId, offset, category, lastDataTo, lastDataFrom, tags: patientTags, sites: clinicSites, limit: LIMIT },
{ skip: !selectedClinicId }
);
@@ -51,6 +61,8 @@ const TideDashboard = () => {
const handleChangeOffset = (newOffset) => dispatch(setOffset(newOffset));
+ const handleResetFilters = () => dispatch(resetTideDashboardFilters());
+
if (!data) return null;
const tableData = data?.data || [];
@@ -63,10 +75,10 @@ const TideDashboard = () => {
- dispatch(resetTideDashboardFilters())}
- />
+
+
+
+
diff --git a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
index 9ad16ea253..bbe7bd6e16 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
@@ -18,7 +18,7 @@ const cgmExclusionQuery = new CGMExclusionQuery()
.addRule(CATEGORY.LOW_CGM_WEAR, 'cgm.timeCGMUsePercent', '<0.695') // <70%
.addRule(CATEGORY.TARGET, 'cgm.timeCGMUsePercent', '>=0.695'); // >=70% and overwrites previous
-export const buildGetTideDashboardPatientsParams = (offset, limit, category, tags = [], sites = []) => {
+export const buildGetTideDashboardPatientsParams = (offset, limit, category, lastDataFrom, lastDataTo, tags = [], sites = []) => {
const formattedTags = tags.length > 0 ? tags.join(',') : undefined;
const formattedSites = sites.length > 0 ? sites.join(',') : undefined;
@@ -27,6 +27,9 @@ export const buildGetTideDashboardPatientsParams = (offset, limit, category, tag
return {
offset,
limit,
+ category,
+ 'cgm.lastDataTo': lastDataTo,
+ 'cgm.lastDataFrom': lastDataFrom,
tags: formattedTags,
sites: formattedSites,
...cgmQueryParams,
@@ -37,8 +40,8 @@ export const buildGetTideDashboardPatientsParams = (offset, limit, category, tag
const tideDashboardApi = RTKQueryApi.injectEndpoints({
endpoints: (builder) => ({
getTideDashboardPatients: builder.query({
- query: ({ clinicId, offset, category, tags, sites, limit }) => {
- const params = buildGetTideDashboardPatientsParams(offset, limit, category, tags, sites);
+ query: ({ clinicId, offset, limit, category, lastDataFrom, lastDataTo, tags, sites }) => {
+ const params = buildGetTideDashboardPatientsParams(offset, limit, category, lastDataFrom, lastDataTo, tags, sites);
return {
url: `/clinics/${clinicId}/patients`,
diff --git a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardFiltersSlice.js b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardFiltersSlice.js
index 744a72ff3e..49c49b2279 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardFiltersSlice.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardFiltersSlice.js
@@ -1,8 +1,10 @@
import { createSlice } from '@reduxjs/toolkit';
const getInitialState = () => ({
+ lastData: 7,
patientTags: [],
clinicSites: [],
+ summaryPeriod: '14d',
});
const tideDashboardFiltersSlice = createSlice({
@@ -10,19 +12,27 @@ const tideDashboardFiltersSlice = createSlice({
initialState: getInitialState(),
reducers: {
setTideDashboardFilters: (_state, action) => action.payload || getInitialState(),
+ setLastDataFilter: (state, action) => {
+ state.lastData = action.payload;
+ },
setPatientTagsFilter: (state, action) => {
state.patientTags = action.payload;
},
setClinicSitesFilter: (state, action) => {
state.clinicSites = action.payload;
},
+ setSummaryPeriodFilter: (state, action) => {
+ state.summaryPeriod = action.payload;
+ },
resetTideDashboardFilters: () => getInitialState(),
},
});
export const {
+ setLastDataFilter,
setPatientTagsFilter,
setClinicSitesFilter,
+ setSummaryPeriodFilter,
setTideDashboardFilters,
resetTideDashboardFilters,
} = tideDashboardFiltersSlice.actions;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/useDerivedDataRecencyEndpoints.js b/app/pages/clinicworkspace/TideDashboardV2/useDerivedDataRecencyEndpoints.js
new file mode 100644
index 0000000000..c548bedac8
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/useDerivedDataRecencyEndpoints.js
@@ -0,0 +1,22 @@
+import React, { useMemo } from 'react';
+import moment from 'moment';
+import { useSelector } from 'react-redux';
+import { utils as vizUtils } from '@tidepool/viz';
+const { getLocalizedCeiling } = vizUtils.datetime;
+
+const useDerivedDataRecencyEndpoints = () => {
+ const lastData = useSelector(state => state.blip.tideDashboardFilters.lastData);
+ const timePrefs = useSelector((state) => state.blip.timePrefs);
+
+ const lastDataTo = useMemo(() => {
+ return getLocalizedCeiling(new Date().toISOString(), timePrefs).toISOString();
+ }, [timePrefs]);
+
+ const lastDataFrom = useMemo(() => {
+ return moment(lastDataTo).subtract(lastData, 'days').toISOString();
+ }, [lastDataTo, lastData]);
+
+ return [lastDataFrom, lastDataTo];
+};
+
+export default useDerivedDataRecencyEndpoints;