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/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/components/surveillance/complaints/complaints-wrapper.jsx b/src/app/components/surveillance/complaints/complaints-wrapper.jsx index d459413539..42ae8f485a 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/components/util/chpl-loading-spinner.jsx b/src/app/components/util/chpl-loading-spinner.jsx new file mode 100644 index 0000000000..ccb403f5f6 --- /dev/null +++ b/src/app/components/util/chpl-loading-spinner.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + Box, + CircularProgress, + makeStyles, +} from '@material-ui/core'; +import { 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 }) { + const classes = useStyles(); + const logoSize = size * 0.55; + + return ( + + + + Loading + + + ); +} + +export default ChplLoadingSpinner; + +ChplLoadingSpinner.propTypes = { + size: number, +}; 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, }; 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 c8baa49b22..1f99c9bdb3 100755 --- a/src/app/components/util/index.js +++ b/src/app/components/util/index.js @@ -10,6 +10,8 @@ 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 ChplRouteLoading from './chpl-route-loading'; import ChplSortControls from './chpl-sort-controls'; import { ChplSortableHeaders } from './sortable-headers'; import ChplSearchResultCard from './chpl-search-result-card'; @@ -28,11 +30,13 @@ export { ChplEllipsis, ChplLink, ChplLoadingCards, + ChplLoadingSpinner, ChplLoadingTable, ChplPageBody, ChplPageHeader, ChplPagination, ChplProgress, + ChplRouteLoading, ChplSearchResultCard, ChplSearchResultControls, ChplSortControls, 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.run.js b/src/app/index.run.js index 327dcb85b8..ec1042f274 100644 --- a/src/app/index.run.js +++ b/src/app/index.run.js @@ -1,6 +1,19 @@ +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) { + // 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) => { let { title } = transition.to().data; 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/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/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) => ( + + ))} + + + + + + ); } 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' && ( )} - + ); 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 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/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

-
-
-
-
- -
-
-
+ 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/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() {
-
-
+
+ ); } 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() {
- + 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 + +
+
+
+ + ); } 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

-
-
-
-
- -
-
-
+ 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', diff --git a/src/index.html b/src/index.html index 7297343857..5de2b76394 100644 --- a/src/index.html +++ b/src/index.html @@ -38,13 +38,129 @@ window.location.replace("unsupported-browser.html") } + - Skip to main content + Skip to main content + +
+
+
+ +
+
+ + +