diff --git a/app/pages/dashboard/PatientDrawer/CGMDeltaSummary/index.js b/app/components/PatientDrawer/CGMDeltaSummary/index.js
similarity index 99%
rename from app/pages/dashboard/PatientDrawer/CGMDeltaSummary/index.js
rename to app/components/PatientDrawer/CGMDeltaSummary/index.js
index 238b1ec451..ed38fbb8e1 100644
--- a/app/pages/dashboard/PatientDrawer/CGMDeltaSummary/index.js
+++ b/app/components/PatientDrawer/CGMDeltaSummary/index.js
@@ -6,7 +6,7 @@ import { utils as vizUtils, colors as vizColors } from '@tidepool/viz';
import styled from '@emotion/styled';
const { bankersRound } = vizUtils.stat;
const { getTimezoneFromTimePrefs } = vizUtils.datetime;
-import { MS_IN_HOUR } from '../../../../core/constants';
+import { MS_IN_HOUR } from '../../../core/constants';
import getReportDaysText from '../getReportDaysText';
diff --git a/app/pages/dashboard/PatientDrawer/CGMStatistics/index.js b/app/components/PatientDrawer/CGMStatistics/index.js
similarity index 98%
rename from app/pages/dashboard/PatientDrawer/CGMStatistics/index.js
rename to app/components/PatientDrawer/CGMStatistics/index.js
index 5645dbab2b..45e9826a72 100644
--- a/app/pages/dashboard/PatientDrawer/CGMStatistics/index.js
+++ b/app/components/PatientDrawer/CGMStatistics/index.js
@@ -5,7 +5,7 @@ import { Flex, Box, Text } from 'theme-ui';
import { utils as vizUtils } from '@tidepool/viz';
const { formatDatum, bankersRound } = vizUtils.stat;
const { getTimezoneFromTimePrefs } = vizUtils.datetime;
-import { MGDL_UNITS } from '../../../../core/constants';
+import { MGDL_UNITS } from '../../../core/constants';
import getReportDaysText from '../getReportDaysText';
const TableRow = ({ label, sublabel, value, units, id }) => {
diff --git a/app/pages/dashboard/PatientDrawer/MenuBar/CGMClipboardButton.js b/app/components/PatientDrawer/MenuBar/CGMClipboardButton.js
similarity index 94%
rename from app/pages/dashboard/PatientDrawer/MenuBar/CGMClipboardButton.js
rename to app/components/PatientDrawer/MenuBar/CGMClipboardButton.js
index bc17372335..6dd7cf9060 100644
--- a/app/pages/dashboard/PatientDrawer/MenuBar/CGMClipboardButton.js
+++ b/app/components/PatientDrawer/MenuBar/CGMClipboardButton.js
@@ -1,8 +1,8 @@
import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
-import Button from '../../../../components/elements/Button';
-import { MS_IN_HOUR } from '../../../../core/constants';
+import Button from '../../../components/elements/Button';
+import { MS_IN_HOUR } from '../../../core/constants';
import { Box, Flex } from 'theme-ui';
import { utils as vizUtils } from '@tidepool/viz';
const { agpCGMText } = vizUtils.text;
diff --git a/app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js b/app/components/PatientDrawer/MenuBar/MenuBar.js
similarity index 91%
rename from app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js
rename to app/components/PatientDrawer/MenuBar/MenuBar.js
index 8cc65c6bf8..724221191a 100644
--- a/app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js
+++ b/app/components/PatientDrawer/MenuBar/MenuBar.js
@@ -1,20 +1,21 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
-import * as actions from '../../../../redux/actions';
+import * as actions from '../../../redux/actions';
import { useSelector, useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { push } from 'connected-react-router';
import { Flex, Box, Text } from 'theme-ui';
import { colors as vizColors } from '@tidepool/viz';
-import Button from '../../../../components/elements/Button';
+import Button from '../../../components/elements/Button';
import moment from 'moment';
-import PatientLastReviewed from '../../../../components/clinic/PatientLastReviewed';
+import PatientLastReviewed from '../../../components/clinic/PatientLastReviewed';
import { useFlags } from 'launchdarkly-react-client-sdk';
import CGMClipboardButton from './CGMClipboardButton';
-import api from '../../../../core/api';
+import api from '../../../core/api';
import { map, keys } from 'lodash';
-import copyIcon from '../../../../core/icons/copyIcon.svg';
-import viewIcon from '../../../../core/icons/viewIcon.svg';
+import copyIcon from '../../../core/icons/copyIcon.svg';
+import viewIcon from '../../../core/icons/viewIcon.svg';
+import { trackMetric } from '../../../core/metricUtils';
export const OVERVIEW_TAB_INDEX = 0;
export const STACKED_DAILY_TAB_INDEX = 1;
@@ -32,7 +33,7 @@ const tabs = {
},
};
-const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab, trackMetric }) => {
+const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { showTideDashboardLastReviewed } = useFlags();
@@ -157,7 +158,6 @@ MenuBar.propTypes = {
onClose: PropTypes.func.isRequired,
onSelectTab: PropTypes.func.isRequired,
selectedTab: PropTypes.number.isRequired,
- trackMetric: PropTypes.func.isRequired,
};
export default MenuBar;
diff --git a/app/pages/dashboard/PatientDrawer/MenuBar/index.js b/app/components/PatientDrawer/MenuBar/index.js
similarity index 100%
rename from app/pages/dashboard/PatientDrawer/MenuBar/index.js
rename to app/components/PatientDrawer/MenuBar/index.js
diff --git a/app/pages/dashboard/PatientDrawer/Overview.js b/app/components/PatientDrawer/Overview.js
similarity index 100%
rename from app/pages/dashboard/PatientDrawer/Overview.js
rename to app/components/PatientDrawer/Overview.js
diff --git a/app/pages/dashboard/PatientDrawer/PatientDrawer.js b/app/components/PatientDrawer/PatientDrawer.js
similarity index 84%
rename from app/pages/dashboard/PatientDrawer/PatientDrawer.js
rename to app/components/PatientDrawer/PatientDrawer.js
index 92df8f3673..aa0f829b39 100644
--- a/app/pages/dashboard/PatientDrawer/PatientDrawer.js
+++ b/app/components/PatientDrawer/PatientDrawer.js
@@ -1,19 +1,17 @@
import React from 'react';
-import Icon from '../../../components/elements/Icon';
+import Icon from '../../components/elements/Icon';
import { useLocation, useHistory } from 'react-router-dom';
import Drawer from '@material-ui/core/Drawer';
import styled from '@emotion/styled';
import { makeStyles } from '@material-ui/core/styles';
import CloseRoundedIcon from '@material-ui/icons/CloseRounded';
import { Box } from 'theme-ui';
-import { useFlags } from 'launchdarkly-react-client-sdk';
import Overview from './Overview';
import StackedDaily from './StackedDaily';
import MenuBar, { OVERVIEW_TAB_INDEX, STACKED_DAILY_TAB_INDEX } from './MenuBar';
import useAgpCGM from './useAgpCGM';
-import { shadows } from '../../../themes/baseTheme';
-import { useScrollToTop } from '../../../core/hooks';
+import { shadows } from '../../themes/baseTheme';
const StyledCloseButton = styled(Icon)`
position: absolute;
@@ -53,7 +51,7 @@ const getAgpPeriodInDays = (period) => {
}
};
-const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
+const DrawerContent = ({ patientId, onClose, api, period }) => {
// Only rendered when patient is selected and isOpen is true
// this will also allow the hook to dismount and for the cleanup to be called
const location = useLocation();
@@ -64,7 +62,6 @@ const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
const agpPeriodInDays = getAgpPeriodInDays(period);
const agpCGMData = useAgpCGM(api, patientId, agpPeriodInDays);
const contentRef = React.useRef(undefined);
- useScrollToTop(contentRef?.current, [selectedTab]);
function setDrawerTabParam(tabIndex) {
const { search, pathname } = location;
@@ -76,16 +73,17 @@ const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
function handleSelectTab(tabIndex) {
setSelectedTab(parseInt(tabIndex, 10));
setDrawerTabParam(tabIndex);
+ contentRef?.current?.scrollTo(0, 0);
}
const handleContentScroll = (e) => {
setScrolledToTop((e.target.scrollTop || 0) <= 5);
- }
+ };
return (
<>
-
+
@@ -108,12 +106,10 @@ const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
)
}
-const PatientDrawer = ({ patientId, onClose, api, trackMetric, period }) => {
+const PatientDrawer = ({ patientId, onClose, api, period }) => {
const classes = useStyles();
- const { showTideDashboardPatientDrawer } = useFlags();
- const isOpen = !!patientId && isValidAgpPeriod(period);
- if (!showTideDashboardPatientDrawer) return null;
+ const isOpen = !!patientId && isValidAgpPeriod(period);
return (
{
flexDirection: 'column',
}}
>
- {isOpen && }
+ {isOpen && }
);
diff --git a/app/pages/dashboard/PatientDrawer/StackedDaily.js b/app/components/PatientDrawer/StackedDaily.js
similarity index 97%
rename from app/pages/dashboard/PatientDrawer/StackedDaily.js
rename to app/components/PatientDrawer/StackedDaily.js
index 246c50175c..d09fab05ea 100644
--- a/app/pages/dashboard/PatientDrawer/StackedDaily.js
+++ b/app/components/PatientDrawer/StackedDaily.js
@@ -15,13 +15,13 @@ const { getLocalizedCeiling } = vizUtils.datetime;
import tidelineBlip from 'tideline/plugins/blip';
const chartDailyFactory = tidelineBlip.oneday;
-import { MS_IN_DAY } from '../../../core/constants';
+import { MS_IN_DAY } from '../../core/constants';
import { NoPatientData } from './Overview';
import { STATUS } from './useAgpCGM';
-import { Body1, Body2 } from '../../../components/elements/FontStyles';
-import Button from '../../../components/elements/Button';
+import { Body1, Body2 } from '../../components/elements/FontStyles';
+import Button from '../../components/elements/Button';
import { STACKED_DAILY_TAB_INDEX } from './MenuBar';
-import BgLegend from '../../../components/chart/BgLegend';
+import BgLegend from '../../components/chart/BgLegend';
const CHART_HEIGHT = 200;
diff --git a/app/pages/dashboard/PatientDrawer/getReportDaysText.js b/app/components/PatientDrawer/getReportDaysText.js
similarity index 94%
rename from app/pages/dashboard/PatientDrawer/getReportDaysText.js
rename to app/components/PatientDrawer/getReportDaysText.js
index 7e88335997..36f62dc66a 100644
--- a/app/pages/dashboard/PatientDrawer/getReportDaysText.js
+++ b/app/components/PatientDrawer/getReportDaysText.js
@@ -1,5 +1,5 @@
import moment from 'moment';
-import { MS_IN_MIN } from '../../../core/constants';
+import { MS_IN_MIN } from '../../core/constants';
import isNumber from 'lodash/isNumber';
import { utils as vizUtils } from '@tidepool/viz';
const { getOffset, formatDateRange } = vizUtils.datetime;
diff --git a/app/pages/dashboard/PatientDrawer/index.js b/app/components/PatientDrawer/index.js
similarity index 100%
rename from app/pages/dashboard/PatientDrawer/index.js
rename to app/components/PatientDrawer/index.js
diff --git a/app/pages/dashboard/PatientDrawer/useAgpCGM/getOpts.js b/app/components/PatientDrawer/useAgpCGM/getOpts.js
similarity index 95%
rename from app/pages/dashboard/PatientDrawer/useAgpCGM/getOpts.js
rename to app/components/PatientDrawer/useAgpCGM/getOpts.js
index f97ece5587..d3597c9f2f 100644
--- a/app/pages/dashboard/PatientDrawer/useAgpCGM/getOpts.js
+++ b/app/components/PatientDrawer/useAgpCGM/getOpts.js
@@ -2,8 +2,8 @@ import moment from 'moment-timezone';
import _ from 'lodash';
import get from 'lodash/get';
import { utils as vizUtils } from '@tidepool/viz';
-import utils from '../../../../core/utils';
-import { getMostRecentDatumTimeByChartType } from '../../../../core/dataViewUtils';
+import utils from '../../../core/utils';
+import { getMostRecentDatumTimeByChartType } from '../../../core/dataViewUtils';
const getTimezoneFromTimePrefs = vizUtils.datetime.getTimezoneFromTimePrefs;
diff --git a/app/pages/dashboard/PatientDrawer/useAgpCGM/getQueries.js b/app/components/PatientDrawer/useAgpCGM/getQueries.js
similarity index 95%
rename from app/pages/dashboard/PatientDrawer/useAgpCGM/getQueries.js
rename to app/components/PatientDrawer/useAgpCGM/getQueries.js
index 6d6750c203..1d1af429f0 100644
--- a/app/pages/dashboard/PatientDrawer/useAgpCGM/getQueries.js
+++ b/app/components/PatientDrawer/useAgpCGM/getQueries.js
@@ -2,8 +2,8 @@ import _ from 'lodash';
import { utils as vizUtils } from '@tidepool/viz';
const { commonStats } = vizUtils.stat;
-import utils from '../../../../core/utils';
-import { DEFAULT_GLYCEMIC_RANGES } from '../../../../core/glycemicRangesUtils';
+import utils from '../../../core/utils';
+import { DEFAULT_GLYCEMIC_RANGES } from '../../../core/glycemicRangesUtils';
const getQueries = (
data,
diff --git a/app/pages/dashboard/PatientDrawer/useAgpCGM/index.js b/app/components/PatientDrawer/useAgpCGM/index.js
similarity index 100%
rename from app/pages/dashboard/PatientDrawer/useAgpCGM/index.js
rename to app/components/PatientDrawer/useAgpCGM/index.js
diff --git a/app/pages/dashboard/PatientDrawer/useAgpCGM/useAgpCGM.js b/app/components/PatientDrawer/useAgpCGM/useAgpCGM.js
similarity index 94%
rename from app/pages/dashboard/PatientDrawer/useAgpCGM/useAgpCGM.js
rename to app/components/PatientDrawer/useAgpCGM/useAgpCGM.js
index 5104fbdc3f..920a57f85e 100644
--- a/app/pages/dashboard/PatientDrawer/useAgpCGM/useAgpCGM.js
+++ b/app/components/PatientDrawer/useAgpCGM/useAgpCGM.js
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import * as actions from '../../../../redux/actions';
+import * as actions from '../../../redux/actions';
import moment from 'moment';
import getOpts from './getOpts';
import getQueries from './getQueries';
import { cloneDeep } from 'lodash';
-import { useGenerateAGPImages } from '../../../../core/agpUtils';
+import { useGenerateAGPImages } from '../../../core/agpUtils';
export const STATUS = {
// States in order of happy path AGP generation sequence
@@ -134,8 +134,8 @@ const useAgpCGM = (
return {
status: lastCompletedStep,
svgDataURLS: isCorrectPatientInState ? pdf.opts?.svgDataURLS : null,
- agpCGM: isCorrectPatientInState ? cloneDeep(pdf.data?.agpCGM) : null,
- offsetAgpCGM: isCorrectPatientInState ? cloneDeep(pdf.data?.offsetAgpCGM) : null,
+ agpCGM: isCorrectPatientInState ? pdf.data?.agpCGM : null,
+ offsetAgpCGM: isCorrectPatientInState ? pdf.data?.offsetAgpCGM : null,
};
};
diff --git a/app/core/navutils.js b/app/core/navutils.js
index 375e32273e..55d3b92c77 100644
--- a/app/core/navutils.js
+++ b/app/core/navutils.js
@@ -44,11 +44,11 @@ export const getPatientListLink = (clinicFlowActive, selectedClinicId, query, pa
const drawerTab = query?.drawerTab;
if (dashboard && drawerTab !== undefined && patientId) {
- return `/dashboard/${dashboard}?drawerPatientId=${patientId}&drawerTab=${drawerTab}`;
+ return `/clinic-workspace/tide-dashboard?drawerPatientId=${patientId}&drawerTab=${drawerTab}`;
}
if (dashboard) {
- return `/dashboard/${dashboard}`;
+ return '/clinic-workspace/tide-dashboard';
}
if (clinicFlowActive && selectedClinicId) {
diff --git a/app/pages/clinicworkspace/TideDashboardV2/Cells.js b/app/pages/clinicworkspace/TideDashboardV2/Cells.js
index 31f5478f4c..e179f9e7a4 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/Cells.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/Cells.js
@@ -1,19 +1,251 @@
import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { Box, Text } from 'theme-ui';
+import { useDispatch, useSelector } from 'react-redux';
+import { useLocation, useHistory } from 'react-router-dom';
+import { useTranslation, withTranslation } from 'react-i18next';
+import { Box, Flex, Text } from 'theme-ui';
+import { utils as vizUtils, colors as vizColors } from '@tidepool/viz';
+const { bankersRound } = vizUtils.stat;
+import { MGDL_UNITS } from '../../../core/constants';
+import { push } from 'connected-react-router';
+import { colors } from '../../../themes/baseTheme';
+
+import BgSummaryCell from '../../../components/clinic/BgSummaryCell';
+import DeltaBar from '../../../components/elements/DeltaBar';
+import utils from '../../../core/utils';
+import { OVERVIEW_TAB_INDEX } from '../../../components/PatientDrawer/MenuBar/MenuBar';
+import { useFlags } from 'launchdarkly-react-client-sdk';
+import { CATEGORY } from './FilterByCategory';
+import isUndefined from 'lodash/isUndefined';
+
+export const COMPACT = '@container (max-width: 1200px)';
export const PatientCell = ({ patient }) => {
const { t } = useTranslation();
+ const { search, pathname } = useLocation();
+ const history = useHistory();
+ const dispatch = useDispatch();
const { fullName, birthDate, mrn } = patient || {};
- return
- {fullName}
- {t('DOB:')} {birthDate}
- {mrn && , {t('MRN: {{mrn}}', { mrn: mrn })}}
+ const handleClick = () => {
+ if (!patient.id) return;
+
+ const params = new URLSearchParams(search);
+ params.set('drawerPatientId', patient.id);
+ params.set('drawerTab', OVERVIEW_TAB_INDEX);
+
+ history.replace({ pathname, search: params.toString() });
+ };
+
+ return
+ {fullName}
+ {t('DOB:')} {birthDate}
+ {mrn && {t('MRN: {{mrn}}', { mrn: mrn })}}
;
};
+export const NumericTemplateCell = ({ value, isPercent = false }) => {
+ if (!value) return ;
+
+ return {value} {isPercent && '%'};
+};
+
+export const AvgGlucoseHeader = withTranslation()(({ t }) => (
+ <>
+ {t('Avg Glucose')}
+ {t('Avg Gluc.')}
+ >
+));
+
+export const AvgGlucoseCell = ({ patient, units }) => { // TODO: Fix for units
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.averageGlucoseMmol;
+ const value = utils.formatDecimal(rawValue, 1);
+
+ return ;
+};
+
+export const TimeInRangePercentBarChartCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
+ const clinicBgUnits = clinic?.preferredBgUnits || MGDL_UNITS;
+
+ // TODO: need to add showExtremeHigh
+
+ return ;
+};
+
+export const TimeInTargetPercentCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInTargetPercent;
+ let value = utils.formatDecimal(rawValue * 100, 0);
+
+ if (isUndefined(rawValue)) value = '';
+
+ return ;
+};
+
+export const TimeInVeryLowPercentCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInVeryLowPercent;
+ const value = utils.formatDecimal(rawValue * 100, 0);
+
+ return ;
+};
+
+export const TimeInAnyLowPercentCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInAnyLowPercent;
+ const value = utils.formatDecimal(rawValue * 100, 0);
+
+ return ;
+};
+
+export const TimeInVeryHighPercentCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInVeryHighPercent;
+ const value = utils.formatDecimal(rawValue * 100, 0);
+
+ return ;
+};
+
+export const TimeInAnyHighPercentCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInAnyHighPercent;
+ const value = utils.formatDecimal(rawValue * 100, 0);
+
+ return ;
+};
+
+export const GMICell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const value = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.glucoseManagementIndicator;
+
+ return ;
+};
+
+export const CGMUseCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const rawValue = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeCGMUsePercent;
+ const value = utils.formatDecimal(rawValue * 100, 0);
+
+ return ;
+};
+
+export const ChangeTIRHeader = withTranslation()(({ t }) => (
+ <>
+ {t('% Change in TIR')}
+ {t('% Δ TIR')}
+ >
+));
+
+export const ChangeTIRCell = ({ patient }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const timeInTargetPercentDelta = patient?.summary?.cgmStats?.periods?.[summaryPeriod]?.timeInTargetPercentDelta;
+
+ if (!timeInTargetPercentDelta) return -;
+
+ const compactDisplayValue = utils.formatDecimal(timeInTargetPercentDelta * 100, 1);
+
+ return <>
+
+
+
+
+
+
+ >
+ ;
+};
+
+export const FlagCell = ({ patient, category = null, }) => {
+ const { t } = useTranslation();
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const period = patient?.summary?.cgmStats?.periods?.[summaryPeriod];
+
+ const { VERY_LOW, ANY_LOW, DROP_IN_TIR, ANY_HIGH, VERY_HIGH, LOW_CGM_WEAR, TARGET } = CATEGORY;
+
+ if (!period) return null;
+
+ const rangeName = (() => {
+ switch(true) {
+ // Current dashboard category takes priority
+ case category === VERY_LOW: return 'veryLow';
+ case category === ANY_LOW: return 'anyLow';
+ case category === VERY_HIGH: return 'veryHigh';
+ case category === ANY_HIGH: return 'anyHigh';
+ case category === DROP_IN_TIR: return 'dropInTIR';
+ case category === LOW_CGM_WEAR: return 'lowSensorUsage';
+ case category === TARGET: return 'target';
+
+ // If no category, then read from summary
+ case period.timeInVeryLowPercent >= 0.005: return 'veryLow'; // >=1%
+ case period.timeInAnyLowPercent >= 0.035: return 'anyLow'; // >=4%
+ case period.timeInTargetPercentDelta <= -0.145: return 'dropInTIR'; // <=-15%
+ case period.timeInAnyHighPercent >= 0.245: return 'anyHigh'; // >=25%
+ case period.timeInVeryHighPercent >= 0.045: return 'veryHigh'; // >=5%
+ case period.timeCGMUsePercent < 0.695: return 'lowSensorUsage'; // <70%
+
+ default: return null;
+ }
+ })();
+
+ if (!rangeName) return null;
+
+ const flagLabels = {
+ veryLow: t('Very Low'),
+ anyLow: t('Low'),
+ veryHigh: t('Very High'),
+ anyHigh: t('High'),
+ dropInTIR: t('Large Drop in TIR'),
+ lowSensorUsage: t('Low CGM Wear Time'),
+ target: t('Meeting Targets'),
+ };
+
+ const flagColor = colors.bg[rangeName] || vizColors.gold30;
+
+ return (
+
+
+
+
+
+ {flagLabels[rangeName] || ''}
+
+
+
+ );
+};
+
export default {
PatientCell,
+ NumericTemplateCell,
+ AvgGlucoseCell,
+ TimeInRangePercentBarChartCell,
+ TimeInVeryLowPercentCell,
+ ChangeTIRCell,
+ GMICell,
+ CGMUseCell,
+ FlagCell,
};
diff --git a/app/pages/clinicworkspace/TideDashboardV2/PatientDrawerController.js b/app/pages/clinicworkspace/TideDashboardV2/PatientDrawerController.js
new file mode 100644
index 0000000000..aae581d4d5
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/PatientDrawerController.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { useLocation, useHistory } from 'react-router-dom';
+import PatientDrawer from '../../../components/PatientDrawer/PatientDrawer';
+
+const PatientDrawerController = ({ api }) => {
+ const summaryPeriod = useSelector(state => state.blip.tideDashboardFilters.summaryPeriod);
+ const { search, pathname } = useLocation();
+ const history = useHistory();
+
+ const drawerPatientId = new URLSearchParams(search)?.get('drawerPatientId') || null;
+
+ const handleClose = () => {
+ const params = new URLSearchParams(search);
+ params.delete('drawerPatientId');
+ params.delete('drawerTab');
+ history.replace({ pathname, search: params.toString() });
+ };
+
+ return (
+
+ );
+};
+
+export default PatientDrawerController;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/TableCategoryHeader.js b/app/pages/clinicworkspace/TideDashboardV2/TableCategoryHeader.js
index 7d50d6fcb3..4844fb8dad 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/TableCategoryHeader.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/TableCategoryHeader.js
@@ -13,9 +13,8 @@ const formatThreshold = (value, bgUnits) => bgUnits === MGDL_UNITS
? value
: utils.formatDecimal(value, 1);
-const useCategoryHeaderCopy = () => {
+const useCategoryHeaderCopy = (category) => {
const { t } = useTranslation();
- const category = useSelector(state => state.blip.tideDashboard.category);
const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
const bgUnits = clinic?.preferredBgUnits || MGDL_UNITS;
@@ -68,8 +67,8 @@ const useCategoryHeaderCopy = () => {
}
};
-const TableCategoryHeader = () => {
- const { title, label } = useCategoryHeaderCopy();
+const TableCategoryHeader = ({ category }) => {
+ const { title, label } = useCategoryHeaderCopy(category);
return
diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
index 78abd0fb07..a4e7bf042a 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
@@ -11,13 +11,13 @@ import FilterBySummaryPeriod from './FilterBySummaryPeriod';
import TableCategoryHeader from './TableCategoryHeader';
import PaginationControls from '../components/PaginationControls';
+import PatientDrawerController from './PatientDrawerController';
-import TagListCell from '../components/TagListCell';
-import { PatientCell } from './Cells';
import { setOffset } from './tideDashboardSlice';
import { useGetTideDashboardPatientsQuery } from './tideDashboardApi';
import useDerivedDataRecencyEndpoints from './useDerivedDataRecencyEndpoints';
import usePruneInvalidFilters from './usePruneInvalidFilters';
+import useTableColumns from './useTableColumns';
import EmptyContentNode from './EmptyContentNode';
import FilterBySites from './FilterBySites';
import PatientCount from '../components/PatientCount';
@@ -27,7 +27,7 @@ const LIMIT = 12;
const Gap = () => ;
-const TideDashboard = () => {
+const TideDashboard = ({ api }) => {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -45,6 +45,12 @@ const TideDashboard = () => {
{ skip: !selectedClinicId }
);
+ // Sync category to data fetching resolution; prevents visual glitch due to
+ // category updating view before the API call resolves and updates it again
+ const resolvedCategory = data?.category || category;
+
+ const tableColumns = useTableColumns(resolvedCategory);
+
const handleChangeOffset = (newOffset) => dispatch(setOffset(newOffset));
if (!data) return null;
@@ -68,28 +74,17 @@ const TideDashboard = () => {
-
+
},
- { title: t('Flag'), field: '', align: 'left' },
- { title: t('Avg Glucose'), field: '', align: 'left' },
- { title: t('% TIR'), field: '', align: 'left' },
- { title: t('% Time in Range'), field: '', align: 'left' },
- { title: t('% Change in TIR'), field: '', align: 'left' },
- { title: t('GMI'), field: '', align: 'left' },
- { title: t('CGM Use'), field: '', align: 'left' },
- { title: t('Tags'), field: 'tags', align: 'left', render: patient => },
- { title: t('Last Reviewed'), field: '', align: 'left' },
- { title: t(''), field: '', align: 'left' }, // More
- ]}
+ columns={tableColumns}
data={tableData}
emptyContentNode={}
+ containerProps={{ sx: { containerType: 'inline-size' } }}
/>
@@ -106,6 +101,8 @@ const TideDashboard = () => {
+
+
>
);
};
diff --git a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
index 4e41a5e40e..2cec1cd032 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
@@ -49,6 +49,10 @@ const tideDashboardApi = RTKQueryApi.injectEndpoints({
params,
};
},
+ transformResponse: (response, _meta, arg) => ({
+ ...response,
+ category: arg.category,
+ }),
}),
}),
});
diff --git a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardSlice.js b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardSlice.js
index 16a1e851fd..b78d9d9905 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardSlice.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardSlice.js
@@ -21,5 +21,9 @@ const tideDashboardSlice = createSlice({
},
});
-export const { setCategory, setOffset, resetTideDashboardState } = tideDashboardSlice.actions;
+export const {
+ setCategory,
+ setOffset,
+ resetTideDashboardState,
+} = tideDashboardSlice.actions;
export default tideDashboardSlice.reducer;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js b/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js
new file mode 100644
index 0000000000..ceaf67bb6e
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js
@@ -0,0 +1,196 @@
+import React, { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+import { CATEGORY } from './FilterByCategory';
+import { MGDL_UNITS } from '../../../core/constants';
+import mapValues from 'lodash/mapValues';
+import { utils as vizUtils } from '@tidepool/viz';
+const { DEFAULT_BG_BOUNDS } = vizUtils.constants;
+
+import {
+ AvgGlucoseHeader,
+ AvgGlucoseCell,
+ CGMUseCell,
+ ChangeTIRHeader,
+ ChangeTIRCell,
+ GMICell,
+ PatientCell,
+ TimeInRangePercentBarChartCell,
+ TimeInVeryLowPercentCell,
+ TimeInAnyLowPercentCell,
+ TimeInVeryHighPercentCell,
+ TimeInAnyHighPercentCell,
+ TimeInTargetPercentCell,
+ FlagCell,
+} from './Cells';
+
+import TagListCell from '../components/TagListCell';
+
+const getColumnTypes = (t, category, thresholds) => ({
+ patientDetails: {
+ title: t('Patient Details'),
+ field: 'fullName',
+ align: 'left',
+ render: patient => ,
+ },
+ flag: {
+ title: t('Flag'),
+ field: 'flag',
+ align: 'center',
+ render: patient => ,
+ },
+ avgGlucose: {
+ title: t('Avg Glucose'),
+ field: 'avgGlucose',
+ align: 'center',
+ titleComponent: () => ,
+ render: patient => ,
+ },
+ timeInRangeBarChart: {
+ title: t('Time in Range'),
+ field: 'timeInRangeBarChart',
+ align: 'center',
+ render: patient => ,
+ },
+ changeInTIR: {
+ title: t('% Change in TIR'),
+ field: 'changeInTIR',
+ align: 'center',
+ titleComponent: () => ,
+ render: patient => ,
+ },
+ timeInVeryLow: {
+ title: `${t('% Time')} < ${thresholds.veryLowThreshold}`,
+ field: 'timeInVeryLow',
+ align: 'center',
+ render: patient => ,
+ },
+ timeInAnyLow: {
+ title: `${t('% Time')} < ${thresholds.targetLowerBound}`,
+ field: 'timeInAnyLow',
+ align: 'center',
+ render: patient => ,
+ },
+ timeInVeryHigh: {
+ title: `${t('% Time')} > ${thresholds.veryHighThreshold}`,
+ field: 'timeInVeryHigh',
+ align: 'center',
+ render: patient => ,
+ },
+ timeInAnyHigh: {
+ title: `${t('% Time')} > ${thresholds.targetUpperBound}`,
+ field: 'timeInAnyHigh',
+ align: 'center',
+ render: patient => ,
+ },
+ timeInTarget: {
+ title: `${t('% TIR')} ${thresholds.targetLowerBound}-${thresholds.targetUpperBound}`,
+ field: 'timeInTarget',
+ align: 'center',
+ render: patient => ,
+ },
+ gmi: {
+ title: t('GMI'),
+ field: 'gmi',
+ align: 'center',
+ render: patient => ,
+ },
+ cgmUse: {
+ title: t('CGM Use'),
+ field: 'cgmUse',
+ align: 'center',
+ render: patient => ,
+ },
+ tags: {
+ title: t('Tags'),
+ field: 'tags',
+ align: 'center',
+ render: patient => ,
+ },
+ lastReviewed: {
+ title: t('Last Reviewed'),
+ field: 'lastReviewed',
+ align: 'center',
+ },
+ moreMenu: {
+ title: t(''),
+ field: 'moreMenu',
+ align: 'center',
+ }, // More
+});
+
+const getFormattedThresholds = (clinicBgUnits) => {
+ const thresholds = DEFAULT_BG_BOUNDS[clinicBgUnits];
+ const precision = clinicBgUnits === MGDL_UNITS ? 0 : 1;
+
+ return mapValues(thresholds, value => value.toFixed(precision));
+};
+
+const useTableColumns = (category) => {
+ const { t } = useTranslation();
+ const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);
+ const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
+ const clinicBgUnits = clinic?.preferredBgUnits || MGDL_UNITS;
+
+ const columns = useMemo(() => {
+ const thresholds = getFormattedThresholds(clinicBgUnits);
+ const columnTypes = getColumnTypes(t, category, thresholds);
+
+ const standardColumnSet = [
+ columnTypes.patientDetails,
+ columnTypes.flag,
+ columnTypes.avgGlucose,
+ columnTypes.timeInRangeBarChart,
+ columnTypes.changeInTIR,
+ columnTypes.gmi,
+ columnTypes.cgmUse,
+ columnTypes.tags,
+ columnTypes.lastReviewed,
+ columnTypes.moreMenu,
+ ];
+
+ const lowColumnSet = [
+ columnTypes.patientDetails,
+ columnTypes.flag,
+ columnTypes.avgGlucose,
+ columnTypes.timeInVeryLow,
+ columnTypes.timeInAnyLow,
+ columnTypes.timeInTarget,
+ columnTypes.timeInRangeBarChart,
+ columnTypes.changeInTIR,
+ columnTypes.tags,
+ columnTypes.lastReviewed,
+ columnTypes.moreMenu,
+ ];
+
+ const highColumnSet = [
+ columnTypes.patientDetails,
+ columnTypes.flag,
+ columnTypes.avgGlucose,
+ columnTypes.timeInVeryHigh,
+ columnTypes.timeInAnyHigh,
+ columnTypes.timeInTarget,
+ columnTypes.timeInRangeBarChart,
+ columnTypes.changeInTIR,
+ columnTypes.tags,
+ columnTypes.lastReviewed,
+ columnTypes.moreMenu,
+ ];
+
+ switch(category) {
+ case CATEGORY.DEFAULT: return standardColumnSet;
+ case CATEGORY.VERY_LOW: return lowColumnSet;
+ case CATEGORY.ANY_LOW: return lowColumnSet;
+ case CATEGORY.DROP_IN_TIR: return standardColumnSet;
+ case CATEGORY.ANY_HIGH: return highColumnSet;
+ case CATEGORY.VERY_HIGH: return highColumnSet;
+ case CATEGORY.LOW_CGM_WEAR: return standardColumnSet;
+ case CATEGORY.TARGET: return standardColumnSet;
+ default: return standardColumnSet;
+ }
+ }, [category, clinicBgUnits]);
+
+ return columns;
+};
+
+export default useTableColumns;
diff --git a/app/pages/clinicworkspace/components/TagListCell.js b/app/pages/clinicworkspace/components/TagListCell.js
index 7f5a8fda77..38d2654858 100644
--- a/app/pages/clinicworkspace/components/TagListCell.js
+++ b/app/pages/clinicworkspace/components/TagListCell.js
@@ -2,6 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { TagList } from '../../../components/elements/Tag';
+const MAX_TAGS = 2;
+
const TagListCell = ({ patient }) => {
const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
@@ -10,7 +12,7 @@ const TagListCell = ({ patient }) => {
const tagIds = patient?.tags || [];
const tags = tagIds.map(tag => patientTags.find(ptTag => ptTag.id === tag)); // TODO: index
- return ;
+ return ;
};
export default TagListCell;
diff --git a/app/pages/dashboard/TideDashboard.js b/app/pages/dashboard/TideDashboard.js
index bd0f8e5173..c5a53b158f 100644
--- a/app/pages/dashboard/TideDashboard.js
+++ b/app/pages/dashboard/TideDashboard.js
@@ -58,7 +58,7 @@ import PopoverMenu from '../../components/elements/PopoverMenu';
import RadioGroup from '../../components/elements/RadioGroup';
import DeltaBar from '../../components/elements/DeltaBar';
import Pill from '../../components/elements/Pill';
-import PatientDrawer, { isValidAgpPeriod } from './PatientDrawer';
+import PatientDrawer, { isValidAgpPeriod } from '../../components/PatientDrawer';
import utils from '../../core/utils';
import {
@@ -85,7 +85,7 @@ import DataInIcon from '../../core/icons/DataInIcon.svg';
import { colors, fontWeights, radii } from '../../themes/baseTheme';
import PatientLastReviewed from '../../components/clinic/PatientLastReviewed';
import { DEFAULT_GLYCEMIC_RANGES } from '../../core/glycemicRangesUtils';
-import { OVERVIEW_TAB_INDEX } from './PatientDrawer/MenuBar/MenuBar';
+import { OVERVIEW_TAB_INDEX } from '../../components/PatientDrawer/MenuBar/MenuBar';
const { Loader } = vizComponents;
const { formatBgValue } = vizUtils.bg;