From 3492d25a794a4ca25441d8792cf0b5bd0281b215 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Fri, 24 Jul 2026 08:25:38 -0400 Subject: [PATCH 01/28] feat!: add filter layout component and enhance filter chips with card layout [#OCD-4930] --- src/app/components/filter/filter-chips.jsx | 222 ++++++++++-------- src/app/components/filter/filter-layout.jsx | 118 ++++++++++ .../components/filter/filter-search-bar.jsx | 12 +- src/app/components/filter/index.js | 2 + 4 files changed, 250 insertions(+), 104 deletions(-) create mode 100644 src/app/components/filter/filter-layout.jsx diff --git a/src/app/components/filter/filter-chips.jsx b/src/app/components/filter/filter-chips.jsx index e820acd2d4..ca16927955 100755 --- a/src/app/components/filter/filter-chips.jsx +++ b/src/app/components/filter/filter-chips.jsx @@ -1,6 +1,8 @@ import React, { useEffect, useState } from 'react'; import { Button, + Card, + CardContent, Chip, FormControlLabel, Switch, @@ -13,48 +15,48 @@ import { useFilterContext } from './filter-context'; import { ChplTooltip } from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getStatusIcon } from 'services/listing.service'; -import theme from 'themes/theme'; import { palette } from 'themes'; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles({ filterContainer: { display: 'flex', - padding: '16px 32px', - marginTop: '-4px', - position: 'relative', - zIndex: 1, - backgroundColor: palette.white, - borderBottom: `1px solid ${palette.greyBorder}`, - flexWrap: 'wrap', - flexFlow: 'column', + flexDirection: 'column', alignItems: 'flex-start', - [theme.breakpoints.up('sm')]: { - flexWrap: 'wrap', - }, + gap: '16px', + width: '100%', }, filterSelectedContainer: { display: 'flex', + flexDirection: 'column', gap: '4px', - alignItems: 'center', + alignItems: 'flex-start', justifyContent: 'flex-start', - flexWrap: 'wrap', + width: '100%', }, filterChipsContainer: { display: 'flex', - gap: '8px', + gap: '16px', alignContent: 'flex-start', - flexWrap: 'wrap', - flexDirection: 'row', - alignItems: 'center', + flexDirection: 'column', + alignItems: 'flex-start', + width: '100%', }, chip: { border: `1px solid ${palette.primary}`, backgroundColor: palette.white, + maxWidth: '100%', }, - chipAvatar: { - backgroundColor: 'transparent !important', + chipDeleteIcon: { + order: -2, + marginLeft: '5px', + marginRight: '-4px', }, -})); + chipLabel: { + alignItems: 'center', + display: 'inline-flex', + gap: '4px', + }, +}); const truncate = (str, n, useWordBoundary) => { if (str.length <= n) { return str; } @@ -127,91 +129,105 @@ function ChplFilterChips() { filterContext.dispatch('toggleShowAll', f); }; + if (filters.length === 0) { return null; } + + const getChipLabel = (f, labelText, iconName) => { + if (f.key !== 'certificationStatuses') { return labelText; } + return ( + + { labelText } + { getStatusIcon({ name: iconName }) } + + ); + }; + return ( - Filters Applied: -
- { filters.map((f) => ( - - - - {f.getFilterDisplay(f)} - - - { f.operatorKey - && ( - toggleOperator(f)} - /> - )} - label={f.operator === 'and' ? 'All' : 'Any'} - /> - )} - { f.developersListingsCriteriaOptionKey - && ( - toggleDevelopersListingsCriteriaOption(f)} - /> - )} - label={f.developersListingsCriteriaOption === 'active' ? 'Active Listings' : 'All Listings'} - /> - )} - {f.values - .filter((v, idx) => f.showAll || idx < DISPLAY_MAX) - .map((v) => ( - - { f.getValueDisplay(v).length > maxLengthForChip - ? ( - - removeChip(f, v)} - variant="outlined" - disabled={f.required && f.values.length === 1} - classes={{ avatar: classes.chipAvatar, root: classes.chip }} - /> - - ) : ( - removeChip(f, v)} - variant="outlined" - disabled={f.required && f.values.length === 1} - classes={{ avatar: classes.chipAvatar, root: classes.chip }} + + + Filters Applied: +
+ { filters.map((f) => ( + + + + {f.getFilterDisplay(f)} + + + { f.operatorKey + && ( + toggleOperator(f)} + /> + )} + label={f.operator === 'and' ? 'All' : 'Any'} + /> + )} + { f.developersListingsCriteriaOptionKey + && ( + toggleDevelopersListingsCriteriaOption(f)} /> )} - - ))} - { f.values.length > DISPLAY_MAX - && ( - - )} - - ))} -
+ label={f.developersListingsCriteriaOption === 'active' ? 'Active Listings' : 'All Listings'} + /> + )} + {f.values + .filter((v, idx) => f.showAll || idx < DISPLAY_MAX) + .map((v) => ( + + { f.getValueDisplay(v).length > maxLengthForChip + ? ( + + removeChip(f, v)} + variant="outlined" + disabled={f.required && f.values.length === 1} + classes={{ root: classes.chip, deleteIcon: classes.chipDeleteIcon }} + /> + + ) : ( + removeChip(f, v)} + variant="outlined" + disabled={f.required && f.values.length === 1} + classes={{ root: classes.chip, deleteIcon: classes.chipDeleteIcon }} + /> + )} + + ))} + { f.values.length > DISPLAY_MAX + && ( + + )} +
+ ))} +
+ +
); } diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx new file mode 100644 index 0000000000..f375e32195 --- /dev/null +++ b/src/app/components/filter/filter-layout.jsx @@ -0,0 +1,118 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Collapse, + makeStyles, + useMediaQuery, +} from '@material-ui/core'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; +import { node } from 'prop-types'; + +import ChplFilterChips from './filter-chips'; +import { useFilterContext } from './filter-context'; + +import { palette, theme } from 'themes'; + +const useStyles = makeStyles({ + layoutContainer: { + display: 'grid', + gridTemplateColumns: '1fr', + gap: '16px', + alignItems: 'start', + margin: '16px 0px', + [theme.breakpoints.up('md')]: { + gridTemplateColumns: '260px 1fr', + gap: '24px', + }, + }, + sidebar: { + display: 'flex', + flexDirection: 'column', + gap: '8px', + [theme.breakpoints.up('md')]: { + position: 'sticky', + top: '16px', + borderRight: `1px solid ${palette.greyBorder}`, + }, + }, + sidebarToggle: { + justifyContent: 'space-between', + color: palette.black, + [theme.breakpoints.up('md')]: { + display: 'none', + }, + }, + sidebarToggleLabel: { + display: 'flex', + alignItems: 'center', + gap: '8px', + }, + content: { + minWidth: 0, + }, +}); + +function ChplFilterLayout({ children }) { + const classes = useStyles(); + const filterContext = useFilterContext(); + const isDesktop = useMediaQuery(theme.breakpoints.up('md')); + const [expanded, setExpanded] = useState(false); + + const hasAppliedFilters = filterContext.filters + .some((filter) => filter.values?.some((v) => v.selected)); + + useEffect(() => { + if (isDesktop) { setExpanded(false); } + }, [isDesktop]); + + if (!hasAppliedFilters) { + return ( +
+ {children} +
+ ); + } + + return ( +
+ + + {isDesktop + ? + : ( + + + + )} + +
+ {children} +
+
+ ); +} + +export default ChplFilterLayout; + +ChplFilterLayout.propTypes = { + children: node, +}; + +ChplFilterLayout.defaultProps = { + children: undefined, +}; diff --git a/src/app/components/filter/filter-search-bar.jsx b/src/app/components/filter/filter-search-bar.jsx index e0dde54a00..6ee5858edb 100755 --- a/src/app/components/filter/filter-search-bar.jsx +++ b/src/app/components/filter/filter-search-bar.jsx @@ -48,19 +48,28 @@ const useStyles = makeStyles({ }, }, }, + sticky: { + position: 'sticky', + top: 0, + zIndex: 2, + }, }); function ChplFilterSearchBar({ hideAdvancedSearch = false, hideSearchTerm = false, placeholder = 'Search by Developer, Product, or CHPL ID...', + sticky = false, toggleMultipleFilters = undefined, }) { const { filters } = useFilterContext(); const classes = useStyles(); return ( -
+
{ !hideSearchTerm && ( Date: Fri, 24 Jul 2026 08:35:28 -0400 Subject: [PATCH 02/28] feat: replace ChplFilterChips with ChplFilterLayout in various views for improved layout consistency [#OCD-4930] --- .../change-request/change-requests-view.jsx | 9 +- src/app/components/products/products-view.jsx | 39 ++-- .../complaints/complaints-view.jsx | 11 +- .../certification-criteria-view.jsx | 131 +++++++------ .../functionalities-tested-view.jsx | 177 +++++++++--------- .../system-maintenance/g1g2/g1g2-view.jsx | 123 ++++++------ .../optional-standards-view.jsx | 101 +++++----- .../standard/standards-view.jsx | 173 +++++++++-------- .../system-maintenance/svap/svaps-view.jsx | 117 ++++++------ .../test-tool/test-tools-view.jsx | 107 ++++++----- 10 files changed, 490 insertions(+), 498 deletions(-) diff --git a/src/app/components/change-request/change-requests-view.jsx b/src/app/components/change-request/change-requests-view.jsx index e39d882102..c1eaa95993 100755 --- a/src/app/components/change-request/change-requests-view.jsx +++ b/src/app/components/change-request/change-requests-view.jsx @@ -28,7 +28,7 @@ import ChplChangeRequestsDownload from './change-requests-download'; import { useFetchChangeRequests } from 'api/change-requests'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -194,14 +194,14 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed placeholder="Search by Developer..." hideSearchTerm={disallowedFilters.includes('searchTerm')} /> - - { isLoading + + { isLoading && (
)} - { !isLoading + { !isLoading && ( <> { isError @@ -349,6 +349,7 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed )} )} +
); diff --git a/src/app/components/products/products-view.jsx b/src/app/components/products/products-view.jsx index ae8f39489a..0c37759d08 100755 --- a/src/app/components/products/products-view.jsx +++ b/src/app/components/products/products-view.jsx @@ -11,7 +11,7 @@ import { arrayOf, func } from 'prop-types'; import ChplProductView from './product-view'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -125,19 +125,17 @@ function ChplProductsView({ products = [], dispatch }) { -
- -
-
-
- Search Results: - { displayedProducts.length === 0 + +
+
+ Search Results: + { displayedProducts.length === 0 && ( <> No results found )} - { displayedProducts.length > 0 + { displayedProducts.length > 0 && ( { displayedProducts.length } @@ -146,18 +144,19 @@ function ChplProductsView({ products = [], dispatch }) { { displayedProducts.length === 1 ? '' : 's' } )} +
-
- { displayedProducts - .sort((a, b) => sortProducts(a, b)) - .map((product) => ( - - ))} + { displayedProducts + .sort((a, b) => sortProducts(a, b)) + .map((product) => ( + + ))} + ); diff --git a/src/app/components/surveillance/complaints/complaints-view.jsx b/src/app/components/surveillance/complaints/complaints-view.jsx index fbab7970f4..78d695b483 100755 --- a/src/app/components/surveillance/complaints/complaints-view.jsx +++ b/src/app/components/surveillance/complaints/complaints-view.jsx @@ -25,7 +25,7 @@ import ChplComplaint from './complaint'; import { useFetchComplaints, usePostReportRequest } from 'api/complaints'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -281,14 +281,12 @@ function ChplComplaintsView(props) { -
- -
- { isLoading + + { isLoading && ( )} - { !isLoading + { !isLoading && ( <>
@@ -375,6 +373,7 @@ function ChplComplaintsView(props) { )} )} + ); diff --git a/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx b/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx index d0ed75d682..c4268c8893 100755 --- a/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx +++ b/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx @@ -7,7 +7,7 @@ import { import { arrayOf } from 'prop-types'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -97,73 +97,72 @@ function ChplCertificationCriteriaView({ certificationCriteria: initialCertifica -
- -
- - - - Search Results - - - {`(${certificationCriteria.length} Result${certificationCriteria.length !== 1 ? 's' : ''})`} - + + + + + Search Results + + + {`(${certificationCriteria.length} Result${certificationCriteria.length !== 1 ? 's' : ''})`} + + + + + - - + + {certificationCriteria + .map((item) => ( + + ) : 'N/A', + }, + { + label: 'Attributes', + value: item.displayAttributes.length > 0 ? item.displayAttributes : 'N/A', + }, + ], + ]} + /> + ))} - - - {certificationCriteria - .map((item) => ( - - ) : 'N/A', - }, - { - label: 'Attributes', - value: item.displayAttributes.length > 0 ? item.displayAttributes : 'N/A', - }, - ], - ]} - /> - ))} - + ); } diff --git a/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx b/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx index 1e9e2b5d75..9f778780f7 100755 --- a/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx +++ b/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx @@ -14,7 +14,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { useFetchFunctionalitiesTestedActivity } from 'api/activity'; import ChplSystemMaintenanceActivity from 'components/activity/system-maintenance-activity'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -80,30 +80,28 @@ function ChplFunctionalitiesTestedView({ dispatch, functionalitiesTested: initia -
- -
- - - - Search Results - - - {`(${functionalitiesTested.length} Result${functionalitiesTested.length !== 1 ? 's' : ''})`} - - - - - - {hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + + + + + Search Results + + + {`(${functionalitiesTested.length} Result${functionalitiesTested.length !== 1 ? 's' : ''})`} + + + + + + {hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} + )} + - - - {functionalitiesTested - .map((item) => ( - + + {functionalitiesTested + .map((item) => ( + )} - fieldGroups={[ - [ - { - label: 'Regulatory Text Citation', - value: item.regulatoryTextCitation || 'N/A', - iconButton: ( - - - - - - ), - }, - { - label: 'Rule', - value: item.rule?.name || 'N/A', - }, - { - label: 'Practice Type', - value: item.practiceType?.name || 'N/A', - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - [ - { - label: 'Start Date', - value: getDisplayDateFormat(item.startDay) || 'N/A', - }, - { - label: 'End Date', - value: getDisplayDateFormat(item.endDay) || 'N/A', - }, - { - label: 'Required Date', - value: getDisplayDateFormat(item.requiredDay) || 'N/A', - }, - { - label: 'Extension End Date', - value: getDisplayDateFormat(item.extensionEndDay) || 'N/A', - }, - ], - ]} - actions={ + fieldGroups={[ + [ + { + label: 'Regulatory Text Citation', + value: item.regulatoryTextCitation || 'N/A', + iconButton: ( + + + + + + ), + }, + { + label: 'Rule', + value: item.rule?.name || 'N/A', + }, + { + label: 'Practice Type', + value: item.practiceType?.name || 'N/A', + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + [ + { + label: 'Start Date', + value: getDisplayDateFormat(item.startDay) || 'N/A', + }, + { + label: 'End Date', + value: getDisplayDateFormat(item.endDay) || 'N/A', + }, + { + label: 'Required Date', + value: getDisplayDateFormat(item.requiredDay) || 'N/A', + }, + { + label: 'Extension End Date', + value: getDisplayDateFormat(item.extensionEndDay) || 'N/A', + }, + ], + ]} + actions={ hasAnyRole(['chpl-admin', 'chpl-onc']) && ( ) } - /> - ))} - + /> + ))} + + ); } diff --git a/src/app/components/system-maintenance/g1g2/g1g2-view.jsx b/src/app/components/system-maintenance/g1g2/g1g2-view.jsx index f729b86e0f..355994f0a7 100755 --- a/src/app/components/system-maintenance/g1g2/g1g2-view.jsx +++ b/src/app/components/system-maintenance/g1g2/g1g2-view.jsx @@ -11,7 +11,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { ChplSearchResultCard, ChplSortControls, ChplTooltip } from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -67,69 +67,68 @@ function ChplG1g2View({ g1g2: initialG1g2 }) { -
- -
- - - Search Results - - {`(${g1g2.length} Result${g1g2.length !== 1 ? 's' : ''})`} - + + + + Search Results + + {`(${g1g2.length} Result${g1g2.length !== 1 ? 's' : ''})`} + + + + + - - + + { g1g2 + .map((item) => ( + + + + + + ), + }, + { + label: 'Domain', + value: item.domainDisplay || 'N/A', + iconButton: ( + + + + + + ), + }, + { + label: 'Required Test', + value: `${item.removed ? 'Removed | ' : ''}${item.requiredTest || 'N/A'}`, + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + ]} + /> + ))} - - - { g1g2 - .map((item) => ( - - - - - - ), - }, - { - label: 'Domain', - value: item.domainDisplay || 'N/A', - iconButton: ( - - - - - - ), - }, - { - label: 'Required Test', - value: `${item.removed ? 'Removed | ' : ''}${item.requiredTest || 'N/A'}`, - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - ]} - /> - ))} - + ); } diff --git a/src/app/components/system-maintenance/optional-standard/optional-standards-view.jsx b/src/app/components/system-maintenance/optional-standard/optional-standards-view.jsx index f43c51eb4d..2147449629 100755 --- a/src/app/components/system-maintenance/optional-standard/optional-standards-view.jsx +++ b/src/app/components/system-maintenance/optional-standard/optional-standards-view.jsx @@ -11,7 +11,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { ChplSearchResultCard, ChplSortControls, ChplTooltip } from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -63,58 +63,57 @@ function ChplOptionalStandardsView({ optionalStandards: initialOptionalStandards -
- -
- - - Search Results - - {`(${optionalStandards.length} Result${optionalStandards.length !== 1 ? 's' : ''})`} - + + + + Search Results + + {`(${optionalStandards.length} Result${optionalStandards.length !== 1 ? 's' : ''})`} + + + + + - - + + { optionalStandards + .map((item) => ( + + + + + + ), + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + ]} + /> + ))} - - - { optionalStandards - .map((item) => ( - - - - - - ), - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - ]} - /> - ))} - + ); } diff --git a/src/app/components/system-maintenance/standard/standards-view.jsx b/src/app/components/system-maintenance/standard/standards-view.jsx index af99a95016..008ada9ac3 100755 --- a/src/app/components/system-maintenance/standard/standards-view.jsx +++ b/src/app/components/system-maintenance/standard/standards-view.jsx @@ -14,7 +14,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { useFetchStandardsActivity } from 'api/activity'; import ChplSystemMaintenanceActivity from 'components/activity/system-maintenance-activity'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -87,28 +87,26 @@ function ChplStandardsView({ dispatch, standards: initialStandards }) { -
- -
- - - Search Results - - {`(${standards.length} Result${standards.length !== 1 ? 's' : ''})`} - - - - - - { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + + + + Search Results + + {`(${standards.length} Result${standards.length !== 1 ? 's' : ''})`} + + + + + + { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} + )} + - - - { standards - .map((item) => ( - + + { standards + .map((item) => ( + )} - fieldGroups={[ - [ - { - label: 'Regulatory Text Citation', - value: item.regulatoryTextCitation || 'N/A', - iconButton: ( - - - - - - ), - }, - { - label: 'Rule', - value: item.rule?.name ?? 'N/A', - }, - { - label: 'Group', - value: item.groupName ?? 'N/A', - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - [ - { - label: 'Start Date', - value: getDisplayDateFormat(item.startDay), - }, - { - label: 'Required Date', - value: getDisplayDateFormat(item.requiredDay), - }, - { - label: 'Extension End Date', - value: getDisplayDateFormat(item.extensionEndDay), - }, - { - label: 'End Date', - value: getDisplayDateFormat(item.endDay), - }, - ], - ]} - actions={ + fieldGroups={[ + [ + { + label: 'Regulatory Text Citation', + value: item.regulatoryTextCitation || 'N/A', + iconButton: ( + + + + + + ), + }, + { + label: 'Rule', + value: item.rule?.name ?? 'N/A', + }, + { + label: 'Group', + value: item.groupName ?? 'N/A', + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + [ + { + label: 'Start Date', + value: getDisplayDateFormat(item.startDay), + }, + { + label: 'Required Date', + value: getDisplayDateFormat(item.requiredDay), + }, + { + label: 'Extension End Date', + value: getDisplayDateFormat(item.extensionEndDay), + }, + { + label: 'End Date', + value: getDisplayDateFormat(item.endDay), + }, + ], + ]} + actions={ hasAnyRole(['chpl-admin', 'chpl-onc']) && ( ) } - /> - ))} - + /> + ))} + + ); } diff --git a/src/app/components/system-maintenance/svap/svaps-view.jsx b/src/app/components/system-maintenance/svap/svaps-view.jsx index b6059eb792..df22de09e2 100755 --- a/src/app/components/system-maintenance/svap/svaps-view.jsx +++ b/src/app/components/system-maintenance/svap/svaps-view.jsx @@ -14,7 +14,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { useFetchSvapsActivity } from 'api/activity'; import ChplSystemMaintenanceActivity from 'components/activity/system-maintenance-activity'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -76,28 +76,26 @@ function ChplSvapsView({ dispatch, svaps: initialSvaps }) { -
- -
- - - Search Results - - {`(${svaps.length} Result${svaps.length !== 1 ? 's' : ''})`} - - - - - - { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + + + + Search Results + + {`(${svaps.length} Result${svaps.length !== 1 ? 's' : ''})`} + + + + + + { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} + )} + - - - { svaps - .map((item) => ( - - - - - - ), - }, + + { svaps + .map((item) => ( + + + + + + ), + }, - { - label: 'Replaced', - value: item.replaced ? 'Yes' : 'No', - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - ]} - actions={ + { + label: 'Replaced', + value: item.replaced ? 'Yes' : 'No', + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + ]} + actions={ hasAnyRole(['chpl-admin', 'chpl-onc']) && ( ) } - /> - ))} - + /> + ))} + + ); } diff --git a/src/app/components/system-maintenance/test-tool/test-tools-view.jsx b/src/app/components/system-maintenance/test-tool/test-tools-view.jsx index 1e7b361f0c..2e1876cfe7 100755 --- a/src/app/components/system-maintenance/test-tool/test-tools-view.jsx +++ b/src/app/components/system-maintenance/test-tool/test-tools-view.jsx @@ -14,7 +14,7 @@ import InfoIcon from '@material-ui/icons/Info'; import { ChplSearchResultCard, ChplSortControls, ChplTooltip } from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -70,24 +70,22 @@ function ChplTestToolsView({ dispatch, testTools: initialTestTools }) { -
- -
- - - Search Results - - {`(${testTools.length} Result${testTools.length !== 1 ? 's' : ''})`} - - - - - { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + + + + Search Results + + {`(${testTools.length} Result${testTools.length !== 1 ? 's' : ''})`} + + + + + { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} + )} + - - - { testTools - .map((item) => ( - - - - - + + { testTools + .map((item) => ( + + + + + )} - fieldGroups={[ - [ - { - label: 'Start Date', - value: getDisplayDateFormat(item.startDay), - }, - { - label: 'End Date', - value: getDisplayDateFormat(item.endDay), - }, - { - label: 'Applicable Criteria', - value: item.criteriaDisplay || 'N/A', - }, - ], - ]} - actions={ + fieldGroups={[ + [ + { + label: 'Start Date', + value: getDisplayDateFormat(item.startDay), + }, + { + label: 'End Date', + value: getDisplayDateFormat(item.endDay), + }, + { + label: 'Applicable Criteria', + value: item.criteriaDisplay || 'N/A', + }, + ], + ]} + actions={ hasAnyRole(['chpl-admin', 'chpl-onc']) && ( ) } - /> - ))} - + /> + ))} + + ); } From 7804e820e1c55d230d3521001c82ab142b39d4a0 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 27 Jul 2026 10:45:50 -0400 Subject: [PATCH 03/28] feat: enhance filter components with improved layout and sticky behavior [#OCD-4930] --- src/app/components/filter/filter-chips.jsx | 7 ++- src/app/components/filter/filter-layout.jsx | 3 +- .../components/filter/filter-search-bar.jsx | 47 +++++++++++++++++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/app/components/filter/filter-chips.jsx b/src/app/components/filter/filter-chips.jsx index ca16927955..5a6081d6e1 100755 --- a/src/app/components/filter/filter-chips.jsx +++ b/src/app/components/filter/filter-chips.jsx @@ -56,6 +56,11 @@ const useStyles = makeStyles({ display: 'inline-flex', gap: '4px', }, + card: { + width: '100%', + maxHeight: '50vh', + overflowY: 'auto', + }, }); const truncate = (str, n, useWordBoundary) => { @@ -143,7 +148,7 @@ function ChplFilterChips() { return ( - + Filters Applied:
diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index f375e32195..af6bfa9d54 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -34,8 +34,7 @@ const useStyles = makeStyles({ gap: '8px', [theme.breakpoints.up('md')]: { position: 'sticky', - top: '16px', - borderRight: `1px solid ${palette.greyBorder}`, + top: '190px', }, }, sidebarToggle: { diff --git a/src/app/components/filter/filter-search-bar.jsx b/src/app/components/filter/filter-search-bar.jsx index 6ee5858edb..af8e904f4e 100755 --- a/src/app/components/filter/filter-search-bar.jsx +++ b/src/app/components/filter/filter-search-bar.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Box, makeStyles, @@ -50,8 +50,20 @@ const useStyles = makeStyles({ }, sticky: { position: 'sticky', - top: 0, - zIndex: 2, + top: '100px', + zIndex: 3, + }, + stuck: { + '&::before': { + content: '""', + position: 'absolute', + left: 0, + right: 0, + bottom: '100%', + height: '100px', + background: `linear-gradient(to top, ${palette.backgroundPage} 55%, transparent)`, + pointerEvents: 'none', + }, }, }); @@ -64,10 +76,24 @@ function ChplFilterSearchBar({ }) { const { filters } = useFilterContext(); const classes = useStyles(); + const sentinelRef = useRef(null); + const [isStuck, setIsStuck] = useState(false); - return ( + useEffect(() => { + if (!sticky) { return undefined; } + const sentinel = sentinelRef.current; + if (!sentinel) { return undefined; } + const observer = new IntersectionObserver( + ([entry]) => setIsStuck(!entry.isIntersecting), + { rootMargin: '-100px 0px 0px 0px', threshold: 0 }, + ); + observer.observe(sentinel); + return () => observer.disconnect(); + }, [sticky]); + + const searchBar = (
{ !hideSearchTerm @@ -91,6 +117,17 @@ function ChplFilterSearchBar({
); + + if (!sticky) { + return searchBar; + } + + return ( + <> +
+ {searchBar} + + ); } export default ChplFilterSearchBar; From ad8dc92bf5ebb0f46e6f5bb4ca980d4009dfc8d2 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 27 Jul 2026 10:46:54 -0400 Subject: [PATCH 04/28] refactor: simplify ChplPagination component by removing unused styles and position change [#OCD-4930] --- src/app/components/util/pagination.jsx | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/app/components/util/pagination.jsx b/src/app/components/util/pagination.jsx index f4208c4742..49694a9071 100755 --- a/src/app/components/util/pagination.jsx +++ b/src/app/components/util/pagination.jsx @@ -1,43 +1,23 @@ import React from 'react'; -import { - TablePagination, - makeStyles, -} from '@material-ui/core'; +import { TablePagination } from '@material-ui/core'; import { arrayOf, - bool, func, number, } from 'prop-types'; import { eventTrack } from 'services/analytics.service'; import { useAnalyticsContext } from 'shared/contexts'; -import { palette, theme } from 'themes'; - -const useStyles = makeStyles({ - pagination: { - position: 'relative', - [theme.breakpoints.up('lg')]: { - position: 'sticky', - bottom: '64px', - width: 'fit-content', - marginLeft: 'auto', - marginRight: 'auto', - }, - }, -}); function ChplPagination({ count, page, rowsPerPage, rowsPerPageOptions, - sticky = true, setPage, setRowsPerPage, }) { const { analytics } = useAnalyticsContext(); - const classes = useStyles(); const handlePageChange = (event, newPage) => { if (analytics) { @@ -65,7 +45,6 @@ function ChplPagination({ return ( Date: Mon, 27 Jul 2026 10:49:00 -0400 Subject: [PATCH 05/28] feat: enhance ChplSearchResultControls with sticky positioning and gradient background [#OCD-4930] --- .../components/util/chpl-search-result-controls.jsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index 556ad87d8b..4f06bd8c7b 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -14,6 +14,9 @@ const useStyles = makeStyles({ flexDirection: 'column', gap: '4px', marginBottom: '16px', + position: 'sticky', + top: '190px', + zIndex: 2, alignItems: 'flex-start', justifyContent: 'space-between', flexWrap: 'wrap', @@ -22,6 +25,16 @@ const useStyles = makeStyles({ borderRadius: '0px 0px 8px 8px', borderTop: `1px solid ${palette.greyBorder}`, boxShadow: `0px 2px 4px -1px ${theme.palette.grey[300]}, 0px 4px 5px 0px ${theme.palette.grey[300]}, 0px 1px 10px 0px ${theme.palette.grey[300]}`, + '&::before': { + content: '""', + position: 'absolute', + left: 0, + right: 0, + bottom: '100%', + height: '90px', + background: `linear-gradient(to top, ${palette.backgroundPage} 40%, transparent)`, + pointerEvents: 'none', + }, [theme.breakpoints.down('sm')]: { gap: '12px', padding: '16px', From 2cf08c629c15d49bde935a6d83048837965b3179 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 27 Jul 2026 13:39:45 -0400 Subject: [PATCH 06/28] feat: replace ChplFilterChips with ChplFilterLayout for improved layout consistency across search views [#OCD-4930] --- .../api-documentation-view.jsx | 16 +++++++++++----- .../banned-developers-view.jsx | 13 ++++++++----- .../corrective-action-view.jsx | 17 ++++++++++++----- .../decertified-products-view.jsx | 13 ++++++++----- .../decision-support-interventions-view.jsx | 13 ++++++++----- .../inactive-certificates-view.jsx | 13 ++++++++----- src/app/pages/search/listings/listings-view.jsx | 13 ++++++++----- .../real-world-testing-view.jsx | 15 ++++++++++----- src/app/pages/search/sed/sed-view.jsx | 13 ++++++++----- src/app/pages/search/svap/svap-view.jsx | 15 ++++++++++----- 10 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/app/pages/search/api-documentation/api-documentation-view.jsx b/src/app/pages/search/api-documentation/api-documentation-view.jsx index 4f8d76829a..b53a76a340 100755 --- a/src/app/pages/search/api-documentation/api-documentation-view.jsx +++ b/src/app/pages/search/api-documentation/api-documentation-view.jsx @@ -20,7 +20,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -170,10 +170,10 @@ function ChplApiDocumentationSearchView({ displayCriteria }) { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> @@ -282,6 +286,7 @@ function ChplApiDocumentationSearchView({ displayCriteria }) { }, { label: 'Mandatory Disclosures URL', + style: { fontSize: '0.85em' }, value: item.mandatoryDisclosures ? ( )} + ); diff --git a/src/app/pages/search/banned-developers/banned-developers-view.jsx b/src/app/pages/search/banned-developers/banned-developers-view.jsx index a1401db6e7..32f5dc6ad4 100755 --- a/src/app/pages/search/banned-developers/banned-developers-view.jsx +++ b/src/app/pages/search/banned-developers/banned-developers-view.jsx @@ -18,7 +18,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -122,17 +122,18 @@ function ChplBannedDevelopersSearchView() { inline /> . - + )} /> - - {isLoading && ()} - {!isLoading + + {isLoading && ()} + {!isLoading && ( <> )} + ); diff --git a/src/app/pages/search/corrective-action/corrective-action-view.jsx b/src/app/pages/search/corrective-action/corrective-action-view.jsx index 34e350bf10..bdbb93792e 100755 --- a/src/app/pages/search/corrective-action/corrective-action-view.jsx +++ b/src/app/pages/search/corrective-action/corrective-action-view.jsx @@ -19,7 +19,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -139,10 +139,10 @@ function ChplCorrectiveActionSearchView() { { directReviewsAvailable && ( <> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + )} diff --git a/src/app/pages/search/decertified-products/decertified-products-view.jsx b/src/app/pages/search/decertified-products/decertified-products-view.jsx index 1774622761..ff209550d3 100755 --- a/src/app/pages/search/decertified-products/decertified-products-view.jsx +++ b/src/app/pages/search/decertified-products/decertified-products-view.jsx @@ -19,7 +19,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -124,10 +124,10 @@ function ChplDecertifiedProductsSearchView() { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/decision-support-interventions/decision-support-interventions-view.jsx b/src/app/pages/search/decision-support-interventions/decision-support-interventions-view.jsx index 531b2bae64..6fd1bf91b2 100644 --- a/src/app/pages/search/decision-support-interventions/decision-support-interventions-view.jsx +++ b/src/app/pages/search/decision-support-interventions/decision-support-interventions-view.jsx @@ -19,7 +19,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -110,10 +110,10 @@ function ChplDecisionSupportInterventionsSearchView() { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/inactive-certificates/inactive-certificates-view.jsx b/src/app/pages/search/inactive-certificates/inactive-certificates-view.jsx index 4084783299..b94d0774b5 100755 --- a/src/app/pages/search/inactive-certificates/inactive-certificates-view.jsx +++ b/src/app/pages/search/inactive-certificates/inactive-certificates-view.jsx @@ -19,7 +19,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -127,10 +127,10 @@ function ChplInactiveCertificatesSearchView() { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/listings/listings-view.jsx b/src/app/pages/search/listings/listings-view.jsx index 91bf06107f..9a308ee5d3 100755 --- a/src/app/pages/search/listings/listings-view.jsx +++ b/src/app/pages/search/listings/listings-view.jsx @@ -24,7 +24,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -138,10 +138,10 @@ function ChplListingsView() { subtitle="Please note that only active and suspended listings are shown by default. Use the Certification Status filter to display retired, withdrawn, or terminated listings." /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/real-world-testing/real-world-testing-view.jsx b/src/app/pages/search/real-world-testing/real-world-testing-view.jsx index 4cd01b33c6..265d79c329 100755 --- a/src/app/pages/search/real-world-testing/real-world-testing-view.jsx +++ b/src/app/pages/search/real-world-testing/real-world-testing-view.jsx @@ -19,7 +19,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -155,10 +155,10 @@ function ChplRealWorldTestingSearchView() { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/sed/sed-view.jsx b/src/app/pages/search/sed/sed-view.jsx index b5b35de25b..2c7dea9d81 100755 --- a/src/app/pages/search/sed/sed-view.jsx +++ b/src/app/pages/search/sed/sed-view.jsx @@ -20,7 +20,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -124,10 +124,10 @@ function ChplSedSearchView() { )} /> - - - { isLoading && ()} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> )} + ); diff --git a/src/app/pages/search/svap/svap-view.jsx b/src/app/pages/search/svap/svap-view.jsx index b045185163..d67817d656 100755 --- a/src/app/pages/search/svap/svap-view.jsx +++ b/src/app/pages/search/svap/svap-view.jsx @@ -20,7 +20,7 @@ import { ChplSortControls, } from 'components/util'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; @@ -232,10 +232,10 @@ function ChplSvapSearchView() { )} /> - - - {isLoading && ()} - {!isLoading + + + {isLoading && ()} + {!isLoading && ( <> )} + ); From e8f098e12adf91ba7edc584532e2ecccedccf45c Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 27 Jul 2026 15:39:07 -0400 Subject: [PATCH 07/28] feat: enhance search result controls and filter components with customizable background and improved layout [#OCD-4930] --- .../components/filter/filter-search-bar.jsx | 6 +- .../certification-criteria-view.jsx | 51 +++++++-------- .../certification-criteria.jsx | 4 +- .../functionalities-tested-view.jsx | 62 +++++++++---------- .../functionalities-tested.jsx | 4 +- .../system-maintenance/g1g2/g1g2-view.jsx | 49 +++++++-------- .../system-maintenance/g1g2/g1g2.jsx | 4 +- .../standard/standards-view.jsx | 59 +++++++----------- .../system-maintenance/standard/standards.jsx | 4 +- .../util/chpl-search-result-controls.jsx | 8 ++- 10 files changed, 115 insertions(+), 136 deletions(-) diff --git a/src/app/components/filter/filter-search-bar.jsx b/src/app/components/filter/filter-search-bar.jsx index af8e904f4e..2ed25e5431 100755 --- a/src/app/components/filter/filter-search-bar.jsx +++ b/src/app/components/filter/filter-search-bar.jsx @@ -61,13 +61,14 @@ const useStyles = makeStyles({ right: 0, bottom: '100%', height: '100px', - background: `linear-gradient(to top, ${palette.backgroundPage} 55%, transparent)`, + background: ({ fadeBackground }) => `linear-gradient(to top, ${fadeBackground} 55%, transparent)`, pointerEvents: 'none', }, }, }); function ChplFilterSearchBar({ + fadeBackground = palette.backgroundPage, hideAdvancedSearch = false, hideSearchTerm = false, placeholder = 'Search by Developer, Product, or CHPL ID...', @@ -75,7 +76,7 @@ function ChplFilterSearchBar({ toggleMultipleFilters = undefined, }) { const { filters } = useFilterContext(); - const classes = useStyles(); + const classes = useStyles({ fadeBackground }); const sentinelRef = useRef(null); const [isStuck, setIsStuck] = useState(false); @@ -133,6 +134,7 @@ function ChplFilterSearchBar({ export default ChplFilterSearchBar; ChplFilterSearchBar.propTypes = { + fadeBackground: string, hideAdvancedSearch: bool, hideSearchTerm: bool, placeholder: string, diff --git a/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx b/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx index c4268c8893..d629acbaaa 100755 --- a/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx +++ b/src/app/components/system-maintenance/certification-criterion/certification-criteria-view.jsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Box, - Typography, - makeStyles, } from '@material-ui/core'; import { arrayOf } from 'prop-types'; @@ -11,11 +9,16 @@ import { ChplFilterSearchBar, useFilterContext, } from 'components/filter'; -import { ChplLink, ChplSearchResultCard, ChplSortControls } from 'components/util'; +import { + ChplLink, + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, +} from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { getDisplayDateFormat } from 'services/date-util'; import { criterion as criterionPropType } from 'shared/prop-types'; -import { utilStyles } from 'themes'; +import { palette } from 'themes'; const sortOptions = [ { property: 'number', text: 'Number' }, @@ -24,10 +27,6 @@ const sortOptions = [ { property: 'endDay', text: 'End Date' }, ]; -const useStyles = makeStyles({ - ...utilStyles, -}); - const getDisplay = (key) => { switch (key) { case 'additionalSoftware': return 'Additional Software'; @@ -63,7 +62,6 @@ function ChplCertificationCriteriaView({ certificationCriteria: initialCertifica const [order, setOrder] = useState('asc'); const [orderBy, setOrderBy] = useState('number'); const filterContext = useFilterContext(); - const classes = useStyles(); useEffect(() => { setCertificationCriteria(initialCertificationCriteria @@ -96,27 +94,24 @@ function ChplCertificationCriteriaView({ certificationCriteria: initialCertifica <> - - - - Search Results - - - {`(${certificationCriteria.length} Result${certificationCriteria.length !== 1 ? 's' : ''})`} - - - - - - - + 0 ? 1 : 0} + pageEnd={certificationCriteria.length} + fadeBackground={palette.white} + > + + + {certificationCriteria .map((item) => ( - + )} /> - + diff --git a/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx b/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx index 9f778780f7..d41596fbe8 100755 --- a/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx +++ b/src/app/components/system-maintenance/functionality-tested/functionalities-tested-view.jsx @@ -3,8 +3,6 @@ import { Box, Button, IconButton, - Typography, - makeStyles, } from '@material-ui/core'; import { arrayOf, func } from 'prop-types'; import AddIcon from '@material-ui/icons/Add'; @@ -19,14 +17,18 @@ import { useFilterContext, } from 'components/filter'; import { - ChplSearchResultCard, ChplSortControls, ChplTooltip, ChplUpdateIndicator, + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, + ChplTooltip, + ChplUpdateIndicator, } from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { sortCriteria } from 'services/criteria.service'; import { getDisplayDateFormat } from 'services/date-util'; import { UserContext } from 'shared/contexts'; import { functionalityTested as functionalityTestedPropType } from 'shared/prop-types'; -import { utilStyles } from 'themes'; +import { palette } from 'themes'; const sortOptions = [ { property: 'value', text: 'Value' }, @@ -37,17 +39,12 @@ const sortOptions = [ { property: 'endDay', text: 'End Date' }, ]; -const useStyles = makeStyles({ - ...utilStyles, -}); - function ChplFunctionalitiesTestedView({ dispatch, functionalitiesTested: initialFunctionalitiesTested }) { const { hasAnyRole } = useContext(UserContext); const [functionalitiesTested, setFunctionalitiesTested] = useState([]); const [order, setOrder] = useState('desc'); const [orderBy, setOrderBy] = useState('value'); const filterContext = useFilterContext(); - const classes = useStyles(); useEffect(() => { setFunctionalitiesTested(initialFunctionalitiesTested @@ -79,29 +76,27 @@ function ChplFunctionalitiesTestedView({ dispatch, functionalitiesTested: initia <> - - - - Search Results - - - {`(${functionalitiesTested.length} Result${functionalitiesTested.length !== 1 ? 's' : ''})`} - - - - - - {hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + 0 ? 1 : 0} + pageEnd={functionalitiesTested.length} + fadeBackground={palette.white} + > + + + {hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} - - - + )} + + {functionalitiesTested .map((item) => ( - + )} /> - + { (deleteFunctionalityTested.isLoading || postFunctionalityTested.isLoading || putFunctionalityTested.isLoading) && ( diff --git a/src/app/components/system-maintenance/g1g2/g1g2-view.jsx b/src/app/components/system-maintenance/g1g2/g1g2-view.jsx index 355994f0a7..b3ff833032 100755 --- a/src/app/components/system-maintenance/g1g2/g1g2-view.jsx +++ b/src/app/components/system-maintenance/g1g2/g1g2-view.jsx @@ -2,13 +2,16 @@ import React, { useEffect, useState } from 'react'; import { Box, IconButton, - Typography, - makeStyles, } from '@material-ui/core'; import { arrayOf, shape, string } from 'prop-types'; import InfoIcon from '@material-ui/icons/Info'; -import { ChplSearchResultCard, ChplSortControls, ChplTooltip } from 'components/util'; +import { + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, + ChplTooltip, +} from 'components/util'; import { sortComparator } from 'components/util/sortable-headers'; import { ChplFilterLayout, @@ -16,7 +19,7 @@ import { useFilterContext, } from 'components/filter'; import { sortCriteria } from 'services/criteria.service'; -import { utilStyles } from 'themes'; +import { palette } from 'themes'; const sortOptions = [ { property: 'abbreviation', text: 'Abbreviation' }, @@ -24,16 +27,11 @@ const sortOptions = [ { property: 'name', text: 'Name' }, ]; -const useStyles = makeStyles({ - ...utilStyles, -}); - function ChplG1g2View({ g1g2: initialG1g2 }) { const [g1g2, setG1g2] = useState([]); const [order, setOrder] = useState('asc'); const [orderBy, setOrderBy] = useState('abbreviation'); const filterContext = useFilterContext(); - const classes = useStyles(); useEffect(() => { setG1g2(initialG1g2 @@ -66,25 +64,24 @@ function ChplG1g2View({ g1g2: initialG1g2 }) { <> - - - Search Results - - {`(${g1g2.length} Result${g1g2.length !== 1 ? 's' : ''})`} - - - - - - - + 0 ? 1 : 0} + pageEnd={g1g2.length} + fadeBackground={palette.white} + > + + + { g1g2 .map((item) => ( - + )} /> - + diff --git a/src/app/components/system-maintenance/standard/standards-view.jsx b/src/app/components/system-maintenance/standard/standards-view.jsx index 008ada9ac3..18d88f82ed 100755 --- a/src/app/components/system-maintenance/standard/standards-view.jsx +++ b/src/app/components/system-maintenance/standard/standards-view.jsx @@ -3,8 +3,6 @@ import { Box, Button, IconButton, - Typography, - makeStyles, } from '@material-ui/core'; import { arrayOf, func } from 'prop-types'; import AddIcon from '@material-ui/icons/Add'; @@ -20,6 +18,7 @@ import { } from 'components/filter'; import { ChplSearchResultCard, + ChplSearchResultControls, ChplSortControls, ChplUpdateIndicator, ChplTooltip, @@ -29,7 +28,7 @@ import { sortCriteria } from 'services/criteria.service'; import { getDisplayDateFormat } from 'services/date-util'; import { UserContext } from 'shared/contexts'; import { standard as standardPropType } from 'shared/prop-types'; -import { utilStyles } from 'themes'; +import { palette } from 'themes'; const sortOptions = [ { property: 'value', text: 'Value' }, @@ -40,21 +39,12 @@ const sortOptions = [ { property: 'endDay', text: 'End Date' }, ]; -const useStyles = makeStyles({ - ...utilStyles, - tableResultsHeaderContainer: { - display: 'flex', - justifyContent: 'flex-end', - }, -}); - function ChplStandardsView({ dispatch, standards: initialStandards }) { const [standards, setStandards] = useState([]); const { hasAnyRole } = useContext(UserContext); const [order, setOrder] = useState('asc'); const [orderBy, setOrderBy] = useState('value'); const filterContext = useFilterContext(); - const classes = useStyles(); useEffect(() => { setStandards(initialStandards @@ -86,27 +76,27 @@ function ChplStandardsView({ dispatch, standards: initialStandards }) { <> - - - Search Results - - {`(${standards.length} Result${standards.length !== 1 ? 's' : ''})`} - - - - - - { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( + 0 ? 1 : 0} + pageEnd={standards.length} + fadeBackground={palette.white} + > + + + { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( - )} - - - + )} + + { standards .map((item) => ( - + )} /> - + { (deleteStandard.isLoading || postStandard.isLoading || putStandard.isLoading) && ( diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index 4f06bd8c7b..c4e0745148 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -4,7 +4,7 @@ import { Typography, makeStyles, } from '@material-ui/core'; -import { node, number } from 'prop-types'; +import { node, number, string } from 'prop-types'; import { palette, theme } from 'themes'; @@ -32,7 +32,7 @@ const useStyles = makeStyles({ right: 0, bottom: '100%', height: '90px', - background: `linear-gradient(to top, ${palette.backgroundPage} 40%, transparent)`, + background: ({ fadeBackground }) => `linear-gradient(to top, ${fadeBackground} 40%, transparent)`, pointerEvents: 'none', }, [theme.breakpoints.down('sm')]: { @@ -76,8 +76,9 @@ function ChplSearchResultControls({ pageStart, pageEnd, children = undefined, + fadeBackground = palette.backgroundPage, }) { - const classes = useStyles(); + const classes = useStyles({ fadeBackground }); return (
@@ -113,4 +114,5 @@ ChplSearchResultControls.propTypes = { pageStart: number.isRequired, pageEnd: number.isRequired, children: node, + fadeBackground: string, }; From e25df56c18d28ae23b789a714b0ab13ded7fff30 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 28 Jul 2026 14:13:56 -0400 Subject: [PATCH 08/28] feat: refactor change request and activity views to use new search result components and improve layout [#OCD-4930] --- .../change-request/change-requests-view.jsx | 286 ++++++++---------- src/app/components/filter/filter-layout.jsx | 31 +- src/app/components/products/products-view.jsx | 2 +- .../pages/reports/activity/activity-view.jsx | 186 ++++-------- .../questionable-activity-view.jsx | 229 +++++--------- 5 files changed, 280 insertions(+), 454 deletions(-) diff --git a/src/app/components/change-request/change-requests-view.jsx b/src/app/components/change-request/change-requests-view.jsx index c1eaa95993..e7a48bd02f 100755 --- a/src/app/components/change-request/change-requests-view.jsx +++ b/src/app/components/change-request/change-requests-view.jsx @@ -1,20 +1,12 @@ import React, { useContext, useEffect, useState } from 'react'; import { + Box, Button, - ButtonGroup, Card, CardContent, CardHeader, - CircularProgress, MenuItem, MenuList, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - Typography, makeStyles, } from '@material-ui/core'; import VisibilityIcon from '@material-ui/icons/Visibility'; @@ -35,61 +27,36 @@ import { import { ChplAvatar, ChplLink, + ChplLoadingCards, ChplPagination, - ChplSortableHeaders, + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, } from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getDisplayDateFormat } from 'services/date-util'; import { useSessionStorage as useStorage } from 'services/storage.service'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; -import { palette, theme, utilStyles } from 'themes'; +import { palette, utilStyles } from 'themes'; const useStyles = makeStyles({ ...utilStyles, - container: { - maxHeight: '64vh', + card: { + overflow: 'visible', }, - tableResultsHeaderContainer: { - display: 'grid', - gap: '8px', - margin: '16px 32px', - gridTemplateColumns: '1fr', - alignItems: 'center', - justifyContent: 'space-between', - [theme.breakpoints.up('sm')]: { - gridTemplateColumns: 'auto auto', - }, - }, - resultsContainer: { - display: 'grid', - gap: '8px', - justifyContent: 'start', - gridTemplateColumns: 'auto auto', - alignItems: 'center', - }, - wrap: { - flexFlow: 'wrap', - }, - tableFirstColumn: { - position: 'sticky', - left: 0, - boxShadow: 'rgba(149, 157, 165, 0.1) 0px 4px 8px', - backgroundColor: palette.white, - }, - tableDeveloperCell: { + developerTitle: { display: 'flex', alignItems: 'center', gap: '8px', }, - developerName: { - fontWeight: '600', - }, noResultsContainer: { padding: '16px 32px', }, }); -function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embedded = false }) { +function ChplChangeRequestsView({ + disallowedFilters, bonusQuery, dispatch, embedded = false, +}) { const storageKey = 'storageKey-changeRequestsView'; const { analytics } = useAnalyticsContext(); const { hasAnyRole } = useContext(UserContext); @@ -128,20 +95,17 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed } }, [data, isLoading, isSuccess]); - /* eslint object-curly-newline: ["error", { "minProperties": 5, "consistent": true }] */ - const headers = hasAnyRole(['chpl-developer']) ? [ - { property: 'change_request_type', text: 'Request Type', sortable: true }, - { property: 'change_request_status', text: 'Request Status', sortable: true }, - { property: 'current_status_change_date_time', text: 'Time Since Last Status Change', sortable: true, reverseDefault: true }, - { text: 'Actions', invisible: true }, + const isDeveloper = hasAnyRole(['chpl-developer']); + const sortOptions = isDeveloper ? [ + { property: 'change_request_type', text: 'Request Type' }, + { property: 'change_request_status', text: 'Request Status' }, + { property: 'current_status_change_date_time', text: 'Time Since Last Status Change', reverseDefault: true }, ] : [ - { property: 'developer', text: 'Developer', sortable: true }, - { property: 'change_request_type', text: 'Request Type', sortable: true }, - { property: 'submitted_date_time', text: 'Creation Date', sortable: true, reverseDefault: true }, - { property: 'change_request_status', text: 'Request Status', sortable: true }, - { property: 'current_status_change_date_time', text: 'Time Since Last Status Change', sortable: true, reverseDefault: true }, - { text: 'Associated ONC-ACBs' }, - { text: 'Actions', invisible: true }, + { property: 'developer', text: 'Developer' }, + { property: 'change_request_type', text: 'Request Type' }, + { property: 'submitted_date_time', text: 'Creation Date', reverseDefault: true }, + { property: 'change_request_status', text: 'Request Status' }, + { property: 'current_status_change_date_time', text: 'Time Since Last Status Change', reverseDefault: true }, ]; const handleDispatch = (action, payload) => { @@ -156,7 +120,7 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed } }; - const handleTableSort = (event, property, orderDirection) => { + const handleSort = (property, orderDirection) => { eventTrack({ ...analytics, event: 'Sort Column', @@ -187,20 +151,18 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed const pageStart = (pageNumber * pageSize) + 1; const pageEnd = Math.min((pageNumber + 1) * pageSize, data?.recordCount); + const recordCount = data?.recordCount ?? 0; const content = ( <> - { isLoading - && ( -
- -
- )} + { isLoading && ()} { !isLoading && ( <> @@ -217,84 +179,60 @@ function ChplChangeRequestsView({ disallowedFilters, bonusQuery, dispatch, embed )} - { isSuccess + { !isError && ( <> -
-
- Search Results: - { changeRequests.length === 0 - && ( - <> - No results found - - )} - { changeRequests.length > 0 - && ( - - {`(${pageStart}-${pageEnd} of ${data?.recordCount} Results)`} - - )} -
+ + { changeRequests.length > 0 && ( - - - + )} -
+ { changeRequests.length > 0 && ( <> - - - - - {changeRequests - .map((item) => ( - - { !hasAnyRole(['chpl-developer']) - && ( - -
-
- -
-
- -
-
-
- )} - {item.changeRequestType.name} - { !hasAnyRole(['chpl-developer']) - && {getDisplayDateFormat(item.submittedDateTime)}} - {item.currentStatus.name} - + + { changeRequests.map((item) => ( + + + + + )} + fieldGroups={isDeveloper ? [ + [ + { label: 'Request Type', value: item.changeRequestType.name }, + { label: 'Request Status', value: item.currentStatus.name }, + { + label: 'Time Since Last Status Change', + value: ( {item.currentStatus.statusChangeDateTime} - - { !hasAnyRole(['chpl-developer']) - && ( - - { item.certificationBodies.length === 0 - ? ( - <> - None - - ) : ( - <> - { item.certificationBodies.map((acb) => acb.name).join('; ') } - - )} - - )} - - - -
- ))} -
-
-
+ {item.currentStatus.statusChangeDateTime} + + ), + }, + { + label: 'Associated ONC-ACBs', + value: item.certificationBodies.length === 0 + ? 'None' + : item.certificationBodies.map((acb) => acb.name).join('; '), + }, + ], + ]} + actions={( + + )} + /> + ))} + + { bonusQuery && ( diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index af6bfa9d54..89feb3b51d 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -9,7 +9,7 @@ import { import FilterListIcon from '@material-ui/icons/FilterList'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -import { node } from 'prop-types'; +import { bool, node } from 'prop-types'; import ChplFilterChips from './filter-chips'; import { useFilterContext } from './filter-context'; @@ -28,6 +28,13 @@ const useStyles = makeStyles({ gap: '24px', }, }, + layoutContainerMobileOnly: { + display: 'grid', + gridTemplateColumns: '1fr', + gap: '16px', + alignItems: 'start', + margin: '16px 0px', + }, sidebar: { display: 'flex', flexDirection: 'column', @@ -37,6 +44,11 @@ const useStyles = makeStyles({ top: '190px', }, }, + sidebarMobileOnly: { + display: 'flex', + flexDirection: 'column', + gap: '8px', + }, sidebarToggle: { justifyContent: 'space-between', color: palette.black, @@ -44,6 +56,10 @@ const useStyles = makeStyles({ display: 'none', }, }, + sidebarToggleMobileOnly: { + justifyContent: 'space-between', + color: palette.black, + }, sidebarToggleLabel: { display: 'flex', alignItems: 'center', @@ -54,10 +70,11 @@ const useStyles = makeStyles({ }, }); -function ChplFilterLayout({ children }) { +function ChplFilterLayout({ children, mobileOnly }) { const classes = useStyles(); const filterContext = useFilterContext(); - const isDesktop = useMediaQuery(theme.breakpoints.up('md')); + const isDesktopWidth = useMediaQuery(theme.breakpoints.up('md')); + const isDesktop = isDesktopWidth && !mobileOnly; const [expanded, setExpanded] = useState(false); const hasAppliedFilters = filterContext.filters @@ -76,10 +93,10 @@ function ChplFilterLayout({ children }) { } return ( -
- +
+
- -
- -
- { isLoading - && ( - - )} - { !isLoading + + + { isLoading && ()} + { !isLoading && ( <> -
-
- Search Results: - { activities.length === 0 - && ( - - No results found - - )} - { activities.length > 0 - && ( - - {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} - - )} -
+ + { activities.length > 0 && (
+ { activities.length > 0 && ( <> - - - - - { activities - .map((item) => ( - - - { item.developerId - && ( - - )} - - {item.productName} - {item.versionName} - - { item.listingId - && ( + + { activities.map((item) => ( + + ) + : item.developerName || 'N/A'} + fieldGroups={[ + [ + { label: 'Product', value: item.productName }, + { label: 'Version', value: item.versionName }, + { + label: 'CHPL ID', + style: { flex: '2 1 320px' }, + value: item.listingId + ? ( - )} - - {item.triggerName} - { getDisplayDateFormat(item.activityDate) } - - { item.reason - && ( - - {item.reason} - - )} - { item.certificationStatusChangeReason - && ( - - {item.certificationStatusChangeReason} - - )} - - - - - - ))} - -
-
+ ) + : item.chplProductNumber, + }, + ], + [ + { label: 'Activity', value: item.triggerName }, + { label: 'Activity Date', value: getDisplayDateFormat(item.activityDate) }, + { + label: 'Reason', + style: { flex: '2 1 320px' }, + value: item.reason || item.certificationStatusChangeReason || 'N/A', + }, + ], + ]} + actions={} + /> + ))} + )} +
); } From e66e5af83c549c4c8d202ada18bc3e30237e1283 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Tue, 28 Jul 2026 16:12:15 -0400 Subject: [PATCH 09/28] feat: enhance filter and sort components with improved layout and user feedback [#OCD-4930] --- src/app/components/filter/filter-layout.jsx | 33 +++++++++- .../util/chpl-search-result-controls.jsx | 6 +- .../components/util/chpl-sort-controls.jsx | 10 ++- .../manage-subscriptions-view.jsx | 64 +++++++------------ 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index 89feb3b51d..3927de6d07 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -2,11 +2,15 @@ import React, { useEffect, useState } from 'react'; import { Box, Button, + Card, + CardContent, Collapse, + Typography, makeStyles, useMediaQuery, } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; +import LabelOffIcon from '@material-ui/icons/LabelOff'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; import { bool, node } from 'prop-types'; @@ -65,6 +69,19 @@ const useStyles = makeStyles({ alignItems: 'center', gap: '8px', }, + emptyCard: { + width: '100%', + }, + emptyContent: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + gap: '8px', + }, + emptyIcon: { + fontSize: '2rem', + }, content: { minWidth: 0, }, @@ -86,8 +103,20 @@ function ChplFilterLayout({ children, mobileOnly }) { if (!hasAppliedFilters) { return ( -
- {children} +
+ + + + + + No filters applied. Please use the advanced search to apply filters and view results. + + + + +
+ {children} +
); } diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index c4e0745148..6135459254 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -23,8 +23,10 @@ const useStyles = makeStyles({ padding: '16px 32px', backgroundColor: palette.white, borderRadius: '0px 0px 8px 8px', - borderTop: `1px solid ${palette.greyBorder}`, - boxShadow: `0px 2px 4px -1px ${theme.palette.grey[300]}, 0px 4px 5px 0px ${theme.palette.grey[300]}, 0px 1px 10px 0px ${theme.palette.grey[300]}`, + borderRight: `1px solid ${palette.divider}`, + borderBottom: `1px solid ${palette.divider}`, + borderLeft: `1px solid ${palette.divider}`, + boxShadow: `0px 6px 8px -4px ${theme.palette.grey[300]}`, '&::before': { content: '""', position: 'absolute', diff --git a/src/app/components/util/chpl-sort-controls.jsx b/src/app/components/util/chpl-sort-controls.jsx index c18bd2a4d3..1ae25db867 100644 --- a/src/app/components/util/chpl-sort-controls.jsx +++ b/src/app/components/util/chpl-sort-controls.jsx @@ -3,6 +3,7 @@ import { Box, Button, ButtonGroup, + Card, Menu, MenuItem, makeStyles, @@ -19,11 +20,14 @@ import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; import SortIcon from '@material-ui/icons/Sort'; -import { theme } from 'themes'; +import { theme, palette } from 'themes'; const useStyles = makeStyles({ container: { marginRight: '16px', + display: 'flex', + border: `1px solid ${theme.palette.divider}`, + alignItems: 'center', [theme.breakpoints.down('sm')]: { width: '100%', marginRight: 0, @@ -83,7 +87,7 @@ function ChplSortControls({ }; return ( - +
+
+ +
); diff --git a/src/app/components/filter/filters/certification-statuses.jsx b/src/app/components/filter/filters/certification-statuses.jsx index f3ce4c83c9..42d4af26e0 100755 --- a/src/app/components/filter/filters/certification-statuses.jsx +++ b/src/app/components/filter/filters/certification-statuses.jsx @@ -32,13 +32,14 @@ const getCertificationStatusValueEntry = ({ filter, handleFilterToggle }) => fil style={{ display: 'flex', alignItems: 'center', + gap: '4px', }} > - {getStatusIcon({ name: filter.getLongValueDisplay(value) })} - {filter.getLongValueDisplay(value)} + {getStatusIcon({ name: filter.getLongValueDisplay(value) })} + ); }); From a1339ccdc0b0d3ae0973576d471ffb088618f4ce Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Wed, 29 Jul 2026 11:22:02 -0400 Subject: [PATCH 11/28] fix: standardize padding values in results containers across multiple views [#OCD-4930] --- src/app/components/filter/filter-layout.jsx | 4 ++-- src/app/pages/reports/activity/activity-view.jsx | 2 +- .../questionable-activity/questionable-activity-view.jsx | 2 +- src/app/pages/subscriptions/manage-subscriptions-view.jsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index 3927de6d07..b259794ffd 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -26,7 +26,7 @@ const useStyles = makeStyles({ gridTemplateColumns: '1fr', gap: '16px', alignItems: 'start', - margin: '16px 0px', + margin: '16px 0', [theme.breakpoints.up('md')]: { gridTemplateColumns: '260px 1fr', gap: '24px', @@ -37,7 +37,7 @@ const useStyles = makeStyles({ gridTemplateColumns: '1fr', gap: '16px', alignItems: 'start', - margin: '16px 0px', + margin: '16px 0', }, sidebar: { display: 'flex', diff --git a/src/app/pages/reports/activity/activity-view.jsx b/src/app/pages/reports/activity/activity-view.jsx index 687e60fdcf..b56e5acab0 100755 --- a/src/app/pages/reports/activity/activity-view.jsx +++ b/src/app/pages/reports/activity/activity-view.jsx @@ -42,7 +42,7 @@ const useStyles = makeStyles({ backgroundColor: '#f9f9f9', }, resultsContainer: { - padding: '0px 32px', + padding: '0 32px', }, }); 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 11613618f9..f5d391c39b 100755 --- a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx +++ b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx @@ -44,7 +44,7 @@ const useStyles = makeStyles({ backgroundColor: '#f9f9f9', }, resultsContainer: { - padding: '0px 32px', + padding: '0 32px', }, }); diff --git a/src/app/pages/subscriptions/manage-subscriptions-view.jsx b/src/app/pages/subscriptions/manage-subscriptions-view.jsx index 632854b819..a690943265 100755 --- a/src/app/pages/subscriptions/manage-subscriptions-view.jsx +++ b/src/app/pages/subscriptions/manage-subscriptions-view.jsx @@ -87,7 +87,7 @@ const useStyles = makeStyles({ }, }, resultsContainer: { - padding: '0px 32px', + padding: '0 32px', }, listContainer: { fontSize: 'smaller', From 79239811803a2af9dd39a0e185c0edb636047d13 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Wed, 29 Jul 2026 14:59:22 -0400 Subject: [PATCH 12/28] feat: Standardize complaints view and fix nav download icons Convert the Complaints page from a table to the standardized card layout (ChplSearchResultCard/Controls/SortControls) with a white sticky fade. Fix the invisible Developer User Guide download icon and show it on mobile. Rename the "Advanced Search" button to "Filters". [#OCD-4930] --- src/app/components/filter/filter-layout.jsx | 2 +- src/app/components/filter/filter-panel.jsx | 2 +- .../complaints/complaints-view.jsx | 196 +++++++++--------- src/app/components/util/chpl-page-body.jsx | 20 +- src/app/navigation/desktop-nav.jsx | 2 +- src/app/navigation/mobile-nav-drawer.jsx | 12 ++ 6 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index b259794ffd..def0be95ab 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -109,7 +109,7 @@ function ChplFilterLayout({ children, mobileOnly }) { - No filters applied. Please use the advanced search to apply filters and view results. + No filters applied. Please use the Filters button to apply filters and view results. diff --git a/src/app/components/filter/filter-panel.jsx b/src/app/components/filter/filter-panel.jsx index d660019e75..1d4cff9f71 100755 --- a/src/app/components/filter/filter-panel.jsx +++ b/src/app/components/filter/filter-panel.jsx @@ -234,7 +234,7 @@ function ChplFilterPanel() { id="filter-panel-toggle" onClick={handleClick} > - Advanced Search + Filters {' '} diff --git a/src/app/components/surveillance/complaints/complaints-view.jsx b/src/app/components/surveillance/complaints/complaints-view.jsx index 78d695b483..eb827bac79 100755 --- a/src/app/components/surveillance/complaints/complaints-view.jsx +++ b/src/app/components/surveillance/complaints/complaints-view.jsx @@ -5,13 +5,6 @@ import { Card, CardContent, CardHeader, - CircularProgress, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, Typography, makeStyles, } from '@material-ui/core'; @@ -29,38 +22,29 @@ import { ChplFilterSearchBar, useFilterContext, } from 'components/filter'; -import { ChplEllipsis, ChplPagination, ChplSortableHeaders } from 'components/util'; +import { + ChplEllipsis, + ChplLoadingCards, + ChplPagination, + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, +} from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getDisplayDateFormat } from 'services/date-util'; import { useSessionStorage as useStorage } from 'services/storage.service'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; -import { palette, theme, utilStyles } from 'themes'; +import { palette, utilStyles } from 'themes'; const useStyles = makeStyles({ ...utilStyles, - container: { - maxHeight: '64vh', - }, - tableResultsHeaderContainer: { - display: 'grid', - gap: '8px', - margin: '16px 32px', - gridTemplateColumns: '1fr', - alignItems: 'center', - justifyContent: 'space-between', - [theme.breakpoints.up('sm')]: { - gridTemplateColumns: 'auto auto', - }, - }, resultsContainer: { - display: 'grid', - gap: '8px', - justifyContent: 'start', - gridTemplateColumns: 'auto auto', - alignItems: 'center', + padding: '0 32px', }, - wrap: { - flexFlow: 'wrap', + emptyActions: { + display: 'flex', + gap: '8px', + padding: '16px 32px', }, statusIndicatorOpen: { color: palette.active, @@ -132,6 +116,14 @@ function ChplComplaintsView(props) { { property: 'actions', text: 'Actions', invisible: true }, ]; + const sortOptions = headers + .filter((header) => header.sortable) + .map((header) => ({ + property: header.property, + text: header.text, + reverseDefault: header.reverseDefault, + })); + const downloadFile = () => { eventTrack({ ...analytics, @@ -176,7 +168,7 @@ function ChplComplaintsView(props) { } }; - const handleTableSort = (event, property, orderDirection) => { + const handleSort = (property, orderDirection) => { eventTrack({ ...analytics, event: 'Sort Column', @@ -184,6 +176,7 @@ function ChplComplaintsView(props) { }); setOrderBy(property); setOrder(orderDirection); + setPageNumber(0); }; if (activeComplaint) { @@ -284,83 +277,86 @@ function ChplComplaintsView(props) { { isLoading && ( - + )} { !isLoading && ( <> -
-
- Search Results: - { complaints.length === 0 - && ( - <> - No results found - - )} - { complaints.length > 0 - && ( - - {`(${pageStart}-${pageEnd} of ${data?.recordCount} Results)`} - - )} -
+ + { getButtons() } -
+ + { complaints.length === 0 + && ( + + { getButtons() } + + )} { complaints.length > 0 && ( <> - - - - - {complaints - .map((complaint) => ( - - { !hasAnyRole(['chpl-onc-acb']) && !bonusQuery - && ( - {complaint.certificationBody.name} - )} - - - {complaint.closedDate ? 'Closed' : 'Open'} - - - {getDisplayDateFormat(complaint.receivedDate)} - {complaint.acbComplaintId} - - { complaint.oncComplaintId && } - - {complaint.complaintTypes?.map((t) => t.name).join(', ')} - {complaint.complainantType.name} - - - - - ))} - -
-
+ + { complaints.map((complaint) => { + const showAcb = !hasAnyRole(['chpl-onc-acb']) && !bonusQuery; + const primaryGroup = []; + if (showAcb) { + primaryGroup.push({ label: 'ONC-ACB', value: complaint.certificationBody.name }); + } + primaryGroup.push({ + label: 'Status', + value: ( + + {complaint.closedDate ? 'Closed' : 'Open'} + + ), + }); + primaryGroup.push({ label: 'Received Date', value: getDisplayDateFormat(complaint.receivedDate) }); + return ( + ) + : 'N/A', + }, + { label: 'Complaint Type(s)', value: complaint.complaintTypes?.map((t) => t.name).join(', ') || 'N/A' }, + { label: 'Complainant Type', value: complaint.complainantType.name }, + ], + ]} + actions={( + + )} + /> + ); + })} + ; } - return ; + return ; }; return ( diff --git a/src/app/navigation/mobile-nav-drawer.jsx b/src/app/navigation/mobile-nav-drawer.jsx index 2e115edd07..f5c2879947 100644 --- a/src/app/navigation/mobile-nav-drawer.jsx +++ b/src/app/navigation/mobile-nav-drawer.jsx @@ -12,6 +12,7 @@ import { Typography, } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; +import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import MenuIcon from '@material-ui/icons/Menu'; @@ -153,6 +154,16 @@ function ChplMobileNavDrawer({ onHomeClick, onSearchClick }) { category: item.analyticsCategory ?? 'Navigation', }); + const getDownloadIcon = (item) => { + if (!item.showDownloadIcon) { + return undefined; + } + if (item.primaryIcon) { + return ; + } + return ; + }; + const widgetSections = [{ key: 'cms', title: 'CMS ID Creator', @@ -240,6 +251,7 @@ function ChplMobileNavDrawer({ onHomeClick, onSearchClick }) { analytics={getItemAnalytics(item)} external={false} router={item.router} + icon={getDownloadIcon(item)} /> ))} From df6bb3d6e63406ee60bd00f45f33e908c2179733 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 3 Aug 2026 10:48:41 -0400 Subject: [PATCH 13/28] feat: Enhance filter and layout components with horizontal support and applied filters count [#OCD-4930] --- .../change-request/change-requests-view.jsx | 3 ++- src/app/components/filter/filter-chips.jsx | 23 ++++++++++++++--- src/app/components/filter/filter-layout.jsx | 25 ++++++++++++++++--- .../util/chpl-search-result-controls.jsx | 10 +++++--- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/app/components/change-request/change-requests-view.jsx b/src/app/components/change-request/change-requests-view.jsx index e7a48bd02f..553deaca1b 100755 --- a/src/app/components/change-request/change-requests-view.jsx +++ b/src/app/components/change-request/change-requests-view.jsx @@ -161,7 +161,7 @@ function ChplChangeRequestsView({ placeholder="Search by Developer..." hideSearchTerm={disallowedFilters.includes('searchTerm')} /> - + { isLoading && ()} { !isLoading && ( @@ -187,6 +187,7 @@ function ChplChangeRequestsView({ pageStart={pageStart} pageEnd={pageEnd} fadeBackground={palette.white} + sticky={!embedded} > { const maxLengthForChip = 40; -function ChplFilterChips() { +function ChplFilterChips({ horizontal }) { const [filters, setFilters] = useState([]); const filterContext = useFilterContext(); const classes = useStyles(); @@ -154,10 +163,10 @@ function ChplFilterChips() {
{ filters.map((f) => ( - + {f.getFilterDisplay(f)} @@ -239,4 +248,10 @@ function ChplFilterChips() { export default ChplFilterChips; -ChplFilterChips.propTypes = {}; +ChplFilterChips.propTypes = { + horizontal: bool, +}; + +ChplFilterChips.defaultProps = { + horizontal: false, +}; diff --git a/src/app/components/filter/filter-layout.jsx b/src/app/components/filter/filter-layout.jsx index def0be95ab..4ff19197d3 100644 --- a/src/app/components/filter/filter-layout.jsx +++ b/src/app/components/filter/filter-layout.jsx @@ -4,6 +4,7 @@ import { Button, Card, CardContent, + Chip, Collapse, Typography, makeStyles, @@ -55,20 +56,33 @@ const useStyles = makeStyles({ }, sidebarToggle: { justifyContent: 'space-between', - color: palette.black, + color: palette.primary, + borderColor: palette.primaryBorder, [theme.breakpoints.up('md')]: { display: 'none', }, }, sidebarToggleMobileOnly: { justifyContent: 'space-between', - color: palette.black, + color: palette.primary, + borderColor: palette.primaryBorder, }, sidebarToggleLabel: { display: 'flex', alignItems: 'center', gap: '8px', }, + countChip: { + backgroundColor: palette.primary, + color: palette.white, + fontWeight: 600, + height: '18px', + fontSize: '0.7rem', + '& .MuiChip-labelSmall': { + paddingLeft: '6px', + paddingRight: '6px', + }, + }, emptyCard: { width: '100%', }, @@ -97,6 +111,9 @@ function ChplFilterLayout({ children, mobileOnly }) { const hasAppliedFilters = filterContext.filters .some((filter) => filter.values?.some((v) => v.selected)); + const appliedCount = filterContext.filters + .reduce((sum, filter) => sum + (filter.values?.filter((v) => v.selected).length ?? 0), 0); + useEffect(() => { if (isDesktop) { setExpanded(false); } }, [isDesktop]); @@ -135,13 +152,15 @@ function ChplFilterLayout({ children, mobileOnly }) { Filters + { appliedCount > 0 + && } {isDesktop ? : ( - + )} diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index 6135459254..53feff5c7d 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -4,7 +4,7 @@ import { Typography, makeStyles, } from '@material-ui/core'; -import { node, number, string } from 'prop-types'; +import { bool, node, number, string } from 'prop-types'; import { palette, theme } from 'themes'; @@ -14,7 +14,7 @@ const useStyles = makeStyles({ flexDirection: 'column', gap: '4px', marginBottom: '16px', - position: 'sticky', + position: ({ sticky }) => (sticky ? 'sticky' : 'static'), top: '190px', zIndex: 2, alignItems: 'flex-start', @@ -28,7 +28,7 @@ const useStyles = makeStyles({ borderLeft: `1px solid ${palette.divider}`, boxShadow: `0px 6px 8px -4px ${theme.palette.grey[300]}`, '&::before': { - content: '""', + content: ({ sticky }) => (sticky ? '""' : 'none'), position: 'absolute', left: 0, right: 0, @@ -79,8 +79,9 @@ function ChplSearchResultControls({ pageEnd, children = undefined, fadeBackground = palette.backgroundPage, + sticky = true, }) { - const classes = useStyles({ fadeBackground }); + const classes = useStyles({ fadeBackground, sticky }); return (
@@ -117,4 +118,5 @@ ChplSearchResultControls.propTypes = { pageEnd: number.isRequired, children: node, fadeBackground: string, + sticky: bool, }; From 3c7b407fbc61ecc854180345589619317cedd5ae Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 3 Aug 2026 13:05:45 -0400 Subject: [PATCH 14/28] fix: Adjust resultsContainer padding in activity and questionable activity views [#OCD-4930] --- .../components/change-request/change-requests-view.jsx | 9 +++++++-- src/app/pages/reports/activity/activity-view.jsx | 2 +- .../questionable-activity/questionable-activity-view.jsx | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/components/change-request/change-requests-view.jsx b/src/app/components/change-request/change-requests-view.jsx index 553deaca1b..4dcc00a34c 100755 --- a/src/app/components/change-request/change-requests-view.jsx +++ b/src/app/components/change-request/change-requests-view.jsx @@ -44,6 +44,9 @@ const useStyles = makeStyles({ card: { overflow: 'visible', }, + resultsContainer: { + padding: ({ embedded }) => (embedded ? '0' : '0 32px'), + }, developerTitle: { display: 'flex', alignItems: 'center', @@ -76,7 +79,7 @@ function ChplChangeRequestsView({ sortDescending: order === 'desc', query: `${queryString()}${bonusQuery}`, }); - const classes = useStyles(); + const classes = useStyles({ embedded }); useEffect(() => { if (data?.recordCount > 0 && pageNumber > 0 && data?.results?.length === 0) { @@ -207,7 +210,7 @@ function ChplChangeRequestsView({ { changeRequests.length > 0 && ( <> - + { changeRequests.map((item) => ( acb.name).join('; '), diff --git a/src/app/pages/reports/activity/activity-view.jsx b/src/app/pages/reports/activity/activity-view.jsx index b56e5acab0..1e48d42853 100755 --- a/src/app/pages/reports/activity/activity-view.jsx +++ b/src/app/pages/reports/activity/activity-view.jsx @@ -42,7 +42,7 @@ const useStyles = makeStyles({ backgroundColor: '#f9f9f9', }, resultsContainer: { - padding: '0 32px', + padding: '0', }, }); 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 f5d391c39b..26c5c24f47 100755 --- a/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx +++ b/src/app/pages/reports/questionable-activity/questionable-activity-view.jsx @@ -44,7 +44,7 @@ const useStyles = makeStyles({ backgroundColor: '#f9f9f9', }, resultsContainer: { - padding: '0 32px', + padding: '0', }, }); From 50de53c34570057461c691aff66b6cdcde8ad14c Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 3 Aug 2026 14:39:39 -0400 Subject: [PATCH 15/28] feat: Update ChplSortControls styles for improved layout and button functionality [#OCD-4930] --- .../components/util/chpl-search-result-controls.jsx | 5 +++-- src/app/components/util/chpl-sort-controls.jsx | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index 53feff5c7d..1ebc7ee3d6 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -30,12 +30,13 @@ const useStyles = makeStyles({ '&::before': { content: ({ sticky }) => (sticky ? '""' : 'none'), position: 'absolute', - left: 0, - right: 0, + left: '-1px', + right: '-1px', bottom: '100%', height: '90px', background: ({ fadeBackground }) => `linear-gradient(to top, ${fadeBackground} 40%, transparent)`, pointerEvents: 'none', + zIndex: 1, }, [theme.breakpoints.down('sm')]: { gap: '12px', diff --git a/src/app/components/util/chpl-sort-controls.jsx b/src/app/components/util/chpl-sort-controls.jsx index 1ae25db867..7a5b59b530 100644 --- a/src/app/components/util/chpl-sort-controls.jsx +++ b/src/app/components/util/chpl-sort-controls.jsx @@ -26,7 +26,8 @@ const useStyles = makeStyles({ container: { marginRight: '16px', display: 'flex', - border: `1px solid ${theme.palette.divider}`, + border: `1px solid ${palette.primaryBorder}`, + borderRadius: theme.shape.borderRadius, alignItems: 'center', [theme.breakpoints.down('sm')]: { width: '100%', @@ -44,6 +45,10 @@ const useStyles = makeStyles({ justifyContent: 'flex-start', }, }, + directionButton: { + borderLeft: `1px solid ${palette.primaryBorder}`, + borderRadius: 0, + }, }); function ChplSortControls({ @@ -87,8 +92,8 @@ function ChplSortControls({ }; return ( - - + +
+ { activities.length > 0 && ( <> - - - - - { activities - .map((item) => ( - - - { item.developerId - && ( - - )} - - {item.productName} - {item.versionName} - - { item.listingId - && ( + + { activities.map((item) => ( + + ) + : item.developerName || 'N/A'} + fieldGroups={[ + [ + { label: 'Product', value: item.productName }, + { label: 'Version', value: item.versionName }, + { + label: 'CHPL ID', + style: { flex: '2 1 320px' }, + value: item.listingId + ? ( - )} - - {item.triggerName} - { getDisplayDateFormat(item.activityDate) } - - { item.reason - && ( - - {item.reason} - - )} - { item.certificationStatusChangeReason - && ( - - {item.certificationStatusChangeReason} - - )} - - - - - - ))} - -
-
+ ) + : item.chplProductNumber, + }, + ], + [ + { label: 'Activity', value: item.triggerName }, + { label: 'Activity Date', value: getDisplayDateFormat(item.activityDate) }, + { + label: 'Reason', + style: { flex: '2 1 320px' }, + value: item.reason || item.certificationStatusChangeReason || 'N/A', + }, + ], + ]} + actions={} + /> + ))} + )} + ); From 733af8f9d19c35ce60c4128df99eb55464cc5277 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Thu, 13 Aug 2026 14:47:22 -0400 Subject: [PATCH 19/28] fix: refactor analytics and download icon functions in mobile nav drawer [#OCD-4930] --- src/app/navigation/mobile-nav-drawer.jsx | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app/navigation/mobile-nav-drawer.jsx b/src/app/navigation/mobile-nav-drawer.jsx index f5c2879947..d42429a770 100644 --- a/src/app/navigation/mobile-nav-drawer.jsx +++ b/src/app/navigation/mobile-nav-drawer.jsx @@ -125,6 +125,22 @@ function ChplMobileNavDrawer({ onHomeClick, onSearchClick }) { setMobileMenuOpen(false); }; + const getDownloadIcon = (item) => { + if (!item.showDownloadIcon) { + return undefined; + } + if (item.primaryIcon) { + return ; + } + return ; + }; + + const getItemAnalytics = (item) => ({ + ...analytics, + event: item.analyticsEvent, + category: item.analyticsCategory ?? 'Navigation', + }); + const handleHomeClick = () => { onHomeClick(); closeMobileMenu(); @@ -148,22 +164,6 @@ function ChplMobileNavDrawer({ onHomeClick, onSearchClick }) { })); }; - const getItemAnalytics = (item) => ({ - ...analytics, - event: item.analyticsEvent, - category: item.analyticsCategory ?? 'Navigation', - }); - - const getDownloadIcon = (item) => { - if (!item.showDownloadIcon) { - return undefined; - } - if (item.primaryIcon) { - return ; - } - return ; - }; - const widgetSections = [{ key: 'cms', title: 'CMS ID Creator', From ffbaff953e00a68dc92b33a48d3e72869bc8d77d Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Wed, 19 Aug 2026 10:44:22 -0400 Subject: [PATCH 20/28] fix: adjust flex properties for Email and API Key columns in api-keys-view [#OCD-4930] --- .../components/system-maintenance/api-key/api-keys-view.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/components/system-maintenance/api-key/api-keys-view.jsx b/src/app/components/system-maintenance/api-key/api-keys-view.jsx index 7d11d555fd..b6c93c0cd8 100755 --- a/src/app/components/system-maintenance/api-key/api-keys-view.jsx +++ b/src/app/components/system-maintenance/api-key/api-keys-view.jsx @@ -85,10 +85,13 @@ function ChplApiKeysView({ dispatch, apiKeys: initialApiKeys }) { { label: 'Email', value: key.email, + flex: 1, + style: { flex: '2 1 320px' }, }, { label: 'API Key', value: key.key, + style: { flex: '2 1 320px' }, }, { label: 'Last Used', From b39a2769a886f93eabe9f04dde3704b847bae5c9 Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Thu, 20 Aug 2026 14:26:49 -0400 Subject: [PATCH 21/28] fix: integrate FlagContext and enhance search result card rendering [#OCD-4930] --- .../real-world-testing-view.jsx | 265 +++++++++++------- 1 file changed, 162 insertions(+), 103 deletions(-) diff --git a/src/app/pages/search/real-world-testing/real-world-testing-view.jsx b/src/app/pages/search/real-world-testing/real-world-testing-view.jsx index 265d79c329..9b71c71938 100755 --- a/src/app/pages/search/real-world-testing/real-world-testing-view.jsx +++ b/src/app/pages/search/real-world-testing/real-world-testing-view.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Box, Typography, @@ -27,7 +27,7 @@ import { eventTrack } from 'services/analytics.service'; import { getDisplayDateFormat } from 'services/date-util'; import { getStatusIcon } from 'services/listing.service'; import { useSessionStorage as useStorage } from 'services/storage.service'; -import { useAnalyticsContext } from 'shared/contexts'; +import { FlagContext, useAnalyticsContext } from 'shared/contexts'; const sortOptions = [ { property: 'chpl_id', text: 'CHPL ID' }, @@ -38,6 +38,7 @@ const sortOptions = [ function ChplRealWorldTestingSearchView() { const storageKey = 'storageKey-realWorldTestingView'; + const { hti5ErdIsOn } = useContext(FlagContext); const { analytics } = useAnalyticsContext(); const [listings, setListings] = useState([]); const [orderBy, setOrderBy] = useStorage(`${storageKey}-orderBy`, 'developer'); @@ -84,6 +85,160 @@ function ChplRealWorldTestingSearchView() { setSortDescending(orderDirection === 'desc'); }; + const buildSearchResultCard = (item) => (hti5ErdIsOn ? ( + + ), + }, { + label: 'Product', + value: item.product.name, + }, { + label: 'Version', + value: item.version.name, + }], [{ + label: 'CHPL ID', + style: { flex: '2 1 320px' }, + value: ( + + ), + }, { + label: 'Certification Date', + value: getDisplayDateFormat(item.certificationDate), + }, { + label: 'Status', + value: getStatusIcon(item.certificationStatus), + iconButton: , + }], [{ + label: 'Real World Testing Results URL', + style: { flex: '1 1 100%' }, + value: item.rwtResultsUrl + ? ( + + ) + : 'N/A', + }]]} + actions={} + /> + ) : ( + + ), + }, { + label: 'Product', + value: item.product.name, + }, { + label: 'Version', + value: item.version.name, + }], [{ + label: 'CHPL ID', + style: { flex: '2 1 320px' }, + value: ( + + ), + }, { + label: 'Certification Date', + value: getDisplayDateFormat(item.certificationDate), + }, { + label: 'Status', + value: getStatusIcon(item.certificationStatus), + iconButton: , + }], [{ + label: 'Real World Testing Plans URL', + style: { flex: '1 1 100%' }, + value: item.rwtPlansUrl + ? ( + + ) + : 'N/A', + }, { + label: 'Real World Testing Results URL', + style: { flex: '1 1 100%' }, + value: item.rwtResultsUrl + ? ( + + ) + : 'N/A', + }]]} + actions={} + /> + )); + const pageStart = (pageNumber * pageSize) + 1; const pageEnd = Math.min((pageNumber + 1) * pageSize, recordCount); @@ -120,7 +275,10 @@ function ChplRealWorldTestingSearchView() { inline /> {' '} - must successfully test their real world use. If applicable, Real World Testing plans are required to be made publicly available on the CHPL annually by December 15th. Additionally, Real World Testing results are to be made publicly available on the CHPL by March 15th of the subsequent year. + must successfully test their real world use. + {' '} + {!hti5ErdIsOn && 'If applicable, Real World Testing plans are required to be made publicly available on the CHPL annually by December 15th. Additionally, '} + Real World Testing results are to be made publicly available on the CHPL by March 15th of the subsequent year.

@@ -181,106 +339,7 @@ function ChplRealWorldTestingSearchView() { && ( <> - {listings.map((item) => ( - - ), - }, - { - label: 'Product', - value: item.product.name, - }, - { - label: 'Version', - value: item.version.name, - }, - ], - [ - { - label: 'CHPL ID', - style: { flex: '2 1 320px' }, - value: ( - - ), - }, - { - label: 'Certification Date', - value: getDisplayDateFormat(item.certificationDate), - }, - { - label: 'Status', - value: getStatusIcon(item.certificationStatus), - iconButton: , - }, - ], - [ - { - label: 'Real World Testing Plans URL', - style: { flex: '1 1 100%' }, - value: item.rwtPlansUrl - ? ( - - ) - : 'N/A', - }, - { - label: 'Real World Testing Results URL', - style: { flex: '1 1 100%' }, - value: item.rwtResultsUrl - ? ( - - ) - : 'N/A', - }, - ], - ]} - actions={} - /> - ))} + {listings.map((item) => buildSearchResultCard(item))} Date: Fri, 21 Aug 2026 11:35:41 -0400 Subject: [PATCH 22/28] fix: enhance CHPL Developer page with new search components [#OCD-4930] --- src/app/components/filter/filter-panel.jsx | 11 +- .../components/filter/filter-search-bar.jsx | 5 +- .../util/chpl-search-result-controls.jsx | 6 +- .../developers/developers-view.jsx | 276 +++++++----------- 4 files changed, 115 insertions(+), 183 deletions(-) diff --git a/src/app/components/filter/filter-panel.jsx b/src/app/components/filter/filter-panel.jsx index a2fe428930..1b3e6c441a 100755 --- a/src/app/components/filter/filter-panel.jsx +++ b/src/app/components/filter/filter-panel.jsx @@ -12,6 +12,7 @@ import { makeStyles, } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; +import { number } from 'prop-types'; import { useFilterContext } from './filter-context'; @@ -56,15 +57,12 @@ const useStyles = makeStyles({ }, filterContainer: { display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gridTemplateColumns: ({ filterGridMinColWidth }) => `repeat(auto-fit, minmax(${filterGridMinColWidth}px, 1fr))`, justifyItems: 'start', alignItems: 'start', gap: '16px', padding: '0 8px', marginTop: '16px', - [theme.breakpoints.up('xl')]: { - gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', - }, }, filterHeaderContainer: { display: 'grid', @@ -109,8 +107,8 @@ const useStyles = makeStyles({ }, }); -function ChplFilterPanel() { - const classes = useStyles(); +function ChplFilterPanel({ filterGridMinColWidth = 200 }) { + const classes = useStyles({ filterGridMinColWidth }); const [anchor, setAnchor] = useState(null); const [open, setOpen] = useState(false); const [activeCategory, setActiveCategory] = useState(null); @@ -429,4 +427,5 @@ function ChplFilterPanel() { export default ChplFilterPanel; ChplFilterPanel.propTypes = { + filterGridMinColWidth: number, }; diff --git a/src/app/components/filter/filter-search-bar.jsx b/src/app/components/filter/filter-search-bar.jsx index 2ed25e5431..337304ddf2 100755 --- a/src/app/components/filter/filter-search-bar.jsx +++ b/src/app/components/filter/filter-search-bar.jsx @@ -6,6 +6,7 @@ import { import { arrayOf, bool, + number, object, string, } from 'prop-types'; @@ -69,6 +70,7 @@ const useStyles = makeStyles({ function ChplFilterSearchBar({ fadeBackground = palette.backgroundPage, + filterGridMinColWidth = 200, hideAdvancedSearch = false, hideSearchTerm = false, placeholder = 'Search by Developer, Product, or CHPL ID...', @@ -107,7 +109,7 @@ function ChplFilterSearchBar({ { !hideAdvancedSearch && ( - + )} { filters.some((f) => f.key === 'quickFilters') && ( @@ -135,6 +137,7 @@ export default ChplFilterSearchBar; ChplFilterSearchBar.propTypes = { fadeBackground: string, + filterGridMinColWidth: number, hideAdvancedSearch: bool, hideSearchTerm: bool, placeholder: string, diff --git a/src/app/components/util/chpl-search-result-controls.jsx b/src/app/components/util/chpl-search-result-controls.jsx index 1ebc7ee3d6..b4bb60e2ba 100644 --- a/src/app/components/util/chpl-search-result-controls.jsx +++ b/src/app/components/util/chpl-search-result-controls.jsx @@ -57,13 +57,13 @@ const useStyles = makeStyles({ display: 'flex', alignItems: 'center', gap: '2px', + flexWrap: ({ wrapActions }) => (wrapActions ? 'wrap' : 'nowrap'), [theme.breakpoints.down('sm')]: { flexWrap: 'wrap', gap: '8px', width: '100%', }, [theme.breakpoints.up('md')]: { - flexWrap: 'nowrap', gap: '2px', width: 'auto', '& > *': { @@ -81,8 +81,9 @@ function ChplSearchResultControls({ children = undefined, fadeBackground = palette.backgroundPage, sticky = true, + wrapActions = false, }) { - const classes = useStyles({ fadeBackground, sticky }); + const classes = useStyles({ fadeBackground, sticky, wrapActions }); return (
@@ -120,4 +121,5 @@ ChplSearchResultControls.propTypes = { children: node, fadeBackground: string, sticky: bool, + wrapActions: bool, }; diff --git a/src/app/pages/organizations/developers/developers-view.jsx b/src/app/pages/organizations/developers/developers-view.jsx index 1382bfdc53..fae9a1f4d5 100755 --- a/src/app/pages/organizations/developers/developers-view.jsx +++ b/src/app/pages/organizations/developers/developers-view.jsx @@ -1,14 +1,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { Button, - CircularProgress, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - Typography, makeStyles, } from '@material-ui/core'; import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'; @@ -17,18 +9,28 @@ import ChplMessaging from './messaging/messaging'; import { useFetchDevelopersBySearch } from 'api/developer'; import { - ChplFilterChips, + ChplFilterLayout, ChplFilterSearchBar, useFilterContext, } from 'components/filter'; import { - ChplLink, ChplPagination, ChplLoadingTable, ChplSortableHeaders, + ChplLink, + ChplLoadingCards, + ChplPagination, + ChplSearchResultCard, + ChplSearchResultControls, + ChplSortControls, } from 'components/util'; import { eventTrack } from 'services/analytics.service'; import { getAngularService } from 'services/angular-react-helper'; import { useSessionStorage as useStorage } from 'services/storage.service'; import { UserContext, useAnalyticsContext } from 'shared/contexts'; -import { palette, theme, utilStyles } from 'themes'; +import { palette, utilStyles } from 'themes'; + +const sortOptions = [ + { property: 'developer_name', text: 'Developer' }, + { property: 'developer_code', text: 'Developer Code' }, +]; const useStyles = makeStyles({ ...utilStyles, @@ -36,47 +38,6 @@ const useStyles = makeStyles({ display: 'grid', borderRadius: 4, gridTemplateRows: '1fr', - backgroundColor: palette.white, - }, - pageContent: { - display: 'grid', - gridTemplateRows: '3fr 1fr', - }, - stickyColumn: { - position: 'sticky', - left: 0, - boxShadow: 'rgba(149, 157, 165, 0.1) 0px 4px 8px', - backgroundColor: palette.white, - overflowWrap: 'anywhere', - [theme.breakpoints.up('sm')]: { - minWidth: '275px', - }, - }, - tableContainer: { - overflowWrap: 'normal', - border: `.5px solid ${palette.divider}`, - margin: '0px 32px', - width: 'auto', - }, - tableResultsHeaderContainer: { - display: 'grid', - gap: '8px', - margin: '16px 32px', - gridTemplateColumns: '1fr', - alignItems: 'center', - justifyContent: 'space-between', - [theme.breakpoints.up('sm')]: { - gridTemplateColumns: 'auto auto', - }, - }, - resultsContainer: { - display: 'flex', - gap: '8px', - justifyContent: 'flex-start', - alignItems: 'center', - }, - wrap: { - flexFlow: 'wrap', }, }); @@ -97,7 +58,7 @@ function ChplDevelopersView() { const classes = useStyles(); const { - data, isError, isLoading, isFetching, + data, isError, isLoading, } = useFetchDevelopersBySearch({ orderBy, pageNumber, @@ -126,12 +87,6 @@ function ChplDevelopersView() { } }, [data?.recordCount, pageNumber, data?.results?.length]); - const headers = [ - { property: 'developer_name', text: 'Developer', sortable: true }, - { property: 'developer_code', text: 'Developer Code', sortable: true }, - { text: 'ONC-ACB for active Listings' }, - ]; - const downloadDevelopers = () => { eventTrack({ ...analytics, @@ -149,7 +104,7 @@ function ChplDevelopersView() { setMessaging(false); }; - const handleTableSort = (event, property, orderDirection) => { + const handleSort = (property, orderDirection) => { eventTrack({ ...analytics, event: 'Sort Column', @@ -196,131 +151,104 @@ function ChplDevelopersView() { <>
-
- -
- { isLoading - && ( - - )} - { !isLoading + + { isLoading && ()} + { !isLoading && ( <> -
-
- Search Results: - { developers.length === 0 - && ( - - No results found - - )} - { developers.length > 0 - && ( - - {`(${pageStart}-${pageEnd} of ${recordCount} Results)`} - - )} - { isFetching - && ( - - )} -
- { developers.length > 0 + + + + { hasAnyRole(['chpl-admin', 'chpl-onc']) && ( -
- - { hasAnyRole(['chpl-admin', 'chpl-onc']) - && ( - - )} -
+ )} -
+ { developers.length > 0 - && ( - <> - - - - - { developers - .map((item) => ( - - - - - - - - { item.code } - - - { item.oncAcbDisplay } - - - ))} - -
-
- + { developers.map((item) => ( + + ), + }, + { + label: 'Developer Code', + value: item.code, + }, + { + label: 'ONC-ACB for Active Listings', + value: item.oncAcbDisplay, + }, + ]]} /> - - )} + ))} + + + )} )} +
); From 00859ac8fcf6a36e1a5f966aeb245da58616945f Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Fri, 21 Aug 2026 12:28:44 -0400 Subject: [PATCH 23/28] fix: add ConditionalWrapper to enhance tooltip rendering for filter chips and update panels for developer view [#OCD-4930] --- src/app/components/filter/filter-chips.jsx | 21 +++++++++++++++---- src/app/components/filter/filter-panel.jsx | 12 ++++++++++- .../developers/developers-view.jsx | 1 - 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/app/components/filter/filter-chips.jsx b/src/app/components/filter/filter-chips.jsx index ee54489e22..4db9614ad6 100755 --- a/src/app/components/filter/filter-chips.jsx +++ b/src/app/components/filter/filter-chips.jsx @@ -72,6 +72,8 @@ const useStyles = makeStyles({ }, }); +const ConditionalWrapper = ({ condition, wrapper, children }) => (condition ? wrapper(children) : children); + const truncate = (str, n, useWordBoundary) => { if (str.length <= n) { return str; } const subString = str.slice(0, n - 1); @@ -205,17 +207,28 @@ function ChplFilterChips({ horizontal = false }) { { f.getValueDisplay(v).length > maxLengthForChip ? ( - ( + + {children} + + )} > removeChip(f, v)} variant="outlined" disabled={f.required && f.values.length === 1} classes={{ root: classes.chip, deleteIcon: classes.chipDeleteIcon }} /> - + ) : ( handleCategoryToggle(f)} > @@ -331,7 +341,7 @@ function ChplFilterPanel({ filterGridMinColWidth = 200 }) { className={classes.clearResetContainer} disableGutters > - + { activeCategory.getFilterDisplay(activeCategory) }
diff --git a/src/app/pages/organizations/developers/developers-view.jsx b/src/app/pages/organizations/developers/developers-view.jsx index fae9a1f4d5..4b190669a1 100755 --- a/src/app/pages/organizations/developers/developers-view.jsx +++ b/src/app/pages/organizations/developers/developers-view.jsx @@ -152,7 +152,6 @@ function ChplDevelopersView() {
From 8148963f5b00c679da6572fa4f2b08a6d784ed0d Mon Sep 17 00:00:00 2001 From: Matthew Stankiewicz Date: Mon, 24 Aug 2026 10:19:56 -0400 Subject: [PATCH 24/28] fix: update filter button width condition for specific filter keys [#OCD-4930] --- src/app/components/filter/filter-panel.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/filter/filter-panel.jsx b/src/app/components/filter/filter-panel.jsx index 124b8c8c73..c45cff2649 100755 --- a/src/app/components/filter/filter-panel.jsx +++ b/src/app/components/filter/filter-panel.jsx @@ -305,7 +305,7 @@ function ChplFilterPanel({ filterGridMinColWidth = 200 }) {
{ filters.map((f) => (