Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f5e08d7
[WEB-4239] Adds CPT-99445 support to RPM report export
krystophv Dec 8, 2025
4d68d29
[WEB-4239] Code review and linting edits
krystophv Feb 5, 2026
df20c52
Merge pull request #1858 from tidepool-org/WEB-4239-rpm-update
krystophv Feb 18, 2026
ab826d0
add abstracted tags filter
henry-tp Feb 24, 2026
714ea46
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
d127721
move filter state to redux
henry-tp Feb 24, 2026
61a04f9
add filter status indicator
henry-tp Feb 24, 2026
aa08ffb
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
b894d67
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
93eb37b
fix bug with stale activeFilters
henry-tp Feb 24, 2026
fab884d
persist values to localStorage
henry-tp Feb 24, 2026
0a367a8
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
e44f04c
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
676f729
flip incorrect boolean check
henry-tp Feb 24, 2026
77485f9
un-redux the filters
henry-tp Feb 24, 2026
70fa8fe
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
7494dd8
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
937c410
fix breaking merge changes
henry-tp Feb 24, 2026
475a095
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
46f46cf
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 24, 2026
65d477b
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
5a68d81
move useClinicPatientsFilters into own file
henry-tp Feb 25, 2026
0c8ed7a
add filter reset button
henry-tp Feb 25, 2026
77dc9f2
rename ResetFilters
henry-tp Feb 25, 2026
1c55089
pull ActiveFilterCount into components directory
henry-tp Feb 25, 2026
dcac76c
Merge branch 'device-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
5ea318e
WEB-4454 initialize epic branch
henry-tp Feb 25, 2026
0f01803
Merge branch 'WEB-4454-epic' into device-dashboard
henry-tp Feb 25, 2026
d391a2c
Merge branch 'WEB-4454-dashboard' into device-dashboard-tags
henry-tp Feb 25, 2026
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
2 changes: 1 addition & 1 deletion app/components/clinic/PatientForm/SelectSites.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import partition from 'lodash/partition';
import Select, { createFilter } from 'react-select';
import { useLocation } from 'react-router-dom';
import useClinicPatientsFilters from '../../../pages/clinicworkspace/useClinicPatientsFilters';
import useClinicPatientsFilters from '../../../pages/clinicworkspace/hooks/useClinicPatientsFilters';
import { useTranslation } from 'react-i18next';
import { noop } from 'lodash';
import utils from '../../../core/utils';
Expand Down
2 changes: 1 addition & 1 deletion app/components/clinic/PatientForm/SelectTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import keyBy from 'lodash/keyBy';
import partition from 'lodash/partition';
import Select, { createFilter } from 'react-select';
import { useLocation } from 'react-router-dom';
import useClinicPatientsFilters from '../../../pages/clinicworkspace/useClinicPatientsFilters';
import useClinicPatientsFilters from '../../../pages/clinicworkspace/hooks/useClinicPatientsFilters';
import { useTranslation } from 'react-i18next';
import { noop } from 'lodash';
import utils from '../../../core/utils';
Expand Down
18 changes: 16 additions & 2 deletions app/components/clinic/RpmReportConfigForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ const log = bows('RpmReportConfigForm');

export const exportRpmReport = ({ config, results }) => {
let { startDate = '', endDate = '' } = config?.rawConfig || {};

// Check if CPT-99445 column should be included (periods starting on or after 1/1/2026)
const reportStartDate = new Date(startDate);
const cpt99445EffectiveDate = new Date('2026-01-01');
const showCpt99445 = reportStartDate >= cpt99445EffectiveDate;

// Convert dates to MM/DD/YYYY for display
startDate = startDate.replace(dateRegex, '$2/$3/$1');
endDate = endDate.replace(dateRegex, '$2/$3/$1');

Expand All @@ -38,6 +45,7 @@ export const exportRpmReport = ({ config, results }) => {
t('MRN'),
t('# Days With Qualifying Data between {{startDate}} and {{endDate}}', { startDate, endDate }),
t('Sufficient Data for {{code}}', { code: config?.code }),
...(showCpt99445 ? [t('Sufficient Data for CPT-99445')] : []),
],
];

