Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e1b4a60
WEB-4460 implement last reviewed
henry-tp Mar 31, 2026
1ce5959
WEB-4460 add in refetch
henry-tp Mar 31, 2026
6d33508
WEB-4406 implement recentlyReviewedThresholdDate
henry-tp Mar 31, 2026
d5ce27a
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Mar 31, 2026
5e750d5
WEb-4460 fix missing flag colors
henry-tp Mar 31, 2026
4d4e61a
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Mar 31, 2026
c0d6e52
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp May 6, 2026
10f9e51
WEB-4460 hide value it TimeInTargetPercent not shown
henry-tp May 12, 2026
7dfc185
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp May 15, 2026
de8ad70
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Jun 23, 2026
e45c78d
WEB-4460 remove showTideDashboardLastReviewed flag
henry-tp Jun 24, 2026
490b3f8
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Jun 30, 2026
751810d
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Jun 30, 2026
c91a4e2
WEB-4460 fix missing tag display
henry-tp Jun 30, 2026
b136503
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Jul 6, 2026
a78a3eb
WEB-4460 initial setup for legacy data issues component
henry-tp Jul 7, 2026
50bfbde
WEB-4460 remove qa tag string from code
henry-tp Jul 7, 2026
ab19603
WEB-4460 attach modal controllers for data issues
henry-tp Jul 7, 2026
a17a0c2
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Jul 8, 2026
3850b2d
Merge branch 'WEB-4460-kebab' into WEB-4460-data-issues
henry-tp Jul 8, 2026
3e13666
WEB-4460 fix bugs and missing resolveConnectState
henry-tp Jul 14, 2026
20d5bb5
WEB-4460 fix data issues label
henry-tp Aug 3, 2026
537697d
WEB-4460 fix margin issues for device issues
henry-tp Aug 10, 2026
717d265
Merge branch 'WEB-4460-kebab' into WEB-4460-data-issues
henry-tp Aug 10, 2026
c6c3a08
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Aug 12, 2026
86fefec
Merge branch 'WEB-4460-kebab' into WEB-4460-data-issues
henry-tp Aug 12, 2026
454b9f3
WEB-4460 rename Low CGM Wear header
henry-tp Aug 13, 2026
9a7d35a
WEB-4460 remove unused table props
henry-tp Aug 13, 2026
973d1f6
WEB-4460 use ClearFilterButtons abstraction
henry-tp Aug 13, 2026
4765a3b
WEB-4460 fix automated feedback issues
henry-tp Aug 13, 2026
064c831
Merge branch 'WEB-4460-setup' into WEB-4460-filters
henry-tp Aug 13, 2026
0b3e04b
Merge branch 'WEB-4460-filters' into WEB-4460-cells
henry-tp Aug 13, 2026
beb266d
WEB-4460 use resolved category in TableCategoryHeader
henry-tp Aug 13, 2026
7069e45
WEB-4460 make hide data recency chip icon
henry-tp Aug 13, 2026
05a2b78
WEB-4460 use global trackMetric fn
henry-tp Aug 13, 2026
9e179c9
WEB-4460 remove unused trackMetric in patientdrawer
henry-tp Aug 13, 2026
a61a62c
WEB-4460 reset TIDE Dashboard state on tab change instead of close
henry-tp Aug 13, 2026
67adebc
Merge branch 'WEB-4460-setup' into WEB-4460-filters
henry-tp Aug 13, 2026
738fdf5
Merge branch 'WEB-4460-filters' into WEB-4460-cells
henry-tp Aug 13, 2026
5d37244
Merge branch 'WEB-4460-cells' into WEB-4460-last-reviewed
henry-tp Aug 13, 2026
9502b40
Merge branch 'WEB-4460-cells' into WEB-4460-kebab
henry-tp Aug 13, 2026
7de48a1
Merge branch 'WEB-4460-kebab' into WEB-4460-data-issues
henry-tp Aug 13, 2026
4992fd4
WEB-4460 fix import issue
henry-tp Aug 13, 2026
c89fea6
Merge branch 'WEB-4460-last-reviewed' into WEb-4460-kebab
henry-tp Aug 13, 2026
58b8bdb
Merge branch 'WEB-4460-kebab' into WEB-4460-data-issues
henry-tp Aug 13, 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
43 changes: 20 additions & 23 deletions app/components/PatientDrawer/MenuBar/MenuBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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 { trackMetric } from '../../../core/metricUtils';

