From 737e8cf07cb934eba9f7bbb3607e428c2d28930c Mon Sep 17 00:00:00 2001 From: henry-tp Date: Mon, 17 Aug 2026 17:01:37 -0700 Subject: [PATCH 01/23] WEB-4460 initialize base branch From a8836e24d982564cc9c7a00a254498eff041a3f4 Mon Sep 17 00:00:00 2001 From: henry-tp Date: Mon, 17 Aug 2026 15:32:42 -0700 Subject: [PATCH 02/23] WEB-4460 add new adapter components to dashboard --- .../TideDashboardV2/AppliedFiltersList.js | 81 +++++++++++++++++++ .../TideDashboardV2/FilterByDataRecency.js | 33 ++++++++ .../TideDashboardV2/FilterBySites.js | 19 +++++ .../TideDashboardV2/FilterBySummaryPeriod.js | 25 ++++++ .../TideDashboardV2/FilterByTags.js | 19 +++++ .../TideDashboardV2/TideDashboardV2.js | 19 ++++- .../components/ActiveFiltersTray.js | 37 +++++---- 7 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 app/pages/clinicworkspace/TideDashboardV2/AppliedFiltersList.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/FilterBySites.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js create mode 100644 app/pages/clinicworkspace/TideDashboardV2/FilterByTags.js diff --git a/app/pages/clinicworkspace/TideDashboardV2/AppliedFiltersList.js b/app/pages/clinicworkspace/TideDashboardV2/AppliedFiltersList.js new file mode 100644 index 0000000000..3709028afc --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/AppliedFiltersList.js @@ -0,0 +1,81 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import without from 'lodash/without'; +import noop from 'lodash/noop'; + +import ActiveFiltersTray from '../components/ActiveFiltersTray'; +import ClearFilterButtons, { PATIENT_QUERY_STATE } from '../components/ClearFilterButtons'; +import { Box } from 'theme-ui'; +import { setClinicSitesFilter, setPatientTagsFilter } from './tideDashboardFiltersSlice'; +import { setOffset } from './tideDashboardSlice'; + +export const getPatientQueryState = (patientTags, clinicSites) => { + const hasFiltersActive = clinicSites?.length > 0 || patientTags?.length > 0; + + if (hasFiltersActive) return PATIENT_QUERY_STATE.FILTER_ONLY; + + return PATIENT_QUERY_STATE.NONE; +}; + +const AppliedFiltersList = ({ patientCount = 0 }) => { + const dispatch = useDispatch(); + + const { lastData, clinicSites, patientTags } = useSelector(state => state.blip.tideDashboardFilters); + + const activeFilters = { + lastDataType: 'cgm', + lastData, + clinicSites, + patientTags, + }; + + const handleResetFilters = () => { + dispatch(setPatientTagsFilter([])); + dispatch(setClinicSitesFilter([])); + dispatch(setOffset(0)); + }; + + const handleRemoveFilter = (filterKey, value) => { + switch (filterKey) { + case 'patientTags': + const updatedTags = without(patientTags, value); + dispatch(setPatientTagsFilter(updatedTags)); + dispatch(setOffset(0)); + break; + + case 'clinicSites': + const updatedSites = without(clinicSites, value); + dispatch(setClinicSitesFilter(updatedSites)); + dispatch(setOffset(0)); + break; + } + }; + + const patientQueryState = getPatientQueryState(patientTags, clinicSites); + + return ( + + + + } + /> + ); +}; + +AppliedFiltersList.propTypes = { + patientCount: PropTypes.number, +}; + +export default AppliedFiltersList; diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js b/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js new file mode 100644 index 0000000000..843092e8e7 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/FilterByDataRecency.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setOffset } from './tideDashboardSlice'; +import { setLastDataFilter } from './tideDashboardFiltersSlice'; + +import { lastDataFilterOptions } from '../../../core/clinicUtils'; + +import DataRecencyFilterDropdown from '../components/DataRecencyFilterDropdown'; + +const FilterByDataRecency = () => { + const dispatch = useDispatch(); + const { lastData } = useSelector(state => state.blip.tideDashboardFilters); + + const handleChange = ({ lastData }) => { + dispatch(setLastDataFilter(lastData)); + dispatch(setOffset(0)); + }; + + const customLastDataFilterOptions = lastDataFilterOptions.filter(opt => [1, 2, 7].includes(opt.value)); + + return ( + + ); +}; + +export default FilterByDataRecency; diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterBySites.js b/app/pages/clinicworkspace/TideDashboardV2/FilterBySites.js new file mode 100644 index 0000000000..8849379403 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/FilterBySites.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setOffset } from './tideDashboardSlice'; +import { setClinicSitesFilter } from './tideDashboardFiltersSlice'; +import SiteFilterDropdown from '../components/SiteFilterDropdown'; + +const FilterBySites = () => { + const dispatch = useDispatch(); + const { clinicSites } = useSelector(state => state.blip.tideDashboardFilters); + + const handleChange = (clinicSites) => { + dispatch(setClinicSitesFilter(clinicSites)); + dispatch(setOffset(0)); + }; + + return ; +}; + +export default FilterBySites; diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js b/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js new file mode 100644 index 0000000000..569a1449c1 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/FilterBySummaryPeriod.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import SummaryPeriodFilterDropdown from '../components/SummaryPeriodFilterDropdown'; +import { setSummaryPeriodFilter } from './tideDashboardFiltersSlice'; +import { setOffset } from './tideDashboardSlice'; + +const FilterBySummaryPeriod = () => { + const dispatch = useDispatch(); + const { summaryPeriod } = useSelector(state => state.blip.tideDashboardFilters); + + const handleChange = (summaryPeriod) => { + dispatch(setSummaryPeriodFilter(summaryPeriod)); + dispatch(setOffset(0)); + }; + + return ( + + ); +}; + +export default FilterBySummaryPeriod; diff --git a/app/pages/clinicworkspace/TideDashboardV2/FilterByTags.js b/app/pages/clinicworkspace/TideDashboardV2/FilterByTags.js new file mode 100644 index 0000000000..eeb5f84f79 --- /dev/null +++ b/app/pages/clinicworkspace/TideDashboardV2/FilterByTags.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setOffset } from './tideDashboardSlice'; +import { setPatientTagsFilter } from './tideDashboardFiltersSlice'; +import TagFilterDropdown from '../components/TagFilterDropdown'; + +const FilterByTags = () => { + const dispatch = useDispatch(); + const { patientTags } = useSelector(state => state.blip.tideDashboardFilters); + + const handleChange = (tags) => { + dispatch(setPatientTagsFilter(tags)); + dispatch(setOffset(0)); + }; + + return ; +}; + +export default FilterByTags; diff --git a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js index 445203ca88..ca6cd56075 100644 --- a/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js +++ b/app/pages/clinicworkspace/TideDashboardV2/TideDashboardV2.js @@ -1,9 +1,12 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import Table from '../../../components/elements/Table'; -import { Flex } from 'theme-ui'; +import { Flex, Text, Box } from 'theme-ui'; import FilterByCategory from './FilterByCategory'; +import FilterByTags from './FilterByTags'; +import FilterByDataRecency from './FilterByDataRecency'; +import FilterBySummaryPeriod from './FilterBySummaryPeriod'; import TableCategoryHeader from './TableCategoryHeader'; import PaginationController from './PaginationController'; @@ -13,6 +16,10 @@ import useTableColumns from './useTableColumns'; import EmptyContentNode from './EmptyContentNode'; import { Redirect, useLocation } from 'react-router-dom'; import useAuthorizationGate from './useAuthorizationGate'; +import FilterBySites from './FilterBySites'; +import AppliedFiltersList from './AppliedFiltersList'; + +const Gap = () => ; const tableContainerProps = { sx: { containerType: 'inline-size' } }; @@ -41,11 +48,21 @@ const TideDashboardV2 = () => { return ( <> + + {t('Filter By')} + + + + + + + + { +const usePrimaryChips = (activeFilters, requiredFilters) => { const { t } = useTranslation(); const { lastData, lastDataType, timeCGMUsePercent, timeInRange = [] } = activeFilters; @@ -47,6 +47,7 @@ const usePrimaryChips = (activeFilters) => { type: 'lastData', value: `${lastDataType}-${lastData}`, label: getLastDataChipLabel(lastDataType, lastData), + required: requiredFilters?.includes('lastData') || false, }), // CGM Wear Time Filter @@ -111,8 +112,9 @@ const useSiteChips = (clinicSites = []) => { .toSorted((a, b) => utils.compareLabels(a.label, b.label)); }; -const Chip = ({ label, onRemove }) => { +const Chip = ({ label, onRemove, required = false }) => { const { t } = useTranslation(); + const hasRemoveIcon = !required; return ( { fontWeight: 'normal', cursor: 'default', ml: 1, - '&:hover': { + '&:hover': hasRemoveIcon ? { color: vizColors.blue80, fontWeight: 'medium', - }, + } : {}, '.remove-filter-icon': { fontSize: '14px', padding: '2px', @@ -154,12 +156,14 @@ const Chip = ({ label, onRemove }) => { {label} - + { hasRemoveIcon && + + } ); }; @@ -175,6 +179,7 @@ const ChipGroup = ({ prefix, chips, onRemove }) => { onRemove(chip)} /> ))} @@ -183,23 +188,22 @@ const ChipGroup = ({ prefix, chips, onRemove }) => { }; const ActiveFiltersTray = ({ + patientCount = 0, filters = {}, + requiredFilters = [], hasSearchActive = false, onRemoveFilter = noop, rightContent = null, }) => { const { t } = useTranslation(); - const selectedClinicId = useSelector(state => state.blip.selectedClinicId); - const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); - - const primaryChips = usePrimaryChips(filters); + const primaryChips = usePrimaryChips(filters, requiredFilters); const tagChips = useTagChips(filters.patientTags); const siteChips = useSiteChips(filters.clinicSites); - const count = clinic?.fetchedPatientCount || 0; - const handleRemoveChip = chip => onRemoveFilter(chip.type, chip.value); + const count = patientCount; + return ( Date: Mon, 17 Aug 2026 15:33:30 -0700 Subject: [PATCH 03/23] WEB-4460 remove DataRecencyType from Data Recency dropdown via prop --- .../components/DataRecencyFilterDropdown.js | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/app/pages/clinicworkspace/components/DataRecencyFilterDropdown.js b/app/pages/clinicworkspace/components/DataRecencyFilterDropdown.js index a1c306ee61..36a7297aff 100644 --- a/app/pages/clinicworkspace/components/DataRecencyFilterDropdown.js +++ b/app/pages/clinicworkspace/components/DataRecencyFilterDropdown.js @@ -18,6 +18,8 @@ import { lastDataFilterOptions } from '../../../core/clinicUtils'; import useClinicMetricsPageName from '../useClinicMetricsPageName'; const DropdownContent = ({ + canSelectLastDataType, + canClearSelection, onClose, onChange, lastData, @@ -40,25 +42,29 @@ const DropdownContent = ({ return ( - - - {t('Device Type')} - - - - - { - setPending({ ...pending, lastDataType: event.target.value || null }); - }} - /> - + {canSelectLastDataType && + <> + + + {t('Device Type')} + + + + + { + setPending({ ...pending, lastDataType: event.target.value || null }); + }} + /> + + + } {t('Data Recency')} @@ -82,17 +88,20 @@ const DropdownContent = ({