diff --git a/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js b/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js new file mode 100644 index 0000000000..4e50aa2a80 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/PatientLastReviewed.js @@ -0,0 +1,115 @@ +import React from 'react'; +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 upperFirst from 'lodash/upperFirst'; + +import HoverButton from '../../../components/elements/HoverButton'; +import Icon from '../../../components/elements/Icon'; +import { useToasts } from '../../../providers/ToastProvider'; +import { useSetClinicPatientLastReviewedMutation, useRevertClinicPatientLastReviewedMutation } from './tideDashboardApi'; +import useTideDashboardPatients from './useTideDashboardPatients'; + +const { + formatTimeAgo, + getTimezoneFromTimePrefs, +} = vizUtils.datetime; + +const trackMetric = () => {}; // TODO: FIX + +const PatientLastReviewed = ({ patient }) => { + const { t } = useTranslation(); + // const { set: setToast } = useToasts(); + const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); + const loggedInUserId = useSelector((state) => state.blip.loggedInUserId); + const timePrefs = useSelector((state) => state.blip.timePrefs); + const patientId = patient?.id; + + const { isFetching } = useTideDashboardPatients(); + + const [setClinicPatientLastReviewed, { isLoading: isSetting }] = useSetClinicPatientLastReviewedMutation(); + const [revertClinicPatientLastReviewed, { isLoading: isReverting }] = useRevertClinicPatientLastReviewedMutation(); + + const handleReview = () => { + // trackMetric('Clinic - Mark patient reviewed', { clinicId: selectedClinicId, source: metricSource }); + setClinicPatientLastReviewed({ clinicId: selectedClinicId, patientId }); + // onReview && onReview(); + }; + + const handleUndo = () => { + // trackMetric('Clinic - Undo mark patient reviewed', { clinicId: selectedClinicId, source: metricSource }); + revertClinicPatientLastReviewed({ clinicId: selectedClinicId, patientId }); + }; + + const recentlyReviewedThresholdDate = moment().startOf('isoWeek').toISOString(); + + 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)} + + + + + ); +}; + +export default PatientLastReviewed; diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js index 24c60c7d23..84de737f1a 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js +++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js @@ -16,7 +16,7 @@ import PaginationControls from '../components/PaginationControls'; import ActiveFilterCount from '../components/ActiveFilterCount'; import { resetTideDashboardState, setOffset } from './tideDashboardSlice'; -import { useGetTideDashboardPatientsQuery } from './tideDashboardApi'; +import useTideDashboardPatients, { LIMIT } from './useTideDashboardPatients'; import ResetFilters from '../components/ResetFilters'; import useActiveFiltersCount from './useActiveFiltersCount'; import useDerivedDataRecencyEndpoints from './useDerivedDataRecencyEndpoints'; @@ -27,8 +27,6 @@ import EmptyContentNode from './EmptyContentNode'; import FilterBySites from './FilterBySites'; import PatientCount from '../components/PatientCount'; -const LIMIT = 12; - const Divider = () => ; const Gap = () => ; @@ -42,14 +40,8 @@ const TideDashboard = () => { 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 } = useSelector(state => state.blip.tideDashboardFilters); - - const [lastDataFrom, lastDataTo] = useDerivedDataRecencyEndpoints(); - const { data } = useGetTideDashboardPatientsQuery( - { clinicId: selectedClinicId, offset, category, 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 fbe93d81dc..13a254fa00 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js +++ b/app/pages/clinicworkspace/TideDashboardV2/tideDashboardApi.js @@ -36,6 +36,11 @@ export const buildGetTideDashboardPatientsParams = (offset, limit, category, las }; }; +const TAGS = { + TIDE_DASHBOARD_PATIENTS: 'TideDashboardPatients', +}; + +const { TIDE_DASHBOARD_PATIENTS } = TAGS; const tideDashboardApi = RTKQueryApi.injectEndpoints({ endpoints: (builder) => ({ @@ -52,8 +57,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..35855488eb 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js +++ b/app/pages/clinicworkspace/TideDashboardV2/useTableColumns.js @@ -25,6 +25,7 @@ import { } from './Cells'; import TagListCell from '../components/TagListCell'; +import PatientLastReviewed from './PatientLastReviewed'; const getColumnTypes = (t, category, thresholds) => ({ patientDetails: { @@ -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/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/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js b/app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js index 8cc65c6bf8..7a47a771db 100644 --- a/app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js +++ b/app/pages/dashboard/PatientDrawer/MenuBar/MenuBar.js @@ -35,7 +35,6 @@ const tabs = { const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab, trackMetric }) => { 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]); @@ -87,26 +86,24 @@ const MenuBar = ({ patientId, onClose, onSelectTab, selectedTab, trackMetric }) - {showTideDashboardLastReviewed && ( - - - {t('Last Reviewed')} - - - - - )} + + + {t('Last Reviewed')} + + + + diff --git a/app/redux/api/baseApi.js b/app/redux/api/baseApi.js index b8b223a09a..2d730c17d2 100644 --- a/app/redux/api/baseApi.js +++ b/app/redux/api/baseApi.js @@ -42,6 +42,7 @@ export const RTKQueryApi = createApi({ baseQuery, { maxRetries: RETRY_COUNT }, ), + tagTypes: ['TideDashboardPatients'], refetchOnMountOrArgChange: true, endpoints: () => ({}), });