From f5e08d7cd284115eb8f4a91e9c1ac851f70c5662 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Mon, 8 Dec 2025 12:25:28 -0500 Subject: [PATCH 01/15] [WEB-4239] Adds CPT-99445 support to RPM report export Implements conditional inclusion of CPT-99445 data column for reports starting on or after 2026-01-01, with corresponding eligibility calculation logic. Updates test fixtures and adds comprehensive test coverage for new functionality and edge cases. --- app/components/clinic/RpmReportConfigForm.js | 16 +- test/fixtures/mockRpmReportPatients.json | 5 +- .../clinic/RpmReportConfigForm.test.js | 366 +++++++++++++++++- 3 files changed, 373 insertions(+), 14 deletions(-) diff --git a/app/components/clinic/RpmReportConfigForm.js b/app/components/clinic/RpmReportConfigForm.js index a07ba2c2f0..45e1721958 100644 --- a/app/components/clinic/RpmReportConfigForm.js +++ b/app/components/clinic/RpmReportConfigForm.js @@ -31,6 +31,11 @@ export const exportRpmReport = ({ config, results }) => { startDate = startDate.replace(dateRegex, '$2/$3/$1'); endDate = endDate.replace(dateRegex, '$2/$3/$1'); + // 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; + const csvRows = [ [ t('Name'), @@ -38,6 +43,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')] : []), ], ]; @@ -53,13 +59,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'); diff --git a/test/fixtures/mockRpmReportPatients.json b/test/fixtures/mockRpmReportPatients.json index fe6a3e778b..98f2f9b531 100644 --- a/test/fixtures/mockRpmReportPatients.json +++ b/test/fixtures/mockRpmReportPatients.json @@ -11,6 +11,9 @@ { "fullName": "Jonathan Seabass", "birthDate": "1993-04-12", "mrn": "994249", "realtimeDays": 16, "hasSufficientData": true }, { "fullName": "Timithon Saltflat", "birthDate": "1977-11-11", "mrn": "994423", "realtimeDays": 29, "hasSufficientData": true }, { "fullName": "Jimithon Nodata", "birthDate": null, "mrn": null, "realtimeDays": 0, "hasSufficientData": false }, - { "fullName": "Flotsam N. Jetsam", "birthDate": "2000-02-29", "mrn": "994234", "realtimeDays": 30, "hasSufficientData": true } + { "fullName": "Flotsam N. Jetsam", "birthDate": "2000-02-29", "mrn": "994234", "realtimeDays": 30, "hasSufficientData": true }, + { "fullName": "CPT99445 True", "birthDate": "1990-05-15", "mrn": "994451", "realtimeDays": 10, "hasSufficientData": false }, + { "fullName": "CPT99445 False Low", "birthDate": "1985-08-20", "mrn": "994452", "realtimeDays": 1, "hasSufficientData": false }, + { "fullName": "CPT99445 False High", "birthDate": "1992-12-01", "mrn": "994453", "realtimeDays": 16, "hasSufficientData": true } ] } diff --git a/test/unit/components/clinic/RpmReportConfigForm.test.js b/test/unit/components/clinic/RpmReportConfigForm.test.js index f2c34260f2..c8396bc10c 100644 --- a/test/unit/components/clinic/RpmReportConfigForm.test.js +++ b/test/unit/components/clinic/RpmReportConfigForm.test.js @@ -104,6 +104,25 @@ describe('RpmReportConfigForm', () => { }); describe('exportRpmReport', () => { + let createBlobSpy; + let createElementStub; + let createObjectURLStub; + + afterEach(() => { + if (createElementStub) { + createElementStub.restore(); + createElementStub = null; + } + if (createObjectURLStub) { + createObjectURLStub.restore(); + createObjectURLStub = null; + } + if (createBlobSpy) { + createBlobSpy.restore(); + createBlobSpy = null; + } + }); + it('should export an RPM csv report from the provided report data', () => { const rpmReportPatients = { ...mockRpmReportPatients, @@ -120,11 +139,11 @@ describe('RpmReportConfigForm', () => { const expectedCsvRows = [ [ - 'Name', - 'Date of Birth', - 'MRN', - '# Days With Qualifying Data between 01/01/2024 and 01/31/2024', - 'Sufficient Data for CPT-99454', + 'Name', + 'Date of Birth', + 'MRN', + '# Days With Qualifying Data between 01/01/2024 and 01/31/2024', + 'Sufficient Data for CPT-99454', ], [ '"Jill Jellyfish"', @@ -168,6 +187,27 @@ describe('RpmReportConfigForm', () => { '30', 'TRUE', ], + [ + '"CPT99445 True"', + '"05/15/1990"', + '"994451"', + '10', + 'FALSE', + ], + [ + '"CPT99445 False Low"', + '"08/20/1985"', + '"994452"', + '1', + 'FALSE', + ], + [ + '"CPT99445 False High"', + '"12/01/1992"', + '"994453"', + '16', + 'TRUE', + ], ]; const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); @@ -175,14 +215,14 @@ describe('RpmReportConfigForm', () => { const expectedUrl = 'mock-url'; const expectedDownloadFileName = 'RPM Report (01-01-2024 - 01-31-2024).csv'; - const createBlobSpy = sinon.spy(window, 'Blob'); + createBlobSpy = sinon.spy(window, 'Blob'); - const createElementStub = sinon.stub(document, 'createElement').returns({ + createElementStub = sinon.stub(document, 'createElement').returns({ href: '', download: '', click: sinon.stub(), }); - const createObjectURLStub = sinon.stub(URL, 'createObjectURL').returns(expectedUrl); + createObjectURLStub = sinon.stub(URL, 'createObjectURL').returns(expectedUrl); exportRpmReport(rpmReportPatients); expect(createBlobSpy.calledOnceWithExactly([expectedCsv], { type: 'text/csv;charset=utf-8;' })).to.be.true; @@ -191,10 +231,314 @@ describe('RpmReportConfigForm', () => { expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); expect(createElementStub.returnValues[0].download).to.equal(expectedDownloadFileName); expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; + }); + + it('should include CPT-99445 column for reports starting on or after 1/1/2026', () => { + const rpmReportPatients2026 = { + ...mockRpmReportPatients, + config: { + ...mockRpmReportPatients.config, + rawConfig: { + startDate: '2026-01-01', + endDate: '2026-01-31', + timezone: 'US/Eastern', + }, + }, + }; + + const expectedCsvRows = [ + [ + 'Name', + 'Date of Birth', + 'MRN', + '# Days With Qualifying Data between 01/01/2026 and 01/31/2026', + 'Sufficient Data for CPT-99454', + 'Sufficient Data for CPT-99445', + ], + [ + '"Jill Jellyfish"', + '"01/01/2000"', + '"123456"', + '17', + 'TRUE', + 'FALSE', // 17 days > 15, so FALSE for CPT-99445 + ], + [ + '"James Flounder"', + '"03/01/1988"', + '"423234"', + '0', + 'FALSE', + 'FALSE', // 0 days < 2, so FALSE for CPT-99445 + ], + [ + '"Jonathan Seabass"', + '"04/12/1993"', + '"994249"', + '16', + 'TRUE', + 'FALSE', // 16 days > 15, so FALSE for CPT-99445 + ], + [ + '"Timithon Saltflat"', + '"11/11/1977"', + '"994423"', + '29', + 'TRUE', + 'FALSE', // 29 days > 15, so FALSE for CPT-99445 + ], + [ + '"Jimithon Nodata"', + 'N/A', + 'N/A', + '0', + 'FALSE', + 'FALSE', // 0 days < 2, so FALSE for CPT-99445 + ], + [ + '"Flotsam N. Jetsam"', + '"02/29/2000"', + '"994234"', + '30', + 'TRUE', + 'FALSE', // 30 days > 15, so FALSE for CPT-99445 + ], + [ + '"CPT99445 True"', + '"05/15/1990"', + '"994451"', + '10', + 'FALSE', + 'TRUE', // 10 days is 2-15, so TRUE for CPT-99445 + ], + [ + '"CPT99445 False Low"', + '"08/20/1985"', + '"994452"', + '1', + 'FALSE', + 'FALSE', // 1 day < 2, so FALSE for CPT-99445 + ], + [ + '"CPT99445 False High"', + '"12/01/1992"', + '"994453"', + '16', + 'TRUE', + 'FALSE', // 16 days > 15, so FALSE for CPT-99445 + ], + ]; + + const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); + const expectedBlob = new Blob([expectedCsv], { type: 'text/csv;charset=utf-8;' }); + const expectedUrl = 'mock-url'; + const expectedDownloadFileName = 'RPM Report (01-01-2026 - 01-31-2026).csv'; + + createBlobSpy = sinon.spy(window, 'Blob'); + + createElementStub = sinon.stub(document, 'createElement').returns({ + href: '', + download: '', + click: sinon.stub(), + }); + createObjectURLStub = sinon.stub(URL, 'createObjectURL').returns(expectedUrl); + + exportRpmReport(rpmReportPatients2026); + expect(createBlobSpy.calledOnceWithExactly([expectedCsv], { type: 'text/csv;charset=utf-8;' })).to.be.true; + expect(createElementStub.calledOnceWithExactly('a')).to.be.true; + expect(createObjectURLStub.calledOnceWithExactly(expectedBlob)).to.be.true; + expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); + expect(createElementStub.returnValues[0].download).to.equal(expectedDownloadFileName); + expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; + }); + + it('should NOT include CPT-99445 column for reports before 1/1/2026', () => { + const rpmReportPatients2025 = { + ...mockRpmReportPatients, + config: { + ...mockRpmReportPatients.config, + rawConfig: { + startDate: '2025-12-01', + endDate: '2025-12-31', + timezone: 'US/Eastern', + }, + }, + }; + + const expectedCsvRows = [ + [ + 'Name', + 'Date of Birth', + 'MRN', + '# Days With Qualifying Data between 12/01/2025 and 12/31/2025', + 'Sufficient Data for CPT-99454', + ], + [ + '"Jill Jellyfish"', + '"01/01/2000"', + '"123456"', + '17', + 'TRUE', + ], + [ + '"James Flounder"', + '"03/01/1988"', + '"423234"', + '0', + 'FALSE', + ], + [ + '"Jonathan Seabass"', + '"04/12/1993"', + '"994249"', + '16', + 'TRUE', + ], + [ + '"Timithon Saltflat"', + '"11/11/1977"', + '"994423"', + '29', + 'TRUE', + ], + [ + '"Jimithon Nodata"', + 'N/A', + 'N/A', + '0', + 'FALSE', + ], + [ + '"Flotsam N. Jetsam"', + '"02/29/2000"', + '"994234"', + '30', + 'TRUE', + ], + [ + '"CPT99445 True"', + '"05/15/1990"', + '"994451"', + '10', + 'FALSE', + ], + [ + '"CPT99445 False Low"', + '"08/20/1985"', + '"994452"', + '1', + 'FALSE', + ], + [ + '"CPT99445 False High"', + '"12/01/1992"', + '"994453"', + '16', + 'TRUE', + ], + ]; + + const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); + const expectedBlob = new Blob([expectedCsv], { type: 'text/csv;charset=utf-8;' }); + const expectedUrl = 'mock-url'; + + createBlobSpy = sinon.spy(window, 'Blob'); + + createElementStub = sinon.stub(document, 'createElement').returns({ + href: '', + download: '', + click: sinon.stub(), + }); + createObjectURLStub = sinon.stub(URL, 'createObjectURL').returns(expectedUrl); + + exportRpmReport(rpmReportPatients2025); + expect(createBlobSpy.calledOnceWithExactly([expectedCsv], { type: 'text/csv;charset=utf-8;' })).to.be.true; + expect(createElementStub.calledOnceWithExactly('a')).to.be.true; + expect(createObjectURLStub.calledOnceWithExactly(expectedBlob)).to.be.true; + expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); + expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; + }); + + it('should correctly calculate CPT-99445 eligibility for edge cases', () => { + const edgeCaseReport = { + config: { + code: 'CPT-99454', + rawConfig: { + startDate: '2026-01-01', + endDate: '2026-01-31', + timezone: 'US/Eastern', + }, + }, + results: [ + { "fullName": "Exactly 2 Days", "birthDate": "2000-01-01", "mrn": "EDGE001", "realtimeDays": 2, "hasSufficientData": false }, + { "fullName": "Exactly 15 Days", "birthDate": "2000-01-01", "mrn": "EDGE002", "realtimeDays": 15, "hasSufficientData": false }, + { "fullName": "Exactly 1 Day", "birthDate": "2000-01-01", "mrn": "EDGE003", "realtimeDays": 1, "hasSufficientData": false }, + { "fullName": "Exactly 16 Days", "birthDate": "2000-01-01", "mrn": "EDGE004", "realtimeDays": 16, "hasSufficientData": true }, + ], + }; + + const expectedCsvRows = [ + [ + 'Name', + 'Date of Birth', + 'MRN', + '# Days With Qualifying Data between 01/01/2026 and 01/31/2026', + 'Sufficient Data for CPT-99454', + 'Sufficient Data for CPT-99445', + ], + [ + '"Exactly 2 Days"', + '"01/01/2000"', + '"EDGE001"', + '2', + 'FALSE', + 'TRUE', // 2 days >= 2 && <= 15, so TRUE for CPT-99445 + ], + [ + '"Exactly 15 Days"', + '"01/01/2000"', + '"EDGE002"', + '15', + 'FALSE', + 'TRUE', // 15 days >= 2 && <= 15, so TRUE for CPT-99445 + ], + [ + '"Exactly 1 Day"', + '"01/01/2000"', + '"EDGE003"', + '1', + 'FALSE', + 'FALSE', // 1 day < 2, so FALSE for CPT-99445 + ], + [ + '"Exactly 16 Days"', + '"01/01/2000"', + '"EDGE004"', + '16', + 'TRUE', + 'FALSE', // 16 days > 15, so FALSE for CPT-99445 + ], + ]; + + const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); + const expectedBlob = new Blob([expectedCsv], { type: 'text/csv;charset=utf-8;' }); + const expectedUrl = 'mock-url'; + + createBlobSpy = sinon.spy(window, 'Blob'); + + createElementStub = sinon.stub(document, 'createElement').returns({ + href: '', + download: '', + click: sinon.stub(), + }); + createObjectURLStub = sinon.stub(URL, 'createObjectURL').returns(expectedUrl); - createElementStub.restore(); - createObjectURLStub.restore(); - createBlobSpy.restore(); + exportRpmReport(edgeCaseReport); + expect(createBlobSpy.calledOnceWithExactly([expectedCsv], { type: 'text/csv;charset=utf-8;' })).to.be.true; + expect(createElementStub.calledOnceWithExactly('a')).to.be.true; + expect(createObjectURLStub.calledOnceWithExactly(expectedBlob)).to.be.true; + expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); + expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; }); }); }); From 4d68d299806aefbb0247ef0a8b391db1da3e4aa2 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Thu, 5 Feb 2026 12:36:35 -0500 Subject: [PATCH 02/15] [WEB-4239] Code review and linting edits lintfix, more robust date handling, consistency for unit test coverage --- app/components/clinic/RpmReportConfigForm.js | 6 ++++-- .../components/clinic/RpmReportConfigForm.test.js | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/components/clinic/RpmReportConfigForm.js b/app/components/clinic/RpmReportConfigForm.js index 45e1721958..76ce56755c 100644 --- a/app/components/clinic/RpmReportConfigForm.js +++ b/app/components/clinic/RpmReportConfigForm.js @@ -28,14 +28,16 @@ const log = bows('RpmReportConfigForm'); export const exportRpmReport = ({ config, results }) => { let { startDate = '', endDate = '' } = config?.rawConfig || {}; - startDate = startDate.replace(dateRegex, '$2/$3/$1'); - endDate = endDate.replace(dateRegex, '$2/$3/$1'); // 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'); + const csvRows = [ [ t('Name'), diff --git a/test/unit/components/clinic/RpmReportConfigForm.test.js b/test/unit/components/clinic/RpmReportConfigForm.test.js index c8396bc10c..29d100f37e 100644 --- a/test/unit/components/clinic/RpmReportConfigForm.test.js +++ b/test/unit/components/clinic/RpmReportConfigForm.test.js @@ -14,6 +14,7 @@ import mockRpmReportPatients from '../../../fixtures/mockRpmReportPatients.json' /* global beforeEach */ /* global before */ /* global after */ +/* global afterEach */ const expect = chai.expect; const mockStore = configureStore([thunk]); @@ -441,6 +442,7 @@ describe('RpmReportConfigForm', () => { const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); const expectedBlob = new Blob([expectedCsv], { type: 'text/csv;charset=utf-8;' }); const expectedUrl = 'mock-url'; + const expectedDownloadFileName = 'RPM Report (12-01-2025 - 12-31-2025).csv'; createBlobSpy = sinon.spy(window, 'Blob'); @@ -456,6 +458,7 @@ describe('RpmReportConfigForm', () => { expect(createElementStub.calledOnceWithExactly('a')).to.be.true; expect(createObjectURLStub.calledOnceWithExactly(expectedBlob)).to.be.true; expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); + expect(createElementStub.returnValues[0].download).to.equal(expectedDownloadFileName); expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; }); @@ -470,10 +473,10 @@ describe('RpmReportConfigForm', () => { }, }, results: [ - { "fullName": "Exactly 2 Days", "birthDate": "2000-01-01", "mrn": "EDGE001", "realtimeDays": 2, "hasSufficientData": false }, - { "fullName": "Exactly 15 Days", "birthDate": "2000-01-01", "mrn": "EDGE002", "realtimeDays": 15, "hasSufficientData": false }, - { "fullName": "Exactly 1 Day", "birthDate": "2000-01-01", "mrn": "EDGE003", "realtimeDays": 1, "hasSufficientData": false }, - { "fullName": "Exactly 16 Days", "birthDate": "2000-01-01", "mrn": "EDGE004", "realtimeDays": 16, "hasSufficientData": true }, + { 'fullName': 'Exactly 2 Days', 'birthDate': '2000-01-01', 'mrn': 'EDGE001', 'realtimeDays': 2, 'hasSufficientData': false }, + { 'fullName': 'Exactly 15 Days', 'birthDate': '2000-01-01', 'mrn': 'EDGE002', 'realtimeDays': 15, 'hasSufficientData': false }, + { 'fullName': 'Exactly 1 Day', 'birthDate': '2000-01-01', 'mrn': 'EDGE003', 'realtimeDays': 1, 'hasSufficientData': false }, + { 'fullName': 'Exactly 16 Days', 'birthDate': '2000-01-01', 'mrn': 'EDGE004', 'realtimeDays': 16, 'hasSufficientData': true }, ], }; @@ -523,6 +526,7 @@ describe('RpmReportConfigForm', () => { const expectedCsv = expectedCsvRows.map((row) => row.join(',')).join('\n'); const expectedBlob = new Blob([expectedCsv], { type: 'text/csv;charset=utf-8;' }); const expectedUrl = 'mock-url'; + const expectedDownloadFileName = 'RPM Report (01-01-2026 - 01-31-2026).csv'; createBlobSpy = sinon.spy(window, 'Blob'); @@ -538,6 +542,7 @@ describe('RpmReportConfigForm', () => { expect(createElementStub.calledOnceWithExactly('a')).to.be.true; expect(createObjectURLStub.calledOnceWithExactly(expectedBlob)).to.be.true; expect(createElementStub.returnValues[0].href).to.equal(expectedUrl); + expect(createElementStub.returnValues[0].download).to.equal(expectedDownloadFileName); expect(createElementStub.returnValues[0].click.calledOnce).to.be.true; }); }); From ab826d04280d637b6e34fbc2aae26be17a168197 Mon Sep 17 00:00:00 2001 From: henry-tp Date: Mon, 23 Feb 2026 16:49:29 -0800 Subject: [PATCH 03/15] add abstracted tags filter --- .../clinicworkspace/DeviceIssues/index.js | 24 +- .../clinicworkspace/Filters/TagsFilter.js | 220 ++++++++++++++++++ 2 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 app/pages/clinicworkspace/Filters/TagsFilter.js diff --git a/app/pages/clinicworkspace/DeviceIssues/index.js b/app/pages/clinicworkspace/DeviceIssues/index.js index 2d76ba0297..81c366a47f 100644 --- a/app/pages/clinicworkspace/DeviceIssues/index.js +++ b/app/pages/clinicworkspace/DeviceIssues/index.js @@ -7,6 +7,7 @@ import { DIABETES_TYPES } from '../../../core/constants'; import { RTKQueryApi } from '../../../redux/api/baseApi'; import { TagList } from '../../../components/elements/Tag'; +import TagsFilter from '../Filters/TagsFilter'; const LIMIT = 50; @@ -19,10 +20,14 @@ const deviceIssuesApi = RTKQueryApi.injectEndpoints({ }), }), getDeviceIssuesPatients: builder.query({ - query: ({ clinicId, offset, limit }) => ({ - url: `/clinics/${clinicId}/patients`, - params: { offset, limit: LIMIT }, - }), + query: ({ clinicId, offset, tags = [] }) => { + const formattedTags = tags.length > 0 ? tags.join(',') : undefined; + + return { + url: `/clinics/${clinicId}/patients`, + params: { offset, tags: formattedTags, limit: LIMIT }, + }; + }, }), }), }); @@ -56,12 +61,13 @@ const DeviceIssues = () => { const selectedClinicId = useSelector(state => state.blip.selectedClinicId); const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); - const [offset, setOffset] = useState(0); - const patientTags = clinic?.patientTags || []; + const [activeTags, setActiveTags] = useState([]); + const [offset, setOffset] = useState(0); + const { data } = useGetDeviceIssuesPatientsQuery( - { clinicId: selectedClinicId, offset, limit: LIMIT }, + { clinicId: selectedClinicId, offset, tags: activeTags, limit: LIMIT }, { skip: !selectedClinicId } ); @@ -71,6 +77,10 @@ const DeviceIssues = () => { return ( <> + + + + noop; +const prefixPopHealthMetric = () => noop; + +import { SPECIAL_FILTER_STATES } from '../ClinicPatients'; + +const TagsFilter = ({ + activeTags = [], + setActiveTags = noop, +}) => { + const { t } = useTranslation(); + const { showTideDashboard } = useFlags(); + + const patientTagsPopupFilterState = usePopupState({ + variant: 'popover', + popupId: 'patientTagFilters', + }); + + const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); + const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); + + const sortedTagFilterOptions = useMemo(() => { + return map(clinic?.patientTags, ({ id, name }) => ({ id, label: name })) + .toSorted((a, b) => utils.compareLabels(a.label, b.label)); + }, [clinic?.patientTags]); + + const [pendingTags, setPendingTags] = useState(activeTags); + + const isFilteringForZeroTags = isEqual(pendingTags, SPECIAL_FILTER_STATES.ZERO_TAGS); + + return ( + <> + { + if (!patientTagsPopupFilterState.isOpen) trackMetric(prefixPopHealthMetric('patient tags filter open'), { clinicId: selectedClinicId }); + }} + sx={{ flexShrink: 0 }} + > + + + + { + trackMetric(prefixPopHealthMetric('Patient tag filter close'), { clinicId: selectedClinicId }); + }} + onClose={() => { + patientTagsPopupFilterState.close(); + setPendingTags(activeTags); + }} + > + + + + + {t('Tags')} + + { sortedTagFilterOptions.length > 0 && + + {t('Only patients with ALL of the tags you select below will be shown.')} + + } + + + { // Render a list of checkboxes + sortedTagFilterOptions.map(({ id, label }) => { + const isChecked = pendingTags?.includes(id); + + return ( + + {label}} + checked={isChecked} + onChange={() => { + if (isFilteringForZeroTags) { + setPendingTags([id]); + } else if (isChecked) { + setPendingTags(pendingTags => without(pendingTags, id)); + } else { + setPendingTags(pendingTags => [...pendingTags, id]); + } + }} + /> + + ); + }) + } + + { // Display an option to filter for patients with zero tags + sortedTagFilterOptions.length > 0 && + + + {t('Patients without any tags')} + } + checked={isFilteringForZeroTags} + onChange={() => { + if (isFilteringForZeroTags) { + setPendingTags([]); + } else { + setPendingTags(SPECIAL_FILTER_STATES.ZERO_TAGS); + } + }} + /> + + } + + { // If no tags exist, display a message + sortedTagFilterOptions.length <= 0 && + + + {t('Tags help you segment your patient population based on criteria you define, such as clinician, type of diabetes, or care groups.')} + + + + } + + + + { sortedTagFilterOptions.length > 0 && + + + + + + } + + + + ); +}; + +export default TagsFilter; From d127721b4b5189070ccf206536b8dc70353efc5f Mon Sep 17 00:00:00 2001 From: henry-tp Date: Mon, 23 Feb 2026 17:20:49 -0800 Subject: [PATCH 04/15] move filter state to redux --- .../clinicworkspace/DeviceIssues/index.js | 4 +-- .../clinicworkspace/Filters/TagsFilter.js | 25 ++++++++++--------- .../clinicWorkspaceFiltersSlice.js | 16 ++++++++++++ app/redux/reducers/index.js | 2 ++ 4 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js diff --git a/app/pages/clinicworkspace/DeviceIssues/index.js b/app/pages/clinicworkspace/DeviceIssues/index.js index 4309a51355..c666553493 100644 --- a/app/pages/clinicworkspace/DeviceIssues/index.js +++ b/app/pages/clinicworkspace/DeviceIssues/index.js @@ -57,7 +57,7 @@ const DeviceIssues = () => { const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); const patientTags = clinic?.patientTags || []; - const [activeTags, setActiveTags] = useState([]); + const activeTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); const [offset, setOffset] = useState(0); const { data } = useGetDeviceIssuesPatientsQuery( @@ -72,7 +72,7 @@ const DeviceIssues = () => { return ( <> - +
noop; const prefixPopHealthMetric = () => noop; import { SPECIAL_FILTER_STATES } from '../ClinicPatients'; +import { setPatientTagsFilter } from '../clinicWorkspaceFiltersSlice'; -const TagsFilter = ({ - activeTags = [], - setActiveTags = noop, -}) => { +const TagsFilter = () => { const { t } = useTranslation(); const { showTideDashboard } = useFlags(); + const dispatch = useDispatch(); + const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); + const patientTagsPopupFilterState = usePopupState({ variant: 'popover', popupId: 'patientTagFilters', @@ -52,7 +53,7 @@ const TagsFilter = ({ .toSorted((a, b) => utils.compareLabels(a.label, b.label)); }, [clinic?.patientTags]); - const [pendingTags, setPendingTags] = useState(activeTags); + const [pendingTags, setPendingTags] = useState(patientTags); const isFilteringForZeroTags = isEqual(pendingTags, SPECIAL_FILTER_STATES.ZERO_TAGS); @@ -67,7 +68,7 @@ const TagsFilter = ({
{ + const { t } = useTranslation(); + const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); + + const activeFiltersCount = without([ + patientTags?.length, + ], null, 0, undefined).length; + + return ( + + 0 ? 'purpleMedium' : 'grays.4', + alignItems: 'center', + gap: 1, + borderLeft: ['none', null, borders.divider], + flexShrink: 0, + }} + > + {activeFiltersCount > 0 ? ( + + ) : ( + + )} + {t('Filter By')} + + + ); +}; + +export default ActiveFilterCount; From 93eb37be7a592e22931d4868bc38dcbfe97887b9 Mon Sep 17 00:00:00 2001 From: henry-tp Date: Mon, 23 Feb 2026 21:41:07 -0800 Subject: [PATCH 06/15] fix bug with stale activeFilters --- app/pages/clinicworkspace/DeviceIssues/index.js | 17 +++++++++++++++-- app/pages/clinicworkspace/Filters/TagsFilter.js | 11 +++++++++-- app/pages/clinicworkspace/clinicworkspace.js | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/pages/clinicworkspace/DeviceIssues/index.js b/app/pages/clinicworkspace/DeviceIssues/index.js index 3767f3067d..7890ca6c6f 100644 --- a/app/pages/clinicworkspace/DeviceIssues/index.js +++ b/app/pages/clinicworkspace/DeviceIssues/index.js @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import Table from '../../../components/elements/Table'; import { Box, Text, Flex } from 'theme-ui'; @@ -10,6 +10,8 @@ import { TagList } from '../../../components/elements/Tag'; import ActiveFilterCount from '../Filters/ActiveFilterCount'; import TagsFilter from '../Filters/TagsFilter'; import { CategorySelector, CategoryTab } from '../Filters/CategoryFilter'; +import useClinicPatientsFilters from '../useClinicPatientsFilters'; +import { setPatientTagsFilter } from '../clinicWorkspaceFiltersSlice'; const LIMIT = 50; @@ -58,6 +60,15 @@ const RenderPatient = ({ patient }) => { ; }; +const useInitialReduxSetup = () => { + const dispatch = useDispatch(); + + const [activeFiltersInLocalStorage] = useClinicPatientsFilters(); + useEffect(() => { + dispatch(setPatientTagsFilter(activeFiltersInLocalStorage.patientTags)); + }, []); +}; + const DeviceIssues = () => { const { t } = useTranslation(); @@ -65,6 +76,8 @@ const DeviceIssues = () => { const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); const [offset, setOffset] = useState(0); + useInitialReduxSetup(); + const { data } = useGetDeviceIssuesPatientsQuery( { clinicId: selectedClinicId, offset, tags: patientTags, limit: LIMIT }, { skip: !selectedClinicId } diff --git a/app/pages/clinicworkspace/Filters/TagsFilter.js b/app/pages/clinicworkspace/Filters/TagsFilter.js index 997b49e1b4..176ccc5ba8 100644 --- a/app/pages/clinicworkspace/Filters/TagsFilter.js +++ b/app/pages/clinicworkspace/Filters/TagsFilter.js @@ -32,10 +32,12 @@ const prefixPopHealthMetric = () => noop; import { SPECIAL_FILTER_STATES } from '../ClinicPatients'; import { setPatientTagsFilter } from '../clinicWorkspaceFiltersSlice'; +import useClinicPatientsFilters from '../useClinicPatientsFilters'; const TagsFilter = () => { const { t } = useTranslation(); const { showTideDashboard } = useFlags(); + const [activeFilters, setActiveFilters] = useClinicPatientsFilters(); const dispatch = useDispatch(); const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); @@ -57,6 +59,11 @@ const TagsFilter = () => { const isFilteringForZeroTags = isEqual(pendingTags, SPECIAL_FILTER_STATES.ZERO_TAGS); + const handleApplyTagFilter = (patientTags) => { + dispatch(setPatientTagsFilter(patientTags)); + setActiveFilters({ ...activeFilters, patientTags }); + }; + return ( <> { onClick={() => { trackMetric(prefixPopHealthMetric('Patient tag filter clear'), { clinicId: selectedClinicId }); setPendingTags([]); - dispatch(setPatientTagsFilter([])); + handleApplyTagFilter([]); patientTagsPopupFilterState.close(); }} > @@ -205,7 +212,7 @@ const TagsFilter = () => { + + + + } + + ); +}; + +const TagsFilter = () => { + const { t } = useTranslation(); + const { showTideDashboard } = useFlags(); + + const clinicWorkspaceFilters = useSelector(state => state.blip.clinicWorkspaceFilters); + const { patientTags } = clinicWorkspaceFilters; + + const patientTagsPopupFilterState = usePopupState({ + variant: 'popover', + popupId: 'patientTagFilters', + }); + + const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); + const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); return ( <> @@ -119,107 +225,11 @@ const TagsFilter = () => { }} onClose={() => { patientTagsPopupFilterState.close(); - setPendingTags(patientTags); }} > - - - - - {t('Tags')} - - { sortedTagFilterOptions.length > 0 && - - {t('Only patients with ALL of the tags you select below will be shown.')} - - } - - - { // Render a list of checkboxes - sortedTagFilterOptions.map(({ id, label }) => { - const isChecked = pendingTags?.includes(id); - - return ( - - {label}} - checked={isChecked} - onChange={() => { - if (isFilteringForZeroTags) { - setPendingTags([id]); - } else if (isChecked) { - setPendingTags(pendingTags => without(pendingTags, id)); - } else { - setPendingTags(pendingTags => [...pendingTags, id]); - } - }} - /> - - ); - }) - } - - { // Display an option to filter for patients with zero tags - sortedTagFilterOptions.length > 0 && - - - {t('Patients without any tags')} - } - checked={isFilteringForZeroTags} - onChange={() => { - if (isFilteringForZeroTags) { - setPendingTags([]); - } else { - setPendingTags(SPECIAL_FILTER_STATES.ZERO_TAGS); - } - }} - /> - - } - - { // If no tags exist, display a message - sortedTagFilterOptions.length <= 0 && - - - {t('Tags help you segment your patient population based on criteria you define, such as clinician, type of diabetes, or care groups.')} - - - - } - - - - { sortedTagFilterOptions.length > 0 && - - - - - + { patientTagsPopupFilterState.isOpen && + patientTagsPopupFilterState.close()} /> } - ); diff --git a/app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js b/app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js index 4a967da9b4..98c040d3ff 100644 --- a/app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js +++ b/app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js @@ -3,14 +3,23 @@ import { createSlice } from '@reduxjs/toolkit'; const clinicWorkspaceFiltersSlice = createSlice({ name: 'clinicWorkspaceFilters', initialState: { + timeCGMUsePercent: null, + lastData: null, + lastDataType: null, + timeInRange: [], + meetsGlycemicTargets: true, patientTags: [], + clinicSites: [], }, reducers: { + setClinicWorkspaceFilters: (state, action) => { + return { ...state, ...action.payload }; + }, setPatientTagsFilter: (state, action) => { state.patientTags = action.payload; }, }, }); -export const { setPatientTagsFilter } = clinicWorkspaceFiltersSlice.actions; +export const { setClinicWorkspaceFilters, setPatientTagsFilter } = clinicWorkspaceFiltersSlice.actions; export default clinicWorkspaceFiltersSlice.reducer; From 676f7299092bc9d9b7308fab2ae3bcb261077bec Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 24 Feb 2026 13:17:22 -0800 Subject: [PATCH 08/15] flip incorrect boolean check --- app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js index 217156ca9a..47cb590676 100644 --- a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js +++ b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js @@ -87,7 +87,7 @@ export const useRequireSummaryDashboardEntitlement = () => { const hasSummaryDashboard = clinic?.entitlements?.summaryDashboard || false; useEffect(() => { - if (isEntitlementsLoaded && hasSummaryDashboard) { + if (isEntitlementsLoaded && !hasSummaryDashboard) { history.push('/clinic-workspace/patients'); } }, [isEntitlementsLoaded, hasSummaryDashboard]); From 77485f9107b6cb38960e90c834adfab305812838 Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 24 Feb 2026 13:40:41 -0800 Subject: [PATCH 09/15] un-redux the filters --- .../DeviceIssues/DeviceIssues.js | 32 ++++++------------- .../Filters/ActiveFilterCount.js | 15 +++------ .../clinicworkspace/Filters/TagsFilter.js | 32 ++++++++++++------- .../clinicWorkspaceFiltersSlice.js | 25 --------------- .../useClinicPatientsFilters.js | 10 ++++++ app/redux/reducers/index.js | 2 -- 6 files changed, 44 insertions(+), 72 deletions(-) delete mode 100644 app/pages/clinicworkspace/clinicWorkspaceFiltersSlice.js diff --git a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js index 47cb590676..84848e3925 100644 --- a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js +++ b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js @@ -12,7 +12,6 @@ import ActiveFilterCount from '../Filters/ActiveFilterCount'; import TagsFilter from '../Filters/TagsFilter'; import { CategorySelector, CategoryTab } from '../Filters/CategoryFilter'; import useClinicPatientsFilters from '../useClinicPatientsFilters'; -import { setClinicWorkspaceFilters } from '../clinicWorkspaceFiltersSlice'; const LIMIT = 50; @@ -39,7 +38,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 ; }; @@ -61,23 +60,6 @@ const RenderPatient = ({ patient }) => { ; }; -const usePersistFiltersToLocalStorage = () => { - const dispatch = useDispatch(); - const clinicWorkspaceFilters = useSelector(state => state.blip.clinicWorkspaceFilters); - - const [activeFilters, setActiveFilters] = useClinicPatientsFilters(); - - // On load, initialize Redux state from localStorage - useEffect(() => { - dispatch(setClinicWorkspaceFilters(activeFilters)); - }, []); - - // Whenever the filters change, push to localStorage - useEffect(() => { - setActiveFilters(clinicWorkspaceFilters); - }, [clinicWorkspaceFilters]); -}; - export const useRequireSummaryDashboardEntitlement = () => { const history = useHistory(); const selectedClinicId = useSelector(state => state.blip.selectedClinicId); @@ -100,11 +82,11 @@ export const useRequireSummaryDashboardEntitlement = () => { const DeviceIssues = () => { const { t } = useTranslation(); - usePersistFiltersToLocalStorage(); const isAuthorized = useRequireSummaryDashboardEntitlement(); + const [activeFilters, setActiveFilters, activeFiltersCount] = useClinicPatientsFilters(); + const { patientTags } = activeFilters; const selectedClinicId = useSelector(state => state.blip.selectedClinicId); - const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); const [offset, setOffset] = useState(0); const { data } = useGetDeviceIssuesPatientsQuery( @@ -116,11 +98,15 @@ const DeviceIssues = () => { const tableData = data?.data || []; + const handleFilterChange = (payload) => { + setActiveFilters({ ...activeFilters, ...payload }); + }; + return ( <> - - + + diff --git a/app/pages/clinicworkspace/Filters/ActiveFilterCount.js b/app/pages/clinicworkspace/Filters/ActiveFilterCount.js index 92c8c4db81..324e298a49 100644 --- a/app/pages/clinicworkspace/Filters/ActiveFilterCount.js +++ b/app/pages/clinicworkspace/Filters/ActiveFilterCount.js @@ -1,21 +1,14 @@ import React from 'react'; import { Flex, Text } from 'theme-ui'; import Pill from '../../../components/elements/Pill'; -import without from 'lodash/without'; import { borders } from '../../../themes/baseTheme'; import Icon from '../../../components/elements/Icon'; import FilterIcon from '../../../core/icons/FilterIcon.svg'; -import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -const ActiveFilterCount = () => { +const ActiveFilterCount = ({ count }) => { const { t } = useTranslation(); - const patientTags = useSelector(state => state.blip.clinicWorkspaceFilters.patientTags); - - const activeFiltersCount = without([ - patientTags?.length, - ], null, 0, undefined).length; return ( { pl={[0, 0, 2]} py={1} sx={{ - color: activeFiltersCount > 0 ? 'purpleMedium' : 'grays.4', + color: count > 0 ? 'purpleMedium' : 'grays.4', alignItems: 'center', gap: 1, borderLeft: ['none', null, borders.divider], flexShrink: 0, }} > - {activeFiltersCount > 0 ? ( + {count > 0 ? ( ) : ( noop; const prefixPopHealthMetric = () => noop; import { SPECIAL_FILTER_STATES } from '../ClinicPatients'; -import { setPatientTagsFilter } from '../clinicWorkspaceFiltersSlice'; const TagsFilterContent = ({ + patientTags = [], onClose = noop, + onChange = noop, }) => { const { t } = useTranslation(); - const dispatch = useDispatch(); const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); - const clinicWorkspaceFilters = useSelector(state => state.blip.clinicWorkspaceFilters); - const { patientTags } = clinicWorkspaceFilters; const [pendingTags, setPendingTags] = useState(patientTags); @@ -52,6 +50,10 @@ const TagsFilterContent = ({ .toSorted((a, b) => utils.compareLabels(a.label, b.label)); }, [clinic?.patientTags]); + const handleApplyFilter = (payload) => { + onChange({ patientTags: payload }); + }; + return ( <> @@ -135,7 +137,7 @@ const TagsFilterContent = ({ onClick={() => { trackMetric(prefixPopHealthMetric('Patient tag filter clear'), { clinicId: selectedClinicId }); setPendingTags([]); - dispatch(setPatientTagsFilter([])); + handleApplyFilter([]); onClose(); }} > @@ -144,7 +146,7 @@ const TagsFilterContent = ({ - - - - } - - ); -}; - -const TagsFilter = ({ - patientTags = [], - onChange = noop, -}) => { - const { t } = useTranslation(); - const { showTideDashboard } = useFlags(); - - const patientTagsPopupFilterState = usePopupState({ - variant: 'popover', - popupId: 'patientTagFilters', - }); - - const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); - const clinic = useSelector(state => state.blip.clinics?.[selectedClinicId]); - - const handleCloseDropdown = () => { - patientTagsPopupFilterState.close(); - }; - - return ( - <> - { - if (!patientTagsPopupFilterState.isOpen) trackMetric(prefixPopHealthMetric('patient tags filter open'), { clinicId: selectedClinicId }); - }} - sx={{ flexShrink: 0 }} - > - - - - { - trackMetric(prefixPopHealthMetric('Patient tag filter close'), { clinicId: selectedClinicId }); - }} - onClose={() => { - patientTagsPopupFilterState.close(); - }} - > - { patientTagsPopupFilterState.isOpen && - - } - - - ); -}; - -export default TagsFilter; From 5a68d811e7279eac141785621ad938db00d90f4c Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 24 Feb 2026 16:07:39 -0800 Subject: [PATCH 11/15] move useClinicPatientsFilters into own file --- app/components/clinic/PatientForm/SelectSites.js | 2 +- app/components/clinic/PatientForm/SelectTags.js | 2 +- app/pages/clinicworkspace/ClinicPatients.js | 2 +- app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js | 2 +- .../clinicworkspace/{ => hooks}/useClinicPatientsFilters.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename app/pages/clinicworkspace/{ => hooks}/useClinicPatientsFilters.js (95%) diff --git a/app/components/clinic/PatientForm/SelectSites.js b/app/components/clinic/PatientForm/SelectSites.js index 0797729daa..6b71472a1d 100644 --- a/app/components/clinic/PatientForm/SelectSites.js +++ b/app/components/clinic/PatientForm/SelectSites.js @@ -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'; diff --git a/app/components/clinic/PatientForm/SelectTags.js b/app/components/clinic/PatientForm/SelectTags.js index 0544eaa9c1..1894b71471 100644 --- a/app/components/clinic/PatientForm/SelectTags.js +++ b/app/components/clinic/PatientForm/SelectTags.js @@ -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'; diff --git a/app/pages/clinicworkspace/ClinicPatients.js b/app/pages/clinicworkspace/ClinicPatients.js index 42451d6df2..cacb9b8b12 100644 --- a/app/pages/clinicworkspace/ClinicPatients.js +++ b/app/pages/clinicworkspace/ClinicPatients.js @@ -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, diff --git a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js index 0e2a7e663d..fc1566fb70 100644 --- a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js +++ b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js @@ -9,7 +9,7 @@ import { DIABETES_TYPES } from '../../../core/constants'; import { RTKQueryApi } from '../../../redux/api/baseApi'; import { TagList } from '../../../components/elements/Tag'; -import useClinicPatientsFilters from '../useClinicPatientsFilters'; +import useClinicPatientsFilters from '../hooks/useClinicPatientsFilters'; import ActiveFilterCount from '../ActiveFilterCount'; import FilterByTags from './FilterByTags'; import FilterByCategory, { CATEGORY_TAB } from './FilterByCategory'; diff --git a/app/pages/clinicworkspace/useClinicPatientsFilters.js b/app/pages/clinicworkspace/hooks/useClinicPatientsFilters.js similarity index 95% rename from app/pages/clinicworkspace/useClinicPatientsFilters.js rename to app/pages/clinicworkspace/hooks/useClinicPatientsFilters.js index 2ecc2d4578..839239d4ce 100644 --- a/app/pages/clinicworkspace/useClinicPatientsFilters.js +++ b/app/pages/clinicworkspace/hooks/useClinicPatientsFilters.js @@ -1,5 +1,5 @@ import { useSelector } from 'react-redux'; -import { useLocalStorage } from '../../core/hooks'; +import { useLocalStorage } from '../../../core/hooks'; import without from 'lodash/without'; export const defaultFilterState = { From 0c8ed7ac0f82e757c94e19292af0a9dd6f382c48 Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 24 Feb 2026 16:31:24 -0800 Subject: [PATCH 12/15] add filter reset button --- .../clinicworkspace/ActiveFilterCount.js | 1 - .../DeviceIssues/DeviceIssues.js | 16 +++++++++--- .../components/ResetFiltersButton.js | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 app/pages/clinicworkspace/components/ResetFiltersButton.js diff --git a/app/pages/clinicworkspace/ActiveFilterCount.js b/app/pages/clinicworkspace/ActiveFilterCount.js index e7724e5526..13a0ece0fe 100644 --- a/app/pages/clinicworkspace/ActiveFilterCount.js +++ b/app/pages/clinicworkspace/ActiveFilterCount.js @@ -12,7 +12,6 @@ const ActiveFilterCount = ({ count }) => { return ( diff --git a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js index fc1566fb70..79f67b4057 100644 --- a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js +++ b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js @@ -9,12 +9,13 @@ import { DIABETES_TYPES } from '../../../core/constants'; import { RTKQueryApi } from '../../../redux/api/baseApi'; import { TagList } from '../../../components/elements/Tag'; -import useClinicPatientsFilters from '../hooks/useClinicPatientsFilters'; +import useClinicPatientsFilters, { defaultFilterState } from '../hooks/useClinicPatientsFilters'; import ActiveFilterCount from '../ActiveFilterCount'; import FilterByTags from './FilterByTags'; import FilterByCategory, { CATEGORY_TAB } from './FilterByCategory'; import DashboardPagination from '../components/DashboardPagination'; import useRequireSummaryDashboardEntitlement from '../hooks/useRequireSummaryDashboardEntitlement'; +import ResetFiltersButton from '../components/ResetFiltersButton'; const LIMIT = 12; @@ -83,15 +84,22 @@ const DeviceIssues = () => { const tableData = data?.data || []; - const handleFilterChange = (payload) => { + const handleActiveFilterChange = (payload) => { setActiveFilters({ ...activeFilters, ...payload }); }; return ( <> - + - + + diff --git a/app/pages/clinicworkspace/components/ResetFiltersButton.js b/app/pages/clinicworkspace/components/ResetFiltersButton.js new file mode 100644 index 0000000000..f065ab934c --- /dev/null +++ b/app/pages/clinicworkspace/components/ResetFiltersButton.js @@ -0,0 +1,26 @@ +import React from 'react'; +import noop from 'lodash/noop'; +import { useTranslation } from 'react-i18next'; +import Button from '../../../components/elements/Button'; + +const ResetFiltersButton = ({ hidden = false, onClick = noop }) => { + const { t } = useTranslation(); + + if (hidden) return null; + + console.log('AYOOO') + + return ( + + ); +}; + +export default ResetFiltersButton; From 77dc9f27c49a8d497388e836338b4244021db09e Mon Sep 17 00:00:00 2001 From: henry-tp Date: Tue, 24 Feb 2026 16:35:51 -0800 Subject: [PATCH 13/15] rename ResetFilters --- app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js | 4 ++-- .../components/{ResetFiltersButton.js => ResetFilters.js} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename app/pages/clinicworkspace/components/{ResetFiltersButton.js => ResetFilters.js} (82%) diff --git a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js index 79f67b4057..a6b6e7a0c2 100644 --- a/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js +++ b/app/pages/clinicworkspace/DeviceIssues/DeviceIssues.js @@ -15,7 +15,7 @@ import FilterByTags from './FilterByTags'; import FilterByCategory, { CATEGORY_TAB } from './FilterByCategory'; import DashboardPagination from '../components/DashboardPagination'; import useRequireSummaryDashboardEntitlement from '../hooks/useRequireSummaryDashboardEntitlement'; -import ResetFiltersButton from '../components/ResetFiltersButton'; +import ResetFilters from '../components/ResetFilters'; const LIMIT = 12; @@ -96,7 +96,7 @@ const DeviceIssues = () => { patientTags={patientTags} onChange={handleActiveFilterChange} /> -