From a78a3eba51c5b031980a8b3691c31e5de01a353a Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 7 Jul 2026 15:29:42 -0700 Subject: [PATCH 1/4] WEB-4460 initial setup for legacy data issues component --- .../TideDashboardV2/DataIssues/DataIssues.js | 170 ++++++++++++++++++ .../TideDashboardV2/DataIssues/index.js | 3 + .../DataIssues/tideDashboardLegacyApi.js | 53 ++++++ .../DataIssues/useTideReportNoDataPatients.js | 38 ++++ .../TideDashboardV2/TideDashboardV2.js | 3 + 5 files changed, 267 insertions(+) create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/index.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/useTideReportNoDataPatients.js diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js new file mode 100644 index 0000000000..09fb7447b5 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js @@ -0,0 +1,170 @@ +import React, { useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import moment from 'moment-timezone'; +import { Box, Flex, Text } from 'theme-ui'; +import includes from 'lodash/includes'; + +import Table from '../../../../components/elements/Table'; +import Pill from '../../../../components/elements/Pill'; +import HoverButton from '../../../../components/elements/HoverButton'; +import { resolveConnectState } from '../../../../components/datasources/DataConnections'; +import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded'; +import { colors, fontWeights } from '../../../../themes/baseTheme'; + +import { PatientCell, MoreMenuCell } from '../Cells'; +import TagListCell from '../../components/TagListCell'; +import EmptyContentNode from '../EmptyContentNode'; +import useTideReportNoDataPatients from './useTideReportNoDataPatients'; + +import { + setDataConnectionsModalIsOpen, + setDataConnectionsModalPatientId, +} from '../tideDashboardSlice'; + +const DexcomConnectionStatusCell = ({ patient }) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const dexcomConnectStateUI = useMemo(() => ({ + noPendingConnections: { colorPalette: 'neutral', icon: null, text: t('No Pending Connections') }, + 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 = () => { + dispatch(setDataConnectionsModalIsOpen(true)); + dispatch(setDataConnectionsModalPatientId(patient.id)); + }; + + const StatusBadge = () => ( + + ); + + if (!showViewButton) return ; + + return ( + + + + + + ); +}; + +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 ( + {daysSinceLastData ?? '-'} + ); +}; + +const DataIssues = () => { + const { t } = useTranslation(); + const { patients } = useTideReportNoDataPatients(); + + const columns = useMemo(() => ([ + { + title: t('Patient Details'), + field: 'fullName', + align: 'left', + render: patient => , + }, + { + title: t('Dexcom Connection Status'), + field: 'dexcomConnectionStatus', + align: 'left', + render: patient => , + }, + { + title: t('Days Since Last Data'), + field: 'daysSinceLastData', + align: 'center', + render: patient => , + }, + { + title: t('Tags'), + field: 'tags', + align: 'center', + render: patient => , + }, + { + title: '', + field: 'moreMenu', + align: 'center', + render: patient => , + }, + ]), [t]); + + return ( + + + {t('Data Issues')} + + + } + containerProps={{ sx: { containerType: 'inline-size' } }} + /> + + ); +}; + +export default DataIssues; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/index.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/index.js new file mode 100644 index 0000000000..cacca4eddb --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/index.js @@ -0,0 +1,3 @@ +import DataIssues from './DataIssues'; + +export default DataIssues; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js new file mode 100644 index 0000000000..debd0e2404 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js @@ -0,0 +1,53 @@ +import { RTKQueryApi } from '../../../../redux/api/baseApi'; +import { tagTypes } from '../tideDashboardApi'; + +// This file isolates legacy TIDE Dashboard API calls (the V1 +// `/v1/clinics/:clinicId/tide_report` endpoint) so they are not entangled with +// the new V2 endpoints in `tideDashboardApi`. It shares the primary +// `RTKQueryApi` instance (so no additional store wiring is required), but should +// be kept separate from new work. Prefer `tideDashboardApi` for all new code. + +const { TIDE_DASHBOARD_PATIENTS } = tagTypes; + +export const buildGetTideReportParams = (period, lastData, tags = [], lastDataCutoff, categories = []) => { + const formattedTags = ['69069fb929795a93a07df446']; + // const formattedTags = tags.length > 0 ? tags.join(',') : undefined; + const formattedCategories = categories.length > 0 ? categories.join(',') : undefined; + + return { + period, + lastData, + tags: formattedTags, + lastDataCutoff, + categories: formattedCategories, + }; +}; + +const tideDashboardLegacyApi = RTKQueryApi.injectEndpoints({ + endpoints: (builder) => ({ + getTideReport: builder.query({ + query: ({ clinicId, period, lastData, tags, lastDataCutoff, categories }) => { + const params = buildGetTideReportParams(period, lastData, tags, lastDataCutoff, categories); + + return { + url: `/clinics/${clinicId}/tide_report`, + params, + }; + }, + // The tide_report response groups patients by category. The "Data Issues" + // table only renders the `noData` group. We flatten each entry so that the + // row === patient (matching how the V2 table cells consume rows), keeping + // the top-level `lastData` needed to derive days since last data. + transformResponse: (response) => { + const noData = response?.results?.noData || []; + + return { + patients: noData.map(({ patient, lastData }) => ({ ...patient, lastData })), + }; + }, + providesTags: [TIDE_DASHBOARD_PATIENTS], + }), + }), +}); + +export const { useGetTideReportQuery } = tideDashboardLegacyApi; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/useTideReportNoDataPatients.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/useTideReportNoDataPatients.js new file mode 100644 index 0000000000..e4b51a48fb --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/useTideReportNoDataPatients.js @@ -0,0 +1,38 @@ +import { useSelector } from 'react-redux'; +import { useGetTideReportQuery } from './tideDashboardLegacyApi'; +import useDerivedDataRecencyEndpoints from '../useDerivedDataRecencyEndpoints'; + +// The Data Issues table always requests the report; `categories` is a required +// param on the endpoint, but the `noData` group is returned regardless of which +// category is requested, so we send a single category to satisfy the contract. +const CATEGORIES = ['meetingTargets']; + +// Fetches the tide_report and returns the `noData` (Data Issues) patients. +// Both the DataIssues table and the parent (for wiring the row action menu's +// modals) call this; RTK Query dedupes the identical request. +const useTideReportNoDataPatients = () => { + const selectedClinicId = useSelector(state => state.blip.selectedClinicId); + const { summaryPeriod, lastData, patientTags } = useSelector(state => state.blip.tideDashboardFilters); + + // `lastDataFrom` is derived identically to V1's `lastDataCutoff`. + const [lastDataCutoff] = useDerivedDataRecencyEndpoints(); + + const result = useGetTideReportQuery( + { + clinicId: selectedClinicId, + period: summaryPeriod, + lastData, + tags: patientTags, + lastDataCutoff, + categories: CATEGORIES, + }, + { skip: !selectedClinicId } + ); + + return { + ...result, + patients: result.data?.patients || [], + }; +}; + +export default useTideReportNoDataPatients; diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js index 78b1ac02d0..8e9ae71ed9 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js +++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js @@ -28,6 +28,7 @@ import FilterBySites from './FilterBySites'; import PatientCount from '../components/PatientCount'; import EditPatientDialogController from './EditPatientDialogController'; import DataConnectionsModalController from './DataConnectionsModalController'; +import DataIssues from './DataIssues'; const LIMIT = 12; @@ -122,6 +123,8 @@ const TideDashboard = ({ api, trackMetric }) => { + + Date: Tue, 7 Jul 2026 15:41:34 -0700 Subject: [PATCH 2/4] WEB-4460 remove qa tag string from code --- .../TideDashboardV2/DataIssues/tideDashboardLegacyApi.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js index debd0e2404..8a120d36b8 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js @@ -10,8 +10,7 @@ import { tagTypes } from '../tideDashboardApi'; const { TIDE_DASHBOARD_PATIENTS } = tagTypes; export const buildGetTideReportParams = (period, lastData, tags = [], lastDataCutoff, categories = []) => { - const formattedTags = ['69069fb929795a93a07df446']; - // const formattedTags = tags.length > 0 ? tags.join(',') : undefined; + const formattedTags = tags.length > 0 ? tags.join(',') : undefined; const formattedCategories = categories.length > 0 ? categories.join(',') : undefined; return { From ab19603d5a9f9ca5329c1b91563384be9ebde68d Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 7 Jul 2026 16:16:02 -0700 Subject: [PATCH 3/4] WEB-4460 attach modal controllers for data issues --- .../TideDashboardV2/DataIssues/Cells.js | 140 ++++++++++++++++ .../DataConnectionsModalController.js | 12 ++ .../TideDashboardV2/DataIssues/DataIssues.js | 155 +++++++----------- .../DataIssues/EditPatientDialogController.js | 30 ++++ .../DataIssues/tideDashboardLegacyApi.js | 10 +- .../TideDashboardV2/TideDashboardV2.js | 2 +- 6 files changed, 247 insertions(+), 102 deletions(-) create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataConnectionsModalController.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/DataIssues/EditPatientDialogController.js diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js new file mode 100644 index 0000000000..065843b4c5 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js @@ -0,0 +1,140 @@ +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') }, + 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 = () => ( + + ); + + if (!showViewButton) return ; + + return ( + + + + + + ); +}; + +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 ( + {daysSinceLastData ?? '-'} + ); +}; + +export const MoreMenuCell = ({ patient, onOpenEditPatientDialog, onOpenDataConnectionsModal }) => { + const { t } = useTranslation(); + + return ( + { + _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, +}; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataConnectionsModalController.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataConnectionsModalController.js new file mode 100644 index 0000000000..f8a7673db9 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataConnectionsModalController.js @@ -0,0 +1,12 @@ +import React from 'react'; +import DataConnectionsModal from '../../../../components/datasources/DataConnectionsModal'; + +const DataConnectionsModalController = ({ isOpen, patient, onClose }) => ( + +); + +export default DataConnectionsModalController; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js index 09fb7447b5..f50ee16e54 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/DataIssues.js @@ -1,119 +1,55 @@ -import React, { useMemo } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import moment from 'moment-timezone'; import { Box, Flex, Text } from 'theme-ui'; -import includes from 'lodash/includes'; import Table from '../../../../components/elements/Table'; -import Pill from '../../../../components/elements/Pill'; -import HoverButton from '../../../../components/elements/HoverButton'; -import { resolveConnectState } from '../../../../components/datasources/DataConnections'; -import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded'; -import { colors, fontWeights } from '../../../../themes/baseTheme'; -import { PatientCell, MoreMenuCell } from '../Cells'; +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'; -import { - setDataConnectionsModalIsOpen, - setDataConnectionsModalPatientId, -} from '../tideDashboardSlice'; +const usePatientFromClinic = (patientId) => { + const selectedClinicId = useSelector(state => state.blip.selectedClinicId); -const DexcomConnectionStatusCell = ({ patient }) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - - const dexcomConnectStateUI = useMemo(() => ({ - noPendingConnections: { colorPalette: 'neutral', icon: null, text: t('No Pending Connections') }, - 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 = () => { - dispatch(setDataConnectionsModalIsOpen(true)); - dispatch(setDataConnectionsModalPatientId(patient.id)); - }; - - const StatusBadge = () => ( - + const { data: patient } = useGetPatientFromClinicQuery( + { clinicId: selectedClinicId, patientId }, + { skip: !selectedClinicId || !patientId } ); - if (!showViewButton) return ; - - return ( - - - - - - ); + return patient; }; -const DaysSinceLastDataCell = ({ patient }) => { - const timePrefs = useSelector(state => state.blip.timePrefs); +const DataIssues = ({ api }) => { + const { t } = useTranslation(); + const { patients } = useTideReportNoDataPatients(); - const daysSinceLastData = useMemo(() => { - if (!patient?.lastData) return null; + const [activePatientId, setActivePatientId] = useState(null); + const [isEditPatientDialogOpen, setIsEditPatientDialogOpen] = useState(false); + const [isDataConnectionsModalOpen, setIsDataConnectionsModalOpen] = useState(false); - 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'); + const activePatient = usePatientFromClinic(activePatientId); - return startOfCurrentDay.diff(startOfLastDataDay, 'days'); - }, [patient?.lastData, timePrefs?.timezoneName]); + const handleOpenEditPatientDialog = useCallback((patientId) => { + setActivePatientId(patientId); + setIsEditPatientDialogOpen(true); + }, []); - return ( - {daysSinceLastData ?? '-'} - ); -}; + const handleOpenDataConnectionsModal = (patientId) => { + setActivePatientId(patientId); + setIsDataConnectionsModalOpen(true); + }; -const DataIssues = () => { - const { t } = useTranslation(); - const { patients } = useTideReportNoDataPatients(); + const handleCloseModals = () => { + setIsEditPatientDialogOpen(false); + setIsDataConnectionsModalOpen(false); + setActivePatientId(null); + }; const columns = useMemo(() => ([ { @@ -126,7 +62,7 @@ const DataIssues = () => { title: t('Dexcom Connection Status'), field: 'dexcomConnectionStatus', align: 'left', - render: patient => , + render: patient => , }, { title: t('Days Since Last Data'), @@ -144,9 +80,15 @@ const DataIssues = () => { title: '', field: 'moreMenu', align: 'center', - render: patient => , + render: patient => ( + + ), }, - ]), [t]); + ]), [t, handleOpenEditPatientDialog, handleOpenDataConnectionsModal]); return ( @@ -163,6 +105,19 @@ const DataIssues = () => { emptyContentNode={} containerProps={{ sx: { containerType: 'inline-size' } }} /> + + + + ); }; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/EditPatientDialogController.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/EditPatientDialogController.js new file mode 100644 index 0000000000..143421772a --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/EditPatientDialogController.js @@ -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 ( + + ); +}; + +export default EditPatientDialogController; diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js index 8a120d36b8..8040d16209 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/tideDashboardLegacyApi.js @@ -46,7 +46,15 @@ const tideDashboardLegacyApi = RTKQueryApi.injectEndpoints({ }, providesTags: [TIDE_DASHBOARD_PATIENTS], }), + getPatientFromClinic: builder.query({ + queryFn: async ({ clinicId, patientId }, _queryApi, _extraOptions, baseQuery) => { + if (!patientId) return { data: null }; + + return baseQuery({ url: `/clinics/${clinicId}/patients/${patientId}` }); + }, + providesTags: [TIDE_DASHBOARD_PATIENTS], + }), }), }); -export const { useGetTideReportQuery } = tideDashboardLegacyApi; +export const { useGetTideReportQuery, useGetPatientFromClinicQuery } = tideDashboardLegacyApi; diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js index 8e9ae71ed9..f83465147e 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js +++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js @@ -123,7 +123,7 @@ const TideDashboard = ({ api, trackMetric }) => { - + Date: Tue, 14 Jul 2026 11:20:59 -0700 Subject: [PATCH 4/4] WEB-4460 fix bugs and missing resolveConnectState --- app/components/datasources/DataConnections.js | 17 ++++++++++++----- .../TideDashboardV2/DataIssues/Cells.js | 1 + .../clinicworkspace/components/TagListCell.js | 4 +++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/components/datasources/DataConnections.js b/app/components/datasources/DataConnections.js index 709ef5f43c..d47ac28a1e 100644 --- a/app/components/datasources/DataConnections.js +++ b/app/components/datasources/DataConnections.js @@ -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 @@ -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 { diff --git a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js index 065843b4c5..1c7877dad4 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js +++ b/app/pages/clinicworkspace/TideDashboardV2/DataIssues/Cells.js @@ -19,6 +19,7 @@ export const DexcomConnectionStatusCell = ({ patient, onOpenDataConnectionsModal 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') }, diff --git a/app/pages/clinicworkspace/components/TagListCell.js b/app/pages/clinicworkspace/components/TagListCell.js index 38d2654858..9cbc941cef 100644 --- a/app/pages/clinicworkspace/components/TagListCell.js +++ b/app/pages/clinicworkspace/components/TagListCell.js @@ -10,7 +10,9 @@ const TagListCell = ({ patient }) => { const patientTags = clinic?.patientTags || []; const tagIds = patient?.tags || []; - const tags = tagIds.map(tag => patientTags.find(ptTag => ptTag.id === tag)); // TODO: index + const tags = tagIds + .map(tag => patientTags.find(ptTag => ptTag.id === tag)) // TODO: index + .filter(Boolean); return ; };