@cdc/dashboard 4.24.11 → 4.24.12-2
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/dist/cdcdashboard.js +49442 -49230
- package/examples/ed-visits-county-file.json +141 -357
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-9199.json +606 -0
- package/examples/private/DEV-9684.json +2135 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +18174 -0
- package/examples/private/art-scratch.json +2406 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/gaza-issue.json +1214 -0
- package/examples/private/workforce.json +2041 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +8 -15
- package/src/CdcDashboardComponent.tsx +53 -38
- package/src/DashboardContext.tsx +2 -0
- package/src/components/CollapsibleVisualizationRow.tsx +8 -2
- package/src/components/DashboardFilters/DashboardFilters.tsx +107 -59
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +2 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +6 -2
- package/src/components/DashboardFilters/dashboardfilter.styles.css +16 -0
- package/src/components/VisualizationRow.tsx +30 -20
- package/src/components/Widget.tsx +1 -1
- package/src/data/initial-state.js +2 -1
- package/src/helpers/addValuesToDashboardFilters.ts +4 -2
- package/src/helpers/apiFilterHelpers.ts +55 -20
- package/src/helpers/changeFilterActive.ts +3 -0
- package/src/helpers/filterData.ts +1 -1
- package/src/helpers/loadAPIFilters.ts +25 -8
- package/src/helpers/reloadURLHelpers.ts +9 -2
- package/src/helpers/shouldLoadAllFilters.ts +30 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +8 -3
- package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
- package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
- package/src/store/dashboard.reducer.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/dashboard",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.12-2",
|
|
4
4
|
"description": "React component for combining multiple visualizations into a single dashboard",
|
|
5
5
|
"moduleName": "CdcDashboard",
|
|
6
6
|
"main": "dist/cdcdashboard",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cdc/chart": "^4.24.
|
|
31
|
-
"@cdc/core": "^4.24.
|
|
32
|
-
"@cdc/data-bite": "^4.24.
|
|
33
|
-
"@cdc/filtered-text": "^4.24.
|
|
34
|
-
"@cdc/map": "^4.24.
|
|
35
|
-
"@cdc/markup-include": "^4.24.
|
|
36
|
-
"@cdc/waffle-chart": "^4.24.
|
|
30
|
+
"@cdc/chart": "^4.24.12-2",
|
|
31
|
+
"@cdc/core": "^4.24.12-2",
|
|
32
|
+
"@cdc/data-bite": "^4.24.12-2",
|
|
33
|
+
"@cdc/filtered-text": "^4.24.12-2",
|
|
34
|
+
"@cdc/map": "^4.24.12-2",
|
|
35
|
+
"@cdc/markup-include": "^4.24.12-2",
|
|
36
|
+
"@cdc/waffle-chart": "^4.24.12-2",
|
|
37
37
|
"html-react-parser": "^3.0.8",
|
|
38
38
|
"js-base64": "^2.5.2",
|
|
39
39
|
"papaparse": "^5.3.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"react": "^18.2.0",
|
|
50
50
|
"react-dom": "^18.2.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "a60edf1148396309eb473ac9f65426ee40797ddf"
|
|
53
53
|
}
|
package/src/CdcDashboard.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import CdcDashboard from './CdcDashboardComponent'
|
|
|
3
3
|
import { MultiDashboardConfig } from './types/MultiDashboard'
|
|
4
4
|
import Loading from '@cdc/core/components/Loading'
|
|
5
5
|
import defaults from './data/initial-state'
|
|
6
|
-
import { processData } from './helpers/processData'
|
|
7
6
|
import { getVizKeys } from './helpers/getVizKeys'
|
|
8
7
|
import { processDataLegacy } from './helpers/processDataLegacy'
|
|
9
8
|
import { WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
@@ -13,7 +12,6 @@ import { InitialState } from './types/InitialState'
|
|
|
13
12
|
import { DashboardConfig } from './types/DashboardConfig'
|
|
14
13
|
import { coveUpdateWorker } from '@cdc/core/helpers/coveUpdateWorker'
|
|
15
14
|
import _ from 'lodash'
|
|
16
|
-
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
17
15
|
import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
|
|
18
16
|
|
|
19
17
|
type MultiDashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
@@ -62,18 +60,13 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
|
62
60
|
loadConfig()
|
|
63
61
|
}, [])
|
|
64
62
|
|
|
65
|
-
const
|
|
63
|
+
const prepareDatasets = (initialConfig: DashboardConfig | MultiDashboardConfig) => {
|
|
66
64
|
let newConfig = { ...initialConfig }
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const data = await processData(initialConfig.datasets[key], !hasApplyBehavior)
|
|
73
|
-
datasets[key] = data || []
|
|
74
|
-
})
|
|
75
|
-
)
|
|
76
|
-
|
|
65
|
+
const datasets: Record<string, Object[]> = Object.keys(initialConfig.datasets).reduce((acc, key) => {
|
|
66
|
+
const dataset = initialConfig.datasets[key]
|
|
67
|
+
acc[key] = dataset.formattedData || dataset.data
|
|
68
|
+
return acc
|
|
69
|
+
}, {})
|
|
77
70
|
getVizKeys(newConfig).forEach(vizKey => {
|
|
78
71
|
const formattedData = datasets[newConfig.visualizations[vizKey].dataKey]
|
|
79
72
|
if (formattedData) {
|
|
@@ -92,7 +85,7 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
|
92
85
|
let newConfig = { ...defaults, ...config } as DashboardConfig
|
|
93
86
|
|
|
94
87
|
if (config.datasets) {
|
|
95
|
-
return
|
|
88
|
+
return prepareDatasets(newConfig)
|
|
96
89
|
} else {
|
|
97
90
|
const dataKey = newConfig.dataFileName || 'backwards-compatibility'
|
|
98
91
|
const data = await processDataLegacy(config)
|
|
@@ -144,7 +137,7 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
|
144
137
|
multiDashboards: multiConfig.multiDashboards,
|
|
145
138
|
activeDashboard: selectedConfig
|
|
146
139
|
} as MultiDashboardConfig
|
|
147
|
-
return
|
|
140
|
+
return prepareDatasets(newConfig)
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
if (!initial) return <Loading />
|
|
@@ -43,13 +43,12 @@ import { type TableConfig } from '@cdc/core/components/DataTable/types/TableConf
|
|
|
43
43
|
|
|
44
44
|
// types
|
|
45
45
|
import { type SharedFilter } from './types/SharedFilter'
|
|
46
|
-
import { type APIFilter } from './types/APIFilter'
|
|
47
46
|
import { type WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
48
47
|
import { type InitialState } from './types/InitialState'
|
|
49
48
|
import MultiTabs from './components/MultiConfigTabs'
|
|
50
49
|
import _ from 'lodash'
|
|
51
50
|
import EditorContext from '../../editor/src/ConfigContext'
|
|
52
|
-
import { APIFilterDropdowns
|
|
51
|
+
import { APIFilterDropdowns } from './components/DashboardFilters'
|
|
53
52
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
54
53
|
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
55
54
|
import VisualizationRow from './components/VisualizationRow'
|
|
@@ -63,10 +62,10 @@ import { addValuesToDashboardFilters } from './helpers/addValuesToDashboardFilte
|
|
|
63
62
|
import { DashboardFilters } from './types/DashboardFilters'
|
|
64
63
|
import DashboardSharedFilters from './components/DashboardFilters'
|
|
65
64
|
import ExpandCollapseButtons from './components/ExpandCollapseButtons'
|
|
66
|
-
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
67
65
|
import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
|
|
68
66
|
import Loader from '@cdc/core/components/Loader'
|
|
69
67
|
import Alert from '@cdc/core/components/Alert'
|
|
68
|
+
import { shouldLoadAllFilters } from './helpers/shouldLoadAllFilters'
|
|
70
69
|
|
|
71
70
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
72
71
|
initialState: InitialState
|
|
@@ -85,7 +84,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
85
84
|
const isPreview = state.tabSelected === 'Dashboard Preview'
|
|
86
85
|
|
|
87
86
|
const inNoDataState = useMemo(() => {
|
|
88
|
-
const vals =
|
|
87
|
+
const vals = reloadURLHelpers.getDatasetKeys(state.config).map(key => state.data[key])
|
|
89
88
|
if (!vals.length) return true
|
|
90
89
|
return vals.some(val => val === undefined)
|
|
91
90
|
}, [state.data])
|
|
@@ -129,7 +128,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
129
128
|
filters.forEach(filter => {
|
|
130
129
|
if (
|
|
131
130
|
filter.type === 'urlfilter' &&
|
|
132
|
-
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations)
|
|
131
|
+
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations, config.rows)
|
|
133
132
|
) {
|
|
134
133
|
if (filter.filterBy === 'File Name') {
|
|
135
134
|
newFileName = reloadURLHelpers.getNewFileName(newFileName, filter, datasetKey)
|
|
@@ -146,10 +145,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
146
145
|
if (!!filter.setByQueryParameter) {
|
|
147
146
|
const windowQueryParams = Object.fromEntries(new URLSearchParams(window.location.search))
|
|
148
147
|
const filterValue = windowQueryParams[filter.setByQueryParameter]
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
updatedQSParams[filter.setByQueryParameter] = filterValue
|
|
148
|
+
const queryParam = filter.apiFilter?.valueSelector || filter.setByQueryParameter
|
|
149
|
+
if (filterValue) {
|
|
150
|
+
updatedQSParams[queryParam] = filterValue
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
153
|
|
|
@@ -162,7 +160,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
162
160
|
}
|
|
163
161
|
})
|
|
164
162
|
|
|
165
|
-
|
|
163
|
+
const dataNeedsUpdate = reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)
|
|
164
|
+
|
|
165
|
+
if (!!newFilters || dataNeedsUpdate) {
|
|
166
166
|
dataWasFetched = true
|
|
167
167
|
const dataUrlFinal = reloadURLHelpers.getDataURL(
|
|
168
168
|
{ ...currentQSParams, ...updatedQSParams },
|
|
@@ -189,6 +189,10 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
189
189
|
})
|
|
190
190
|
.catch(e => {
|
|
191
191
|
console.error(e)
|
|
192
|
+
dispatchErrorMessages({
|
|
193
|
+
type: 'ADD_ERROR_MESSAGE',
|
|
194
|
+
payload: 'There was a problem returning data. Please try again.'
|
|
195
|
+
})
|
|
192
196
|
newDatasets[datasetKey].data = []
|
|
193
197
|
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
194
198
|
newData[datasetKey] = []
|
|
@@ -197,16 +201,30 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
const datasetsWithFiles = _.pickBy(newDatasets, dataset => !dataset.dataUrl)
|
|
205
|
+
|
|
206
|
+
if (dataWasFetched || Object.keys(datasetsWithFiles).length) {
|
|
207
|
+
const dataFiles = Object.keys(datasetsWithFiles).reduce((acc, key) => {
|
|
208
|
+
acc[key] = datasetsWithFiles[key].data
|
|
209
|
+
return acc
|
|
210
|
+
}, {})
|
|
211
|
+
const _newData = { ...newData, ...dataFiles }
|
|
212
|
+
dispatch({ type: 'SET_DATA', payload: _newData })
|
|
213
|
+
const dataFilterIndexes = config.dashboard.sharedFilters.reduce((acc, filter, index) => {
|
|
214
|
+
if (filter.type === 'datafilter') acc.push(index)
|
|
215
|
+
return acc
|
|
216
|
+
}, [])
|
|
217
|
+
const appliedFilterIndexes = Object.values(config.visualizations)
|
|
218
|
+
.filter(viz => viz.type === 'dashboardFilters')
|
|
219
|
+
.flatMap(viz => viz.sharedFilterIndexes)
|
|
220
|
+
.filter(index => !dataFilterIndexes.includes(index))
|
|
221
|
+
const filtersWithNewValues = addValuesToDashboardFilters(filters, _newData, appliedFilterIndexes)
|
|
222
|
+
|
|
223
|
+
const dashboardConfig = { ...config.dashboard, sharedFilters: filtersWithNewValues }
|
|
206
224
|
const filteredData = getFilteredData(
|
|
207
225
|
{ ...state, config: { ...state.config, dashboard: dashboardConfig } },
|
|
208
226
|
{},
|
|
209
|
-
|
|
227
|
+
_newData
|
|
210
228
|
)
|
|
211
229
|
dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
|
|
212
230
|
const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(config.visualizations, newData)
|
|
@@ -246,15 +264,16 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
246
264
|
}
|
|
247
265
|
|
|
248
266
|
useEffect(() => {
|
|
249
|
-
if (isEditor && !isPreview) return
|
|
250
267
|
const { config } = state
|
|
251
|
-
|
|
252
|
-
reloadURLData()
|
|
253
|
-
}
|
|
254
|
-
|
|
268
|
+
const loadAllFilters = shouldLoadAllFilters(config, isEditor && !isPreview)
|
|
255
269
|
const sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
|
|
256
|
-
|
|
257
|
-
|
|
270
|
+
|
|
271
|
+
loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns, loadAllFilters).then(newFilters => {
|
|
272
|
+
const allValuesSelected = newFilters.every(filter => {
|
|
273
|
+
return filter.type === 'datafilter' || filter.active
|
|
274
|
+
})
|
|
275
|
+
if (allValuesSelected) reloadURLData(newFilters)
|
|
276
|
+
})
|
|
258
277
|
}, [isEditor, isPreview, state.config?.activeDashboard])
|
|
259
278
|
|
|
260
279
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
@@ -278,13 +297,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
278
297
|
}
|
|
279
298
|
}
|
|
280
299
|
|
|
281
|
-
const updateFilteredData = (sharedFilters = undefined) => {
|
|
282
|
-
const clonedState = _.cloneDeep(state)
|
|
283
|
-
if (sharedFilters) clonedState.config.dashboard.sharedFilters = sharedFilters
|
|
284
|
-
const newFilteredData = getFilteredData(clonedState)
|
|
285
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
286
|
-
}
|
|
287
|
-
|
|
288
300
|
const resizeObserver = new ResizeObserver(entries => {
|
|
289
301
|
for (let entry of entries) {
|
|
290
302
|
let newViewport = getViewport(entry.contentRect.width)
|
|
@@ -437,8 +449,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
437
449
|
)
|
|
438
450
|
break
|
|
439
451
|
case 'dashboardFilters':
|
|
440
|
-
|
|
441
|
-
body = !hideFilter ? (
|
|
452
|
+
body = (
|
|
442
453
|
<>
|
|
443
454
|
<Header visualizationKey={visualizationKey} subEditor='Filter Dropdowns' />
|
|
444
455
|
<DashboardSharedFilters
|
|
@@ -448,8 +459,6 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
448
459
|
setConfig={_updateConfig}
|
|
449
460
|
/>
|
|
450
461
|
</>
|
|
451
|
-
) : (
|
|
452
|
-
<></>
|
|
453
462
|
)
|
|
454
463
|
break
|
|
455
464
|
case 'table':
|
|
@@ -469,11 +478,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
469
478
|
body = (
|
|
470
479
|
<>
|
|
471
480
|
<Header visualizationKey={visualizationKey} subEditor='Footnotes' />
|
|
481
|
+
{/* Datasets are passed in just for reference and need to be removed */}
|
|
472
482
|
<FootnotesStandAlone
|
|
473
483
|
visualizationKey={visualizationKey}
|
|
474
484
|
config={{ ...visualizationConfig, datasets: state.config.datasets }}
|
|
475
485
|
isEditor={true}
|
|
476
|
-
updateConfig={_updateConfig}
|
|
486
|
+
updateConfig={conf => _updateConfig(_.omit(conf, 'datasets'))}
|
|
477
487
|
/>
|
|
478
488
|
</>
|
|
479
489
|
)
|
|
@@ -542,7 +552,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
542
552
|
return (
|
|
543
553
|
<>
|
|
544
554
|
{/* Expand/Collapse All */}
|
|
545
|
-
{row.expandCollapseAllButtons === true && (
|
|
555
|
+
{!inNoDataState && row.expandCollapseAllButtons === true && (
|
|
546
556
|
<ExpandCollapseButtons setAllExpanded={setAllExpanded} />
|
|
547
557
|
)}
|
|
548
558
|
{Object.keys(dataGroups).map(groupName => {
|
|
@@ -559,6 +569,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
559
569
|
updateChildConfig={updateChildConfig}
|
|
560
570
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
561
571
|
currentViewport={currentViewport}
|
|
572
|
+
inNoDataState={inNoDataState}
|
|
562
573
|
/>
|
|
563
574
|
)
|
|
564
575
|
})}
|
|
@@ -576,11 +587,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
576
587
|
updateChildConfig={updateChildConfig}
|
|
577
588
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
578
589
|
currentViewport={currentViewport}
|
|
590
|
+
inNoDataState={inNoDataState}
|
|
579
591
|
/>
|
|
580
592
|
)
|
|
581
593
|
}
|
|
582
594
|
})}
|
|
583
595
|
|
|
596
|
+
{inNoDataState ? <div className='mt-5'>Please complete your selection to continue.</div> : <></>}
|
|
597
|
+
|
|
584
598
|
{/* Image or PDF Inserts */}
|
|
585
599
|
<section className='download-buttons'>
|
|
586
600
|
{config.table?.downloadImageButton && (
|
|
@@ -691,7 +705,8 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
691
705
|
isDebug,
|
|
692
706
|
loadAPIFilters,
|
|
693
707
|
setAPIFilterDropdowns,
|
|
694
|
-
reloadURLData
|
|
708
|
+
reloadURLData,
|
|
709
|
+
setAPILoading
|
|
695
710
|
}}
|
|
696
711
|
>
|
|
697
712
|
<DashboardDispatchContext.Provider value={dispatch}>
|
package/src/DashboardContext.tsx
CHANGED
|
@@ -17,6 +17,7 @@ type ConfigCTX = DashboardState & {
|
|
|
17
17
|
recursiveLimit?: number
|
|
18
18
|
) => Promise<SharedFilter[]>
|
|
19
19
|
setAPIFilterDropdowns: (dropdowns: APIFilterDropdowns) => void
|
|
20
|
+
setAPILoading: (loading: boolean) => void
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const firstTab: Tab = 'Dashboard Description'
|
|
@@ -33,6 +34,7 @@ const initialContext: ConfigCTX = {
|
|
|
33
34
|
outerContainerRef: () => {},
|
|
34
35
|
setParentConfig: () => {},
|
|
35
36
|
setAPIFilterDropdowns: () => {},
|
|
37
|
+
setAPILoading: () => {},
|
|
36
38
|
reloadURLData: () => {},
|
|
37
39
|
loadAPIFilters: () => Promise.resolve([]),
|
|
38
40
|
isDebug: false,
|
|
@@ -8,10 +8,16 @@ type CollapsableVizRow = {
|
|
|
8
8
|
groupName: string
|
|
9
9
|
currentViewport: string
|
|
10
10
|
}
|
|
11
|
-
const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
|
|
11
|
+
const CollapsibleVisualizationRow: React.FC<CollapsableVizRow> = ({
|
|
12
|
+
allExpanded,
|
|
13
|
+
fontSize,
|
|
14
|
+
groupName,
|
|
15
|
+
currentViewport,
|
|
16
|
+
children
|
|
17
|
+
}) => {
|
|
12
18
|
const [isExpanded, setIsExpanded] = useState(allExpanded)
|
|
13
19
|
const fontSizes = { small: 16, medium: 18, large: 20 }
|
|
14
|
-
const titleFontSize = ['
|
|
20
|
+
const titleFontSize = ['xs', 'xxs'].includes(currentViewport) ? '13px' : `${fontSizes[fontSize]}px`
|
|
15
21
|
|
|
16
22
|
useEffect(() => {
|
|
17
23
|
setIsExpanded(allExpanded)
|
|
@@ -6,6 +6,8 @@ import { FILTER_STYLE } from '../../types/FilterStyles'
|
|
|
6
6
|
import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
|
|
7
7
|
import NestedDropdown from '@cdc/core/components/NestedDropdown'
|
|
8
8
|
import { MouseEventHandler } from 'react'
|
|
9
|
+
import Loader from '@cdc/core/components/Loader'
|
|
10
|
+
import _ from 'lodash'
|
|
9
11
|
|
|
10
12
|
type DashboardFilterProps = {
|
|
11
13
|
show: number[]
|
|
@@ -48,25 +50,31 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
48
50
|
<form className='d-flex flex-wrap'>
|
|
49
51
|
{sharedFilters.map((filter, filterIndex) => {
|
|
50
52
|
const urlFilterType = filter.type === 'urlfilter'
|
|
53
|
+
const label = filter.key
|
|
54
|
+
|
|
51
55
|
if (
|
|
52
56
|
(!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
|
|
53
57
|
(show && !show.includes(filterIndex))
|
|
54
58
|
)
|
|
55
|
-
return <React.Fragment key={`${
|
|
59
|
+
return <React.Fragment key={`${label}-filtersection-${filterIndex}-option`} />
|
|
56
60
|
const values: JSX.Element[] = []
|
|
57
61
|
|
|
58
|
-
if (filter.resetLabel) {
|
|
59
|
-
values.push(
|
|
60
|
-
<option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
|
|
61
|
-
{filter.resetLabel}
|
|
62
|
-
</option>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
const _key = filter.apiFilter?.apiEndpoint
|
|
63
|
+
const loading = apiFilterDropdowns[_key] === null
|
|
67
64
|
|
|
68
65
|
const multiValues: { value; label }[] = []
|
|
66
|
+
const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
|
|
67
|
+
([key, data]) => [
|
|
68
|
+
[key, key], // Main option: [value, text]
|
|
69
|
+
Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
|
|
70
|
+
]
|
|
71
|
+
)
|
|
69
72
|
|
|
73
|
+
const activeSubGroupValue = _.get(
|
|
74
|
+
filter?.subGrouping?.valuesLookup,
|
|
75
|
+
[filter?.active as string, 'values', 0],
|
|
76
|
+
null // Default to null if the path is invalid
|
|
77
|
+
)
|
|
70
78
|
if (_key && apiFilterDropdowns[_key]) {
|
|
71
79
|
// URL Filter
|
|
72
80
|
if (filter.filterStyle !== FILTER_STYLE.nestedDropdown) {
|
|
@@ -83,64 +91,104 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
83
91
|
// Data Filter
|
|
84
92
|
filter.values?.forEach((filterOption, index) => {
|
|
85
93
|
const labeledOpt = filter.labels && filter.labels[filterOption]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
const resetLabelHasMatch = (filterOption || labeledOpt) === filter.resetLabel
|
|
95
|
+
|
|
96
|
+
if (!resetLabelHasMatch) {
|
|
97
|
+
values.push(
|
|
98
|
+
<option key={`${filter.key}-option-${index}`} value={filterOption}>
|
|
99
|
+
{labeledOpt || filterOption}
|
|
100
|
+
</option>
|
|
101
|
+
)
|
|
102
|
+
} else {
|
|
103
|
+
// add label to the front of list if it matches with reset label
|
|
104
|
+
values.unshift(
|
|
105
|
+
<option key={`${filter.key}-option-${index}`} value={filterOption}>
|
|
106
|
+
{labeledOpt || filterOption}
|
|
107
|
+
</option>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
91
111
|
multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
|
|
92
112
|
})
|
|
93
113
|
}
|
|
94
114
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
115
|
+
const isDisabled = !values.length
|
|
116
|
+
// push reset label only if it does not includes in filter values options
|
|
117
|
+
if (filter.resetLabel && !filter.values.includes(filter.resetLabel)) {
|
|
118
|
+
values.unshift(
|
|
119
|
+
<option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
|
|
120
|
+
{filter.resetLabel}
|
|
121
|
+
</option>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const formGroupClass = `form-group mr-3 mb-1${loading ? ' loading-filter' : ''}`
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className={formGroupClass} key={`${label}-filtersection-${filterIndex}`}>
|
|
129
|
+
{label && (
|
|
130
|
+
<label className='font-weight-bold mt-1 mb-0' htmlFor={`filter-${filterIndex}`}>
|
|
131
|
+
{label}
|
|
132
|
+
</label>
|
|
133
|
+
)}
|
|
134
|
+
{filter.filterStyle === FILTER_STYLE.multiSelect ? (
|
|
135
|
+
<MultiSelect
|
|
136
|
+
label={label}
|
|
137
|
+
options={multiValues}
|
|
138
|
+
fieldName={filterIndex}
|
|
139
|
+
updateField={updateField}
|
|
140
|
+
selected={filter.active as string[]}
|
|
141
|
+
limit={filter.selectLimit || 5}
|
|
142
|
+
loading={loading}
|
|
143
|
+
/>
|
|
144
|
+
) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
|
|
145
|
+
<NestedDropdown
|
|
146
|
+
activeGroup={filter.active as string}
|
|
147
|
+
activeSubGroup={_key ? filter.subGrouping?.active : activeSubGroupValue}
|
|
148
|
+
filterIndex={filterIndex}
|
|
149
|
+
options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
|
|
150
|
+
listLabel={label}
|
|
151
|
+
handleSelectedItems={value => updateField(null, null, filterIndex, value)}
|
|
152
|
+
loading={loading}
|
|
153
|
+
/>
|
|
154
|
+
) : (
|
|
155
|
+
<>
|
|
156
|
+
<select
|
|
157
|
+
id={`filter-${filterIndex}`}
|
|
158
|
+
className='cove-form-select'
|
|
159
|
+
data-index='0'
|
|
160
|
+
value={loading ? 'Loading...' : filter.queuedActive || filter.active}
|
|
161
|
+
onChange={val => {
|
|
162
|
+
handleOnChange(filterIndex, val.target.value)
|
|
163
|
+
}}
|
|
164
|
+
disabled={loading || isDisabled}
|
|
165
|
+
>
|
|
166
|
+
{loading && <option value='Loading...'>Loading...</option>}
|
|
167
|
+
{nullVal(filter) && (
|
|
168
|
+
<option key={`select`} value=''>
|
|
169
|
+
{filter.resetLabel || '- Select -'}
|
|
170
|
+
</option>
|
|
171
|
+
)}
|
|
172
|
+
{values}
|
|
173
|
+
</select>
|
|
174
|
+
{loading && <Loader spinnerType={'text-secondary'} />}
|
|
175
|
+
</>
|
|
176
|
+
)}
|
|
139
177
|
</div>
|
|
140
178
|
)
|
|
141
179
|
})}
|
|
142
180
|
{showSubmit && (
|
|
143
|
-
<button
|
|
181
|
+
<button
|
|
182
|
+
className='btn btn-primary mb-1'
|
|
183
|
+
onClick={applyFilters}
|
|
184
|
+
disabled={show.some(filterIndex => {
|
|
185
|
+
const emptyFilterValues = [undefined, '', '- Select -']
|
|
186
|
+
return (
|
|
187
|
+
emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
|
|
188
|
+
emptyFilterValues.includes(sharedFilters[filterIndex].active)
|
|
189
|
+
)
|
|
190
|
+
})}
|
|
191
|
+
>
|
|
144
192
|
{applyFiltersButtonText || 'GO!'}
|
|
145
193
|
</button>
|
|
146
194
|
)}
|
|
@@ -274,7 +274,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
274
274
|
</select>
|
|
275
275
|
</label>
|
|
276
276
|
) : (
|
|
277
|
+
|
|
277
278
|
<button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
|
|
279
|
+
|
|
278
280
|
Add Existing Dashboard Filter
|
|
279
281
|
</button>
|
|
280
282
|
)}
|