@cdc/dashboard 4.24.12 → 4.25.2-25

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.
Files changed (60) hide show
  1. package/dist/cdcdashboard.js +74365 -72646
  2. package/examples/all-components.json +529 -4607
  3. package/examples/dashboard-gallery.json +397 -397
  4. package/examples/private/DEV-10120.json +1294 -0
  5. package/examples/private/DEV-10527.json +564 -0
  6. package/examples/private/DEV-10586.json +54319 -0
  7. package/examples/private/DEV-10856.json +54319 -0
  8. package/examples/private/DEV-9989.json +229 -0
  9. package/examples/private/art-dashboard.json +2 -2
  10. package/examples/private/bird-flu-2.json +440 -0
  11. package/examples/private/bird-flu.json +413 -0
  12. package/examples/private/dashboard-config-ehdi.json +29915 -0
  13. package/examples/private/dashboard-map-filter.json +815 -0
  14. package/examples/private/dashboard-margins.js +15 -0
  15. package/examples/private/dataset.json +1452 -0
  16. package/examples/private/dev-10856-2.json +1348 -0
  17. package/examples/private/ehdi-data.json +29502 -0
  18. package/examples/private/exposure-source-h5-data.csv +26 -0
  19. package/examples/private/feelings.json +1 -0
  20. package/examples/private/nhis.json +1792 -0
  21. package/examples/private/workforce.json +2041 -0
  22. package/index.html +5 -8
  23. package/package.json +9 -9
  24. package/src/CdcDashboard.tsx +5 -8
  25. package/src/CdcDashboardComponent.tsx +70 -60
  26. package/src/_stories/Dashboard.stories.tsx +63 -0
  27. package/src/_stories/_mock/dashboard-filter-asc.json +551 -0
  28. package/src/_stories/_mock/data-bite-dash-test.json +1 -0
  29. package/src/_stories/_mock/data-bite-dash-test_1.json +1 -0
  30. package/src/_stories/_mock/data-bite-dash-test_1_1.json +1 -0
  31. package/src/_stories/_mock/data-bite-dash-test_1_1_1.json +1 -0
  32. package/src/components/CollapsibleVisualizationRow.tsx +3 -3
  33. package/src/components/Column.tsx +12 -1
  34. package/src/components/DashboardFilters/DashboardFilters.tsx +14 -9
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +23 -8
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +13 -3
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +130 -41
  38. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -7
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -12
  40. package/src/components/DashboardFilters/dashboardfilter.styles.css +2 -2
  41. package/src/components/ExpandCollapseButtons.tsx +1 -1
  42. package/src/components/Header/Header.tsx +1 -2
  43. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +2 -2
  44. package/src/components/MultiConfigTabs/MultiTabs.tsx +1 -1
  45. package/src/components/VisualizationRow.tsx +13 -3
  46. package/src/components/Widget.tsx +9 -3
  47. package/src/helpers/addValuesToDashboardFilters.ts +6 -5
  48. package/src/helpers/apiFilterHelpers.ts +11 -6
  49. package/src/helpers/changeFilterActive.ts +17 -4
  50. package/src/helpers/getFilteredData.ts +13 -4
  51. package/src/helpers/getUpdateConfig.ts +11 -4
  52. package/src/helpers/loadAPIFilters.ts +6 -4
  53. package/src/helpers/tests/updatesChildFilters.test.ts +56 -0
  54. package/src/helpers/updateChildFilters.ts +50 -0
  55. package/src/index.tsx +1 -0
  56. package/src/scss/main.scss +1 -15
  57. package/src/store/dashboard.actions.ts +2 -2
  58. package/src/store/dashboard.reducer.ts +60 -29
  59. package/src/types/DashboardConfig.ts +2 -0
  60. package/src/types/SharedFilter.ts +1 -1
@@ -37,6 +37,11 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
37
37
  handleOnChange(fieldName, value) // fieldName is the sharedFilterIndex
38
38
  }
39
39
 