export const OVERVIEW_TAB_INDEX = 0;
export const STACKED_DAILY_TAB_INDEX = 1;
Expand All @@ -32,10 +33,9 @@ const tabs = {
},
};

const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab, trackMetric }) => {
const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { showTideDashboardLastReviewed } = useFlags();

const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const patient = useSelector(state => state.blip.clinics[state.blip.selectedClinicId]?.patients?.[patientId]);
Expand Down Expand Up @@ -87,26 +87,24 @@ const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab, trackMetric })
</Flex>

<Flex sx={{ fontSize: 0, alignItems: 'center' }}>
{showTideDashboardLastReviewed && (
<Flex data-testid="last-reviewed-section" sx={{ alignItems: 'center', justifyContent: 'space-between', gap: 3 }}>
<Text sx={{
color: vizColors.purple90,
fontWeight: 'medium',
}}>
{t('Last Reviewed')}
</Text>

<PatientLastReviewed
sx={{ flexGrow: 1 }}
api={api}
trackMetric={trackMetric}
metricSource="TIDE dashboard"
patientId={patientId}
recentlyReviewedThresholdDate={recentlyReviewedThresholdDate}
onReview={handleReviewSuccess}
/>
</Flex>
)}
<Flex data-testid="last-reviewed-section" sx={{ alignItems: 'center', justifyContent: 'space-between', gap: 3 }}>
<Text sx={{
color: vizColors.purple90,
fontWeight: 'medium',
}}>
{t('Last Reviewed')}
</Text>

<PatientLastReviewed
sx={{ flexGrow: 1 }}
api={api}
trackMetric={trackMetric}
metricSource="TIDE dashboard"
patientId={patientId}
recentlyReviewedThresholdDate={recentlyReviewedThresholdDate}
onReview={handleReviewSuccess}
/>
</Flex>
</Flex>
</Flex>

Expand Down Expand Up @@ -157,7 +155,6 @@ MenuBar.propTypes = {
onClose: PropTypes.func.isRequired,
onSelectTab: PropTypes.func.isRequired,
selectedTab: PropTypes.number.isRequired,
trackMetric: PropTypes.func.isRequired,
};

export default MenuBar;
10 changes: 5 additions & 5 deletions app/components/PatientDrawer/PatientDrawer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import Icon from '../../components/elements/Icon';
import { useLocation, useHistory } from 'react-router-dom';
import Drawer from '@material-ui/core/Drawer';
Expand Down Expand Up @@ -51,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();
Expand Down Expand Up @@ -83,7 +83,7 @@ const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
return (
<>
<Box sx={{ flexShrink: 0 }}>
<MenuBar patientId={patientId} trackMetric={trackMetric} onClose={onClose} selectedTab={selectedTab} onSelectTab={handleSelectTab} />
<MenuBar patientId={patientId} onClose={onClose} selectedTab={selectedTab} onSelectTab={handleSelectTab} />

<Box className='sticky-shadow' sx={{ height: '8px', position: 'sticky', zIndex: 1, visibility: scrolledToTop ? 'hidden' : 'visible', boxShadow: shadows.large }} />
</Box>
Expand All @@ -106,7 +106,7 @@ const DrawerContent = ({ patientId, onClose, api, trackMetric, period }) => {
)
}

const PatientDrawer = ({ patientId, onClose, api, trackMetric, period }) => {
const PatientDrawer = ({ patientId, onClose, api, period }) => {
const classes = useStyles();

const isOpen = !!patientId && isValidAgpPeriod(period);
Expand All @@ -129,7 +129,7 @@ const PatientDrawer = ({ patientId, onClose, api, trackMetric, period }) => {
flexDirection: 'column',
}}
>
{isOpen && <DrawerContent patientId={patientId} onClose={onClose} api={api} trackMetric={trackMetric} period={period} />}
{isOpen && <DrawerContent patientId={patientId} onClose={onClose} api={api} period={period} />}
</Box>
</StyledDrawer>
);
Expand Down
26 changes: 11 additions & 15 deletions app/components/datasources/DataConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,26 +332,13 @@ export const getConnectStateUI = (patient, isLoggedInUser, providerName) => {
}
};

