-
Notifications
You must be signed in to change notification settings - Fork 11
TTAHUB-5344 and TTAHUB-5348 - Compliant Follow-up Reviews with TTA Support widget Table View #3681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
994f818
Compliant Follow-up Reviews with TTA Support widget
Andrew565 cf7e2b6
fixes and updates to get graph to show and with proper formatting
AdamAdHocTeam e5b7c73
fix table export and total table row
AdamAdHocTeam 9a56d96
Dynamic label spacing to prevent squishing of small values
AdamAdHocTeam 2ec26d9
suggested change to how the widget scopes are implemented, plus tests
hardwarehuman 1b08cd3
Apply suggestions from code review
Andrew565 5fc7bc7
streamline sql filtering
hardwarehuman 1e94c4b
Merge remote-tracking branch 'origin/main' into 5344-5348-backend-sug…
Andrew565 0e7bb07
Merge pull request #3686 from HHS/5344-5348-backend-suggestion
Andrew565 fdce45d
Apply suggestions from code review
Andrew565 a802a68
Apply scopes.activityReport to TTA support check in compliantFollowUp…
Copilot 2670bf7
Revert "Apply scopes.activityReport to TTA support check in compliant…
Andrew565 532636f
Adding frontend tests
Andrew565 0204549
Putting widget behind feature flag and changing DeliveredReviews to "…
Andrew565 2a4da3d
Merge remote-tracking branch 'origin/main' into 5344-5348-compliant-r…
Andrew565 54a7ada
Updates for tests
Andrew565 bf505f6
Fix title casing to be sentence case
Andrew565 c6dab3e
Hiding annotations from graph
Andrew565 0c08da9
Renaming migration file again
Andrew565 3d2a866
Merge remote-tracking branch 'origin/main' into 5344-5348-compliant-r…
Andrew565 9b5a111
Removing inner text numbers from graph
Andrew565 abb944c
Fixing help drawer and graph order
Andrew565 df4f228
Moving CompliantReviewsGrid to separate file
Andrew565 d9078fa
Adding grid tests
Andrew565 730fe79
Adding more frontend tests
Andrew565 c385016
Merge remote-tracking branch 'origin/main' into 5344-5348-compliant-r…
Andrew565 0ccb9f2
Fix to sticky last cell
Andrew565 07cc746
Removing no-longer-needed label and annotation code from reviews grid…
Andrew565 8aaa920
More frontend tests
Andrew565 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
177 changes: 177 additions & 0 deletions
177
frontend/src/widgets/CompliantFollowUpReviewsWithTtaSupport.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import PropTypes from 'prop-types'; | ||
| import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; | ||
| import AppLoadingContext from '../AppLoadingContext'; | ||
| import ContentFromFeedByTag from '../components/ContentFromFeedByTag'; | ||
| import Drawer from '../components/Drawer'; | ||
| import DrawerTriggerButton from '../components/DrawerTriggerButton'; | ||
| import WidgetContainer from '../components/WidgetContainer'; | ||
| import WidgetContainerSubtitle from '../components/WidgetContainer/WidgetContainerSubtitle'; | ||
| import useMediaCapture from '../hooks/useMediaCapture'; | ||
| import useWidgetExport from '../hooks/useWidgetExport'; | ||
| import useWidgetMenuItems from '../hooks/useWidgetMenuItems'; | ||
| import CompliantReviewsGrid from './CompliantReviewsGrid'; | ||
| import HorizontalTableWidget from './HorizontalTableWidget'; | ||
| import withWidgetData from './withWidgetData'; | ||
| import './CompliantFollowUpReviewsWithTtaSupport.css'; | ||
| import NoResultsFound from '../components/NoResultsFound'; | ||
|
|
||
| const EXPORT_NAME = 'Compliant follow-up reviews with TTA support'; | ||
|
|
||
| export function CompliantFollowUpReviewsWithTtaSupport({ loading, data }) { | ||
| const { setIsAppLoading } = useContext(AppLoadingContext); | ||
| const drawerTriggerRef = useRef(null); | ||
| const widgetRef = useRef(null); | ||
| const capture = useMediaCapture(widgetRef, EXPORT_NAME); | ||
| const [showTabularData, setShowTabularData] = useState(false); | ||
|
|
||
|
Andrew565 marked this conversation as resolved.
|
||
| useEffect(() => { | ||
| setIsAppLoading(loading); | ||
| }, [loading, setIsAppLoading]); | ||
|
|
||
| const months = useMemo(() => { | ||
| if (!data?.months?.length) return []; | ||
| return data.months; | ||
| }, [data]); | ||
|
Andrew565 marked this conversation as resolved.
|
||
|
|
||
| // Build rows for HorizontalTableWidget (table view / export) | ||
| // Separate non-Total rows from the Total row so it can go in tfoot (bold) | ||
| const { tableData, footerData } = useMemo(() => { | ||
| const reviews = data?.reviews || []; | ||
| const nonTotalRows = reviews.filter((row) => !/total/i.test(row.name)); | ||
| const totalRow = reviews.find((row) => /total/i.test(row.name)); | ||
|
|
||
| const rows = nonTotalRows.map((row) => ({ | ||
| heading: row.name, | ||
| id: row.name, | ||
| tooltip: true, | ||
| hideSortingIndicator: true, | ||
| data: [ | ||
| ...row.values.map((value) => ({ value: value.toString() })), | ||
| { value: row.values.reduce((sum, v) => sum + Number(v), 0).toString() }, | ||
| ], | ||
| })); | ||
|
|
||
| const footer = totalRow | ||
| ? [ | ||
| 'Total', | ||
| ...totalRow.values.map(String), | ||
| totalRow.values.reduce((sum, v) => sum + Number(v), 0).toString(), | ||
| ] | ||
| : false; | ||
|
|
||
| return { tableData: rows, footerData: footer }; | ||
| }, [data]); | ||
|
|
||
| const { exportRows } = useWidgetExport( | ||
| tableData, | ||
| [...(months || []), 'Total'], | ||
| {}, | ||
| 'Follow-up reviews', | ||
| EXPORT_NAME | ||
| ); | ||
|
|
||
| const menuItems = useWidgetMenuItems( | ||
|
Andrew565 marked this conversation as resolved.
|
||
| showTabularData, | ||
| setShowTabularData, | ||
| capture, | ||
| {}, | ||
| exportRows | ||
| ); | ||
|
|
||
| const subtitle = ( | ||
| <div className="margin-bottom-3"> | ||
| <WidgetContainerSubtitle> | ||
| Compliant follow-up reviews, broken out by those with and without citations addressed by | ||
| approved activity reports during the correction period. | ||
| </WidgetContainerSubtitle> | ||
| <div className="margin-top-1"> | ||
| <DrawerTriggerButton drawerTriggerRef={drawerTriggerRef}> | ||
| About this data | ||
| </DrawerTriggerButton> | ||
| </div> | ||
|
Andrew565 marked this conversation as resolved.
|
||
| </div> | ||
| ); | ||
|
|
||
| const showEmptyState = !loading && !data?.months?.length; | ||
| if (showEmptyState) { | ||
| return ( | ||
| <> | ||
| <Drawer triggerRef={drawerTriggerRef} title="Compliant follow-up reviews with TTA support"> | ||
| <ContentFromFeedByTag tagName="ttahub-compliant-follow-up-reviews" /> | ||
| </Drawer> | ||
| <WidgetContainer | ||
| title="Compliant follow-up reviews with TTA support" | ||
| subtitle={subtitle} | ||
| menuItems={[]} | ||
| loading={loading} | ||
| titleMargin={{ bottom: 1 }} | ||
| > | ||
| <NoResultsFound | ||
| drawerConfig={{ | ||
| tagName: 'ttahub-regional-dash-monitoring-filters', | ||
| title: 'Monitoring dashboard filters', | ||
| }} | ||
| /> | ||
| </WidgetContainer> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <Drawer triggerRef={drawerTriggerRef} title="Compliant follow-up reviews with TTA support"> | ||
| <ContentFromFeedByTag tagName="ttahub-compliant-follow-up-reviews" /> | ||
| </Drawer> | ||
| <WidgetContainer | ||
| title="Compliant follow-up reviews with TTA support" | ||
| subtitle={subtitle} | ||
| menuItems={menuItems} | ||
| loading={loading} | ||
| titleMargin={{ bottom: 1 }} | ||
| > | ||
| {showTabularData ? ( | ||
| <HorizontalTableWidget | ||
| headers={months} | ||
| data={tableData} | ||
| caption="Compliant follow-up reviews with TTA support" | ||
| firstHeading="Follow-up reviews" | ||
| lastHeading="Totals" | ||
| showTotalColumn | ||
| stickyFirstColumn | ||
| stickyLastColumn | ||
| enableCheckboxes={false} | ||
| selectAllIdPrefix="compliant-follow-up-reviews" | ||
| hideFirstColumnBorder | ||
| footerData={footerData} | ||
| /> | ||
| ) : ( | ||
| <CompliantReviewsGrid data={data} widgetRef={widgetRef} /> | ||
| )} | ||
|
Andrew565 marked this conversation as resolved.
|
||
| </WidgetContainer> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| CompliantFollowUpReviewsWithTtaSupport.propTypes = { | ||
| data: PropTypes.shape({ | ||
| months: PropTypes.arrayOf(PropTypes.string), | ||
| reviews: PropTypes.arrayOf( | ||
| PropTypes.shape({ | ||
| name: PropTypes.string, | ||
| values: PropTypes.arrayOf(PropTypes.number), | ||
| }) | ||
| ), | ||
| }), | ||
| loading: PropTypes.bool.isRequired, | ||
| filters: PropTypes.arrayOf(PropTypes.shape({})), | ||
| }; | ||
|
|
||
| CompliantFollowUpReviewsWithTtaSupport.defaultProps = { | ||
| data: null, | ||
| filters: [], | ||
| }; | ||
|
Andrew565 marked this conversation as resolved.
|
||
|
|
||
| export default withWidgetData( | ||
| CompliantFollowUpReviewsWithTtaSupport, | ||
| 'compliantFollowUpReviewsWithTtaSupport' | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import PropTypes from 'prop-types'; | ||
| import React, { useEffect, useState } from 'react'; | ||
| import createPlotlyComponent from 'react-plotly.js/factory'; | ||
| import colors from '../colors'; | ||
| import useSize from '../hooks/useSize'; | ||
|
|
||
| let Plot = null; | ||
| import('plotly.js-basic-dist').then((Plotly) => { | ||
| Plot = createPlotlyComponent(Plotly); | ||
| }); | ||
|
|
||
| const SERIES_COLORS = [colors.ttahubMediumBlue, colors.ttahubOrange, colors.ttahubMediumDeepTeal]; | ||
|
|
||
| export default function CompliantReviewsGrid({ data, widgetRef }) { | ||
| const [plotData, setPlotData] = useState(null); | ||
| const size = useSize(widgetRef); | ||
|
|
||
| useEffect(() => { | ||
| if (!data || !size) return; | ||
| const { months, reviews } = data; | ||
| // Exclude Total; put "with TTA" first so "without TTA" renders on top in stacked mode | ||
| const filtered = (reviews || []).filter((s) => !/total/i.test(s.name)); | ||
| const withTta = filtered.filter((s) => /with tta/i.test(s.name)); | ||
| const withoutTta = filtered.filter((s) => !/with tta/i.test(s.name)); | ||
| const ordered = [...withTta, ...withoutTta]; | ||
|
|
||
| const traces = ordered.map((series, i) => ({ | ||
| type: 'bar', | ||
| name: series.name, | ||
| x: months, | ||
| y: series.values, | ||
| text: [], | ||
| textposition: 'inside', | ||
| insidetextanchor: 'middle', | ||
| insidetextfont: { color: i === 0 ? '#fff' : colors.baseDarkest, size: 10 }, | ||
| marker: { color: SERIES_COLORS[i % SERIES_COLORS.length] }, | ||
| hovertemplate: '%{y}<extra></extra>', | ||
| hoverlabel: { | ||
| bgcolor: colors.baseDarkest, | ||
| bordercolor: colors.baseDarkest, | ||
| font: { color: '#fff', size: 16 }, | ||
| }, | ||
| })); | ||
|
|
||
| setPlotData({ | ||
| traces, | ||
| layout: { | ||
| barmode: 'stack', | ||
| height: 350, | ||
| width: size.width, | ||
| margin: { l: 90, r: 20, t: 28, b: 80 }, | ||
| font: { color: colors.baseDarkest }, | ||
| xaxis: { | ||
| automargin: true, | ||
| title: { | ||
| text: 'Follow-up review received date', | ||
| font: { family: 'Source Sans Pro, sans-serif', size: 16 }, | ||
| }, | ||
| }, | ||
| yaxis: { | ||
| tickformat: ',.0d', | ||
| autorange: true, | ||
| title: { | ||
| text: 'Compliant follow-up reviews', | ||
| font: { family: 'Source Sans Pro, sans-serif', size: 16 }, | ||
| }, | ||
| }, | ||
| showlegend: false, | ||
| }, | ||
| config: { responsive: true, displayModeBar: false }, | ||
| }); | ||
| }, [data, size]); | ||
|
|
||
| return ( | ||
| <div ref={widgetRef} className="padding-3"> | ||
| {plotData && ( | ||
| <> | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| justifyContent: 'center', | ||
| gap: '24px', | ||
| marginBottom: '8px', | ||
| fontFamily: 'Source Sans Pro, sans-serif', | ||
| fontSize: '16px', | ||
| }} | ||
| > | ||
| {plotData.traces.map((trace) => ( | ||
| <div key={trace.name} style={{ display: 'flex', alignItems: 'center', gap: '10px' }}> | ||
| <div | ||
| style={{ | ||
| width: '26px', | ||
| height: '26px', | ||
| borderRadius: '4px', | ||
| backgroundColor: trace.marker.color, | ||
| flexShrink: 0, | ||
| }} | ||
| /> | ||
| <span>{trace.name}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <Plot data={plotData.traces} layout={plotData.layout} config={plotData.config} /> | ||
| </> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| CompliantReviewsGrid.propTypes = { | ||
| data: PropTypes.shape({ | ||
| months: PropTypes.arrayOf(PropTypes.string), | ||
| reviews: PropTypes.arrayOf( | ||
| PropTypes.shape({ | ||
| name: PropTypes.string, | ||
| values: PropTypes.arrayOf(PropTypes.number), | ||
| }) | ||
| ), | ||
| }).isRequired, | ||
| widgetRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }).isRequired, | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a large file can anything be broken out into a helper file?