40
+ const stripDuplicateLabelIncrement = (label: string): string => {
41
+ // converts 'Label (1)' to 'Label'
42
+ return label.replace(/\s\(\d+\)$/, '')
43
+ }
44
+
40
45
  const getNestedDropdownOptions = (options?: DropdownOptions): NestedOptions => {
41
46
  if (!options) return []
42
47
  const getValueTextTuple = (value: string, text?: string): ValueTextPair => (text ? [value, text] : [value])
@@ -50,13 +55,13 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
50
55
  <form className='d-flex flex-wrap'>
51
56
  {sharedFilters.map((filter, filterIndex) => {
52
57
  const urlFilterType = filter.type === 'urlfilter'
53
- const label = filter.key
58
+ const label = stripDuplicateLabelIncrement(filter.key || '')
54
59
 
55
60
  if (
56
61
  (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
57
62
  (show && !show.includes(filterIndex))
58
63
  )
59
- return <React.Fragment key={`${label}-filtersection-${filterIndex}-option`} />
64
+ return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
60
65
  const values: JSX.Element[] = []
61
66
 
62
67
  const _key = filter.apiFilter?.apiEndpoint
@@ -89,7 +94,8 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
89
94
  }
90
95
  } else {
91
96
  // Data Filter
92
- filter.values?.forEach((filterOption, index) => {
97
+ const orderedFilterValues = filter.orderedValues || filter.values
98
+ orderedFilterValues?.forEach((filterOption, index) => {
93
99
  const labeledOpt = filter.labels && filter.labels[filterOption]
94
100
  const resetLabelHasMatch = (filterOption || labeledOpt) === filter.resetLabel
95
101
 
@@ -122,12 +128,11 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
122
128
  )
123
129
  }
124
130
 
125
- const formGroupClass = `form-group mr-3 mb-1${loading ? ' loading-filter' : ''}`
126
-
131
+ const formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
127
132
  return (
128
- <div className={formGroupClass} key={`${label}-filtersection-${filterIndex}`}>
133
+ <div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
129
134
  {label && (
130
- <label className='font-weight-bold mt-1 mb-0' htmlFor={`filter-${filterIndex}`}>
135
+ <label className='font-weight-bold mb-2' htmlFor={`filter-${filterIndex}`}>
131
136
  {label}
132
137
  </label>
133
138
  )}
@@ -143,8 +148,8 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
143
148
  />
144
149
  ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
145
150
  <NestedDropdown
146
- activeGroup={filter.active as string}
147
- activeSubGroup={_key ? filter.subGrouping?.active : activeSubGroupValue}
151
+ activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
152
+ activeSubGroup={_key ? filter.queuedActive?.[1] || filter.subGrouping?.active : activeSubGroupValue}
148
153
  filterIndex={filterIndex}
149
154
  options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
150
155
  listLabel={label}
@@ -20,6 +20,7 @@ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
20
20
  import DeleteFilterModal from './components/DeleteFilterModal'
21
21
  import { addValuesToDashboardFilters } from '../../../helpers/addValuesToDashboardFilters'
22
22
  import { FILTER_STYLE } from '../../../types/FilterStyles'
23
+ import { handleSorting } from '@cdc/core/components/Filters'
23
24
 
24
25
  type DashboardFitlersEditorProps = {
25
26
  vizConfig: DashboardFilters
@@ -40,7 +41,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
40
41
  Number
41
42
  )
42
43
  return config.dashboard.sharedFilters
43
- .map<[number, string]>(({ key }, i) => [i, key])
44
+ ?.map<[number, string]>(({ key }, i) => [i, key])
44
45
  .filter(([filterIndex]) => !sharedFilterIndexes.includes(filterIndex)) // filter out already added filters
45
46
  .map(([filterIndex, filterName]) => (
46
47
  <option key={filterIndex} value={filterIndex}>{`${filterIndex} - ${filterName}`}</option>
@@ -60,11 +61,11 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
60
61
  subgroupTextSelector: oldSubgroupTextSelector
61
62
  } = sharedFilters[index].apiFilter || {}
62
63
  const apiFilterChanged =
63
- value.apiEndpoint !== oldEndpoint ||
64
- value.valueSelector !== oldValueSelector ||
65
- value.textSelector !== oldTextSelector ||
66
- value.subgroupValueSelector !== oldSubgroupValueSelector ||
67
- value.subgroupTextSelector !== oldSubgroupTextSelector
64
+ value?.apiEndpoint !== oldEndpoint ||
65
+ value?.valueSelector !== oldValueSelector ||
66
+ value?.textSelector !== oldTextSelector ||
67
+ value?.subgroupValueSelector !== oldSubgroupValueSelector ||
68
+ value?.subgroupTextSelector !== oldSubgroupTextSelector
68
69
 
69
70
  newSharedFilters[index][prop] = value
70
71
  if (prop === 'columnName') {
@@ -98,10 +99,22 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
98
99
  // automatically dispatches SET_SHARED_FILTERS
99
100
  loadAPIFilters(newSharedFilters, {})
100
101
  } else {
102
+ handleSorting(newSharedFilters[index])
101
103
  dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
102
104
  }
103
105
  }
104
106
 
107
+ const toggleNestedQueryParameters = (index, checked: boolean) => {
108
+ const newSharedFilters = _.cloneDeep(sharedFilters)
109
+ const filter = newSharedFilters[index]
110
+ const isUrlFilter = filter.type === 'urlfilter'
111
+ const groupColumnName = isUrlFilter ? filter.apiFilter.valueSelector : filter.columnName
112
+ const subGroupColumnName = isUrlFilter ? filter.apiFilter.subgroupValueSelector : filter.subGrouping.columnName
113
+ filter.setByQueryParameter = checked ? groupColumnName : undefined
114
+ filter.subGrouping.setByQueryParameter = checked ? subGroupColumnName : undefined
115
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
116
+ }
117
+
105
118
  const removeFilter = index => {
106
119
  const newSharedFilters = _.cloneDeep(sharedFilters)
107
120
 
@@ -228,9 +241,13 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
228
241
  >
229
242
  <FilterEditor
230
243
  filter={filter}
244
+ filterIndex={index}
231
245
  updateFilterProp={(name, value) => {
232
246
  updateFilterProp(name, index, value)
233
247
  }}
248
+ toggleNestedQueryParameters={checked => {
249
+ toggleNestedQueryParameters(index, checked)
250
+ }}
234
251
  config={config}
235
252
  />
236
253
  </FieldSetWrapper>
@@ -274,9 +291,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
274
291
  </select>
275
292
  </label>
276
293
  ) : (
277
-
278
294
  <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
279
-
280
295
  Add Existing Dashboard Filter
281
296
  </button>
282
297
  )}
@@ -10,12 +10,22 @@ type DeleteFilterProps = {
10
10
  filterIndex: number
11
11
  }
12
12
 
13
- const DeleteFilterModal: React.FC<DeleteFilterProps> = ({ removeFilterCompletely, removeFilterFromViz, filterIndex }) => {
13
+ const DeleteFilterModal: React.FC<DeleteFilterProps> = ({
14
+ removeFilterCompletely,
15
+ removeFilterFromViz,
16
+ filterIndex
17
+ }) => {
14
18
  const { overlay } = useGlobalContext()
15
19
  const { config } = useContext(DashboardContext)
16
- const filterUsedByMany = Object.values(config.visualizations).filter(viz => (viz as DashboardFilters).sharedFilterIndexes?.map(Number).includes(Number(filterIndex))).length > 1
17
20
 
18
- const message = filterUsedByMany ? 'This filter is used by multiple visualizations. You can either delete the filter from this visualization only or you can delete the filter completely, which will also remove it from other visualizations.' : 'Are you sure you want to delete this filter?'
21
+ const filterUsedByMany =
22
+ Object.values(config.visualizations).filter(viz => {
23
+ return (viz as DashboardFilters).sharedFilterIndexes?.map(Number).includes(Number(filterIndex))
24
+ }).length > 1
25
+
26
+ const message = filterUsedByMany
27
+ ? 'This filter is used by multiple visualizations. You can either delete the filter from this visualization only or you can delete the filter completely, which will also remove it from other visualizations.'
28
+ : 'Are you sure you want to delete this filter?'
19
29
  return (
20
30
  <Modal showClose={true}>
21
31
  <Modal.Content>
@@ -1,10 +1,16 @@
1
1
  import _ from 'lodash'
2
2
  import { APIFilter } from '../../../../types/APIFilter'
3
3
  import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
4
- import { TextField } from '@cdc/core/components/EditorPanel/Inputs'
4
+ import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
5
5
  import DataTransform from '@cdc/core/helpers/DataTransform'
6
6
  import { useEffect, useMemo, useState } from 'react'
7
7
  import { SharedFilter } from '../../../../types/SharedFilter'
8
+
9
+ // Add defaultValue to SharedFilter type
10
+ interface SharedFilter {
11
+ defaultValue?: string
12
+ resetLabel?: string
13
+ }
8
14
  import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
9
15
  import Tooltip from '@cdc/core/components/ui/Tooltip'
10
16
  import Icon from '@cdc/core/components/ui/Icon'
@@ -14,14 +20,24 @@ import { Visualization } from '@cdc/core/types/Visualization'
14
20
  import { hasDashboardApplyBehavior } from '../../../../helpers/hasDashboardApplyBehavior'
15
21
  import NestedDropDownDashboard from './NestedDropDownDashboard'
16
22
  import { FILTER_STYLE } from '../../../../types/FilterStyles'
23
+ import { filterOrderOptions } from '@cdc/core/components/Filters'
24
+ import FilterOrder from '@cdc/core/components/EditorPanel/VizFilterEditor/components/FilterOrder'
17
25
 
18
26
  type FilterEditorProps = {
19
27
  config: DashboardConfig
20
28
  filter: SharedFilter
29
+ filterIndex: number
21
30
  updateFilterProp: (name: keyof SharedFilter, value: any) => void
31
+ toggleNestedQueryParameters: (checked: boolean) => void
22
32
  }
23
33
 
24
- const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilterProp }) => {
34
+ const FilterEditor: React.FC<FilterEditorProps> = ({
35
+ filter,
36
+ filterIndex,
37
+ config,
38
+ updateFilterProp,
39
+ toggleNestedQueryParameters
40
+ }) => {
25
41
  const [columns, setColumns] = useState<string[]>([])
26
42
  const transform = new DataTransform()
27
43
  const filterStyles = Object.values(FILTER_STYLE)
@@ -59,6 +75,11 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
59
75
  return [nameLookup, [...vizOptions, ...rowsNotSelected]]
60
76
  }, [config.visualizations, filter.usedBy, filter.setBy, vizRowColumnLocator])
61
77
 
78
+ const useParameters = useMemo(() => {
79
+ if (filter.subGrouping) return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
80
+ return !!filter.setByQueryParameter
81
+ }, [config, filterIndex])
82
+
62
83
  const loadColumnData = async () => {
63
84
  const columns = {}
64
85
  const dataKeys = Object.keys(config.datasets)
@@ -94,21 +115,6 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
94
115
  loadColumnData()
95
116
  }, [config.datasets])
96
117
 
97
- const addFilterUsedBy = (filter, value) => {
98
- if (value === '') return
99
- if (!filter.usedBy) filter.usedBy = []
100
- filter.usedBy.push(value)
101
- updateFilterProp('usedBy', filter.usedBy)
102
- }
103
-
104
- const removeFilterUsedBy = (filter, value) => {
105
- let usedByIndex = filter.usedBy.indexOf(value)
106
- if (usedByIndex !== -1) {
107
- filter.usedBy.splice(usedByIndex, 1)
108
- updateFilterProp('usedBy', filter.usedBy)
109
- }
110
- }
111
-
112
118
  const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
113
119
  const filterClone = _.cloneDeep(filter)
114
120
  const _filter = filterClone.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
@@ -116,8 +122,12 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
116
122
  updateFilterProp('apiFilter', newAPIFilter)
117
123
  }
118
124
 
119
- const handleFilterStyleChange = value => {
120
- updateFilterProp('filterStyle', value)
125
+ const updateLabel = (value: string) => {
126
+ const duplicateLabels = config.dashboard.sharedFilters.filter(
127
+ (filter, i) => filter.key === value && filterIndex !== i
128
+ )
129
+ // If there are duplicate labels, append the number of duplicates to the label similar functionality to duplicate file names
130
+ updateFilterProp('key', duplicateLabels.length ? value + ` (${duplicateLabels.length})` : value)
121
131
  }
122
132
 
123
133
  const isNestedDropDown = filter.filterStyle === FILTER_STYLE.nestedDropdown
@@ -215,7 +225,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
215
225
  <span className='edit-label column-heading'>Filter Style: </span>
216
226
  <select
217
227
  value={filter.filterStyle || FILTER_STYLE.dropdown}
218
- onChange={e => handleFilterStyleChange(e.target.value)}
228
+ onChange={e => updateFilterProp('filterStyle', e.target.value)}
219
229
  >
220
230
  {filterStyles.map(dataKey => (
221
231
  <option value={dataKey} key={`filter-style-select-item-${dataKey}`}>
@@ -226,7 +236,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
226
236
  </label>
227
237
  {filter.filterStyle === FILTER_STYLE.dropdown && (
228
238
  <label>
229
- <span className='mr-1'>Show Dropdown</span>
239
+ <span className='me-1'>Show Dropdown</span>
230
240
  <input
231
241
  type='checkbox'
232
242
  checked={filter.showDropdown}
@@ -240,7 +250,9 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
240
250
  <TextField
241
251
  label='Label'
242
252
  value={filter.key}
243
- updateField={(_section, _subSection, _key, value) => updateFilterProp('key', value)}
253
+ updateField={(_section, _subSection, _key, value) => {
254
+ updateLabel(value)
255
+ }}
244
256
  />
245
257
  {filter.filterStyle === FILTER_STYLE.multiSelect && (
246
258
  <TextField
@@ -361,6 +373,30 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
361
373
 
362
374
  {isNestedDropDown && <APIInputs isSubgroup={true} />}
363
375
 
376
+ <label>
377
+ <input
378
+ type='checkbox'
379
+ checked={useParameters}
380
+ aria-label='Create query parameters'
381
+ disabled={!filter.apiFilter?.valueSelector && !filter.apiFilter?.subgroupValueSelector}
382
+ onChange={e => toggleNestedQueryParameters(e.target.checked)}
383
+ />
384
+ <span>
385
+ {' '}
386
+ Create query parameters{' '}
387
+ <Tooltip style={{ textTransform: 'none' }}>
388
+ <Tooltip.Target>
389
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
390
+ </Tooltip.Target>
391
+ <Tooltip.Content>
392
+ <p>
393
+ Query parameters will be added to the URL which correspond to the respective value selector.
394
+ </p>
395
+ </Tooltip.Content>
396
+ </Tooltip>
397
+ </span>
398
+ </label>
399
+
364
400
  {!!parentFilters.length && (
365
401
  <label>
366
402
  <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
@@ -409,12 +445,6 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
409
445
  value={filter.resetLabel || ''}
410
446
  updateField={(_section, _subSection, _key, value) => updateFilterProp('resetLabel', value)}
411
447
  />
412
-
413
- <TextField
414
- label='Default Value Set By Query String Parameter: '
415
- value={filter.setByQueryParameter || ''}
416
- updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
417
- />
418
448
  </>
419
449
  )}
420
450
 
@@ -439,6 +469,38 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
439
469
  </select>
440
470
  </label>
441
471
 
472
+ <Select
473
+ value={filter.defaultValue}
474
+ options={
475
+ filter.resetLabel
476
+ ? [filter.resetLabel, ...config.dashboard.sharedFilters[filterIndex].values]
477
+ : config.dashboard.sharedFilters[filterIndex].values
478
+ }
479
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('defaultValue', value)}
480
+ label={'Filter Default Value'}
481
+ initial={'Select'}
482
+ />
483
+
484
+ <Select
485
+ value={filter.order || 'asc'}
486
+ options={filterOrderOptions}
487
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('order', value)}
488
+ label={'Filter Order'}
489
+ />
490
+
491
+ {/* if custom order is set use react-dnd library to sort the values */}
492
+ {filter.order === 'cust' && (
493
+ <FilterOrder
494
+ orderedValues={filter.orderedValues || filter.values}
495
+ handleFilterOrder={(index1, index2) => {
496
+ const values = [...filter.values]
497
+ const [removed] = values.splice(index1, 1)
498
+ values.splice(index2, 0, removed)
499
+ updateFilterProp('orderedValues', values)
500
+ }}
501
+ />
502
+ )}
503
+
442
504
  <label>
443
505
  <span className='edit-label column-heading'>Show Dropdown</span>
444
506
  <input
@@ -451,14 +513,39 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
451
513
  </label>
452
514
  </>
453
515
  ) : (
454
- <NestedDropDownDashboard
455
- filter={filter}
456
- updateFilterProp={(name, value) => {
457
- updateFilterProp(name, value)
458
- }}
459
- isDashboard={true}
460
- config={config}
461
- />
516
+ <>
517
+ <NestedDropDownDashboard
518
+ filter={filter}
519
+ updateFilterProp={(name, value) => {
520
+ updateFilterProp(name, value)
521
+ }}
522
+ isDashboard={true}
523
+ config={config}
524
+ />
525
+ <label>
526
+ <input
527
+ type='checkbox'
528
+ checked={useParameters}
529
+ aria-label='Create query parameters'
530
+ disabled={!filter.columnName || !filter.subGrouping?.columnName}
531
+ onChange={e => toggleNestedQueryParameters(e.target.checked)}
532
+ />
533
+ <span>
534
+ {' '}
535
+ Create query parameters{' '}
536
+ <Tooltip style={{ textTransform: 'none' }}>
537
+ <Tooltip.Target>
538
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
539
+ </Tooltip.Target>
540
+ <Tooltip.Content>
541
+ <p>
542
+ Query parameters will be added to the URL which correspond to the respective column name.
543
+ </p>
544
+ </Tooltip.Content>
545
+ </Tooltip>
546
+ </span>
547
+ </label>
548
+ </>
462
549
  )}
463
550
  <label>
464
551
  <span className='edit-label column-heading'>Set By: </span>
@@ -527,11 +614,13 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
527
614
  </select>
528
615
  </label>
529
616
 
530
- <TextField
531
- label='Default Value Set By Query String Parameter: '
532
- value={filter.setByQueryParameter || ''}
533
- updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
534
- />
617
+ {!isNestedDropDown && (
618
+ <TextField
619
+ label='Default Value Set By Query String Parameter: '
620
+ value={filter.setByQueryParameter || ''}
621
+ updateField={(_section, _subSection, _key, value) => updateFilterProp('setByQueryParameter', value)}
622
+ />
623
+ )}
535
624
  </>
536
625
  )}
537
626
  </>
@@ -22,13 +22,16 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
22
22
  const datasets = Object.keys(config.datasets)
23
23
  const columnNameOptionsInDataset = []
24
24
  datasets.map(datasetKey => {
25
- const columnNamesInDataset = Object.keys(config.datasets[datasetKey].data[0])
26
- columnNamesInDataset.forEach(columnName =>
27
- columnNameOptionsInDataset.push({
28
- datasetKey,
29
- columnName
30
- })
31
- )
25
+ const data = config.datasets[datasetKey].data
26
+ if (data) {
27
+ const columnNamesInDataset = Object.keys(data[0])
28
+ columnNamesInDataset.forEach(columnName =>
29
+ columnNameOptionsInDataset.push({
30
+ datasetKey,
31
+ columnName
32
+ })
33
+ )
34
+ }
32
35
  })
33
36
 
34
37
  const subGroupingColumnNameOptions = []
@@ -14,6 +14,7 @@ import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavi
14
14
  import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
15
  import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
16
  import './dashboardfilter.styles.css'
17
+ import { updateChildFilters } from '../../helpers/updateChildFilters'
17
18
 
18
19
  type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
19
20
 
@@ -64,17 +65,15 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
64
65
  const queryParams = getQueryParams()
65
66
  let needsQueryUpdate = false
66
67
  dashboardConfig.sharedFilters.forEach(sharedFilter => {
67
- if (sharedFilter.queuedActive) {
68
- applyQueuedActive(sharedFilter)
69
- if (
70
- sharedFilter.setByQueryParameter &&
71
- queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
72
- ) {
73
- queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
74
- ? sharedFilter.active.join(',')
75
- : sharedFilter.active
76
- needsQueryUpdate = true
77
- }
68
+ if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
69
+ if (
70
+ sharedFilter.setByQueryParameter &&
71
+ queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
72
+ ) {
73
+ queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
74
+ ? sharedFilter.active.join(',')
75
+ : sharedFilter.active
76
+ needsQueryUpdate = true
78
77
  }
79
78
  })
80
79
 
@@ -183,7 +182,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
183
182
  >
184
183
  <Filters
185
184
  show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
186
- filters={dashboardConfig.dashboard.sharedFilters || []}
185
+ filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
187
186
  apiFilterDropdowns={apiFilterDropdowns}
188
187
  handleOnChange={handleOnChange}
189
188
  showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
@@ -1,7 +1,7 @@
1
1
  .cove-dashboard-filters-container {
2
2
  :is(label) {
3
- margin-bottom: 0;
4
- margin-top: 0.5rem;
3
+ font-size: var(--filter-label-font-size);
4
+ font-weight: 700;
5
5
  }
6
6
  .btn {
7
7
  /* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
@@ -9,7 +9,7 @@ const ExpandCollapseButtons: React.FC<ExpandCollapseButtonsProps> = ({ setAllExp
9
9
  <button className='btn expand-collapse-buttons' onClick={() => setAllExpanded(false)}>
10
10
  - Collapse All
11
11
  </button>
12
- <button className='btn expand-collapse-buttons mr-2' onClick={() => setAllExpanded(true)}>
12
+ <button className='btn expand-collapse-buttons me-2' onClick={() => setAllExpanded(true)}>
13
13
  + Expand All
14
14
  </button>
15
15
  </div>
@@ -6,7 +6,6 @@ import './index.scss'
6
6
  import MultiConfigTabs from '../MultiConfigTabs'
7
7
  import { Tab } from '../../types/Tab'
8
8
  import _ from 'lodash'
9
- import { getVizRowColumnLocator } from '../../helpers/getVizRowColumnLocator'
10
9
 
11
10
  type HeaderProps = {
12
11
  back?: any
@@ -29,7 +28,7 @@ const Header = (props: HeaderProps) => {
29
28
  // the Widget component will do a data fetch if no data is available for the visualization
30
29
  // this is intended to help visualization developers.
31
30
  type SampleData = Record<string, { sample: boolean }> & Object[]
32
- if (Object.values(data).some((d: SampleData) => d.sample)) {
31
+ if (Object.values(data).some((d: SampleData) => d?.sample)) {
33
32
  const sampleDataRemoved = Object.keys(data).reduce((acc, key) => {
34
33
  if ((data[key] as SampleData).sample) {
35
34
  acc[key] = []
@@ -86,7 +86,7 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
86
86
  ) : (
87
87
  <>
88
88
  {name}
89
- <button className='btn btn-danger border-0 ml-1' onClick={handleRemove}>
89
+ <button className='btn btn-danger border-0 ms-1' onClick={handleRemove}>
90
90
  X
91
91
  </button>
92
92
  </>
@@ -117,7 +117,7 @@ const MultiConfigTabs = () => {
117
117
 
118
118
  if (!config.multiDashboards) return null
119
119
  return (
120
- <ul className='nav nav-tabs multi-config-tabs'>
120
+ <ul className='nav nav-tabs multi-config-tabs mb-4'>
121
121
  {tabs.map((tab, index) => (
122
122
  <Tab
123
123
  key={tab + index}
@@ -21,7 +21,7 @@ const MultiTabs = () => {
21
21
 
22
22
  if (!config.multiDashboards) return null
23
23
  return (
24
- <ul className='nav nav-tabs multi-config-tabs'>
24
+ <ul className='nav nav-tabs multi-config-tabs mb-4'>
25
25
  {tabs.map((tab, index) => (
26
26
  <li key={tab + index} className='nav-item'>
27
27
  <a
@@ -3,7 +3,6 @@ import React, { useContext, useMemo } from 'react'
3
3
  import Toggle from './Toggle'
4
4
  import _ from 'lodash'
5
5
  import { ConfigRow } from '../types/ConfigRow'
6
- import CdcChart from '@cdc/chart/src/CdcChart'
7
6
  import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
8
7
  import CdcMap from '@cdc/map/src/CdcMap'
9
8
  import CdcWaffleChart from '@cdc/waffle-chart/src/CdcWaffleChart'
@@ -18,6 +17,7 @@ import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAl
18
17
  import CollapsibleVisualizationRow from './CollapsibleVisualizationRow'
19
18
  import { DashboardFilters } from '../types/DashboardFilters'
20
19
  import { hasDashboardApplyBehavior } from '../helpers/hasDashboardApplyBehavior'
20
+ import CdcChart from '@cdc/chart/src/CdcChartComponent'
21
21
 
22
22
  type VisualizationWrapperProps = {
23
23
  allExpanded: boolean
@@ -68,6 +68,7 @@ type VizRowProps = {
68
68
  updateChildConfig: Function
69
69
  apiFilterDropdowns: APIFilterDropdowns
70
70
  currentViewport: ViewPort
71
+ isLastRow: boolean
71
72
  }
72
73
 
73
74
  const VisualizationRow: React.FC<VizRowProps> = ({
@@ -80,7 +81,8 @@ const VisualizationRow: React.FC<VizRowProps> = ({
80
81
  setSharedFilter,
81
82
  updateChildConfig,
82
83
  apiFilterDropdowns,
83
- currentViewport
84
+ currentViewport,
85
+ isLastRow
84
86
  }) => {
85
87
  const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
86
88
  const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
@@ -156,11 +158,19 @@ const VisualizationRow: React.FC<VizRowProps> = ({
156
158
 
157
159
  const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
158
160
 
161
+ const hiddenDashboardFilters =
162
+ visualizationConfig.type === 'dashboardFilters' &&
163
+ visualizationConfig.sharedFilterIndexes &&
164
+ visualizationConfig.sharedFilterIndexes.filter(
165
+ idx => config.dashboard.sharedFilters?.[idx]?.showDropdown === false
166
+ ).length === visualizationConfig.sharedFilterIndexes.length
167
+ const hasMarginBottom = !isLastRow && !hiddenDashboardFilters
168
+
159
169
  return (
160
170
  <div
161
171
  key={`vis__${index}__${colIndex}`}
162
172
  className={`col-12 col-md-${col.width}${!shouldShow ? ' d-none' : ''}${
163
- hideVisualization ? ' hide-parent-visualization' : ' mt-5 p-1'
173
+ hideVisualization ? ' hide-parent-visualization' : hasMarginBottom ? ' mb-4' : ''
164
174
  }`}
165
175
  >
166
176
  {row.toggle && !hideVisualization && (