Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 12 additions & 5 deletions app/components/datasources/DataConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,13 @@ export const getConnectStateUI = (patient, isLoggedInUser, providerName) => {
}
};

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

let connectState;

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();

let connectState;

if (dataSource?.state) {
connectState = includes(keys(connectStateUI), dataSource.state)
? dataSource.state
Expand All @@ -331,6 +329,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
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;
125 changes: 125 additions & 0 deletions app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Box, Flex, Text } from 'theme-ui';

import Table from '../../../../components/elements/Table';

import { PatientCell } from '../Cells';
import { DexcomConnectionStatusCell, DaysSinceLastDataCell, MoreMenuCell } from './Cells';
import TagListCell from '../../components/TagListCell';
import EmptyContentNode from '../EmptyContentNode';
import useTideReportNoDataPatients from './useTideReportNoDataPatients';
import EditPatientDialogController from './EditPatientDialogController';
import DataConnectionsModalController from './DataConnectionsModalController';
import { useGetPatientFromClinicQuery } from './tideDashboardLegacyApi';

const usePatientFromClinic = (patientId) => {
const selectedClinicId = useSelector(state => state.blip.selectedClinicId);

const { data: patient } = useGetPatientFromClinicQuery(
{ clinicId: selectedClinicId, patientId },
{ skip: !selectedClinicId || !patientId }
);

return patient;
};

const DataIssues = ({ api }) => {
const { t } = useTranslation();
const { patients } = useTideReportNoDataPatients();

const [activePatientId, setActivePatientId] = useState(null);
const [isEditPatientDialogOpen, setIsEditPatientDialogOpen] = useState(false);
const [isDataConnectionsModalOpen, setIsDataConnectionsModalOpen] = useState(false);

const activePatient = usePatientFromClinic(activePatientId);

const handleOpenEditPatientDialog = useCallback((patientId) => {
setActivePatientId(patientId);
setIsEditPatientDialogOpen(true);
}, []);

const handleOpenDataConnectionsModal = (patientId) => {
setActivePatientId(patientId);
setIsDataConnectionsModalOpen(true);
};

const handleCloseModals = () => {
setIsEditPatientDialogOpen(false);
setIsDataConnectionsModalOpen(false);
setActivePatientId(null);
};

const columns = useMemo(() => ([
{
title: t('Patient Details'),
field: 'fullName',
align: 'left',
render: patient => <PatientCell patient={patient} />,
},
{
title: t('Dexcom Connection Status'),
field: 'dexcomConnectionStatus',
align: 'left',
render: patient => <DexcomConnectionStatusCell patient={patient} onOpenDataConnectionsModal={handleOpenDataConnectionsModal} />,
},
{
title: t('Days Since Last Data'),
field: 'daysSinceLastData',
align: 'center',
render: patient => <DaysSinceLastDataCell patient={patient} />,
},
{
title: t('Tags'),
field: 'tags',
align: 'center',
render: patient => <TagListCell patient={patient} />,
},
{
title: '',
field: 'moreMenu',
align: 'center',
render: patient => (
<MoreMenuCell
patient={patient}
onOpenEditPatientDialog={handleOpenEditPatientDialog}
onOpenDataConnectionsModal={handleOpenDataConnectionsModal}
/>
),
},
]), [t, handleOpenEditPatientDialog, handleOpenDataConnectionsModal]);

return (
<Box id="tide-dashboard-data-issues" mt={4}>
<Flex className="data-issues-section-label" sx={{ color: 'purples.9', gap: 1 }} mb={2}>
<Text sx={{ fontSize: 1, fontWeight: 'medium' }}>{t('Data Issues')}</Text>
</Flex>

<Table
id="tideDashboardDataIssuesTable"
variant="condensed"
label="tideDashboardDataIssuesTable"
columns={columns}
data={patients}
emptyContentNode={<EmptyContentNode />}
containerProps={{ sx: { containerType: 'inline-size' } }}
/>

<EditPatientDialogController
api={api}
isOpen={isEditPatientDialogOpen}
patient={activePatient}
onClose={handleCloseModals}
/>

<DataConnectionsModalController
isOpen={isDataConnectionsModalOpen}
patient={activePatient}
onClose={handleCloseModals}
/>
</Box>
);
};

export default DataIssues;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { RTKQueryApi } from '../../../../redux/api/baseApi';
import EditPatientDialog from '../../../../components/modals/EditPatientDialog';
import { tagTypes } from '../tideDashboardApi';

const { TIDE_DASHBOARD_PATIENTS } = tagTypes;

const trackMetric = () => {};

const EditPatientDialogController = ({ api, isOpen, patient, onClose }) => {
const dispatch = useDispatch();

const handleEditSuccess = () => {
dispatch(RTKQueryApi.util.invalidateTags([TIDE_DASHBOARD_PATIENTS]));
};

return (
<EditPatientDialog
api={api}
trackMetric={trackMetric}
clinicPatient={patient}
isOpen={isOpen && !!patient}
onClose={onClose}
onEditSuccess={handleEditSuccess}
/>
);
};

export default EditPatientDialogController;
3 changes: 3 additions & 0 deletions app/pages/clinicworkspace/TideDashboardV2/DataIssues/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DataIssues from './DataIssues';

export default DataIssues;
Loading