From de7414b7740d8dcd378aaab663ef60c51d14699b Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 10:48:09 -0400 Subject: [PATCH 01/14] feat!: enhance layout with flex properties in AppWrapper and footer [#OCD-5313] --- src/app/app-wrapper.jsx | 28 +++++++++++++++++-- src/app/navigation/navigation-bottom.jsx | 1 + .../pages/search/listings/landing-page.jsx | 3 +- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/app/app-wrapper.jsx b/src/app/app-wrapper.jsx index d2600e1d7d..6b441be873 100755 --- a/src/app/app-wrapper.jsx +++ b/src/app/app-wrapper.jsx @@ -3,6 +3,7 @@ import React from 'react'; import { ThemeProvider, + makeStyles, } from '@material-ui/core'; import { bool, node } from 'prop-types'; import { CookiesProvider } from 'react-cookie'; @@ -19,7 +20,24 @@ import ChplNavigationBottom from 'navigation/navigation-bottom'; import ChplNavigationTop from 'navigation/navigation-top'; import theme from 'themes/theme'; +const useStyles = makeStyles({ + appContainer: { + display: 'flex', + flexDirection: 'column', + minHeight: '100vh', + }, + content: { + flex: '1 0 auto', + display: 'flex', + flexDirection: 'column', + width: '100%', + minWidth: 0, + }, +}); + function AppWrapper({ children, showQueryTools = DEVELOPER_MODE }) { + const classes = useStyles(); + return ( @@ -36,9 +54,13 @@ function AppWrapper({ children, showQueryTools = DEVELOPER_MODE }) { domain: '.healthit.gov', }} > - - {children} - +
+ +
+ {children} +
+ +
diff --git a/src/app/navigation/navigation-bottom.jsx b/src/app/navigation/navigation-bottom.jsx index f4030ebfac..068de6e616 100755 --- a/src/app/navigation/navigation-bottom.jsx +++ b/src/app/navigation/navigation-bottom.jsx @@ -16,6 +16,7 @@ import { palette, theme } from 'themes'; const useStyles = makeStyles({ footer: { position: 'sticky', + flexShrink: 0, width: '100%', backgroundColor: `${palette.navBackground} !important`, padding: '4px 32px', diff --git a/src/app/pages/search/listings/landing-page.jsx b/src/app/pages/search/listings/landing-page.jsx index b90429e291..3a72d0706b 100755 --- a/src/app/pages/search/listings/landing-page.jsx +++ b/src/app/pages/search/listings/landing-page.jsx @@ -167,6 +167,7 @@ const useStyles = makeStyles({ backgroundBlendMode: 'normal, soft-light', backgroundSize: '500px', minHeight: '75vh', + flexGrow: 1, }, subHeaders: { color: palette.white, @@ -210,7 +211,7 @@ function ChplLandingPage() { - + From b06a78e2a1da93f4f6bc139fe35c2af880f3bec4 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 10:53:43 -0400 Subject: [PATCH 02/14] feat: integrate ChplPageHeader and ChplPageBody components into surveillance pages for improved layout [#OCD-5313] --- .../complaints/complaints-wrapper.jsx | 14 +- .../activity-reporting/activity-reporting.jsx | 130 +++++++++--------- .../surveillance/reporting/reporting.jsx | 65 +++++---- src/app/pages/surveillance/surveillance.html | 13 +- 4 files changed, 106 insertions(+), 116 deletions(-) diff --git a/src/app/components/surveillance/complaints/complaints-wrapper.jsx b/src/app/components/surveillance/complaints/complaints-wrapper.jsx index d459413539..d14649da25 100755 --- a/src/app/components/surveillance/complaints/complaints-wrapper.jsx +++ b/src/app/components/surveillance/complaints/complaints-wrapper.jsx @@ -3,6 +3,7 @@ import { arrayOf, bool, string } from 'prop-types'; import ChplComplaints from './complaints'; +import { ChplPageBody, ChplPageHeader } from 'components/util'; import AppWrapper from 'app-wrapper'; function ChplComplaintsWrapper(props) { @@ -14,11 +15,14 @@ function ChplComplaintsWrapper(props) { return ( - + + + + ); } diff --git a/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx b/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx index 30186db41c..5fae95bf98 100755 --- a/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx +++ b/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx @@ -5,7 +5,6 @@ import { Card, CardContent, CardHeader, - Container, MenuItem, makeStyles, } from '@material-ui/core'; @@ -16,7 +15,7 @@ import * as yup from 'yup'; import { LocalDate } from '@js-joda/core'; import { usePostSurveillanceActivityReport } from 'api/surveillance'; -import { ChplTextField } from 'components/util'; +import { ChplPageBody, ChplPageHeader, ChplTextField } from 'components/util'; import { utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -25,7 +24,6 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'column', alignItems: 'stretch', - paddingTop: '16px', gap: '16px', }, formContent:{ @@ -34,9 +32,6 @@ const useStyles = makeStyles({ gap: '16px', marginBottom: '16px', }, - fixFooterSpacing: { - minHeight: 'calc(100vh - 100px)', - }, }); const validationSchema = yup.object({ @@ -123,67 +118,70 @@ function ChplSurveillanceActivityReporting() { }); return ( - -
- - - - - - { getYears().map((year) => ( - - {year} - - ))} - - + + +
+ + + + + + { getYears().map((year) => ( + + {year} + + ))} + + + All + Q1 + Q2 + Q3 + Q4 + + + - - -
- + Download Results + +
+
+
+ + ); } diff --git a/src/app/pages/surveillance/reporting/reporting.jsx b/src/app/pages/surveillance/reporting/reporting.jsx index 72acdd79b0..c9a26c3be9 100755 --- a/src/app/pages/surveillance/reporting/reporting.jsx +++ b/src/app/pages/surveillance/reporting/reporting.jsx @@ -5,7 +5,6 @@ import { Card, CardContent, Chip, - Container, Typography, makeStyles, } from '@material-ui/core'; @@ -13,6 +12,7 @@ import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; import { useFetchAcbs } from 'api/acbs'; import ChplReport from 'components/surveillance/reporting/report'; +import { ChplPageBody, ChplPageHeader } from 'components/util'; import { theme, utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -21,7 +21,6 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'column', alignItems: 'stretch', - paddingTop: '16px', gap: '16px', [theme.breakpoints.up('md')]: { display: 'grid', @@ -29,9 +28,6 @@ const useStyles = makeStyles({ alignItems: 'start', }, }, - fixFooterSpacing: { - minHeight: 'calc(100vh - 100px)', - }, menuItems: { padding: '8px', justifyContent: 'space-between', @@ -77,9 +73,11 @@ function ChplSurveillanceReporting() { }; return ( - -
1 && state === '') ? classes.container : ''}> - { acbs.length > 1 && state === '' + <> + + +
1 && state === '') ? classes.container : ''}> + { acbs.length > 1 && state === '' && ( { acbs.map((acb) => ( @@ -102,31 +100,32 @@ function ChplSurveillanceReporting() { ))} )} - { !activeAcb - && ( - - - - Quarterly and Annual Surveillance Reporting - - - To view detailed quarterly and annual reports, start by selecting an ONC-ACB from the menu on the left. - - - Once selected, the corresponding reports will appear here, giving you access to key performance data and year-end summaries. - - - - )} - { activeAcb - && ( - - )} -
- + { !activeAcb + && ( + + + + Quarterly and Annual Surveillance Reporting + + + To view detailed quarterly and annual reports, start by selecting an ONC-ACB from the menu on the left. + + + Once selected, the corresponding reports will appear here, giving you access to key performance data and year-end summaries. + + + + )} + { activeAcb + && ( + + )} +
+ + ); } diff --git a/src/app/pages/surveillance/surveillance.html b/src/app/pages/surveillance/surveillance.html index 99ed67255b..5105d51131 100644 --- a/src/app/pages/surveillance/surveillance.html +++ b/src/app/pages/surveillance/surveillance.html @@ -1,12 +1 @@ -
-
-
-

CHPL Surveillance

-
-
-
-
- -
-
-
+ From 752bc3a5735942d47f960d995e678101bd86d374 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 13:34:08 -0400 Subject: [PATCH 03/14] feat: refactor activity and questionable activity views to use ChplPageHeader and ChplPageBody components for improved layout [#OCD-5313] --- .../pages/reports/activity/activity-view.jsx | 36 +-- .../questionable-activity-view.jsx | 266 +++++++++--------- src/app/pages/reports/reports.html | 13 +- 3 files changed, 137 insertions(+), 178 deletions(-) diff --git a/src/app/pages/reports/activity/activity-view.jsx b/src/app/pages/reports/activity/activity-view.jsx index 970404d988..1f5071ddda 100755 --- a/src/app/pages/reports/activity/activity-view.jsx +++ b/src/app/pages/reports/activity/activity-view.jsx @@ -14,6 +14,8 @@ import ChplActivityDetails from './activity-details'; import { useFetchActivity } from 'api/questionable-activity'; import { ChplLink, + ChplPageBody, + ChplPageHeader, ChplPagination, ChplLoadingTable, ChplSortableHeaders } @@ -30,23 +32,9 @@ import { useAnalyticsContext } from 'shared/contexts'; import { palette, theme } from 'themes'; const useStyles = makeStyles({ - fixFooterSpacing: { - minHeight: 'calc(100vh - 188px)', - }, linkWrap: { overflowWrap: 'anywhere', }, - pageHeader: { - padding: '32px', - backgroundColor: '#ffffff', - }, - pageBody: { - display: 'grid', - gridTemplateColumns: ' 1fr', - gap: '16px', - padding: '16px 32px', - backgroundColor: '#f9f9f9', - }, pageContent: { display: 'grid', gridTemplateRows: '3fr 1fr', @@ -75,13 +63,13 @@ const useStyles = makeStyles({ tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', - margin: '0px 32px', + marginTop: '16px', width: 'auto', }, tableResultsHeaderContainer: { display: 'grid', gap: '8px', - margin: '16px 32px', + marginTop: '16px', gridTemplateColumns: '1fr', alignItems: 'center', justifyContent: 'space-between', @@ -241,17 +229,13 @@ function ChplActivityView() { const pageEnd = Math.min((pageNumber + 1) * pageSize, recordCount); return ( -
-
- Activity -
-
+ <> + + -
- -
+ { isLoading && ( @@ -321,8 +305,8 @@ function ChplActivityView() { )} )} -
-
+ + ); } diff --git a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx index 7631d1ddd0..af1e814de5 100755 --- a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx +++ b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx @@ -15,6 +15,8 @@ import { useFetchQuestionableActivity } from 'api/questionable-activity'; import ChplQuestionableActivityDetails from 'components/activity/questionable-activity-details'; import { ChplLink, + ChplPageBody, + ChplPageHeader, ChplPagination, ChplLoadingTable, ChplSortableHeaders, @@ -32,23 +34,9 @@ import { useAnalyticsContext } from 'shared/contexts'; import { theme } from 'themes'; const useStyles = makeStyles({ - fixFooterSpacing: { - minHeight: 'calc(100vh - 188px)', - }, linkWrap: { overflowWrap: 'anywhere', }, - pageHeader: { - padding: '32px', - backgroundColor: '#ffffff', - }, - pageBody: { - display: 'grid', - gridTemplateColumns: ' 1fr', - gap: '16px', - padding: '16px 32px', - backgroundColor: '#f9f9f9', - }, pageContent: { display: 'grid', gridTemplateRows: '3fr 1fr', @@ -66,13 +54,13 @@ const useStyles = makeStyles({ tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', - margin: '0px 32px', + marginTop: '16px', width: 'auto', }, tableResultsHeaderContainer: { display: 'grid', gap: '8px', - margin: '16px 32px', + marginTop: '16px', gridTemplateColumns: '1fr', alignItems: 'center', justifyContent: 'space-between', @@ -171,147 +159,145 @@ function ChplQuestionableActivityView() { const pageEnd = Math.min((pageNumber + 1) * pageSize, recordCount); return ( -
-
- Questionable Activity -
-
-
+ <> + The Questionable Activity Report offers users the ability to monitor the activities performed on specific CHPL products by ONC-Authorized Certification Bodies and assess their adherence to ONC-established rules. This feature serves as an essential transparency mechanism, allowing ONC to identify any questionable actions that may warrant further investigation. Users can filter and search this list based on their specific interests or concerns. -
-
- -
- -
- { isLoading - && ( - )} - { !isLoading - && ( - <> -
-
- Search Results: - { activities.length === 0 - && ( - - No results found - - )} + /> + + + + { isLoading + && ( + + )} + { !isLoading + && ( + <> +
+
+ Search Results: + { activities.length === 0 + && ( + + No results found + + )} + { activities.length > 0 + && ( + + {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} + + )} +
{ activities.length > 0 && ( - - {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} - + )}
{ activities.length > 0 && ( - - )} -
- { activities.length > 0 - && ( - <> - - - + +
- - { activities - .map((item) => ( - - - { item.developerId - && ( + aria-label="Questionable Activity table" + > + + + { activities + .map((item) => ( + + + { item.developerId + && ( + + )} + + {item.productName} + {item.versionName} + + { item.listingId + && ( - )} - - {item.productName} - {item.versionName} - - { item.listingId - && ( - - )} - - {item.triggerName} - { getDisplayDateFormat(item.activityDate) } - - { item.reason - && ( - - {item.reason} - - )} - { item.certificationStatusChangeReason - && ( - - {item.certificationStatusChangeReason} - - )} - - - - - - ))} - -
-
- - - )} - - )} -
+ )} + + {item.triggerName} + { getDisplayDateFormat(item.activityDate) } + + { item.reason + && ( + + {item.reason} + + )} + { item.certificationStatusChangeReason + && ( + + {item.certificationStatusChangeReason} + + )} + + + + + + ))} + + + + + + )} + + )} + + ); } diff --git a/src/app/pages/reports/reports.html b/src/app/pages/reports/reports.html index 7e17351267..5105d51131 100644 --- a/src/app/pages/reports/reports.html +++ b/src/app/pages/reports/reports.html @@ -1,12 +1 @@ -
-
-
-

CHPL Reports

-
-
-
-
- -
-
-
+ From 1265f3b4cdeea167b571c486333af7d54d84bc35 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 13:36:06 -0400 Subject: [PATCH 04/14] feat: refactor resource pages to utilize ChplPageHeader and ChplPageBody components for improved layout consistency [#OCD-5313] --- src/app/pages/resources/api/api.jsx | 292 ++++++++--------- .../pages/resources/cms-lookup/cms-lookup.jsx | 205 ++++++------ src/app/pages/resources/download/download.jsx | 308 +++++++++--------- src/app/pages/resources/overview/overview.jsx | 30 +- 4 files changed, 387 insertions(+), 448 deletions(-) diff --git a/src/app/pages/resources/api/api.jsx b/src/app/pages/resources/api/api.jsx index f3ab9b4d78..790863ca68 100755 --- a/src/app/pages/resources/api/api.jsx +++ b/src/app/pages/resources/api/api.jsx @@ -6,7 +6,6 @@ import { CardActions, CardContent, CardHeader, - Container, Divider, MenuItem, Typography, @@ -15,7 +14,12 @@ import { import GetAppIcon from '@material-ui/icons/GetApp'; import SwaggerUI from 'swagger-ui-react'; -import { ChplLink, ChplTextField } from 'components/util'; +import { + ChplLink, + ChplPageBody, + ChplPageHeader, + ChplTextField, +} from 'components/util'; import { ChplApiKeyRegistration } from 'components/api-key'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; @@ -37,8 +41,6 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'column', gap: '16px', - backgroundColor: palette.background, - padding: '32px 0', }, downloadSection: { display: 'flex', @@ -61,10 +63,6 @@ const useStyles = makeStyles({ marginTop: '.7em', }, }, - pageHeader: { - padding: '32px 0', - backgroundColor: palette.white, - }, warningBox: { padding: '16px', backgroundColor: palette.warningLight, @@ -123,161 +121,151 @@ function ChplResourcesApi() { return ( <> - -
- + + +
+ - CHPL API + Definitions & Guidelines - -
- -
- + Certified Health IT Products + + +
+ +
    +
  • + Certified Products: + {' '} + Entire collection of a set of certified products, including all data elements. The file is in a JSON format, and the definition of that structure can be found in the "Schemas" section of the "Certified Health IT Product Listing API" documentation. +
      +
    • + The Active products summary file is updated nightly. +
    • +
    • + The Inactive products summary file is updated nightly. +
    • +
    • + The 2014 Edition Products file and the 2011 Edition Products file are updated quarterly. +
    • +
    +
  • +
+
+ + + + + + To download a list of certified health IT products listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File button. + +
+ setSelectedOption(event.target.value)} + > + { downloadOptions.map((item) => ( + {item} + ))} + +
+
+
+ + + +
+
+
+ + Access API Documentation + + +
+
+ - Definitions & Guidelines + The ONC CHPL API provides programmatic access to ONC published data on Certified Health IT Products. ONC CHPL's API includes methods for retrieving a subset of our statistical data and the metadata that describes it. Users must complete the CHPL API registration. After completing the CHPL API registration, the user will be given a unique 32-character API key. This API key will also be emailed to the user. - Certified Health IT Products - - -
- -
    -
  • - Certified Products: - {' '} - Entire collection of a set of certified products, including all data elements. The file is in a JSON format, and the definition of that structure can be found in the "Schemas" section of the "Certified Health IT Product Listing API" documentation. -
      -
    • - The Active products summary file is updated nightly. -
    • -
    • - The Inactive products summary file is updated nightly. -
    • -
    • - The 2014 Edition Products file and the 2011 Edition Products file are updated quarterly. -
    • -
    -
  • -
-
- - - - - - To download a list of certified health IT products listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File button. - -
- setSelectedOption(event.target.value)} - > - { downloadOptions.map((item) => ( - {item} - ))} - -
-
-
- - - -
-
-
- Access API Documentation + This API key must be used when making a call to the CHPL API. For example, if you wanted to implement the /acbs API, you would make the following call (switching out the key in the URL for your key): + {' '} + https://chpl.healthit.gov/rest/acbs?api_key=YOUR_KEY_HERE - -
-
- - - The ONC CHPL API provides programmatic access to ONC published data on Certified Health IT Products. ONC CHPL's API includes methods for retrieving a subset of our statistical data and the metadata that describes it. Users must complete the CHPL API registration. After completing the CHPL API registration, the user will be given a unique 32-character API key. This API key will also be emailed to the user. - - - This API key must be used when making a call to the CHPL API. For example, if you wanted to implement the /acbs API, you would make the following call (switching out the key in the URL for your key): - {' '} - https://chpl.healthit.gov/rest/acbs?api_key=YOUR_KEY_HERE - -
- - A sample Java application using the CHPL API can be found at - {' '} - - -
- - Release notes for the CHPL API can be found in the - {' '} - - -
-
- - - -
-
-
- +
+ + A sample Java application using the CHPL API can be found at + {' '} + + +
+ + Release notes for the CHPL API can be found in the + {' '} + + + +
+ + +
- - +
+ +
+
+ ); } diff --git a/src/app/pages/resources/cms-lookup/cms-lookup.jsx b/src/app/pages/resources/cms-lookup/cms-lookup.jsx index 96b4635bc4..c36cb4587b 100755 --- a/src/app/pages/resources/cms-lookup/cms-lookup.jsx +++ b/src/app/pages/resources/cms-lookup/cms-lookup.jsx @@ -3,7 +3,6 @@ import { Box, Button, ButtonGroup, - Container, List, ListItem, ListItemIcon, @@ -23,12 +22,11 @@ import { ExportToCsv } from 'export-to-csv'; import { useFetchListings } from 'api/cms'; import ChplChips from 'components/cms/chips'; import ChplSearchTerm from 'components/cms/search-term'; -import { ChplLink } from 'components/util'; +import { ChplLink, ChplPageBody, ChplPageHeader } from 'components/util'; import { ChplSortableHeaders } from 'components/util/sortable-headers'; import { eventTrack } from 'services/analytics.service'; import { useLocalStorage as useStorage } from 'services/storage.service'; import { FlagContext, useAnalyticsContext } from 'shared/contexts'; -import { palette } from 'themes'; const csvOptions = { filename: 'cms-id-data', @@ -45,25 +43,17 @@ const csvOptions = { const headers = csvOptions.headers.map((h) => ({ text: h.headerName })); const useStyles = makeStyles({ - pageHeader: { - padding: '32px', - backgroundColor: palette.white, - }, pageBody: { display: 'grid', gap: '16px', - padding: '16px 32px', - backgroundColor: '#f9f9f9', }, tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', - margin: '0px 32px', width: 'auto', }, tableResultsHeaderContainer: { display: 'flex', - margin: '16px 32px', justifyContent: 'flex-end', }, errorListIcon: { @@ -71,9 +61,6 @@ const useStyles = makeStyles({ paddingRight: '16px', minWidth: 'auto', }, - fixFooterSpacing: { - minHeight: 'calc(100vh - 137px)', - }, wrap: { flexFlow: 'wrap', }, @@ -143,110 +130,108 @@ function ChplCmsLookup() { if (cmsDisabledIsOn) { return ( - -
- CMS ID Reverse Lookup -
-
+ <> + + Access to the CMS ID Creator has been paused. Please check back periodically for updates. -
-
+ + ); } return ( - -
- CMS ID Reverse Lookup -
-
- Lookup CMS EHR Certification IDs - - Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. - -
- - - { errors.length > 0 - && ( - - - { errors - .map((msg) => ( - - - - - {msg} - - ))} - - - )} - { listings.length > 0 - && ( - <> -
- - - -
- - - - - { listings - .map((item) => ( - - { item.certificationId } - { item.name } - { item.version } - { item.vendor } - - - - + <> + + +
+ Lookup CMS EHR Certification IDs + + Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. + + + + { errors.length > 0 + && ( + + + { errors + .map((msg) => ( + + + + + {msg} + ))} - -
-
- - )} -
+ + + )} + { listings.length > 0 + && ( + <> +
+ + + +
+ + + + + { listings + .map((item) => ( + + { item.certificationId } + { item.name } + { item.version } + { item.vendor } + + + + + ))} + +
+
+ + )} +
+
+ ); } diff --git a/src/app/pages/resources/download/download.jsx b/src/app/pages/resources/download/download.jsx index 47b16d14e4..aeb9ee8819 100755 --- a/src/app/pages/resources/download/download.jsx +++ b/src/app/pages/resources/download/download.jsx @@ -6,7 +6,6 @@ import { CardActions, CardContent, CardHeader, - Container, Divider, MenuItem, Typography, @@ -16,7 +15,12 @@ import CodeIcon from '@material-ui/icons/Code'; import GetAppIcon from '@material-ui/icons/GetApp'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; -import { ChplLink, ChplTextField } from 'components/util'; +import { + ChplLink, + ChplPageBody, + ChplPageHeader, + ChplTextField, +} from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; @@ -50,16 +54,6 @@ const useStyles = makeStyles({ listHeaders: { marginBottom: '8px', }, - pageHeader: { - padding: '32px 0', - backgroundColor: palette.white, - }, - pageBody: { - display: 'flex', - flexDirection: 'column', - backgroundColor: palette.background, - padding: '32px 0', - }, content: { display: 'flex', gap: '16px', @@ -143,157 +137,147 @@ function ChplResourcesDownload() { return ( <> - -
- - - Download the Latest Certified Health IT Product List - - -
-
- -
- - Definitions & Guidelines - - Certified Health IT Products - -
- - -
    + + +
    + + Definitions & Guidelines + + Certified Health IT Products + +
    + + +
      +
    • + Certified Products Summary: + {' '} + Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. +
      • - Certified Products Summary: - {' '} - Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. -
          -
        • - The Active products summary file is updated nightly. -
        • -
        • - The Inactive products summary file is updated nightly. -
        • -
        • - The 2014 Edition Summary file is updated quarterly. -
        • -
        + The Active products summary file is updated nightly.
      • - Standards Version Advancement Process (SVAP) Summary: - {' '} - Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. + The Inactive products summary file is updated nightly. +
      • +
      • + The 2014 Edition Summary file is updated quarterly.
      - - - - The JSON files have been moved to the - {' '} - - . - - - - - - - - To download a list of certified health IT products or compliance activities listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File or Definition File button as needed. -
      - setSelectedOption(event.target.value)} - > - { downloadOptions.map((item) => ( - {item} - ))} - -
      -
      -
      - - - { files[selectedOption]?.definition - && ( - - )} - -
      - - - - Compliance Activities - - - -
        +
      • - Service Base URL List Availability: + Standards Version Advancement Process (SVAP) Summary: {' '} - The Service Base URL List Report provides information on the public availability of Service Base URL Lists for certified Health IT Modules. For more details, visit + Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. +
      • +
      + + + + The JSON files have been moved to the {' '} . -
    • -
    • - Surveillance Activity: + + + + + + + + To download a list of certified health IT products or compliance activities listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File or Definition File button as needed. +
      + setSelectedOption(event.target.value)} + > + { downloadOptions.map((item) => ( + {item} + ))} + +
      +
      +
      + +
    • - { hasAnyRole(['chpl-admin', 'chpl-onc']) + + + { files[selectedOption]?.definition + && ( + + )} + + + + + + Compliance Activities + + + +
        +
      • + Service Base URL List Availability: + {' '} + The Service Base URL List Report provides information on the public availability of Service Base URL Lists for certified Health IT Modules. For more details, visit + {' '} + + . +
      • +
      • + Surveillance Activity: + {' '} + Entire collection of surveillance activity reported to the CHPL. +
      • + { hasAnyRole(['chpl-admin', 'chpl-onc']) && (
      • Surveillance (Basic): @@ -301,22 +285,20 @@ function ChplResourcesDownload() { Entire collection of surveillance activity reported to the CHPL, with only basic details about non-conformities. Includes statistics on timeframes related to discovered non-conformities.
      • )} -
      • - Surveillance Non-Conformities: - {' '} - Collection of surveillance activities that resulted in a non-conformity. This is a subset of the data available in the above "Surveillance Activity" file. -
      • -
      • - Direct Review Activity: - {' '} - Entire collection of Direct Review activity reported to the CHPL. -
      • -
      -
    -
    - +
  • + Surveillance Non-Conformities: + {' '} + Collection of surveillance activities that resulted in a non-conformity. This is a subset of the data available in the above "Surveillance Activity" file. +
  • +
  • + Direct Review Activity: + {' '} + Entire collection of Direct Review activity reported to the CHPL. +
  • +
+
- + ); } diff --git a/src/app/pages/resources/overview/overview.jsx b/src/app/pages/resources/overview/overview.jsx index b929f9fc5f..d9e7f1d66d 100755 --- a/src/app/pages/resources/overview/overview.jsx +++ b/src/app/pages/resources/overview/overview.jsx @@ -3,7 +3,6 @@ import { Box, Card, CardContent, - Container, Divider, Table, TableBody, @@ -26,6 +25,8 @@ import { useFetchAnnouncements } from 'api/announcements'; import { useFetchAtls } from 'api/atls'; import { ChplLink, + ChplPageBody, + ChplPageHeader, InternalScrollButton, } from 'components/util'; import { useAnalyticsContext } from 'shared/contexts'; @@ -54,9 +55,6 @@ const useStyles = makeStyles({ pageBody: { display: 'grid', gap: '16px', - paddingTop: '32px', - paddingBottom: '32px', - minHeight: 'calc(100vh - 100px)', gridTemplateColumns: '1fr', [theme.breakpoints.up('md')]: { gridTemplateColumns: 'auto 1fr', @@ -81,12 +79,6 @@ const useStyles = makeStyles({ gap: 0, }, }, - pageBackground: { - backgroundColor: '#f9f9f9', - }, - pageHeader: { - padding: '32px 0', - }, subheader: { fontWeight: 600, paddingTop: '8px', @@ -123,17 +115,9 @@ function ChplResourcesOverview() { return ( <> -
- - - CHPL Overview - - -
-
- + + +
{announcements.length > 0 && ( @@ -653,8 +637,8 @@ function ChplResourcesOverview() {
-
-
+
+ ); } From 536f2d898a58515af3f5d4a9c481fe7c35ddf40c Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 13:37:20 -0400 Subject: [PATCH 05/14] feat: add loading spinner and skip link for improved accessibility and user experience [#OCD-5313] --- src/app/components/util/index.js | 2 + src/app/index.run.js | 39 +++++++++++ src/index.html | 111 ++++++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/src/app/components/util/index.js b/src/app/components/util/index.js index c8baa49b22..db48cf2ca5 100755 --- a/src/app/components/util/index.js +++ b/src/app/components/util/index.js @@ -10,6 +10,7 @@ import ChplPagination from './pagination'; import ChplProgress from './progress'; import ChplLoadingTable from './loading-table'; import ChplLoadingCards from './chpl-loading-cards'; +import ChplLoadingSpinner from './chpl-loading-spinner'; import ChplSortControls from './chpl-sort-controls'; import { ChplSortableHeaders } from './sortable-headers'; import ChplSearchResultCard from './chpl-search-result-card'; @@ -28,6 +29,7 @@ export { ChplEllipsis, ChplLink, ChplLoadingCards, + ChplLoadingSpinner, ChplLoadingTable, ChplPageBody, ChplPageHeader, diff --git a/src/app/index.run.js b/src/app/index.run.js index 327dcb85b8..2f686a0059 100644 --- a/src/app/index.run.js +++ b/src/app/index.run.js @@ -1,8 +1,46 @@ (() => { /** @ngInject */ function runBlock($anchorScroll, $location, $rootScope, $state, $timeout, $transitions, $window, Title, authService) { + // Show a global loading indicator during every route transition. + // UI-Router transitions without async resolves complete synchronously, so + // onStart -> onSuccess can happen within a single frame and the overlay + // would never actually paint. We keep it visible for a minimum duration so + // the user always sees the custom loader whenever the URL changes. + const loadingElement = $window.document.getElementById('chpl-route-loading'); + const MIN_VISIBLE_MS = 500; + let shownAt = 0; + let hideTimeout = null; + + const showRouteLoading = () => { + if (!loadingElement) { return; } + if (hideTimeout) { + $timeout.cancel(hideTimeout); + hideTimeout = null; + } + shownAt = Date.now(); + loadingElement.classList.add('is-active'); + }; + + const hideRouteLoading = () => { + if (!loadingElement) { return; } + const elapsed = Date.now() - shownAt; + const remaining = Math.max(0, MIN_VISIBLE_MS - elapsed); + if (hideTimeout) { + $timeout.cancel(hideTimeout); + } + hideTimeout = $timeout(() => { + loadingElement.classList.remove('is-active'); + hideTimeout = null; + }, remaining); + }; + + $transitions.onStart({}, () => { + showRouteLoading(); + }); + // Update page title on state change $transitions.onSuccess({}, (transition) => { + hideRouteLoading(); let { title } = transition.to().data; if (title) { if (title instanceof Function) { @@ -42,6 +80,7 @@ }); $transitions.onError({}, (transition) => { + hideRouteLoading(); const error = transition.error(); if ((!error.detail?.name || error.detail.name() !== 'login') && error.message !== 'The transition was ignored') { transition.router.stateService.go('not-found', { diff --git a/src/index.html b/src/index.html index 7297343857..9f572ddb3c 100644 --- a/src/index.html +++ b/src/index.html @@ -38,13 +38,122 @@ window.location.replace("unsupported-browser.html") } + - Skip to main content + Skip to main content +
+
+
+ +
+
+
From 6eb936503c03538b132591bede379f36ee58be3c Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 20 Jul 2026 14:03:34 -0400 Subject: [PATCH 06/14] feat: add ChplLoadingSpinner component for enhanced loading experience [#OCD-5313] --- .../components/util/chpl-loading-spinner.jsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/app/components/util/chpl-loading-spinner.jsx diff --git a/src/app/components/util/chpl-loading-spinner.jsx b/src/app/components/util/chpl-loading-spinner.jsx new file mode 100644 index 0000000000..08c1008e14 --- /dev/null +++ b/src/app/components/util/chpl-loading-spinner.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { + Box, + CircularProgress, + makeStyles, +} from '@material-ui/core'; +import { bool, number } from 'prop-types'; + +import ChplLogo from '../../../assets/favicons/android-chrome-192x192.png'; + +import { palette } from 'themes'; + +const useStyles = makeStyles({ + container: { + display: 'flex', + flex: '1 0 auto', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + padding: '64px 16px', + }, + spinnerWrapper: { + position: 'relative', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + }, + logo: { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + }, +}); + +function ChplLoadingSpinner({ size = 96, fullHeight = true }) { + const classes = useStyles(); + const logoSize = size * 0.55; + + return ( + + + + Loading + + + ); +} + +export default ChplLoadingSpinner; + +ChplLoadingSpinner.propTypes = { + size: number, + fullHeight: bool, +}; From cd54873a568be0abda7a4e1771ed17f3b358f7b1 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 21 Jul 2026 11:16:23 -0400 Subject: [PATCH 07/14] refactor: Migrate API, Download, and CMS Lookup pages to shared page layout [#OCD-5313] --- src/app/index.js | 2 +- src/app/index.scss | 5 + src/app/pages/resources/api/api.jsx | 173 ++++++++-------- .../pages/resources/cms-lookup/cms-lookup.jsx | 189 ++++++++++-------- src/app/pages/resources/download/download.jsx | 186 ++++++++--------- src/app/themes/palette.js | 1 + 6 files changed, 294 insertions(+), 262 deletions(-) diff --git a/src/app/index.js b/src/app/index.js index e763223813..ccf232ced9 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -1,8 +1,8 @@ // Import base SCSS file and then all SCSS files in directories import 'angular-loading-bar/build/loading-bar.min.css'; +import 'swagger-ui-react/swagger-ui.css'; import './index.scss'; import '../assets/favicons/favicons'; -import 'swagger-ui-react/swagger-ui.css'; import angular from 'angular'; import /* angularConfirm from */ 'angular-confirm'; diff --git a/src/app/index.scss b/src/app/index.scss index 2b20f673b3..b313b4c35c 100644 --- a/src/app/index.scss +++ b/src/app/index.scss @@ -690,6 +690,11 @@ label { .swagger-ui { + .info { + margin: 0 0 24px; + border-top: none!important; + } + .put { h5, diff --git a/src/app/pages/resources/api/api.jsx b/src/app/pages/resources/api/api.jsx index 790863ca68..5edb2cc39a 100755 --- a/src/app/pages/resources/api/api.jsx +++ b/src/app/pages/resources/api/api.jsx @@ -42,16 +42,21 @@ const useStyles = makeStyles({ flexDirection: 'column', gap: '16px', }, + swaggerCardContent: { + padding: '16px', + '&:last-child': { + paddingBottom: '16px', + }, + }, downloadSection: { - display: 'flex', - flexDirection: 'column', - width: '100%', - alignItems: 'flex-start', - justifyContent: 'space-between', + display: 'grid', + gap: '16px', + gridTemplateColumns: '1fr', + alignItems: 'start', + gridColumn: '1 / -1', [theme.breakpoints.up('md')]: { - flexDirection: 'row', + gridTemplateColumns: '1fr auto', }, - gridColumn: '1 / -1', }, listHeaders: { marginBottom: '8px', @@ -135,26 +140,28 @@ function ChplResourcesApi() {
- -
    -
  • - Certified Products: - {' '} - Entire collection of a set of certified products, including all data elements. The file is in a JSON format, and the definition of that structure can be found in the "Schemas" section of the "Certified Health IT Product Listing API" documentation. -
      -
    • - The Active products summary file is updated nightly. -
    • -
    • - The Inactive products summary file is updated nightly. -
    • -
    • - The 2014 Edition Products file and the 2011 Edition Products file are updated quarterly. -
    • -
    -
  • -
-
+ + +
    +
  • + Certified Products: + {' '} + Entire collection of a set of certified products, including all data elements. The file is in a JSON format, and the definition of that structure can be found in the "Schemas" section of the "Certified Health IT Product Listing API" documentation. +
      +
    • + The Active products summary file is updated nightly. +
    • +
    • + The Inactive products summary file is updated nightly. +
    • +
    • + The 2014 Edition Products file and the 2011 Edition Products file are updated quarterly. +
    • +
    +
  • +
+
+
@@ -203,67 +210,69 @@ function ChplResourcesApi() {
- - - The ONC CHPL API provides programmatic access to ONC published data on Certified Health IT Products. ONC CHPL's API includes methods for retrieving a subset of our statistical data and the metadata that describes it. Users must complete the CHPL API registration. After completing the CHPL API registration, the user will be given a unique 32-character API key. This API key will also be emailed to the user. - - - This API key must be used when making a call to the CHPL API. For example, if you wanted to implement the /acbs API, you would make the following call (switching out the key in the URL for your key): - {' '} - https://chpl.healthit.gov/rest/acbs?api_key=YOUR_KEY_HERE - -
- - A sample Java application using the CHPL API can be found at - {' '} - - -
- - Release notes for the CHPL API can be found in the - {' '} - - -
+ + + + The ONC CHPL API provides programmatic access to ONC published data on Certified Health IT Products. ONC CHPL's API includes methods for retrieving a subset of our statistical data and the metadata that describes it. Users must complete the CHPL API registration. After completing the CHPL API registration, the user will be given a unique 32-character API key. This API key will also be emailed to the user. + + + This API key must be used when making a call to the CHPL API. For example, if you wanted to implement the /acbs API, you would make the following call (switching out the key in the URL for your key): + {' '} + https://chpl.healthit.gov/rest/acbs?api_key=YOUR_KEY_HERE + +
+ + A sample Java application using the CHPL API can be found at + {' '} + + +
+ + Release notes for the CHPL API can be found in the + {' '} + + +
+
-
- -
+ + + + +
diff --git a/src/app/pages/resources/cms-lookup/cms-lookup.jsx b/src/app/pages/resources/cms-lookup/cms-lookup.jsx index c36cb4587b..930a58f32c 100755 --- a/src/app/pages/resources/cms-lookup/cms-lookup.jsx +++ b/src/app/pages/resources/cms-lookup/cms-lookup.jsx @@ -3,6 +3,8 @@ import { Box, Button, ButtonGroup, + Card, + CardContent, List, ListItem, ListItemIcon, @@ -27,6 +29,7 @@ import { ChplSortableHeaders } from 'components/util/sortable-headers'; import { eventTrack } from 'services/analytics.service'; import { useLocalStorage as useStorage } from 'services/storage.service'; import { FlagContext, useAnalyticsContext } from 'shared/contexts'; +import { palette } from 'themes'; const csvOptions = { filename: 'cms-id-data', @@ -43,10 +46,6 @@ const csvOptions = { const headers = csvOptions.headers.map((h) => ({ text: h.headerName })); const useStyles = makeStyles({ - pageBody: { - display: 'grid', - gap: '16px', - }, tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', @@ -133,9 +132,13 @@ function ChplCmsLookup() { <> - - Access to the CMS ID Creator has been paused. Please check back periodically for updates. - + + + + Access to the CMS ID Creator has been paused. Please check back periodically for updates. + + + ); @@ -145,91 +148,101 @@ function ChplCmsLookup() { <> -
- Lookup CMS EHR Certification IDs - - Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. - - - - { errors.length > 0 - && ( - - - { errors - .map((msg) => ( - - - - - {msg} - - ))} - + + + + + Lookup CMS EHR Certification IDs + + Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. + + + + { errors.length > 0 + && ( + + + { errors + .map((msg) => ( + + + + + {msg} + + ))} + + + )} - )} + + { listings.length > 0 && ( - <> -
- - - -
- - - - - { listings - .map((item) => ( - - { item.certificationId } - { item.name } - { item.version } - { item.vendor } - - - - - ))} - -
-
- + + + +
+ + + +
+ + + + + { listings + .map((item) => ( + + { item.certificationId } + { item.name } + { item.version } + { item.vendor } + + + + + ))} + +
+
+
+
+
)} -
+
); diff --git a/src/app/pages/resources/download/download.jsx b/src/app/pages/resources/download/download.jsx index aeb9ee8819..8776ba73da 100755 --- a/src/app/pages/resources/download/download.jsx +++ b/src/app/pages/resources/download/download.jsx @@ -35,13 +35,12 @@ const useStyles = makeStyles({ }, }, downloadSection: { - display: 'flex', - flexDirection: 'column', - width: '100%', - alignItems: 'flex-start', - justifyContent: 'space-between', + display: 'grid', + gap: '16px', + gridTemplateColumns: '1fr', + alignItems: 'start', [theme.breakpoints.up('md')]: { - flexDirection: 'row', + gridTemplateColumns: '1fr auto', }, }, listSpacing: { @@ -150,50 +149,52 @@ function ChplResourcesDownload() {
- -
    -
  • - Certified Products Summary: - {' '} - Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. -
      -
    • - The Active products summary file is updated nightly. -
    • -
    • - The Inactive products summary file is updated nightly. -
    • -
    • - The 2014 Edition Summary file is updated quarterly. -
    • -
    -
  • -
  • - Standards Version Advancement Process (SVAP) Summary: - {' '} - Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. -
  • -
- - - - The JSON files have been moved to the - {' '} - - . - - -
+ + +
    +
  • + Certified Products Summary: + {' '} + Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. +
      +
    • + The Active products summary file is updated nightly. +
    • +
    • + The Inactive products summary file is updated nightly. +
    • +
    • + The 2014 Edition Summary file is updated quarterly. +
    • +
    +
  • +
  • + Standards Version Advancement Process (SVAP) Summary: + {' '} + Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. +
  • +
+ + + + The JSON files have been moved to the + {' '} + + . + + +
+
@@ -244,9 +245,8 @@ function ChplResourcesDownload() {
- + @@ -254,48 +254,52 @@ function ChplResourcesDownload() { -
    -
  • - Service Base URL List Availability: - {' '} - The Service Base URL List Report provides information on the public availability of Service Base URL Lists for certified Health IT Modules. For more details, visit - {' '} - - . -
  • -
  • - Surveillance Activity: - {' '} - Entire collection of surveillance activity reported to the CHPL. -
  • - { hasAnyRole(['chpl-admin', 'chpl-onc']) - && ( + + +
      +
    • + Service Base URL List Availability: + {' '} + The Service Base URL List Report provides information on the public availability of Service Base URL Lists for certified Health IT Modules. For more details, visit + {' '} + + . +
    • +
    • + Surveillance Activity: + {' '} + Entire collection of surveillance activity reported to the CHPL. +
    • + { hasAnyRole(['chpl-admin', 'chpl-onc']) + && ( +
    • + Surveillance (Basic): + {' '} + Entire collection of surveillance activity reported to the CHPL, with only basic details about non-conformities. Includes statistics on timeframes related to discovered non-conformities. +
    • + )}
    • - Surveillance (Basic): + Surveillance Non-Conformities: {' '} - Entire collection of surveillance activity reported to the CHPL, with only basic details about non-conformities. Includes statistics on timeframes related to discovered non-conformities. + Collection of surveillance activities that resulted in a non-conformity. This is a subset of the data available in the above "Surveillance Activity" file.
    • - )} -
    • - Surveillance Non-Conformities: - {' '} - Collection of surveillance activities that resulted in a non-conformity. This is a subset of the data available in the above "Surveillance Activity" file. -
    • -
    • - Direct Review Activity: - {' '} - Entire collection of Direct Review activity reported to the CHPL. -
    • -
    +
  • + Direct Review Activity: + {' '} + Entire collection of Direct Review activity reported to the CHPL. +
  • +
+ +
diff --git a/src/app/themes/palette.js b/src/app/themes/palette.js index 3e845e09a8..b7b4d9cbab 100755 --- a/src/app/themes/palette.js +++ b/src/app/themes/palette.js @@ -7,6 +7,7 @@ const palette = { dividerLight: '#d9e2ec', error: '#c44f65', errorDark: '#853544', + errorLight: '#fbeaed', grey: '#c6d5e5', greyBorder: '#bbb', greyLight: '#efefef', From 4c327b8ade786c5033d4d0d146a9ad6eaefdc105 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 21 Jul 2026 11:34:44 -0400 Subject: [PATCH 08/14] refactor: Re-apply resources/reports layout after staging merge Restore ChplPageHeader/ChplPageBody + Card layout migration on top of the upstream/staging merge, reconciled with the CloudDownloadOutlinedIcon standardization and HashProvider from staging. [#OCD-5313] --- src/app/app-wrapper.jsx | 27 +- .../questionable-activity-view.jsx | 268 ++++++++--------- src/app/pages/resources/api/api.jsx | 193 ++++++------ .../pages/resources/cms-lookup/cms-lookup.jsx | 230 +++++++------- src/app/pages/resources/download/download.jsx | 284 +++++++++--------- .../activity-reporting/activity-reporting.jsx | 130 ++++---- 6 files changed, 559 insertions(+), 573 deletions(-) diff --git a/src/app/app-wrapper.jsx b/src/app/app-wrapper.jsx index 041b0e9f0f..7dc07bb314 100755 --- a/src/app/app-wrapper.jsx +++ b/src/app/app-wrapper.jsx @@ -3,6 +3,7 @@ import React from 'react'; import { ThemeProvider, + makeStyles, } from '@material-ui/core'; import { bool, node } from 'prop-types'; import { CookiesProvider } from 'react-cookie'; @@ -19,7 +20,23 @@ import ChplNavigationBottom from 'navigation/navigation-bottom'; import ChplNavigationTop from 'navigation/navigation-top'; import theme from 'themes/theme'; +const useStyles = makeStyles({ + appContainer: { + display: 'flex', + flexDirection: 'column', + minHeight: '100vh', + }, + content: { + flex: '1 0 auto', + display: 'flex', + flexDirection: 'column', + width: '100%', + minWidth: 0, + }, +}); + function AppWrapper({ children, showQueryTools = DEVELOPER_MODE }) { + const classes = useStyles(); return ( @@ -37,9 +54,13 @@ function AppWrapper({ children, showQueryTools = DEVELOPER_MODE }) { domain: '.healthit.gov', }} > - - {children} - +
+ +
+ {children} +
+ +
diff --git a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx index cac626caec..9faa9b51d5 100755 --- a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx +++ b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx @@ -16,6 +16,8 @@ import { useFetchQuestionableActivity } from 'api/questionable-activity'; import ChplQuestionableActivityDetails from 'components/activity/questionable-activity-details'; import { ChplLink, + ChplPageBody, + ChplPageHeader, ChplPagination, ChplLoadingTable, ChplSortableHeaders, @@ -33,23 +35,9 @@ import { useAnalyticsContext } from 'shared/contexts'; import { theme } from 'themes'; const useStyles = makeStyles({ - fixFooterSpacing: { - minHeight: 'calc(100vh - 188px)', - }, linkWrap: { overflowWrap: 'anywhere', }, - pageHeader: { - padding: '32px', - backgroundColor: '#ffffff', - }, - pageBody: { - display: 'grid', - gridTemplateColumns: ' 1fr', - gap: '16px', - padding: '16px 32px', - backgroundColor: '#f9f9f9', - }, pageContent: { display: 'grid', gridTemplateRows: '3fr 1fr', @@ -67,13 +55,13 @@ const useStyles = makeStyles({ tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', - margin: '0px 32px', + marginTop: '16px', width: 'auto', }, tableResultsHeaderContainer: { display: 'grid', gap: '8px', - margin: '16px 32px', + marginTop: '16px', gridTemplateColumns: '1fr', alignItems: 'center', justifyContent: 'space-between', @@ -172,148 +160,146 @@ function ChplQuestionableActivityView() { const pageEnd = Math.min((pageNumber + 1) * pageSize, recordCount); return ( -
-
- Questionable Activity -
-
-
+ <> + The Questionable Activity Report offers users the ability to monitor the activities performed on specific CHPL products by ONC-Authorized Certification Bodies and assess their adherence to ONC-established rules. This feature serves as an essential transparency mechanism, allowing ONC to identify any questionable actions that may warrant further investigation. Users can filter and search this list based on their specific interests or concerns. -
-
- -
- -
- { isLoading - && ( - )} - { !isLoading - && ( - <> -
-
- Search Results: - { activities.length === 0 - && ( - - No results found - - )} + /> + + + + { isLoading + && ( + + )} + { !isLoading + && ( + <> +
+
+ Search Results: + { activities.length === 0 + && ( + + No results found + + )} + { activities.length > 0 + && ( + + {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} + + )} +
{ activities.length > 0 && ( - - {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} - + )}
{ activities.length > 0 && ( - - )} -
- { activities.length > 0 - && ( - <> - - - + +
- - { activities - .map((item) => ( - - - { item.developerId - && ( + aria-label="Questionable Activity table" + > + + + { activities + .map((item) => ( + + + { item.developerId + && ( + + )} + + {item.productName} + {item.versionName} + + { item.listingId + && ( - )} - - {item.productName} - {item.versionName} - - { item.listingId - && ( - - )} - - {item.triggerName} - { getDisplayDateFormat(item.activityDate) } - - { item.reason - && ( - - {item.reason} - - )} - { item.certificationStatusChangeReason - && ( - - {item.certificationStatusChangeReason} - - )} - - - - - - ))} - -
-
- - - )} - - )} -
+ )} + + {item.triggerName} + { getDisplayDateFormat(item.activityDate) } + + { item.reason + && ( + + {item.reason} + + )} + { item.certificationStatusChangeReason + && ( + + {item.certificationStatusChangeReason} + + )} + + + + + + ))} + + + + + + )} + + )} + + ); } diff --git a/src/app/pages/resources/api/api.jsx b/src/app/pages/resources/api/api.jsx index 56fbec76a3..e345ba7fad 100755 --- a/src/app/pages/resources/api/api.jsx +++ b/src/app/pages/resources/api/api.jsx @@ -6,7 +6,6 @@ import { CardActions, CardContent, CardHeader, - Container, Divider, MenuItem, Typography, @@ -15,7 +14,12 @@ import { import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; import SwaggerUI from 'swagger-ui-react'; -import { ChplLink, ChplTextField } from 'components/util'; +import { + ChplLink, + ChplPageBody, + ChplPageHeader, + ChplTextField, +} from 'components/util'; import { ChplApiKeyRegistration } from 'components/api-key'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; @@ -37,19 +41,22 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'column', gap: '16px', - backgroundColor: palette.background, - padding: '32px 0', + }, + swaggerCardContent: { + padding: '16px', + '&:last-child': { + paddingBottom: '16px', + }, }, downloadSection: { - display: 'flex', - flexDirection: 'column', - width: '100%', - alignItems: 'flex-start', - justifyContent: 'space-between', + display: 'grid', + gap: '16px', + gridTemplateColumns: '1fr', + alignItems: 'start', + gridColumn: '1 / -1', [theme.breakpoints.up('md')]: { - flexDirection: 'row', + gridTemplateColumns: '1fr auto', }, - gridColumn: '1 / -1', }, listHeaders: { marginBottom: '8px', @@ -61,10 +68,6 @@ const useStyles = makeStyles({ marginTop: '.7em', }, }, - pageHeader: { - padding: '32px 0', - backgroundColor: palette.white, - }, warningBox: { padding: '16px', backgroundColor: palette.warningLight, @@ -123,30 +126,22 @@ function ChplResourcesApi() { return ( <> - -
- + + +
+ - CHPL API + Definitions & Guidelines - -
- -
- - - Definitions & Guidelines - - Certified Health IT Products - - -
- + Certified Health IT Products + + +
+ +
  • Certified Products: @@ -165,55 +160,57 @@ function ChplResourcesApi() {
- - - - - - - To download a list of certified health IT products listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File button. - -
- setSelectedOption(event.target.value)} - > - { downloadOptions.map((item) => ( - {item} - ))} - -
-
-
- - - -
-
-
- - Access API Documentation - - -
-
- + + + + + + + + To download a list of certified health IT products listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File button. + +
+ setSelectedOption(event.target.value)} + > + { downloadOptions.map((item) => ( + {item} + ))} + +
+
+
+ + + +
+
+
+ + Access API Documentation + + +
+
+ + @@ -258,25 +255,25 @@ function ChplResourcesApi() { inline /> - -
- - - -
+
+
+
+ + +
-
+
+ + -
-
- - + + +
+
); } diff --git a/src/app/pages/resources/cms-lookup/cms-lookup.jsx b/src/app/pages/resources/cms-lookup/cms-lookup.jsx index 463b016edd..ea62dd17ba 100755 --- a/src/app/pages/resources/cms-lookup/cms-lookup.jsx +++ b/src/app/pages/resources/cms-lookup/cms-lookup.jsx @@ -3,7 +3,8 @@ import { Box, Button, ButtonGroup, - Container, + Card, + CardContent, List, ListItem, ListItemIcon, @@ -23,7 +24,7 @@ import { ExportToCsv } from 'export-to-csv'; import { useFetchListings } from 'api/cms'; import ChplChips from 'components/cms/chips'; import ChplSearchTerm from 'components/cms/search-term'; -import { ChplLink } from 'components/util'; +import { ChplLink, ChplPageBody, ChplPageHeader } from 'components/util'; import { ChplSortableHeaders } from 'components/util/sortable-headers'; import { eventTrack } from 'services/analytics.service'; import { useLocalStorage as useStorage } from 'services/storage.service'; @@ -45,25 +46,13 @@ const csvOptions = { const headers = csvOptions.headers.map((h) => ({ text: h.headerName })); const useStyles = makeStyles({ - pageHeader: { - padding: '32px', - backgroundColor: palette.white, - }, - pageBody: { - display: 'grid', - gap: '16px', - padding: '16px 32px', - backgroundColor: '#f9f9f9', - }, tableContainer: { overflowWrap: 'normal', border: '.5px solid #c2c6ca', - margin: '0px 32px', width: 'auto', }, tableResultsHeaderContainer: { display: 'flex', - margin: '16px 32px', justifyContent: 'flex-end', }, errorListIcon: { @@ -71,9 +60,6 @@ const useStyles = makeStyles({ paddingRight: '16px', minWidth: 'auto', }, - fixFooterSpacing: { - minHeight: 'calc(100vh - 137px)', - }, wrap: { flexFlow: 'wrap', }, @@ -143,110 +129,122 @@ function ChplCmsLookup() { if (cmsDisabledIsOn) { return ( - -
- CMS ID Reverse Lookup -
-
- - Access to the CMS ID Creator has been paused. Please check back periodically for updates. - -
-
+ <> + + + + + + Access to the CMS ID Creator has been paused. Please check back periodically for updates. + + + + + ); } return ( - -
- CMS ID Reverse Lookup -
-
- Lookup CMS EHR Certification IDs - - Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. - -
- - - { errors.length > 0 - && ( - - - { errors - .map((msg) => ( - - - - - {msg} - - ))} - - - )} - { listings.length > 0 - && ( - <> -
- - - -
- - - + + + + + + + Lookup CMS EHR Certification IDs + + Use the box below to determine which products were used to create a specific CMS EHR Certification ID. Enter a CMS EHR Certification ID to display the products which were used to create the associated CMS EHR Certification ID. Additional IDs may be added individually. + + + - - { listings - .map((item) => ( - - { item.certificationId } - { item.name } - { item.version } - { item.vendor } - - - - - ))} - -
-
- - )} -
+ { errors.length > 0 + && ( + + + { errors + .map((msg) => ( + + + + + {msg} + + ))} + + + )} + + + + { listings.length > 0 + && ( + + + +
+ + + +
+ + + + + { listings + .map((item) => ( + + { item.certificationId } + { item.name } + { item.version } + { item.vendor } + + + + + ))} + +
+
+
+
+
+ )} + + + ); } diff --git a/src/app/pages/resources/download/download.jsx b/src/app/pages/resources/download/download.jsx index afa4ac87f8..fa6381bd78 100755 --- a/src/app/pages/resources/download/download.jsx +++ b/src/app/pages/resources/download/download.jsx @@ -6,7 +6,6 @@ import { CardActions, CardContent, CardHeader, - Container, Divider, MenuItem, Typography, @@ -16,7 +15,12 @@ import CodeIcon from '@material-ui/icons/Code'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; -import { ChplLink, ChplTextField } from 'components/util'; +import { + ChplLink, + ChplPageBody, + ChplPageHeader, + ChplTextField, +} from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; @@ -31,13 +35,12 @@ const useStyles = makeStyles({ }, }, downloadSection: { - display: 'flex', - flexDirection: 'column', - width: '100%', - alignItems: 'flex-start', - justifyContent: 'space-between', + display: 'grid', + gap: '16px', + gridTemplateColumns: '1fr', + alignItems: 'start', [theme.breakpoints.up('md')]: { - flexDirection: 'row', + gridTemplateColumns: '1fr auto', }, }, listSpacing: { @@ -50,16 +53,6 @@ const useStyles = makeStyles({ listHeaders: { marginBottom: '8px', }, - pageHeader: { - padding: '32px 0', - backgroundColor: palette.white, - }, - pageBody: { - display: 'flex', - flexDirection: 'column', - backgroundColor: palette.background, - padding: '32px 0', - }, content: { display: 'flex', gap: '16px', @@ -143,132 +136,125 @@ function ChplResourcesDownload() { return ( <> - -
- - - Download the Latest Certified Health IT Product List - - -
-
- -
- - Definitions & Guidelines - - Certified Health IT Products - -
- - -
    -
  • - Certified Products Summary: - {' '} - Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. -
      -
    • - The Active products summary file is updated nightly. -
    • -
    • - The Inactive products summary file is updated nightly. -
    • -
    • - The 2014 Edition Summary file is updated quarterly. -
    • -
    -
  • -
  • - Standards Version Advancement Process (SVAP) Summary: - {' '} - Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. -
  • -
- - - - The JSON files have been moved to the - {' '} - - . - - + + +
+ + Definitions & Guidelines + + Certified Health IT Products + +
+ + + +
    +
  • + Certified Products Summary: + {' '} + Entire collection of a set of certified products, with only a subset of data elements included. Data elements included are: Certification edition, CHPL ID, ONC-ACB Certification ID, Certification Date, ONC-ACB Name, Developer Name, Product Name, Version, Practice Type (only for 2014 Edition products), Certification Status, Previous Certifying ACB, Total Number of Corrective Action Plans Over Time, Count of Currently Open Corrective Action Plans, and Certification Criteria to which that Certified Product attests. +
      +
    • + The Active products summary file is updated nightly. +
    • +
    • + The Inactive products summary file is updated nightly. +
    • +
    • + The 2014 Edition Summary file is updated quarterly. +
    • +
    +
  • +
  • + Standards Version Advancement Process (SVAP) Summary: + {' '} + Entire collection of SVAP values that have been associated with a criterion for a certified product. Multiple rows for a single product will appear in the file for any products containing multiple SVAP values and/or SVAP values for multiple criteria. Updated nightly. +
  • +
+ + + + The JSON files have been moved to the + {' '} + + . + + +
+
+ + + + + To download a list of certified health IT products or compliance activities listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File or Definition File button as needed. +
+ setSelectedOption(event.target.value)} + > + { downloadOptions.map((item) => ( + {item} + ))} + +
- - - - - To download a list of certified health IT products or compliance activities listed on the CHPL, please select from one of the categories below in the dropdown menu, and then click the Data File or Definition File button as needed. -
- setSelectedOption(event.target.value)} - > - { downloadOptions.map((item) => ( - {item} - ))} - -
-
-
- +
+ + + { files[selectedOption]?.definition + && ( - { files[selectedOption]?.definition - && ( - - )} - -
-
- - - Compliance Activities - - - + )} + + + + + + Compliance Activities + + + + +
  • Service Base URL List Availability: @@ -293,13 +279,13 @@ function ChplResourcesDownload() { Entire collection of surveillance activity reported to the CHPL.
  • { hasAnyRole(['chpl-admin', 'chpl-onc']) - && ( -
  • - Surveillance (Basic): - {' '} - Entire collection of surveillance activity reported to the CHPL, with only basic details about non-conformities. Includes statistics on timeframes related to discovered non-conformities. -
  • - )} + && ( +
  • + Surveillance (Basic): + {' '} + Entire collection of surveillance activity reported to the CHPL, with only basic details about non-conformities. Includes statistics on timeframes related to discovered non-conformities. +
  • + )}
  • Surveillance Non-Conformities: {' '} @@ -311,11 +297,11 @@ function ChplResourcesDownload() { Entire collection of Direct Review activity reported to the CHPL.
-
-
- + + +
- + ); } diff --git a/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx b/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx index f37d5bc55c..0d922d04a1 100755 --- a/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx +++ b/src/app/pages/surveillance/activity-reporting/activity-reporting.jsx @@ -5,7 +5,6 @@ import { Card, CardContent, CardHeader, - Container, MenuItem, makeStyles, } from '@material-ui/core'; @@ -16,7 +15,7 @@ import * as yup from 'yup'; import { LocalDate } from '@js-joda/core'; import { usePostSurveillanceActivityReport } from 'api/surveillance'; -import { ChplTextField } from 'components/util'; +import { ChplPageBody, ChplPageHeader, ChplTextField } from 'components/util'; import { utilStyles } from 'themes'; const useStyles = makeStyles({ @@ -25,7 +24,6 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'column', alignItems: 'stretch', - paddingTop: '16px', gap: '16px', }, formContent: { @@ -34,9 +32,6 @@ const useStyles = makeStyles({ gap: '16px', marginBottom: '16px', }, - fixFooterSpacing: { - minHeight: 'calc(100vh - 100px)', - }, }); const validationSchema = yup.object({ @@ -123,67 +118,70 @@ function ChplSurveillanceActivityReporting() { }); return ( - -
- - - - - - { getYears().map((year) => ( - - {year} - - ))} - - + + +
+ + + + + + { getYears().map((year) => ( + + {year} + + ))} + + + All + Q1 + Q2 + Q3 + Q4 + + + - - -
- + Download Results + +
+
+
+ + ); } From ee70f0b17c41e01b59ee053bed530b145293b7de Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 21 Jul 2026 13:19:09 -0400 Subject: [PATCH 09/14] refactor: Enhance ChplPageHeader component with actions and titleAdornment props [#OCD-5313] --- src/app/components/util/chpl-page-header.jsx | 61 +++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/app/components/util/chpl-page-header.jsx b/src/app/components/util/chpl-page-header.jsx index 91e4b79d22..affe01fe3a 100644 --- a/src/app/components/util/chpl-page-header.jsx +++ b/src/app/components/util/chpl-page-header.jsx @@ -13,33 +13,44 @@ import { node, string } from 'prop-types'; import { eventTrack } from 'services/analytics.service'; import { palette } from 'themes'; -function ChplPageHeader({ text, subtitle }) { +function ChplPageHeader({ + text, subtitle, actions, titleAdornment, +}) { const [expanded, setExpanded] = useState(true); return ( - - - { text } - - { subtitle + + + + { text } + + { titleAdornment } + + { (actions || subtitle) && ( - + + { actions } + { subtitle + && ( + + )} + )} { subtitle @@ -62,4 +73,12 @@ export default ChplPageHeader; ChplPageHeader.propTypes = { text: string.isRequired, subtitle: node, + actions: node, + titleAdornment: node, +}; + +ChplPageHeader.defaultProps = { + subtitle: null, + actions: null, + titleAdornment: null, }; From 73d008e5471a33c4b455a09ba6ff83d733194545 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 21 Jul 2026 14:17:56 -0400 Subject: [PATCH 10/14] refactor: Replace container and header with ChplPageHeader and ChplPageBody components in ChplComparePage [#OCD-5313] --- src/app/pages/compare/compare.jsx | 169 ++++++++++++++---------------- 1 file changed, 76 insertions(+), 93 deletions(-) diff --git a/src/app/pages/compare/compare.jsx b/src/app/pages/compare/compare.jsx index 0ffd2bd55f..c76778fdf7 100755 --- a/src/app/pages/compare/compare.jsx +++ b/src/app/pages/compare/compare.jsx @@ -3,7 +3,6 @@ import { Box, Card, CircularProgress, - Container, IconButton, Table, TableBody, @@ -11,7 +10,6 @@ import { TableContainer, TableHead, TableRow, - Typography, makeStyles, } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; @@ -23,7 +21,12 @@ import { string } from 'prop-types'; import { useFetchListing } from 'api/listing'; import ChplBrowserComparedWidget from 'components/browser/browser-compared-widget'; -import { ChplLink, ChplTooltip } from 'components/util'; +import { + ChplLink, + ChplPageBody, + ChplPageHeader, + ChplTooltip, +} from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { sortCriteria } from 'services/criteria.service'; import { sortCqms } from 'services/cqms.service'; @@ -33,15 +36,6 @@ import { palette, theme, utilStyles } from 'themes'; const useStyles = makeStyles({ ...utilStyles, - container: { - padding: '32px 0', - backgroundColor: palette.background, - minHeight: 'calc(100vh - 259px)', - }, - pageHeader: { - padding: '32px', - backgroundColor: palette.white, - }, headerRow: { boxShadow: 'rgba(149, 157, 165, 0.1) 0 16px 8px', '&.MuiTableRow-root.MuiTableRow-hover:hover': { @@ -261,88 +255,77 @@ function ChplComparePage({ ids }) { } return ( - -
- - - - Compare Products - - - { listings.length } - {' '} - Products Selected | For the best experience, we suggest comparing up to four products at a time. If you want to compare more than four products, you can still do so! Just remember to scroll horizontally on the page to access all the products you've added. While you have the flexibility to compare more items; we encourage you to focus on the most relevant products for your needs. - - - -
- - - - - - - - Data item - { listings.map((listing) => ( - - - { listing.product.name } - - dropListing(listing)} - disabled={listings.length <= 2} - > - - - - - ))} - - - - { makeRow('Developer', (listing) => listing.developer.name) } - { makeRow('Version', (listing) => listing.version.version) } - { makeRow('Certification Status', (listing) => listing.currentStatus.status.name) } - { showPracticeType ? makeRow('Practice Type', (listing) => (listing.practiceType.name ? listing.practiceType.name : 'N/A')) : null } - { makeRow('Certifying Body', (listing) => listing.certifyingBody.name) } - { makeRow('Certification Date', (listing) => getDisplayDateFormat(listing.certificationDay)) } - { makeRow('Inactive/Decertified Date', (listing) => getDisplayDateFormat(listing.decertificationDay)) } - { makeRow('CHPL Product Number', (listing) => listing.chplProductNumber) } - { makeRow('Number of Open Non-Conformities', (listing) => listing.countOpenNonconformities) } - { makeRow('Certification Criteria', (listing) => `${listing.countCerts} met`) } - { criteria.map(makeCriterionRow) } - { makeRow('Clinical Quality Measures', (listing) => `${listing.countCqms} met`) } - { cqms.map(makeCqmRow) } - { makeRow('View product details', (listing) => ( - + <> + + { listings.length } + {' '} + Products Selected | For the best experience, we suggest comparing up to four products at a time. If you want to compare more than four products, you can still do so! Just remember to scroll horizontally on the page to access all the products you've added. While you have the flexibility to compare more items; we encourage you to focus on the most relevant products for your needs. + + )} + /> + + + +
+ + + Data item + { listings.map((listing) => ( + + + { listing.product.name } + + dropListing(listing)} + disabled={listings.length <= 2} + > + + + + ))} - -
-
-
-
-
-
+ + + + { makeRow('Developer', (listing) => listing.developer.name) } + { makeRow('Version', (listing) => listing.version.version) } + { makeRow('Certification Status', (listing) => listing.currentStatus.status.name) } + { showPracticeType ? makeRow('Practice Type', (listing) => (listing.practiceType.name ? listing.practiceType.name : 'N/A')) : null } + { makeRow('Certifying Body', (listing) => listing.certifyingBody.name) } + { makeRow('Certification Date', (listing) => getDisplayDateFormat(listing.certificationDay)) } + { makeRow('Inactive/Decertified Date', (listing) => getDisplayDateFormat(listing.decertificationDay)) } + { makeRow('CHPL Product Number', (listing) => listing.chplProductNumber) } + { makeRow('Number of Open Non-Conformities', (listing) => listing.countOpenNonconformities) } + { makeRow('Certification Criteria', (listing) => `${listing.countCerts} met`) } + { criteria.map(makeCriterionRow) } + { makeRow('Clinical Quality Measures', (listing) => `${listing.countCqms} met`) } + { cqms.map(makeCqmRow) } + { makeRow('View product details', (listing) => ( + + ))} + + + + + + ); } From b38bf73ee566e7a69b87fd5bb6eb962723c55bac Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 21 Jul 2026 14:36:27 -0400 Subject: [PATCH 11/14] refactor: Replace Container and Box components with ChplPageHeader and ChplPageBody in multiple pages [#OCD-5313] --- .../listing/listing-edit-upload.jsx | 352 +++++++++--------- src/app/pages/listing/listing-edit.jsx | 101 +++-- src/app/pages/listing/listing.jsx | 196 +++++----- .../developers/developer/developer.jsx | 28 +- 4 files changed, 310 insertions(+), 367 deletions(-) diff --git a/src/app/components/listing/listing-edit-upload.jsx b/src/app/components/listing/listing-edit-upload.jsx index a1e21d83e3..8d3254731b 100755 --- a/src/app/components/listing/listing-edit-upload.jsx +++ b/src/app/components/listing/listing-edit-upload.jsx @@ -36,8 +36,6 @@ const useStyles = makeStyles({ flexDirection: 'column', gridTemplateColumns: '1fr 1fr', gap: '32px', - padding: '32px 0', - backgroundColor: palette.background, [theme.breakpoints.up('md')]: { display: 'grid', alignItems: 'start', @@ -96,10 +94,6 @@ const useStyles = makeStyles({ backgroundColor: palette.white, boxShadow: 'rgba(149, 157, 165, 0.1) 0px 4px 8px', }, - pageHeader: { - padding: '32px 0', - backgroundColor: palette.white, - }, placeholderContainer: { background: palette.white, border: `.5px solid ${palette.divider}`, @@ -210,98 +204,97 @@ function ChplListingEditUpload({ } return ( - - - - - -
-
-
- - Current Listing - -
-
- - Updated Listing - -
- - - Listing Information - - - - - - { !newListing ? ( - - - Upload a file above to display your new listing. - - ) : ( - - - - Listing Information - - - + + + +
+
+
+ + Current Listing + +
+
+ + Updated Listing + +
+ + + Listing Information + + + + + + { !newListing ? ( + + + Upload a file above to display your new listing. + + ) : ( + + + + Listing Information + + + + + + + )} + + + Certification Criteria +
+ - - - - )} - - - Certification Criteria -
- )} - label="See all Certification Criteria" - /> - ( - {listing.certificationResults.filter((cr) => cr.success).length} - {' '} - found) -
-
- - - -
-
- { newListing + ( + {listing.certificationResults.filter((cr) => cr.success).length} + {' '} + found) +
+ + + + + +
+ { newListing && ( @@ -335,39 +328,39 @@ function ChplListingEditUpload({ )} -
- - - Clinical Quality Measures -
- +
+ + + Clinical Quality Measures +
+ )} - label="See all CQMs" - /> - ( - {listing.cqmResults.filter((cqm) => cqm.success).length} - {' '} - found) -
-
- - - -
-
- { newListing + ( + {listing.cqmResults.filter((cqm) => cqm.success).length} + {' '} + found) +
+
+ + + +
+
+ { newListing && ( @@ -402,19 +395,19 @@ function ChplListingEditUpload({ )} -
- - - Safety Enhanced Design (SED) - - - - - -
- { newListing +
+ + + Safety Enhanced Design (SED) + + + + + +
+ { newListing && ( @@ -429,19 +422,19 @@ function ChplListingEditUpload({ )} -
- - - G1/G2 Measures - - - - - -
- { newListing +
+ + + G1/G2 Measures + + + + + +
+ { newListing && ( @@ -456,20 +449,20 @@ function ChplListingEditUpload({ )} -
- - - Additional Information - - - - - -
- { newListing +
+ + + Additional Information + + + + + +
+ { newListing && ( @@ -485,27 +478,27 @@ function ChplListingEditUpload({ )} -
- - - - Differences - - - { diff.length === 0 && !newListing +
+ + + + Differences + + + { diff.length === 0 && !newListing && ( Waiting for upload to show results... )} - { diff.length === 0 && newListing + { diff.length === 0 && newListing && ( No differences found )} - { diff.length > 0 + { diff.length > 0 && ( 0}> @@ -517,9 +510,8 @@ function ChplListingEditUpload({ )} - -
- + +
0} showWarningAcknowledgement={warnings.length > 0} /> - + ); } diff --git a/src/app/pages/listing/listing-edit.jsx b/src/app/pages/listing/listing-edit.jsx index 5fbfe4c897..b801127069 100755 --- a/src/app/pages/listing/listing-edit.jsx +++ b/src/app/pages/listing/listing-edit.jsx @@ -1,13 +1,11 @@ import React, { useContext, useRef, useState } from 'react'; import { - Box, Button, Card, CardContent, CardHeader, CircularProgress, ClickAwayListener, - Container, Grow, List, ListItem, @@ -24,7 +22,11 @@ import { func } from 'prop-types'; import { usePutListing } from 'api/listing'; import ChplListingEdit from 'components/listing/listing-edit'; import ChplListingEditUpload from 'components/listing/listing-edit-upload'; -import { ChplTextField } from 'components/util'; +import { + ChplPageBody, + ChplPageHeader, + ChplTextField, +} from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { AnalyticsContext, ListingContext, useAnalyticsContext } from 'shared/contexts'; import { utilStyles, palette } from 'themes'; @@ -142,12 +144,10 @@ function ChplListingEditPage({ dispatch }) { return ( - - - - - {listing.product.name} - + + )} + + { hasAnyRole(['chpl-admin']) && listing.id >= 10912 + && ( + - )} - - { hasAnyRole(['chpl-admin']) && listing.id >= 10912 - && ( - - )} - { canGetCurrentCsv() - && ( - - )} - - - - + Current CSV + + )} + + )} + /> + +
+ + +
- -
- - - -
-
- +
); } diff --git a/src/app/pages/organizations/developers/developer/developer.jsx b/src/app/pages/organizations/developers/developer/developer.jsx index 9bb1a5cf08..54a7b620da 100755 --- a/src/app/pages/organizations/developers/developer/developer.jsx +++ b/src/app/pages/organizations/developers/developer/developer.jsx @@ -1,10 +1,6 @@ import React, { useEffect, useState } from 'react'; import { - Box, CircularProgress, - Container, - Typography, - makeStyles, } from '@material-ui/core'; import { useSnackbar } from 'notistack'; import { number, oneOfType, string } from 'prop-types'; @@ -27,15 +23,8 @@ import ChplAttestationEdit from 'components/attestation/attestation-edit'; import ChplDemographicsCreate from 'components/demographics/demographics-create'; import ChplRwtResultsCreate from 'components/real-world-testing/rwt-results-create'; import ChplSbulCreate from 'components/sbul/sbul-create'; +import { ChplPageBody, ChplPageHeader } from 'components/util'; import { AnalyticsContext, DeveloperContext, useAnalyticsContext } from 'shared/contexts'; -import { utilStyles } from 'themes'; - -const useStyles = makeStyles({ - ...utilStyles, - fixFooterSpacing: { - minHeight: 'calc(100vh)', - }, -}); function ChplDeveloperPage({ id }) { const { analytics } = useAnalyticsContext(); @@ -48,7 +37,6 @@ function ChplDeveloperPage({ id }) { const [product, setProduct] = useState(undefined); const [version, setVersion] = useState(undefined); const [state, setState] = useState('view'); - const classes = useStyles(); useEffect(() => { if (isLoading || !isSuccess) { @@ -155,16 +143,8 @@ function ChplDeveloperPage({ id }) { return ( - - - - { developer.name } - - - - + + { state === 'view' && ( )} - + ); From ba229f86fd4c16cebe10c2b71962ab3056555b6a Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Thu, 23 Jul 2026 08:03:36 -0400 Subject: [PATCH 12/14] refactor: Introduce ChplRouteLoading component for route transition handling [#OCD-5313] --- .../components/util/chpl-route-loading.jsx | 86 +++++++++++++++++++ src/app/components/util/index.js | 2 + src/app/index.run.js | 50 +++-------- src/index.html | 9 +- 4 files changed, 108 insertions(+), 39 deletions(-) create mode 100644 src/app/components/util/chpl-route-loading.jsx diff --git a/src/app/components/util/chpl-route-loading.jsx b/src/app/components/util/chpl-route-loading.jsx new file mode 100644 index 0000000000..4351a4f24c --- /dev/null +++ b/src/app/components/util/chpl-route-loading.jsx @@ -0,0 +1,86 @@ +import React, { useEffect, useState } from 'react'; +import { Box, makeStyles } from '@material-ui/core'; + +import ChplLoadingSpinner from './chpl-loading-spinner'; + +import { getAngularService } from 'services/angular-react-helper'; + +const MIN_VISIBLE_MS = 500; + +const useStyles = makeStyles({ + overlay: { + position: 'fixed', + inset: 0, + zIndex: 2000, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.75)', + }, +}); + +function ChplRouteLoading() { + const classes = useStyles(); + const [active, setActive] = useState(false); + + useEffect(() => { + // Dismiss the vanilla cold-boot overlay in index.html; React now owns the + // route-transition loader. + const vanillaOverlay = document.getElementById('chpl-route-loading'); + if (vanillaOverlay) { + vanillaOverlay.classList.remove('is-active'); + } + + const $transitions = getAngularService('$transitions'); + let shownAt = 0; + let hideTimeout = null; + + // UI-Router transitions without async resolves complete synchronously, so + // onStart -> onSuccess can happen within a single frame. Keep the loader + // visible for a minimum duration so it always paints when the URL changes. + const show = () => { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + shownAt = Date.now(); + setActive(true); + }; + + const hide = () => { + const remaining = Math.max(0, MIN_VISIBLE_MS - (Date.now() - shownAt)); + if (hideTimeout) { + clearTimeout(hideTimeout); + } + hideTimeout = setTimeout(() => { + setActive(false); + hideTimeout = null; + }, remaining); + }; + + const deregisterStart = $transitions.onStart({}, () => { show(); }); + const deregisterSuccess = $transitions.onSuccess({}, () => { hide(); }); + const deregisterError = $transitions.onError({}, () => { hide(); }); + + return () => { + if (hideTimeout) { + clearTimeout(hideTimeout); + } + deregisterStart(); + deregisterSuccess(); + deregisterError(); + }; + }, []); + + if (!active) { + return null; + } + + return ( + + + + ); +} + +export default ChplRouteLoading; diff --git a/src/app/components/util/index.js b/src/app/components/util/index.js index db48cf2ca5..1f99c9bdb3 100755 --- a/src/app/components/util/index.js +++ b/src/app/components/util/index.js @@ -11,6 +11,7 @@ import ChplProgress from './progress'; import ChplLoadingTable from './loading-table'; import ChplLoadingCards from './chpl-loading-cards'; import ChplLoadingSpinner from './chpl-loading-spinner'; +import ChplRouteLoading from './chpl-route-loading'; import ChplSortControls from './chpl-sort-controls'; import { ChplSortableHeaders } from './sortable-headers'; import ChplSearchResultCard from './chpl-search-result-card'; @@ -35,6 +36,7 @@ export { ChplPageHeader, ChplPagination, ChplProgress, + ChplRouteLoading, ChplSearchResultCard, ChplSearchResultControls, ChplSortControls, diff --git a/src/app/index.run.js b/src/app/index.run.js index 2f686a0059..ec1042f274 100644 --- a/src/app/index.run.js +++ b/src/app/index.run.js @@ -1,46 +1,21 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; + +import { ChplRouteLoading } from './components/util'; + (() => { /** @ngInject */ function runBlock($anchorScroll, $location, $rootScope, $state, $timeout, $transitions, $window, Title, authService) { - // Show a global loading indicator during every route transition. - // UI-Router transitions without async resolves complete synchronously, so - // onStart -> onSuccess can happen within a single frame and the overlay - // would never actually paint. We keep it visible for a minimum duration so - // the user always sees the custom loader whenever the URL changes. - const loadingElement = $window.document.getElementById('chpl-route-loading'); - const MIN_VISIBLE_MS = 500; - let shownAt = 0; - let hideTimeout = null; - - const showRouteLoading = () => { - if (!loadingElement) { return; } - if (hideTimeout) { - $timeout.cancel(hideTimeout); - hideTimeout = null; - } - shownAt = Date.now(); - loadingElement.classList.add('is-active'); - }; - - const hideRouteLoading = () => { - if (!loadingElement) { return; } - const elapsed = Date.now() - shownAt; - const remaining = Math.max(0, MIN_VISIBLE_MS - elapsed); - if (hideTimeout) { - $timeout.cancel(hideTimeout); - } - hideTimeout = $timeout(() => { - loadingElement.classList.remove('is-active'); - hideTimeout = null; - }, remaining); - }; - - $transitions.onStart({}, () => { - showRouteLoading(); - }); + // Mount the React route-transition loader into its persistent root (outside + // ui-view, so it survives navigations). ChplRouteLoading subscribes to the + // UI-Router transition hooks itself and owns showing/hiding the loader. + const routeLoadingMount = $window.document.getElementById('chpl-route-loading-react'); + if (routeLoadingMount) { + createRoot(routeLoadingMount).render(React.createElement(ChplRouteLoading)); + } // Update page title on state change $transitions.onSuccess({}, (transition) => { - hideRouteLoading(); let { title } = transition.to().data; if (title) { if (title instanceof Function) { @@ -80,7 +55,6 @@ }); $transitions.onError({}, (transition) => { - hideRouteLoading(); const error = transition.error(); if ((!error.detail?.name || error.detail.name() !== 'login') && error.message !== 'The transition was ignored') { transition.router.stateService.go('not-found', { diff --git a/src/index.html b/src/index.html index 9f572ddb3c..5de2b76394 100644 --- a/src/index.html +++ b/src/index.html @@ -138,6 +138,11 @@ height="0" width="0" style="display:none;visibility:hidden"> Skip to main content +
@@ -146,7 +151,7 @@
+ +
From 73acd2451b767da42ab3255b68b8339833d8221b Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Thu, 23 Jul 2026 08:57:05 -0400 Subject: [PATCH 13/14] refactor: Remove unused fullHeight prop from ChplLoadingSpinner component [#OCD-5313] --- src/app/components/util/chpl-loading-spinner.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/components/util/chpl-loading-spinner.jsx b/src/app/components/util/chpl-loading-spinner.jsx index 08c1008e14..ccb403f5f6 100644 --- a/src/app/components/util/chpl-loading-spinner.jsx +++ b/src/app/components/util/chpl-loading-spinner.jsx @@ -4,7 +4,7 @@ import { CircularProgress, makeStyles, } from '@material-ui/core'; -import { bool, number } from 'prop-types'; +import { number } from 'prop-types'; import ChplLogo from '../../../assets/favicons/android-chrome-192x192.png'; @@ -33,12 +33,12 @@ const useStyles = makeStyles({ }, }); -function ChplLoadingSpinner({ size = 96, fullHeight = true }) { +function ChplLoadingSpinner({ size = 96 }) { const classes = useStyles(); const logoSize = size * 0.55; return ( - + Date: Tue, 28 Jul 2026 16:28:34 -0400 Subject: [PATCH 14/14] fix: header naming for complaints reporting [#OCD-5313] --- .../components/surveillance/complaints/complaints-wrapper.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/surveillance/complaints/complaints-wrapper.jsx b/src/app/components/surveillance/complaints/complaints-wrapper.jsx index d14649da25..42ae8f485a 100755 --- a/src/app/components/surveillance/complaints/complaints-wrapper.jsx +++ b/src/app/components/surveillance/complaints/complaints-wrapper.jsx @@ -15,7 +15,7 @@ function ChplComplaintsWrapper(props) { return ( - +