diff --git a/app/components/PatientDrawer/MenuBar/MenuBar.js b/app/components/PatientDrawer/MenuBar/MenuBar.js
index 724221191a..ceac651ebf 100644
--- a/app/components/PatientDrawer/MenuBar/MenuBar.js
+++ b/app/components/PatientDrawer/MenuBar/MenuBar.js
@@ -36,7 +36,6 @@ const tabs = {
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]);
@@ -88,26 +87,24 @@ const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab }) => {
- {showTideDashboardLastReviewed && (
-
-
- {t('Last Reviewed')}
-
-
-
-
- )}
+
+
+ {t('Last Reviewed')}
+
+
+
+
diff --git a/app/components/clinic/PatientLastReviewed.js b/app/components/clinic/PatientLastReviewed.js
index 822851733b..3b46d59f38 100644
--- a/app/components/clinic/PatientLastReviewed.js
+++ b/app/components/clinic/PatientLastReviewed.js
@@ -1,36 +1,19 @@
import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
-import { Text, Box, FlexProps } from 'theme-ui';
-import { withTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
-import moment from 'moment-timezone';
-import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
-import { utils as vizUtils } from '@tidepool/viz';
import get from 'lodash/get';
-import upperFirst from 'lodash/upperFirst';
-import HoverButton from '../elements/HoverButton';
-import Icon from '../elements/Icon';
-import i18next from '../../core/language';
+import ReviewPatientToggle from '../../pages/clinicworkspace/components/ReviewPatientToggle';
import * as actions from '../../redux/actions';
import { useIsFirstRender } from '../../core/hooks';
import { useToasts } from '../../providers/ToastProvider';
-const {
- formatTimeAgo,
- getTimezoneFromTimePrefs,
-} = vizUtils.datetime;
-
-const t = i18next.t.bind(i18next);
-
-export const PatientLastReviewed = ({ api, patientId, recentlyReviewedThresholdDate, trackMetric, metricSource, onReview = null }) => {
+export const PatientLastReviewed = ({ api, patientId, recentlyReviewedThresholdDate, onReview = null }) => {
const dispatch = useDispatch();
const isFirstRender = useIsFirstRender();
const { set: setToast } = useToasts();
const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);
const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]);
- const loggedInUserId = useSelector((state) => state.blip.loggedInUserId);
- const timePrefs = useSelector((state) => state.blip.timePrefs);
const patient = clinic?.patients?.[patientId];
const {
@@ -67,86 +50,32 @@ export const PatientLastReviewed = ({ api, patientId, recentlyReviewedThresholdD
}, [revertingClinicPatientLastReviewed]);
const handleReview = () => {
- trackMetric('Clinic - Mark patient reviewed', { clinicId: selectedClinicId, source: metricSource, patientID: patientId });
dispatch(actions.async.setClinicPatientLastReviewed(api, selectedClinicId, patientId));
onReview && onReview();
};
const handleUndo = () => {
- trackMetric('Clinic - Undo mark patient reviewed', { clinicId: selectedClinicId, source: metricSource });
dispatch(actions.async.revertClinicPatientLastReviewed(api, selectedClinicId, patientId));
};
- let clickHandler = handleReview;
- let buttonText = t('Mark Reviewed');
-
- let formattedLastReviewed = { daysText: '-' };
- let lastReviewIsToday = false;
- let reviewIsRecent = false;
- let canReview = true;
- let color = 'feedback.warning';
-
- if (patient?.reviews?.[0]?.time) {
- formattedLastReviewed = formatTimeAgo(patient.reviews[0].time, timePrefs);
- lastReviewIsToday = moment.utc(patient.reviews[0].time).tz(getTimezoneFromTimePrefs(timePrefs)).isSame(moment(), 'day');
-
- if (lastReviewIsToday) {
- canReview = false;
- clickHandler = null;
- }
-
- if (moment.utc(patient.reviews[0].time).isSameOrAfter(moment(recentlyReviewedThresholdDate))) {
- reviewIsRecent = true;
- }
-
- if (lastReviewIsToday && patient.reviews[0].clinicianId === loggedInUserId) {
- clickHandler = handleUndo;
- buttonText = t('Undo');
- };
-
- if (reviewIsRecent) {
- color = 'feedback.success';
- }
- }
+ const processing = settingClinicPatientLastReviewed.inProgress || revertingClinicPatientLastReviewed.inProgress;
return (
-
-
-
- {reviewIsRecent && }
- {upperFirst(formattedLastReviewed.daysText)}
-
-
-
+
);
};
PatientLastReviewed.propTypes = {
- ...FlexProps,
api: PropTypes.object.isRequired,
- metricSource: PropTypes.string.isRequired,
patientId: PropTypes.string.isRequired,
recentlyReviewedThresholdDate: PropTypes.string.isRequired,
- trackMetric: PropTypes.func.isRequired,
+ onReview: PropTypes.func,
}
-export default withTranslation()(PatientLastReviewed);
+export default PatientLastReviewed;
diff --git a/app/pages/clinicworkspace/ClinicPatients.js b/app/pages/clinicworkspace/ClinicPatients.js
index 4b391fc236..8f4478280c 100644
--- a/app/pages/clinicworkspace/ClinicPatients.js
+++ b/app/pages/clinicworkspace/ClinicPatients.js
@@ -2834,10 +2834,10 @@ export const ClinicPatients = (props) => {
const renderLastReviewed = useCallback((patient) => {
return (
event.stopPropagation()}>
-
+
);
- }, [api, trackMetric]);
+ }, [api]);
const renderMore = useCallback((patient) => {
return {
);
};
+export const PatientLastReviewedCell = ({ patient }) => {
+ return ;
+};
+
export default {
PatientCell,
NumericTemplateCell,
@@ -248,4 +253,5 @@ export default {
GMICell,
CGMUseCell,
FlagCell,
+ PatientLastReviewedCell,
};
diff --git a/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js b/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js
new file mode 100644
index 0000000000..51c08b4d70
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+import ReviewPatientToggle from '../components/ReviewPatientToggle';
+import { useSetClinicPatientLastReviewedMutation, useRevertClinicPatientLastReviewedMutation } from './tideDashboardApi';
+import useTideDashboardPatients from './useTideDashboardPatients';
+
+const PatientLastReviewed = ({ patient }) => {
+ const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);
+ const patientId = patient?.id;
+
+ const { isFetching } = useTideDashboardPatients();
+
+ const [setClinicPatientLastReviewed, { isLoading: isSetting }] = useSetClinicPatientLastReviewedMutation();
+ const [revertClinicPatientLastReviewed, { isLoading: isReverting }] = useRevertClinicPatientLastReviewedMutation();
+
+ const handleReview = () => setClinicPatientLastReviewed({ clinicId: selectedClinicId, patientId });
+
+ const handleUndo = () => revertClinicPatientLastReviewed({ clinicId: selectedClinicId, patientId });
+
+ const processing = isSetting || isReverting || isFetching;
+
+ return (
+
+ );
+};
+
+export default PatientLastReviewed;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
index a4e7bf042a..45864a6110 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js
@@ -14,8 +14,7 @@ import PaginationControls from '../components/PaginationControls';
import PatientDrawerController from './PatientDrawerController';
import { setOffset } from './tideDashboardSlice';
-import { useGetTideDashboardPatientsQuery } from './tideDashboardApi';
-import useDerivedDataRecencyEndpoints from './useDerivedDataRecencyEndpoints';
+import useTideDashboardPatients, { LIMIT } from './useTideDashboardPatients';
import usePruneInvalidFilters from './usePruneInvalidFilters';
import useTableColumns from './useTableColumns';
import EmptyContentNode from './EmptyContentNode';
@@ -23,8 +22,6 @@ import FilterBySites from './FilterBySites';
import PatientCount from '../components/PatientCount';
import AppliedFiltersList from './AppliedFiltersList';
-const LIMIT = 12;
-
const Gap = () => ;
const TideDashboard = ({ api }) => {
@@ -33,17 +30,10 @@ const TideDashboard = ({ api }) => {
usePruneInvalidFilters();
- const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const category = useSelector(state => state.blip.tideDashboard.category);
const offset = useSelector(state => state.blip.tideDashboard.offset);
- const { patientTags, clinicSites, summaryPeriod } = useSelector(state => state.blip.tideDashboardFilters);
-
- const [lastDataFrom, lastDataTo] = useDerivedDataRecencyEndpoints();
- const { data } = useGetTideDashboardPatientsQuery(
- { clinicId: selectedClinicId, offset, category, summaryPeriod, lastDataTo, lastDataFrom, tags: patientTags, sites: clinicSites, limit: LIMIT },
- { skip: !selectedClinicId }
- );
+ const { data } = useTideDashboardPatients();
// Sync category to data fetching resolution; prevents visual glitch due to
// category updating view before the API call resolves and updates it again
diff --git a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
index 2cec1cd032..8da4915ba7 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js
@@ -37,6 +37,11 @@ export const buildGetTideDashboardPatientsParams = (offset, limit, category, sum
};
};
+const TAGS = {
+ TIDE_DASHBOARD_PATIENTS: 'TideDashboardPatients',
+};
+
+const { TIDE_DASHBOARD_PATIENTS } = TAGS;
const tideDashboardApi = RTKQueryApi.injectEndpoints({
endpoints: (builder) => ({
@@ -53,8 +58,27 @@ const tideDashboardApi = RTKQueryApi.injectEndpoints({
...response,
category: arg.category,
}),
+ providesTags: [TIDE_DASHBOARD_PATIENTS],
+ }),
+ setClinicPatientLastReviewed: builder.mutation({
+ query: ({ clinicId, patientId }) => ({
+ url: `/clinics/${clinicId}/patients/${patientId}/reviews`,
+ method: 'PUT',
+ }),
+ invalidatesTags: [TIDE_DASHBOARD_PATIENTS],
+ }),
+ revertClinicPatientLastReviewed: builder.mutation({
+ query: ({ clinicId, patientId }) => ({
+ url: `/clinics/${clinicId}/patients/${patientId}/reviews`,
+ method: 'DELETE',
+ }),
+ invalidatesTags: [TIDE_DASHBOARD_PATIENTS],
}),
}),
});
-export const { useGetTideDashboardPatientsQuery } = tideDashboardApi;
+export const {
+ useGetTideDashboardPatientsQuery,
+ useSetClinicPatientLastReviewedMutation,
+ useRevertClinicPatientLastReviewedMutation,
+} = tideDashboardApi;
diff --git a/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js b/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js
index ceaf67bb6e..3c6e49de0f 100644
--- a/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js
+++ b/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js
@@ -22,6 +22,7 @@ import {
TimeInAnyHighPercentCell,
TimeInTargetPercentCell,
FlagCell,
+ PatientLastReviewedCell,
} from './Cells';
import TagListCell from '../components/TagListCell';
@@ -111,6 +112,7 @@ const getColumnTypes = (t, category, thresholds) => ({
title: t('Last Reviewed'),
field: 'lastReviewed',
align: 'center',
+ render: patient => ,
},
moreMenu: {
title: t(''),
diff --git a/app/pages/clinicworkspace/TideDashboardV2/useTideDashboardPatients.js b/app/pages/clinicworkspace/TideDashboardV2/useTideDashboardPatients.js
new file mode 100644
index 0000000000..cf8e438cc3
--- /dev/null
+++ b/app/pages/clinicworkspace/TideDashboardV2/useTideDashboardPatients.js
@@ -0,0 +1,21 @@
+import { useSelector } from 'react-redux';
+import { useGetTideDashboardPatientsQuery } from './tideDashboardApi';
+import useDerivedDataRecencyEndpoints from './useDerivedDataRecencyEndpoints';
+
+const LIMIT = 12;
+
+const useTideDashboardPatients = () => {
+ const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
+ const category = useSelector(state => state.blip.tideDashboard.category);
+ const offset = useSelector(state => state.blip.tideDashboard.offset);
+ const patientTags = useSelector(state => state.blip.tideDashboardFilters.patientTags);
+ const [lastDataFrom, lastDataTo] = useDerivedDataRecencyEndpoints();
+
+ return useGetTideDashboardPatientsQuery(
+ { clinicId: selectedClinicId, offset, category, lastDataTo, lastDataFrom, tags: patientTags, limit: LIMIT },
+ { skip: !selectedClinicId }
+ );
+};
+
+export { LIMIT };
+export default useTideDashboardPatients;
diff --git a/app/pages/clinicworkspace/components/ReviewPatientToggle.js b/app/pages/clinicworkspace/components/ReviewPatientToggle.js
new file mode 100644
index 0000000000..29e5a2b166
--- /dev/null
+++ b/app/pages/clinicworkspace/components/ReviewPatientToggle.js
@@ -0,0 +1,114 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+import { Text, Box, FlexProps } from 'theme-ui';
+import moment from 'moment-timezone';
+import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
+import { utils as vizUtils } from '@tidepool/viz';
+import noop from 'lodash/noop';
+import upperFirst from 'lodash/upperFirst';
+
+import HoverButton from '../../../components/elements/HoverButton';
+import Icon from '../../../components/elements/Icon';
+import useClinicMetricsPageName from '../useClinicMetricsPageName';
+import { trackMetric } from '../../../core/metricUtils';
+const { formatTimeAgo, getTimezoneFromTimePrefs } = vizUtils.datetime;
+
+const ReviewPatientToggle = ({
+ patient,
+ onReview = noop,
+ onUndo = noop,
+ processing = false,
+ recentlyReviewedThresholdDate = moment().startOf('isoWeek').toISOString(),
+}) => {
+ const { t } = useTranslation();
+ const pageName = useClinicMetricsPageName();
+ const selectedClinicId = useSelector((state) => state.blip.selectedClinicId);
+ const loggedInUserId = useSelector((state) => state.blip.loggedInUserId);
+ const timePrefs = useSelector((state) => state.blip.timePrefs);
+
+ const handleReview = () => {
+ trackMetric('Clinic - Mark patient reviewed', { clinicId: selectedClinicId, pageName });
+ onReview();
+ };
+
+ const handleUndo = () => {
+ trackMetric('Clinic - Undo mark patient reviewed', { clinicId: selectedClinicId, pageName });
+ onUndo();
+ };
+
+ let clickHandler = handleReview;
+ let buttonText = t('Mark Reviewed');
+
+ let formattedLastReviewed = { daysText: '-' };
+ let lastReviewIsToday = false;
+ let reviewIsRecent = false;
+ let canReview = true;
+ let color = 'feedback.warning';
+
+ if (patient?.reviews?.[0]?.time) {
+ formattedLastReviewed = formatTimeAgo(patient.reviews[0].time, timePrefs);
+ lastReviewIsToday = moment.utc(patient.reviews[0].time).tz(getTimezoneFromTimePrefs(timePrefs)).isSame(moment(), 'day');
+
+ if (lastReviewIsToday) {
+ canReview = false;
+ clickHandler = null;
+ }
+
+ if (moment.utc(patient.reviews[0].time).isSameOrAfter(moment(recentlyReviewedThresholdDate))) {
+ reviewIsRecent = true;
+ }
+
+ if (lastReviewIsToday && patient.reviews[0].clinicianId === loggedInUserId) {
+ clickHandler = handleUndo;
+ buttonText = t('Undo');
+ };
+
+ if (reviewIsRecent) {
+ color = 'feedback.success';
+ }
+ }
+
+ return (
+
+
+
+
+ {reviewIsRecent && }
+ {upperFirst(formattedLastReviewed.daysText)}
+
+
+
+
+ );
+};
+
+ReviewPatientToggle.propTypes = {
+ patient: PropTypes.object,
+ onReview: PropTypes.func,
+ onUndo: PropTypes.func,
+ processing: PropTypes.bool,
+ recentlyReviewedThresholdDate: PropTypes.string,
+};
+
+export default ReviewPatientToggle;
diff --git a/app/pages/clinicworkspace/components/TagListCell.js b/app/pages/clinicworkspace/components/TagListCell.js
index 38d2654858..b500a5cb60 100644
--- a/app/pages/clinicworkspace/components/TagListCell.js
+++ b/app/pages/clinicworkspace/components/TagListCell.js
@@ -10,7 +10,7 @@ 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)).filter(Boolean); // TODO: index
return ;
};
diff --git a/app/redux/api/baseApi.js b/app/redux/api/baseApi.js
index 92bc608cec..c70ae24e9e 100644
--- a/app/redux/api/baseApi.js
+++ b/app/redux/api/baseApi.js
@@ -45,6 +45,7 @@ export const RTKQueryApi = createApi({
baseQuery,
{ maxRetries: RETRY_COUNT },
),
+ tagTypes: ['TideDashboardPatients'],
refetchOnMountOrArgChange: true,
endpoints: () => ({}),
});