Expand All @@ -53,13 +61,19 @@ export const exportRpmReport = ({ config, results }) => {
results.forEach(patient => {
const { fullName, birthDate, mrn, realtimeDays, hasSufficientData } = patient;

csvRows.push([
// Calculate CPT-99445 eligibility: 2-15 days of qualifying data
const hasSufficientData99445 = realtimeDays >= 2 && realtimeDays <= 15;

const patientRow = [
csvEscape(fullName),
isNull(birthDate) ? t('N/A') : csvEscape(birthDate.replace(dateRegex, '$2/$3/$1')),
csvEscape(mrn),
csvEscape(realtimeDays),
hasSufficientData ? t('TRUE') : t('FALSE'),
]);
...(showCpt99445 ? [hasSufficientData99445 ? t('TRUE') : t('FALSE')] : []),
];

csvRows.push(patientRow);
});

const csv = csvRows.map((row) => row.join(',')).join('\n');
Expand Down
2 changes: 1 addition & 1 deletion app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { scroller } from 'react-scroll';
import { Formik, Form } from 'formik';
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
import { Link as RouterLink } from 'react-router-dom';
import useClinicPatientsFilters, { defaultFilterState } from './useClinicPatientsFilters';
import useClinicPatientsFilters, { defaultFilterState } from './hooks/useClinicPatientsFilters';

import {
bindPopover,
Expand Down
38 changes: 32 additions & 6 deletions app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import React, { useEffect, useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import Table from '../../../components/elements/Table';
import { Box, Text, Flex } from 'theme-ui';
import { DIABETES_TYPES } from '../../../core/constants';

import { RTKQueryApi } from '../../../redux/api/baseApi';
import { TagList } from '../../../components/elements/Tag';

import useClinicPatientsFilters, { defaultFilterState } from '../hooks/useClinicPatientsFilters';
import ActiveFilterCount from '../components/ActiveFilterCount';
import FilterByTags from './FilterByTags';
import FilterByCategory, { CATEGORY_TAB } from './FilterByCategory';
import DashboardPagination from '../components/DashboardPagination';
import ResetFilters from '../components/ResetFilters';

const LIMIT = 12;

const deviceIssuesApi = RTKQueryApi.injectEndpoints({
endpoints: (builder) => ({
getDeviceIssuesPatients: builder.query({
query: ({ clinicId, offset, category, limit }) => {
query: ({ clinicId, offset, category, tags, limit }) => {
const formattedTags = tags.length > 0 ? tags.join(',') : undefined;

return {
url: `/clinics/${clinicId}/patients`,
params: { offset, category, limit },
params: { offset, category, tags: formattedTags, limit },
};
},
}),
Expand All @@ -34,7 +41,7 @@ const RenderTags = ({ 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(patientTag => patientTag.id === tag)); // TODO: index

return <TagList tags={tags} />;
};
Expand All @@ -59,21 +66,40 @@ const RenderPatient = ({ patient }) => {
const DeviceIssues = () => {
const { t } = useTranslation();

const [activeFilters, setActiveFilters, activeFiltersCount] = useClinicPatientsFilters();
const { patientTags } = activeFilters;

const selectedClinicId = useSelector(state => state.blip.selectedClinicId);
const [category, setCategory] = useState(CATEGORY_TAB.DEFAULT);
const [offset, setOffset] = useState(0);

const { data } = useGetDeviceIssuesPatientsQuery(
{ clinicId: selectedClinicId, offset, category, limit: LIMIT },
{ clinicId: selectedClinicId, offset, category, tags: patientTags, limit: LIMIT },
{ skip: !selectedClinicId }
);

if (!data) return null;

const tableData = data?.data || [];

const handleActiveFilterChange = (payload) => {
setActiveFilters({ ...activeFilters, ...payload });
};

return (
<>
<Flex mb={3} sx={{ gap: 2, alignItems: 'center' }}>
<ActiveFilterCount count={activeFiltersCount} />
<FilterByTags
patientTags={patientTags}
onChange={handleActiveFilterChange}
/>
<ResetFilters
hidden={activeFiltersCount <= 0}
onClick={() => handleActiveFilterChange(defaultFilterState)}
/>
</Flex>

<Flex mb={3} sx={{ justifyContent: 'center' }}>
<FilterByCategory value={category} onChange={setCategory} />
</Flex>
Expand Down
Loading