@cdc/dashboard 4.26.3 → 4.26.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +219 -0
- package/README.md +60 -20
- package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
- package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
- package/dist/cdcdashboard.js +61559 -58048
- package/examples/__data__/data-2.json +6 -0
- package/examples/__data__/data.json +6 -0
- package/examples/dashboard-conditions-filters-incomplete.json +221 -0
- package/examples/dashboard-missing-datasets-multi.json +174 -0
- package/examples/dashboard-missing-datasets-single.json +121 -0
- package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
- package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
- package/examples/dashboard-stale-dataset-keys.json +181 -0
- package/examples/dashboard-tiered-filter-regression.json +190 -0
- package/examples/legend-issue.json +1 -1
- package/examples/minimal-example.json +34 -0
- package/examples/private/cfa-dashboard.json +651 -0
- package/examples/private/data-bite-wrap.json +6936 -0
- package/examples/private/dengue.json +4640 -0
- package/examples/private/link_to_file.json +16662 -0
- package/examples/private/multi-dash-fix.json +16963 -0
- package/examples/private/versions.json +41612 -0
- package/examples/sankey.json +3 -3
- package/examples/test-api-filter-reset.json +4 -4
- package/examples/tp5-test.json +86 -4
- package/examples/us-map-filter-example.json +1074 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +6 -2
- package/src/CdcDashboardComponent.tsx +179 -88
- package/src/DashboardCopyPasteContext.test.tsx +33 -0
- package/src/DashboardCopyPasteContext.tsx +48 -0
- package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
- package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
- package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
- package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
- package/src/_stories/Dashboard.stories.tsx +337 -2
- package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
- package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
- package/src/_stories/_mock/tp5-test.json +86 -5
- package/src/components/Column.test.tsx +176 -0
- package/src/components/Column.tsx +214 -13
- package/src/components/DashboardConditionModal.test.tsx +420 -0
- package/src/components/DashboardConditionModal.tsx +367 -0
- package/src/components/DashboardConditionSummary.tsx +59 -0
- package/src/components/DashboardEditors.tsx +23 -0
- package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
- package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
- package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
- package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
- package/src/components/DataDesignerModal.tsx +2 -1
- package/src/components/ExpandCollapseButtons.tsx +6 -4
- package/src/components/Grid.tsx +12 -7
- package/src/components/Header/Header.tsx +36 -17
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
- package/src/components/Row.test.tsx +228 -0
- package/src/components/Row.tsx +104 -28
- package/src/components/VisualizationRow.test.tsx +396 -0
- package/src/components/VisualizationRow.tsx +177 -51
- package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
- package/src/components/Widget/Widget.test.tsx +218 -0
- package/src/components/Widget/Widget.tsx +123 -20
- package/src/components/Widget/widget.styles.css +58 -14
- package/src/components/dashboard-condition-modal.css +76 -0
- package/src/components/dashboard-condition-summary.css +87 -0
- package/src/data/initial-state.js +1 -0
- package/src/helpers/addValuesToDashboardFilters.ts +3 -5
- package/src/helpers/addVisualization.ts +17 -4
- package/src/helpers/cloneDashboardWidget.ts +127 -0
- package/src/helpers/dashboardColumnWidgets.ts +99 -0
- package/src/helpers/dashboardConditionUi.ts +47 -0
- package/src/helpers/dashboardConditions.ts +200 -0
- package/src/helpers/dashboardFilterTargets.ts +156 -0
- package/src/helpers/filterData.ts +4 -9
- package/src/helpers/filterVisibility.ts +20 -0
- package/src/helpers/formatConfigBeforeSave.ts +2 -2
- package/src/helpers/getFilteredData.ts +18 -5
- package/src/helpers/getUpdateConfig.ts +43 -12
- package/src/helpers/getVizRowColumnLocator.ts +11 -1
- package/src/helpers/iconHash.tsx +9 -3
- package/src/helpers/mapDataToConfig.ts +31 -29
- package/src/helpers/reloadURLHelpers.ts +25 -5
- package/src/helpers/removeDashboardFilter.ts +33 -33
- package/src/helpers/tests/addVisualization.test.ts +53 -9
- package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
- package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
- package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
- package/src/helpers/tests/dashboardConditions.test.ts +428 -0
- package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
- package/src/helpers/tests/getFilteredData.test.ts +265 -86
- package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
- package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
- package/src/index.tsx +6 -3
- package/src/scss/grid.scss +281 -22
- package/src/scss/main.scss +215 -64
- package/src/store/dashboard.actions.ts +17 -4
- package/src/store/dashboard.reducer.test.ts +538 -0
- package/src/store/dashboard.reducer.ts +136 -22
- package/src/test/CdcDashboard.test.jsx +24 -0
- package/src/test/CdcDashboard.test.tsx +148 -0
- package/src/test/CdcDashboardComponent.test.tsx +935 -2
- package/src/types/ConfigRow.ts +15 -0
- package/src/types/DashboardFilters.ts +4 -0
- package/src/types/SharedFilter.ts +2 -0
- package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
- package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
- package/examples/DEV-6574.json +0 -2224
- package/examples/api-dashboard-data.json +0 -272
- package/examples/api-dashboard-years.json +0 -11
- package/examples/api-geographies-data.json +0 -11
- package/examples/chart-data.json +0 -5409
- package/examples/custom/css/respiratory.css +0 -236
- package/examples/custom/js/respiratory.js +0 -242
- package/examples/default-data.json +0 -368
- package/examples/default-filter-control.json +0 -209
- package/examples/default-multi-dataset-shared-filter.json +0 -1729
- package/examples/default-multi-dataset.json +0 -506
- package/examples/ed-visits-county-file.json +0 -402
- package/examples/filters/Alabama.json +0 -72
- package/examples/filters/Alaska.json +0 -1737
- package/examples/filters/Arkansas.json +0 -4713
- package/examples/filters/California.json +0 -212
- package/examples/filters/Colorado.json +0 -1500
- package/examples/filters/Connecticut.json +0 -559
- package/examples/filters/Delaware.json +0 -63
- package/examples/filters/DistrictofColumbia.json +0 -63
- package/examples/filters/Florida.json +0 -4217
- package/examples/filters/States.json +0 -146
- package/examples/state-level.json +0 -90136
- package/examples/state-points.json +0 -10474
- package/examples/temp-example-data.json +0 -130
- package/examples/test-dashboard-simple.json +0 -503
- package/examples/test-example.json +0 -752
- package/examples/test-file.json +0 -147
- package/examples/test.json +0 -752
- package/examples/testing.json +0 -94456
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
- /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
- /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
- /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
- /package/examples/api-test/{years.json → __data__/years.json} +0 -0
- /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
|
@@ -14,10 +14,10 @@ import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavi
|
|
|
14
14
|
import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
|
|
15
15
|
import * as filterResetHelpers from '../../helpers/filterResetHelpers'
|
|
16
16
|
import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
|
|
17
|
-
import './dashboardfilter.styles.css'
|
|
18
17
|
import { updateChildFilters } from '../../helpers/updateChildFilters'
|
|
19
18
|
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
20
19
|
import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
|
|
20
|
+
import { hasVisibleDashboardFiltersForIndexes } from '../../helpers/filterVisibility'
|
|
21
21
|
|
|
22
22
|
type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
|
|
23
23
|
|
|
@@ -193,7 +193,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
const newFilteredData = getFilteredData(clonedState)
|
|
196
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
196
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: { filteredData: newFilteredData } })
|
|
197
197
|
|
|
198
198
|
publishAnalyticsEvent({
|
|
199
199
|
vizType: dashboardConfig.type,
|
|
@@ -260,7 +260,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
260
260
|
newSharedFilters[index].queuedActive = value
|
|
261
261
|
|
|
262
262
|
// Don't clear data immediately - keep existing data until new data loads
|
|
263
|
-
// Only update the
|
|
263
|
+
// Only update the dashboard filters and prepare for reload
|
|
264
264
|
setAPIFilterDropdowns(loadingFilterMemo)
|
|
265
265
|
loadAPIFilters(newSharedFilters, loadingFilterMemo, undefined, undefined, isStale)
|
|
266
266
|
}
|
|
@@ -280,7 +280,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
const newFilteredData = getFilteredData(clonedState)
|
|
283
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
283
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: { filteredData: newFilteredData } })
|
|
284
284
|
dispatch({ type: 'SET_SHARED_FILTERS', payload: updatedFilters })
|
|
285
285
|
}
|
|
286
286
|
}
|
|
@@ -294,13 +294,28 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
294
294
|
})
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
?.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
const hasVisibleFilterControls = hasVisibleDashboardFiltersForIndexes(
|
|
298
|
+
dashboardConfig.dashboard.sharedFilters,
|
|
299
|
+
visualizationConfig?.sharedFilterIndexes
|
|
300
|
+
)
|
|
301
|
+
const filterControls = (
|
|
302
|
+
<Filters
|
|
303
|
+
show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
|
|
304
|
+
filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
|
|
305
|
+
apiFilterDropdowns={apiFilterDropdowns}
|
|
306
|
+
handleOnChange={handleOnChange}
|
|
307
|
+
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
308
|
+
filterIntro={visualizationConfig.filterIntro}
|
|
309
|
+
applyFilters={applyFilters}
|
|
310
|
+
applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
|
|
311
|
+
handleReset={
|
|
312
|
+
visualizationConfig.filterBehavior === FilterBehavior.Apply && (visualizationConfig.showClearButton ?? true)
|
|
313
|
+
? handleReset
|
|
314
|
+
: undefined
|
|
315
|
+
}
|
|
316
|
+
/>
|
|
317
|
+
)
|
|
318
|
+
if (!hasVisibleFilterControls && !isEditor) return <></>
|
|
304
319
|
return (
|
|
305
320
|
<VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
|
|
306
321
|
{isEditor && (
|
|
@@ -314,28 +329,18 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
314
329
|
</Sidebar>
|
|
315
330
|
)}
|
|
316
331
|
|
|
317
|
-
{
|
|
332
|
+
{hasVisibleFilterControls && (
|
|
318
333
|
<Responsive isEditor={isEditor}>
|
|
319
334
|
<div
|
|
320
335
|
className={`${
|
|
321
336
|
isEditor ? ' is-editor' : ''
|
|
322
337
|
} cove-visualization__inner cove-visualization__body col-12 cove-dashboard-filters-container`}
|
|
323
338
|
>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
330
|
-
applyFilters={applyFilters}
|
|
331
|
-
applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
|
|
332
|
-
handleReset={
|
|
333
|
-
visualizationConfig.filterBehavior === FilterBehavior.Apply &&
|
|
334
|
-
(visualizationConfig.showClearButton ?? true)
|
|
335
|
-
? handleReset
|
|
336
|
-
: undefined
|
|
337
|
-
}
|
|
338
|
-
/>
|
|
339
|
+
{visualizationConfig.visual?.grayBackground ? (
|
|
340
|
+
<div className='cdc-callout cdc-callout--dashboard-filters'>{filterControls}</div>
|
|
341
|
+
) : (
|
|
342
|
+
filterControls
|
|
343
|
+
)}
|
|
339
344
|
</div>
|
|
340
345
|
</Responsive>
|
|
341
346
|
)}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
|
-
.
|
|
2
|
-
:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
.dashboard-filters__form {
|
|
2
|
+
align-items: flex-end;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-wrap: wrap;
|
|
5
|
+
gap: 1rem 1.5rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.dashboard-filters__field {
|
|
9
|
+
margin: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.cove-dashboard-filters-container {
|
|
13
|
+
.cdc-callout--dashboard-filters {
|
|
14
|
+
--cdc-callout-background: #f4f8fa;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:is(label) {
|
|
18
|
+
font-size: var(--filter-label-font-size);
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
}
|
|
21
|
+
.btn {
|
|
22
|
+
align-self: flex-end;
|
|
23
|
+
/* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
|
|
24
|
+
height: calc(1.5em + 0.75rem + 2px);
|
|
25
|
+
}
|
|
26
|
+
.loading-filter {
|
|
27
|
+
position: relative;
|
|
28
|
+
.spinner-border {
|
|
29
|
+
height: 1.5rem;
|
|
30
|
+
position: absolute;
|
|
31
|
+
right: 10%;
|
|
32
|
+
top: 55%;
|
|
33
|
+
width: 1.5rem;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
:is(select):disabled {
|
|
37
|
+
background-color: var(--lightestGray);
|
|
38
|
+
& > :is(option) {
|
|
39
|
+
color: var(--darkGray);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -11,6 +11,7 @@ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
|
11
11
|
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
12
12
|
import { ConfigureData } from '@cdc/core/types/ConfigureData'
|
|
13
13
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
14
|
+
import { getColumnWidgetKeys } from '../helpers/dashboardColumnWidgets'
|
|
14
15
|
|
|
15
16
|
type DataDesignerModalProps = {
|
|
16
17
|
rowIndex: number
|
|
@@ -79,7 +80,7 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
const removeDatasetsFromVisualizations = () => {
|
|
82
|
-
const columnVisualizations = config.rows[rowIndex].columns.
|
|
83
|
+
const columnVisualizations = config.rows[rowIndex].columns.flatMap(column => getColumnWidgetKeys(column))
|
|
83
84
|
columnVisualizations.forEach(currentVisualizationKey => {
|
|
84
85
|
dispatch({ type: 'RESET_VISUALIZATION', payload: { vizKey: currentVisualizationKey } })
|
|
85
86
|
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
2
|
+
|
|
1
3
|
type ExpandCollapseButtonsProps = {
|
|
2
4
|
setAllExpanded: Function
|
|
3
5
|
}
|
|
@@ -6,12 +8,12 @@ const ExpandCollapseButtons: React.FC<ExpandCollapseButtonsProps> = ({ setAllExp
|
|
|
6
8
|
return (
|
|
7
9
|
<div className='d-block '>
|
|
8
10
|
<div className='d-flex flex-row-reverse mb-2'>
|
|
9
|
-
<
|
|
11
|
+
<Button variant='light' onClick={() => setAllExpanded(false)}>
|
|
10
12
|
- Collapse All
|
|
11
|
-
</
|
|
12
|
-
<
|
|
13
|
+
</Button>
|
|
14
|
+
<Button variant='light' className='me-2' onClick={() => setAllExpanded(true)}>
|
|
13
15
|
+ Expand All
|
|
14
|
-
</
|
|
16
|
+
</Button>
|
|
15
17
|
</div>
|
|
16
18
|
</div>
|
|
17
19
|
)
|
package/src/components/Grid.tsx
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import Row from './Row'
|
|
3
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
3
4
|
|
|
4
5
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
5
6
|
import { ConfigRow } from '../types/ConfigRow'
|
|
7
|
+
import { createCoveId } from '@cdc/core/helpers/createCoveId'
|
|
6
8
|
|
|
7
9
|
const Grid = () => {
|
|
8
10
|
const { config } = useContext(DashboardContext)
|
|
11
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
9
12
|
if (!config) return null
|
|
10
13
|
const rows = config.rows
|
|
11
|
-
const dispatch = useContext(DashboardDispatchContext)
|
|
12
14
|
const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
|
|
13
15
|
const addRow = () => {
|
|
14
|
-
const
|
|
16
|
+
const existingRowUuids = rows?.flatMap(row => (row.uuid === undefined ? [] : [row.uuid]))
|
|
17
|
+
const blankRow: Partial<ConfigRow> = {
|
|
18
|
+
columns: [{ width: 12 }],
|
|
19
|
+
uuid: createCoveId('row', { existingIds: existingRowUuids })
|
|
20
|
+
}
|
|
15
21
|
updateConfig({
|
|
16
22
|
...config,
|
|
17
|
-
rows: [...rows, blankRow]
|
|
18
|
-
uuid: Date.now()
|
|
23
|
+
rows: [...rows, blankRow]
|
|
19
24
|
})
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
return (
|
|
23
28
|
<div className='builder-grid'>
|
|
24
29
|
{(rows || []).map((row, idx) => (
|
|
25
|
-
<Row row={row} idx={idx} uuid={row.uuid} key={idx} />
|
|
30
|
+
<Row row={row} idx={idx} uuid={row.uuid ?? idx} key={idx} />
|
|
26
31
|
))}
|
|
27
|
-
<
|
|
32
|
+
<Button variant='primary' className='col' onClick={addRow}>
|
|
28
33
|
Add Row
|
|
29
|
-
</
|
|
34
|
+
</Button>
|
|
30
35
|
</div>
|
|
31
36
|
)
|
|
32
37
|
}
|
|
@@ -13,6 +13,8 @@ type HeaderProps = {
|
|
|
13
13
|
visualizationKey?: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
type DownloadImageMode = 'off' | 'button' | 'link'
|
|
17
|
+
|
|
16
18
|
const Header = (props: HeaderProps) => {
|
|
17
19
|
const tabs: Tab[] = ['Dashboard Description', 'Data Table Settings', 'Dashboard Preview']
|
|
18
20
|
const { visualizationKey, subEditor } = props
|
|
@@ -51,7 +53,7 @@ const Header = (props: HeaderProps) => {
|
|
|
51
53
|
return acc
|
|
52
54
|
}, {})
|
|
53
55
|
|
|
54
|
-
dispatch({ type: 'SET_DATA', payload: sampleDataRemoved })
|
|
56
|
+
dispatch({ type: 'SET_DATA', payload: { data: sampleDataRemoved } })
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -62,6 +64,18 @@ const Header = (props: HeaderProps) => {
|
|
|
62
64
|
dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
const getDownloadImageMode = (): DownloadImageMode => {
|
|
68
|
+
if (!config.table?.downloadImageButton) return 'off'
|
|
69
|
+
return config.table.downloadImageButtonStyle === 'link' ? 'link' : 'button'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const changeDownloadImageMode = (mode: DownloadImageMode) => {
|
|
73
|
+
const newConfig = { ...config, table: { ...(config.table || {}) } }
|
|
74
|
+
newConfig.table.downloadImageButton = mode !== 'off'
|
|
75
|
+
if (mode !== 'off') newConfig.table.downloadImageButtonStyle = mode
|
|
76
|
+
dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
const convertStateToConfig = () => {
|
|
66
80
|
const strippedState = cloneConfig(config)
|
|
67
81
|
delete strippedState.newViz
|
|
@@ -231,22 +245,27 @@ const Header = (props: HeaderProps) => {
|
|
|
231
245
|
/>
|
|
232
246
|
Show URL to Automatically Updated Data
|
|
233
247
|
</label>
|
|
234
|
-
<
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
<div className='download-image-controls'>
|
|
249
|
+
<select
|
|
250
|
+
aria-label='Download image display'
|
|
251
|
+
className='download-image-mode-select'
|
|
252
|
+
value={getDownloadImageMode()}
|
|
253
|
+
onChange={e => changeDownloadImageMode(e.target.value as DownloadImageMode)}
|
|
254
|
+
>
|
|
255
|
+
<option value='off'>Download Image Off</option>
|
|
256
|
+
<option value='button'>Download Image Button</option>
|
|
257
|
+
<option value='link'>Download Image Link</option>
|
|
258
|
+
</select>
|
|
259
|
+
{getDownloadImageMode() !== 'off' && (
|
|
260
|
+
<input
|
|
261
|
+
className='download-image-label-input'
|
|
262
|
+
type='text'
|
|
263
|
+
placeholder='Customize label'
|
|
264
|
+
defaultValue={config.table.downloadImageLabel}
|
|
265
|
+
onChange={e => changeConfigValue('table', 'downloadImageLabel', e.target.value)}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
250
269
|
</div>
|
|
251
270
|
</>
|
|
252
271
|
)}
|
|
@@ -1,140 +1,141 @@
|
|
|
1
|
-
import { createRef, useContext, useMemo, useState } from 'react'
|
|
2
|
-
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
-
import Modal from '@cdc/core/components/ui/Modal'
|
|
4
|
-
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
5
|
-
import '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
dispatch({ type: '
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
import { createRef, useContext, useMemo, useState } from 'react'
|
|
2
|
+
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
4
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
5
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
6
|
+
import './multiconfigtabs.styles.css'
|
|
7
|
+
|
|
8
|
+
const AreYouSure = deleteCallback => {
|
|
9
|
+
return (
|
|
10
|
+
<Modal>
|
|
11
|
+
<Modal.Content>
|
|
12
|
+
<p>Are you sure you want to delete this dashboard? </p>
|
|
13
|
+
<Button variant='danger' onClick={deleteCallback}>
|
|
14
|
+
DELETE
|
|
15
|
+
</Button>
|
|
16
|
+
</Modal.Content>
|
|
17
|
+
</Modal>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
22
|
+
const [editing, setEditing] = useState(false)
|
|
23
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
24
|
+
const { overlay } = useGlobalContext()
|
|
25
|
+
const inputRef = createRef<HTMLInputElement>()
|
|
26
|
+
|
|
27
|
+
const saveName = e => {
|
|
28
|
+
e.stopPropagation()
|
|
29
|
+
const newVal = inputRef.current.value
|
|
30
|
+
const sameName = newVal === name
|
|
31
|
+
const blankName = !newVal
|
|
32
|
+
const duplicateName = tabs.includes(newVal)
|
|
33
|
+
if (!sameName && !blankName && !duplicateName) {
|
|
34
|
+
dispatch({ type: 'RENAME_DASHBOARD_TAB', payload: { current: name, new: newVal } })
|
|
35
|
+
}
|
|
36
|
+
setEditing(false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const onClick = e => {
|
|
40
|
+
// ignore click on delete button
|
|
41
|
+
if (e.target.className === 'remove') return
|
|
42
|
+
if (active) {
|
|
43
|
+
setEditing(true)
|
|
44
|
+
} else {
|
|
45
|
+
handleClick()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleRemove = () => {
|
|
50
|
+
const deleteCallback = () => {
|
|
51
|
+
dispatch({ type: 'REMOVE_MULTIDASHBOARD_AT_INDEX', payload: index })
|
|
52
|
+
overlay?.actions.toggleOverlay(false)
|
|
53
|
+
}
|
|
54
|
+
overlay?.actions.openOverlay(AreYouSure(deleteCallback))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleReorder = (index: number, moveTo: -1 | 1) => {
|
|
58
|
+
const newIndex = index + moveTo
|
|
59
|
+
const inbounds = newIndex > -1 && newIndex <= tabs.length - 1
|
|
60
|
+
if (inbounds) {
|
|
61
|
+
dispatch({ type: 'REORDER_MULTIDASHBOARDS', payload: { currentIndex: index, newIndex: index + moveTo } })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const canMoveLeft = index !== 0
|
|
66
|
+
const canMoveRight = index <= tabs.length - 2
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<li className='nav-item d-flex mt-0'>
|
|
70
|
+
{canMoveLeft && editing && (
|
|
71
|
+
<button className='border-0' onClick={() => handleReorder(index, -1)}>
|
|
72
|
+
{'<'}
|
|
73
|
+
</button>
|
|
74
|
+
)}
|
|
75
|
+
<div
|
|
76
|
+
className={`edit nav-link${active ? ' active' : ''}`}
|
|
77
|
+
aria-current={active ? 'page' : null}
|
|
78
|
+
onClick={onClick}
|
|
79
|
+
>
|
|
80
|
+
{editing ? (
|
|
81
|
+
<div className='d-flex'>
|
|
82
|
+
<input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
|
|
83
|
+
<Button variant='link' className='save' onClick={saveName}>
|
|
84
|
+
save
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
<>
|
|
89
|
+
{name}
|
|
90
|
+
<Button variant='danger' className='border-0 ms-1' onClick={handleRemove}>
|
|
91
|
+
X
|
|
92
|
+
</Button>
|
|
93
|
+
</>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
{canMoveRight && editing && (
|
|
97
|
+
<button className='border-0' onClick={() => handleReorder(index, 1)}>
|
|
98
|
+
{'>'}
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</li>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const MultiConfigTabs = () => {
|
|
106
|
+
const { config } = useContext(DashboardContext)
|
|
107
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
108
|
+
const tabs = useMemo<string[]>(
|
|
109
|
+
() => (config.multiDashboards || []).map(({ label }) => label),
|
|
110
|
+
[config.multiDashboards]
|
|
111
|
+
)
|
|
112
|
+
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
113
|
+
|
|
114
|
+
const saveAndLoad = (indexToSwitchTo: number) => {
|
|
115
|
+
dispatch({ type: 'SAVE_CURRENT_CHANGES' })
|
|
116
|
+
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!config.multiDashboards) return null
|
|
120
|
+
return (
|
|
121
|
+
<ul className='nav nav-tabs multi-config-tabs mb-4'>
|
|
122
|
+
{tabs.map((tab, index) => (
|
|
123
|
+
<Tab
|
|
124
|
+
key={tab + index}
|
|
125
|
+
name={tab}
|
|
126
|
+
tabs={tabs}
|
|
127
|
+
index={index}
|
|
128
|
+
handleClick={() => saveAndLoad(index)}
|
|
129
|
+
active={index === activeTab}
|
|
130
|
+
/>
|
|
131
|
+
))}
|
|
132
|
+
<li className='nav-item'>
|
|
133
|
+
<button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
|
|
134
|
+
+
|
|
135
|
+
</button>
|
|
136
|
+
</li>
|
|
137
|
+
</ul>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default MultiConfigTabs
|