export const getDataConnectionProps = (patient, isLoggedInUser, selectedClinicId, setActiveHandler) => reduce(availableProviders, (result, providerName) => {

const provider = providers[providerName];
export const resolveConnectState = (patient, providerName, isLoggedInUser = false) => {
const dataSource = getCurrentDataSourceForProvider(patient, providerName);
const connectStateUI = getConnectStateUI(patient, isLoggedInUser, providerName);
const inviteExpired = dataSource?.expirationTime < moment.utc().toISOString();

// If the provider requires a logged in user to create the connection, then ensure that is the case.
if (!!provider.requiresLoggedInUser && !isLoggedInUser) {
return result;
}

// If the provider requires an existing data source to create the connection, then ensure that is the case.
// This mechanism can be used to limit access to certain providers to only users who have previously connected
// or where Tidepool has created a data source on their behalf.
if (!!provider.requiresExistingDataSource && !dataSource) {
return result;
}

let connectState;

if (dataSource?.state) {
connectState = includes(keys(connectStateUI), dataSource.state)
? dataSource.state
Expand All @@ -368,6 +355,15 @@ export const getDataConnectionProps = (patient, isLoggedInUser, selectedClinicId
connectState = 'noPendingConnections';
}

return connectState;
};

export const getDataConnectionProps = (patient, isLoggedInUser, selectedClinicId, setActiveHandler) => reduce(availableProviders, (result, providerName) => {
result[providerName] = {};

const connectStateUI = getConnectStateUI(patient, isLoggedInUser, providerName);
const connectState = resolveConnectState(patient, providerName, isLoggedInUser);

const { color, icon, message, text, handler } = connectStateUI[connectState];

const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const AppliedFiltersList = ({ patientCount = 0 }) => {
patientCount={patientCount}
hasSearchActive={false} // No patient search in TIDE Dashboard
filters={activeFilters}
requiredFilters={['lastData']}
onRemoveFilter={handleRemoveFilter}
rightContent={
<Box sx={{ fontSize: 0 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export default class CGMExclusionQuery {

negate(threshold) {
switch (true) {
case threshold.startsWith('>='): return threshold.replace('>=', '<');
case threshold.startsWith('<='): return threshold.replace('<=', '>');
case threshold.startsWith('>'): return threshold.replace('>', '<=');
case threshold.startsWith('<'): return threshold.replace('<', '>=');
case threshold.startsWith('>='): return threshold.replace(/^>=/, '<');
case threshold.startsWith('<='): return threshold.replace(/^<=/, '>');
case threshold.startsWith('>'): return threshold.replace(/^>/, '<=');
case threshold.startsWith('<'): return threshold.replace(/^</, '>=');

default: return threshold;
}
Expand Down
141 changes: 141 additions & 0 deletions app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import moment from 'moment-timezone';
import { Box, Text } from 'theme-ui';
import { resolveConnectState } from '../../../../components/datasources/DataConnections';
import includes from 'lodash/includes';

import Pill from '../../../../components/elements/Pill';
import HoverButton from '../../../../components/elements/HoverButton';
import PopoverMenu from '../../../../components/elements/PopoverMenu';
import { colors, fontWeights } from '../../../../themes/baseTheme';
import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded';
import EditIcon from '@material-ui/icons/EditRounded';
import DataInIcon from '../../../../core/icons/DataInIcon.svg';

export const DexcomConnectionStatusCell = ({ patient, onOpenDataConnectionsModal }) => {
const { t } = useTranslation();

const dexcomConnectStateUI = useMemo(() => ({
noPendingConnections: { colorPalette: 'neutral', icon: null, text: t('No Pending Connections') },
inviteJustSent: { colorPalette: 'info', icon: null, text: t('Invite Sent') },
pending: { colorPalette: 'info', icon: null, text: t('Invite Sent') },
pendingReconnect: { colorPalette: 'info', icon: null, text: t('Invite Sent') },
pendingExpired: { colorPalette: 'warning', icon: ErrorRoundedIcon, text: t('Invite Expired') },
connected: { colorPalette: 'info', icon: null, text: t('Connected') },
disconnected: { colorPalette: 'warning', icon: ErrorRoundedIcon, text: t('Patient Disconnected') },
error: { colorPalette: 'warning', icon: ErrorRoundedIcon, text: t('Error Connecting') },
unknown: { colorPalette: 'warning', icon: ErrorRoundedIcon, text: t('Unknown Status') },
}), [t]);

const dexcomConnectState = resolveConnectState(patient, 'dexcom');

if (!dexcomConnectState) return null;

const showViewButton = includes([
'disconnected',
'error',
'noPendingConnections',
'pendingExpired',
'unknown',
], dexcomConnectState);

const handleOpenDataConnectionsModal = () => onOpenDataConnectionsModal(patient.id);

const StatusBadge = () => (
<Pill
className="patient-dexcom-connection-status"
icon={dexcomConnectStateUI[dexcomConnectState].icon}
text={dexcomConnectStateUI[dexcomConnectState].text}
label={t('dexcom connection status')}
colorPalette={dexcomConnectStateUI[dexcomConnectState].colorPalette}
condensed
/>
);

if (!showViewButton) return <StatusBadge />;

return (
<HoverButton
buttonText={t('View')}
buttonProps={{
onClick: handleOpenDataConnectionsModal,
variant: 'textSecondary',
ml: -2,
sx: {
fontSize: 0,
fontWeight: fontWeights.medium,
textDecoration: 'underline',
color: colors.purpleMedium,
':hover': {
color: colors.purpleMedium,
textDecoration: 'underline',
},
},
}}
>
<Box sx={{ whiteSpace: 'nowrap' }}>
<StatusBadge />
</Box>
</HoverButton>
);
};

export const DaysSinceLastDataCell = ({ patient }) => {
const timePrefs = useSelector(state => state.blip.timePrefs);

const daysSinceLastData = useMemo(() => {
if (!patient?.lastData) return null;

const timezone = timePrefs?.timezoneName || new Intl.DateTimeFormat().resolvedOptions().timeZone;
const startOfLastDataDay = moment.utc(patient.lastData).tz(timezone).startOf('day');
const startOfCurrentDay = moment.utc().tz(timezone).startOf('day');

return startOfCurrentDay.diff(startOfLastDataDay, 'days');
}, [patient?.lastData, timePrefs?.timezoneName]);

return (
<Text sx={{ fontWeight: 'medium' }}>{daysSinceLastData ?? '-'}</Text>
);
};

export const MoreMenuCell = ({ patient, onOpenEditPatientDialog, onOpenDataConnectionsModal }) => {
const { t } = useTranslation();

return (
<PopoverMenu
id={`action-menu-${patient?.id}`}
items={[{
icon: EditIcon,
iconLabel: t('Edit Patient Details'),
iconPosition: 'left',
id: `edit-${patient?.id}`,
variant: 'actionListItem',
onClick: (_popupState) => {
_popupState.close();
onOpenEditPatientDialog(patient.id);
},
text: t('Edit Patient Details'),
}, {
iconSrc: DataInIcon,
iconLabel: t('Bring Data into Tidepool'),
iconPosition: 'left',
id: `edit-data-connections-${patient?.id}`,
variant: 'actionListItem',
onClick: (_popupState) => {
_popupState.close();
onOpenDataConnectionsModal(patient.id);
},
text: t('Bring Data into Tidepool'),
}]}
sx={{ position: 'relative', left: '-2px' }}
/>
);
};

export default {
DexcomConnectionStatusCell,
DaysSinceLastDataCell,
MoreMenuCell,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import DataConnectionsModal from '../../../../components/datasources/DataConnectionsModal';

const DataConnectionsModalController = ({ isOpen, patient, onClose }) => (
<DataConnectionsModal
open={isOpen && !!patient}
patient={patient}
onClose={onClose}
/>
);

export default DataConnectionsModalController;
Loading