@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.
- package/dist/cdcdashboard.js +74365 -72646
- package/examples/all-components.json +529 -4607
- package/examples/dashboard-gallery.json +397 -397
- package/examples/private/DEV-10120.json +1294 -0
- package/examples/private/DEV-10527.json +564 -0
- package/examples/private/DEV-10586.json +54319 -0
- package/examples/private/DEV-10856.json +54319 -0
- package/examples/private/DEV-9989.json +229 -0
- package/examples/private/art-dashboard.json +2 -2
- package/examples/private/bird-flu-2.json +440 -0
- package/examples/private/bird-flu.json +413 -0
- package/examples/private/dashboard-config-ehdi.json +29915 -0
- package/examples/private/dashboard-map-filter.json +815 -0
- package/examples/private/dashboard-margins.js +15 -0
- package/examples/private/dataset.json +1452 -0
- package/examples/private/dev-10856-2.json +1348 -0
- package/examples/private/ehdi-data.json +29502 -0
- package/examples/private/exposure-source-h5-data.csv +26 -0
- package/examples/private/feelings.json +1 -0
- package/examples/private/nhis.json +1792 -0
- package/examples/private/workforce.json +2041 -0
- package/index.html +5 -8
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +5 -8
- package/src/CdcDashboardComponent.tsx +70 -60
- package/src/_stories/Dashboard.stories.tsx +63 -0
- package/src/_stories/_mock/dashboard-filter-asc.json +551 -0
- package/src/_stories/_mock/data-bite-dash-test.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1_1.json +1 -0
- package/src/_stories/_mock/data-bite-dash-test_1_1_1.json +1 -0
- package/src/components/CollapsibleVisualizationRow.tsx +3 -3
- package/src/components/Column.tsx +12 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +14 -9
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +23 -8
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +13 -3
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +130 -41
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +10 -7
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +11 -12
- package/src/components/DashboardFilters/dashboardfilter.styles.css +2 -2
- package/src/components/ExpandCollapseButtons.tsx +1 -1
- package/src/components/Header/Header.tsx +1 -2
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +2 -2
- package/src/components/MultiConfigTabs/MultiTabs.tsx +1 -1
- package/src/components/VisualizationRow.tsx +13 -3
- package/src/components/Widget.tsx +9 -3
- package/src/helpers/addValuesToDashboardFilters.ts +6 -5
- package/src/helpers/apiFilterHelpers.ts +11 -6
- package/src/helpers/changeFilterActive.ts +17 -4
- package/src/helpers/getFilteredData.ts +13 -4
- package/src/helpers/getUpdateConfig.ts +11 -4
- package/src/helpers/loadAPIFilters.ts +6 -4
- package/src/helpers/tests/updatesChildFilters.test.ts +56 -0
- package/src/helpers/updateChildFilters.ts +50 -0
- package/src/index.tsx +1 -0
- package/src/scss/main.scss +1 -15
- package/src/store/dashboard.actions.ts +2 -2
- package/src/store/dashboard.reducer.ts +60 -29
- package/src/types/DashboardConfig.ts +2 -0
- 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={`${
|
|
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.
|
|
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
|
|
126
|
-
|
|
131
|
+
const formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
|
|
127
132
|
return (
|
|
128
|
-
<div className={formGroupClass} key={`${
|
|
133
|
+
<div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
|
|
129
134
|
{label && (
|
|
130
|
-
<label className='font-weight-bold
|
|
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
|
-
|
|
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
|
|
64
|
-
value
|
|
65
|
-
value
|
|
66
|
-
value
|
|
67
|
-
value
|
|
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
|
)}
|
package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx
CHANGED
|
@@ -10,12 +10,22 @@ type DeleteFilterProps = {
|
|
|
10
10
|
filterIndex: number
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const DeleteFilterModal: React.FC<DeleteFilterProps> = ({
|
|
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
|
|
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> = ({
|
|
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
|
|
120
|
-
|
|
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 =>
|
|
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='
|
|
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) =>
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
updateFilterProp(name, value)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
|
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
|
|
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
|
|
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' : '
|
|
173
|
+
hideVisualization ? ' hide-parent-visualization' : hasMarginBottom ? ' mb-4' : ''
|
|
164
174
|
}`}
|
|
165
175
|
>
|
|
166
176
|
{row.toggle && !hideVisualization && (
|