@cdc/core 4.24.9 → 4.24.11
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/LICENSE +201 -0
- package/assets/icon-combo-chart.svg +1 -0
- package/assets/icon-epi-chart.svg +27 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
- package/components/Alert/components/Alert.tsx +34 -8
- package/components/BlurStrokeText.tsx +44 -0
- package/components/DataTable/DataTable.tsx +62 -36
- package/components/DataTable/DataTableStandAlone.tsx +37 -6
- package/components/DataTable/components/ChartHeader.tsx +31 -26
- package/components/DataTable/components/MapHeader.tsx +19 -10
- package/components/DataTable/components/SortIcon/index.tsx +25 -0
- package/components/DataTable/components/SortIcon/sort-icon.css +21 -0
- package/{styles/_data-table.scss → components/DataTable/data-table.css} +250 -298
- package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
- package/components/DataTable/helpers/customSort.ts +11 -15
- package/components/DataTable/helpers/getChartCellValue.ts +23 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +5 -1
- package/components/DataTable/helpers/getNewSortBy.ts +35 -0
- package/components/DataTable/helpers/tests/customSort.test.ts +52 -0
- package/components/DataTable/helpers/tests/getNewSortBy.test.ts +26 -0
- package/components/EditorPanel/ColumnsEditor.tsx +81 -36
- package/components/EditorPanel/DataTableEditor.tsx +149 -43
- package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
- package/components/EditorPanel/Inputs.tsx +68 -20
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +25 -7
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
- package/components/{Filters.tsx → Filters/Filters.tsx} +60 -43
- package/components/Filters/helpers/applyQueuedActive.ts +12 -0
- package/components/Filters/helpers/getNestedOptions.ts +29 -0
- package/components/Filters/helpers/handleSorting.ts +18 -0
- package/components/Filters/helpers/tests/applyQueuedActive.test.ts +49 -0
- package/components/Filters/helpers/tests/getNestedOptions.test.ts +93 -0
- package/components/Filters/helpers/tests/handleSorting.test.ts +68 -0
- package/components/Filters/index.ts +5 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +1 -7
- package/components/Layout/components/Visualization/visualizations.scss +1 -1
- package/components/Legend/Legend.Gradient.tsx +44 -36
- package/components/Loader/Loader.tsx +33 -0
- package/components/Loader/index.ts +1 -0
- package/components/Loader/loader.styles.css +13 -0
- package/components/MultiSelect/MultiSelect.tsx +85 -62
- package/components/MultiSelect/multiselect.styles.css +10 -7
- package/components/NestedDropdown/NestedDropdown.tsx +118 -56
- package/components/NestedDropdown/nestedDropdownHelpers.ts +34 -0
- package/components/NestedDropdown/nesteddropdown.styles.css +22 -13
- package/components/NestedDropdown/tests/nestedDropdownHelpers.test.ts +58 -0
- package/components/Table/Table.tsx +102 -34
- package/components/Table/components/GroupRow.tsx +1 -1
- package/components/_stories/BlurStrokeTest.stories.tsx +27 -0
- package/components/_stories/DataTable.stories.tsx +14 -0
- package/components/_stories/Filters.stories.tsx +57 -0
- package/components/_stories/NestedDropdown.stories.tsx +22 -46
- package/components/_stories/_mocks/DataTable/no-data.json +108 -0
- package/components/_stories/_mocks/nested-dropdown.json +30 -0
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Icon.tsx +19 -6
- package/components/ui/{Tooltip.jsx → Tooltip.tsx} +38 -14
- package/data/colorPalettes.js +107 -10
- package/dist/cove-main.css +6080 -0
- package/dist/cove-main.css.map +1 -0
- package/helpers/DataTransform.ts +2 -1
- package/helpers/addValuesToFilters.ts +8 -3
- package/helpers/cove/{number.js → number.ts} +62 -27
- package/helpers/coveUpdateWorker.ts +6 -7
- package/helpers/fetchRemoteData.js +32 -37
- package/helpers/formatConfigBeforeSave.ts +17 -1
- package/helpers/gatherQueryParams.ts +12 -2
- package/helpers/pivotData.ts +52 -11
- package/helpers/queryStringUtils.ts +6 -0
- package/helpers/tests/gatherQueryParams.test.ts +34 -0
- package/helpers/tests/pivotData.test.ts +50 -0
- package/helpers/useDataVizClasses.ts +42 -20
- package/helpers/ver/4.24.10.ts +47 -0
- package/helpers/ver/4.24.9.ts +0 -3
- package/helpers/ver/tests/4.24.10.test.ts +45 -0
- package/helpers/viewports.ts +9 -0
- package/package.json +7 -3
- package/styles/_button-section.scss +5 -1
- package/styles/_global-variables.scss +20 -2
- package/styles/_global.scss +22 -30
- package/styles/_reset.scss +2 -26
- package/styles/base.scss +0 -1
- package/styles/cove-main.scss +6 -0
- package/styles/filters.scss +6 -26
- package/styles/v2/base/_reset.scss +0 -7
- package/styles/v2/components/editor.scss +0 -4
- package/styles/v2/components/icon.scss +1 -1
- package/styles/v2/components/ui/tooltip.scss +42 -40
- package/styles/v2/layout/_component.scss +0 -6
- package/styles/v2/layout/index.scss +0 -1
- package/types/Axis.ts +4 -0
- package/types/BoxPlot.ts +5 -3
- package/types/Color.ts +1 -1
- package/types/General.ts +1 -0
- package/types/Legend.ts +1 -2
- package/types/MarkupInclude.ts +1 -0
- package/types/Runtime.ts +3 -1
- package/types/Series.ts +8 -1
- package/types/Table.ts +3 -2
- package/types/Visualization.ts +19 -8
- package/types/VizFilter.ts +2 -1
- package/components/DataTable/components/Icons.tsx +0 -10
- package/components/_stories/EditorPanel.stories.tsx +0 -54
- package/components/_stories/Layout.Debug.stories.tsx +0 -91
- package/components/ui/Select.jsx +0 -30
- package/helpers/getGradientLegendWidth.ts +0 -15
|
@@ -17,6 +17,7 @@ export type TextFieldProps = {
|
|
|
17
17
|
value: string | number
|
|
18
18
|
type?: 'text' | 'number' | 'textarea' | 'date'
|
|
19
19
|
min?: number
|
|
20
|
+
maxLength?: number
|
|
20
21
|
max?: number
|
|
21
22
|
i?: number
|
|
22
23
|
id?: string
|
|
@@ -27,20 +28,24 @@ export type CheckboxProps = {
|
|
|
27
28
|
min?: number
|
|
28
29
|
i?: number
|
|
29
30
|
className?: string
|
|
30
|
-
} & Input
|
|
31
|
-
|
|
32
|
-
export type SelectProps = {
|
|
33
|
-
value?: string
|
|
34
|
-
options?: string[]
|
|
35
|
-
required?: boolean
|
|
36
|
-
initial?: string
|
|
37
|
-
|
|
38
|
-
// all other props
|
|
39
|
-
[x: string]: any
|
|
40
|
-
} & Input
|
|
31
|
+
} & Input &
|
|
32
|
+
Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'>
|
|
41
33
|
|
|
42
34
|
const TextField = memo((props: TextFieldProps) => {
|
|
43
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
display = true,
|
|
37
|
+
label,
|
|
38
|
+
tooltip,
|
|
39
|
+
section = null,
|
|
40
|
+
subsection = null,
|
|
41
|
+
fieldName,
|
|
42
|
+
updateField,
|
|
43
|
+
value: stateValue,
|
|
44
|
+
type = 'text',
|
|
45
|
+
i = null,
|
|
46
|
+
min = null,
|
|
47
|
+
...attributes
|
|
48
|
+
} = props
|
|
44
49
|
const [value, setValue] = useState(stateValue)
|
|
45
50
|
const [debouncedValue] = useDebounce(value, 500)
|
|
46
51
|
|
|
@@ -93,7 +98,17 @@ const TextField = memo((props: TextFieldProps) => {
|
|
|
93
98
|
})
|
|
94
99
|
|
|
95
100
|
const CheckBox = memo((props: CheckboxProps) => {
|
|
96
|
-
const {
|
|
101
|
+
const {
|
|
102
|
+
display = true,
|
|
103
|
+
label,
|
|
104
|
+
value,
|
|
105
|
+
fieldName,
|
|
106
|
+
section = null,
|
|
107
|
+
subsection = null,
|
|
108
|
+
tooltip,
|
|
109
|
+
updateField,
|
|
110
|
+
...attributes
|
|
111
|
+
} = props
|
|
97
112
|
if (!display) {
|
|
98
113
|
return <></>
|
|
99
114
|
}
|
|
@@ -116,13 +131,46 @@ const CheckBox = memo((props: CheckboxProps) => {
|
|
|
116
131
|
)
|
|
117
132
|
})
|
|
118
133
|
|
|
134
|
+
export type SelectProps = {
|
|
135
|
+
value?: string
|
|
136
|
+
options?: string[] | { label: string; value: string }[]
|
|
137
|
+
required?: boolean
|
|
138
|
+
initial?: string
|
|
139
|
+
|
|
140
|
+
// all other props
|
|
141
|
+
[x: string]: any
|
|
142
|
+
} & Input
|
|
143
|
+
|
|
119
144
|
const Select = memo((props: SelectProps) => {
|
|
120
|
-
const {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
const {
|
|
146
|
+
display = true,
|
|
147
|
+
label,
|
|
148
|
+
value,
|
|
149
|
+
options,
|
|
150
|
+
fieldName,
|
|
151
|
+
section = null,
|
|
152
|
+
subsection = null,
|
|
153
|
+
required = false,
|
|
154
|
+
tooltip,
|
|
155
|
+
updateField,
|
|
156
|
+
initial: initialValue,
|
|
157
|
+
...attributes
|
|
158
|
+
} = props
|
|
159
|
+
const optionsJsx = options.map((option, index) => {
|
|
160
|
+
if (typeof option === 'string') {
|
|
161
|
+
return (
|
|
162
|
+
<option value={option} key={index}>
|
|
163
|
+
{option}
|
|
164
|
+
</option>
|
|
165
|
+
)
|
|
166
|
+
} else {
|
|
167
|
+
return (
|
|
168
|
+
<option value={option.value} key={index}>
|
|
169
|
+
{option.label}
|
|
170
|
+
</option>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
126
174
|
|
|
127
175
|
if (initialValue) {
|
|
128
176
|
optionsJsx.unshift(
|
|
@@ -142,7 +190,7 @@ const Select = memo((props: SelectProps) => {
|
|
|
142
190
|
{tooltip}
|
|
143
191
|
</span>
|
|
144
192
|
<select
|
|
145
|
-
className={required && !value ? 'warning' : ''}
|
|
193
|
+
className={`cove-form-select ${required && !value ? 'warning' : ''}`}
|
|
146
194
|
name={fieldName}
|
|
147
195
|
value={value}
|
|
148
196
|
onChange={event => {
|
|
@@ -3,6 +3,7 @@ import { SubGrouping, VizFilter, OrderBy } from '../../../types/VizFilter'
|
|
|
3
3
|
import { filterOrderOptions, handleSorting } from '../../Filters'
|
|
4
4
|
import FilterOrder from './components/FilterOrder'
|
|
5
5
|
import { Visualization } from '../../../types/Visualization'
|
|
6
|
+
import { useMemo } from 'react'
|
|
6
7
|
|
|
7
8
|
type NestedDropdownEditorProps = {
|
|
8
9
|
config: Visualization
|
|
@@ -121,6 +122,25 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
121
122
|
|
|
122
123
|
const columnNameOptions = dataColumns.filter(columnName => !listOfUsedColumnNames.includes(columnName))
|
|
123
124
|
|
|
125
|
+
const useParameters = useMemo(() => {
|
|
126
|
+
const filter = config.filters[filterIndex]
|
|
127
|
+
return !!(filter.setByQueryParameter && filter.subGrouping?.setByQueryParameter)
|
|
128
|
+
}, [config, filterIndex])
|
|
129
|
+
|
|
130
|
+
const handleParametersCheckboxClick = e => {
|
|
131
|
+
const updatedFilters = config.filters
|
|
132
|
+
const { checked } = e.target
|
|
133
|
+
const groupColumnName = checked ? filter.columnName : ''
|
|
134
|
+
const subGroupColumnName = checked ? subGrouping.columnName : ''
|
|
135
|
+
updatedFilters[filterIndex] = {
|
|
136
|
+
...config.filters[filterIndex],
|
|
137
|
+
setByQueryParameter: groupColumnName,
|
|
138
|
+
subGrouping: { ...subGrouping, setByQueryParameter: subGroupColumnName }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
updateField(null, null, 'filters', updatedFilters)
|
|
142
|
+
}
|
|
143
|
+
|
|
124
144
|
return (
|
|
125
145
|
<div className='nesteddropdown-editor'>
|
|
126
146
|
<label>
|
|
@@ -175,15 +195,13 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
175
195
|
<label>
|
|
176
196
|
<input
|
|
177
197
|
type='checkbox'
|
|
178
|
-
checked={
|
|
198
|
+
checked={useParameters}
|
|
179
199
|
aria-label='Create query parameters'
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter: subGrouping.columnName })
|
|
183
|
-
}}
|
|
200
|
+
disabled={!filter.columnName || !subGrouping?.columnName}
|
|
201
|
+
onChange={e => handleParametersCheckboxClick(e)}
|
|
184
202
|
/>
|
|
185
203
|
<span> Create query parameters</span>
|
|
186
|
-
{
|
|
204
|
+
{useParameters && (
|
|
187
205
|
<>
|
|
188
206
|
<span className='edit-label column-heading mt-2'>
|
|
189
207
|
Grouping: Default Value Set By Query String Parameter
|
|
@@ -200,7 +218,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
|
200
218
|
</span>
|
|
201
219
|
<input
|
|
202
220
|
type='text'
|
|
203
|
-
value={subGrouping
|
|
221
|
+
value={subGrouping?.setByQueryParameter}
|
|
204
222
|
onChange={e => {
|
|
205
223
|
const setByQueryParameter = e.target.value
|
|
206
224
|
updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter })
|
|
@@ -140,43 +140,24 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
140
140
|
controls={openControls}
|
|
141
141
|
deleteField={() => removeFilter(filterIndex)}
|
|
142
142
|
>
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
{filterStyleOptions.map((item, index) => {
|
|
153
|
-
return (
|
|
154
|
-
<option key={`filter-style-${index}`} value={item}>
|
|
155
|
-
{item}
|
|
156
|
-
</option>
|
|
157
|
-
)
|
|
158
|
-
})}
|
|
159
|
-
</select>
|
|
160
|
-
</label>
|
|
143
|
+
<Select
|
|
144
|
+
value={filter.filterStyle}
|
|
145
|
+
fieldName='filterStyle'
|
|
146
|
+
label='Filter Style'
|
|
147
|
+
updateField={(_section, _subsection, _field, value) => updateFilterStyle(filterIndex, value)}
|
|
148
|
+
options={filterStyleOptions}
|
|
149
|
+
/>
|
|
161
150
|
|
|
162
151
|
{filter.filterStyle !== 'nested-dropdown' ? (
|
|
163
152
|
<>
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<option value=''>- Select Option -</option>
|
|
173
|
-
{dataColumns.map((dataKey, filterIndex) => (
|
|
174
|
-
<option value={dataKey} key={filterIndex}>
|
|
175
|
-
{dataKey}
|
|
176
|
-
</option>
|
|
177
|
-
))}
|
|
178
|
-
</select>
|
|
179
|
-
</label>
|
|
153
|
+
<Select
|
|
154
|
+
value={filter.columnName}
|
|
155
|
+
fieldName='columnName'
|
|
156
|
+
label='Filter'
|
|
157
|
+
updateField={(_section, _subsection, _field, value) => handleNameChange(filterIndex, value)}
|
|
158
|
+
options={dataColumns}
|
|
159
|
+
initial='- Select Option -'
|
|
160
|
+
/>
|
|
180
161
|
|
|
181
162
|
<label>
|
|
182
163
|
<span className='edit-showDropdown column-heading'>Show Filter Input</span>
|
|
@@ -233,27 +214,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
|
|
|
233
214
|
/>
|
|
234
215
|
</label>
|
|
235
216
|
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<FilterOrder
|
|
252
|
-
orderedValues={filter.orderedValues || filter.values}
|
|
253
|
-
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
254
|
-
/>
|
|
255
|
-
)}
|
|
256
|
-
</label>
|
|
217
|
+
<Select
|
|
218
|
+
value={filter.order || 'asc'}
|
|
219
|
+
fieldName='order'
|
|
220
|
+
label='Filter Order'
|
|
221
|
+
updateField={(_section, _subSection, _field, value) =>
|
|
222
|
+
updateFilterProp('order', filterIndex, value)
|
|
223
|
+
}
|
|
224
|
+
options={filterOrderOptions}
|
|
225
|
+
/>
|
|
226
|
+
{filter.order === 'cust' && (
|
|
227
|
+
<FilterOrder
|
|
228
|
+
orderedValues={filter.orderedValues || filter.values}
|
|
229
|
+
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
257
232
|
</>
|
|
258
233
|
) : (
|
|
259
234
|
<NestedDropdownEditor
|
|
@@ -2,18 +2,32 @@ import { useState, useEffect, useMemo } from 'react'
|
|
|
2
2
|
import { useId } from 'react'
|
|
3
3
|
|
|
4
4
|
// CDC
|
|
5
|
-
import Button from '
|
|
6
|
-
import { getQueryParams, updateQueryString } from '
|
|
7
|
-
import MultiSelect from '
|
|
8
|
-
import { Visualization } from '
|
|
9
|
-
import { MultiSelectFilter, OrderBy, VizFilter } from '
|
|
10
|
-
import { filterVizData } from '
|
|
11
|
-
import { addValuesToFilters } from '
|
|
12
|
-
import { DimensionsType } from '
|
|
13
|
-
import NestedDropdown from '
|
|
5
|
+
import Button from '../elements/Button'
|
|
6
|
+
import { getQueryParams, updateQueryString } from '../../helpers/queryStringUtils'
|
|
7
|
+
import MultiSelect from '../MultiSelect'
|
|
8
|
+
import { Visualization } from '../../types/Visualization'
|
|
9
|
+
import { MultiSelectFilter, OrderBy, VizFilter } from '../../types/VizFilter'
|
|
10
|
+
import { filterVizData } from '../../helpers/filterVizData'
|
|
11
|
+
import { addValuesToFilters } from '../../helpers/addValuesToFilters'
|
|
12
|
+
import { DimensionsType } from '../../types/Dimensions'
|
|
13
|
+
import NestedDropdown from '../NestedDropdown'
|
|
14
14
|
import _ from 'lodash'
|
|
15
|
+
import { getNestedOptions } from './helpers/getNestedOptions'
|
|
16
|
+
import { applyQueuedActive } from './helpers/applyQueuedActive'
|
|
17
|
+
import { handleSorting } from './helpers/handleSorting'
|
|
15
18
|
|
|
16
|
-
export const
|
|
19
|
+
export const VIZ_FILTER_STYLE = {
|
|
20
|
+
dropdown: 'dropdown',
|
|
21
|
+
nestedDropdown: 'nested-dropdown',
|
|
22
|
+
pill: 'pill',
|
|
23
|
+
tab: 'tab',
|
|
24
|
+
tabBar: 'tab bar',
|
|
25
|
+
multiSelect: 'multi-select'
|
|
26
|
+
} as const
|
|
27
|
+
|
|
28
|
+
export type VizFilterStyle = (typeof VIZ_FILTER_STYLE)[keyof typeof VIZ_FILTER_STYLE]
|
|
29
|
+
|
|
30
|
+
export const filterStyleOptions = Object.values(VIZ_FILTER_STYLE)
|
|
17
31
|
|
|
18
32
|
export const filterOrderOptions: { label: string; value: OrderBy }[] = [
|
|
19
33
|
{
|
|
@@ -30,23 +44,6 @@ export const filterOrderOptions: { label: string; value: OrderBy }[] = [
|
|
|
30
44
|
}
|
|
31
45
|
]
|
|
32
46
|
|
|
33
|
-
export const handleSorting = singleFilter => {
|
|
34
|
-
const singleFilterValues = _.cloneDeep(singleFilter.values)
|
|
35
|
-
if (singleFilter.order === 'cust' && singleFilter.filterStyle !== 'nested-dropdown') {
|
|
36
|
-
singleFilter.values = singleFilter.orderedValues?.length ? singleFilter.orderedValues : singleFilterValues
|
|
37
|
-
return singleFilter
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const sort = (a, b) => {
|
|
41
|
-
const asc = singleFilter.order !== 'desc'
|
|
42
|
-
return (asc ? a : b).toString().localeCompare((asc ? b : a).toString(), 'en', { numeric: true })
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
singleFilter.values = singleFilterValues.sort(sort)
|
|
46
|
-
|
|
47
|
-
return singleFilter
|
|
48
|
-
}
|
|
49
|
-
|
|
50
47
|
const hasStandardFilterBehavior = ['chart', 'table']
|
|
51
48
|
|
|
52
49
|
export const useFilters = props => {
|
|
@@ -116,6 +113,14 @@ export const useFilters = props => {
|
|
|
116
113
|
queryParams[newFilter.setByQueryParameter] = newFilter.active
|
|
117
114
|
updateQueryString(queryParams)
|
|
118
115
|
}
|
|
116
|
+
if (
|
|
117
|
+
newFilter?.subGrouping?.setByQueryParameter &&
|
|
118
|
+
queryParams[newFilter?.subGrouping?.setByQueryParameter] !== newFilter?.subGrouping.active
|
|
119
|
+
) {
|
|
120
|
+
queryParams[newFilter?.subGrouping?.setByQueryParameter] = newFilter.subGrouping.active
|
|
121
|
+
updateQueryString(queryParams)
|
|
122
|
+
}
|
|
123
|
+
setFilteredData(newFilters[index])
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
if (!visualizationConfig.dynamicSeries) {
|
|
@@ -186,8 +191,7 @@ export const useFilters = props => {
|
|
|
186
191
|
const queryParams = getQueryParams()
|
|
187
192
|
newFilters.forEach(newFilter => {
|
|
188
193
|
if (newFilter.queuedActive) {
|
|
189
|
-
newFilter
|
|
190
|
-
delete newFilter.queuedActive
|
|
194
|
+
applyQueuedActive(newFilter)
|
|
191
195
|
if (newFilter.setByQueryParameter && queryParams[newFilter.setByQueryParameter] !== newFilter.active) {
|
|
192
196
|
queryParams[newFilter.setByQueryParameter] = newFilter.active
|
|
193
197
|
needsQueryUpdate = true
|
|
@@ -347,7 +351,7 @@ const Filters = (props: FilterProps) => {
|
|
|
347
351
|
id={`filter-${outerIndex}`}
|
|
348
352
|
name={label}
|
|
349
353
|
aria-label={`Filter by ${label}`}
|
|
350
|
-
className='
|
|
354
|
+
className='cove-form-select'
|
|
351
355
|
data-index='0'
|
|
352
356
|
value={active}
|
|
353
357
|
onChange={e => {
|
|
@@ -379,7 +383,6 @@ const Filters = (props: FilterProps) => {
|
|
|
379
383
|
const { active, queuedActive, label, filterStyle } = singleFilter as VizFilter
|
|
380
384
|
|
|
381
385
|
handleSorting(singleFilter)
|
|
382
|
-
|
|
383
386
|
singleFilter.values?.forEach((filterOption, index) => {
|
|
384
387
|
const pillClassList = ['pill', active === filterOption ? 'pill--active' : null, theme && theme]
|
|
385
388
|
const tabClassList = ['tab', active === filterOption && 'tab--active', theme && theme]
|
|
@@ -436,6 +439,7 @@ const Filters = (props: FilterProps) => {
|
|
|
436
439
|
|
|
437
440
|
const classList = [
|
|
438
441
|
'single-filters',
|
|
442
|
+
'form-group mr-3',
|
|
439
443
|
mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
|
|
440
444
|
]
|
|
441
445
|
const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
|
|
@@ -443,7 +447,11 @@ const Filters = (props: FilterProps) => {
|
|
|
443
447
|
return (
|
|
444
448
|
<div className={classList.join(' ')} key={outerIndex}>
|
|
445
449
|
<>
|
|
446
|
-
{label &&
|
|
450
|
+
{label && (
|
|
451
|
+
<label className='text-capitalize font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
|
|
452
|
+
{label}
|
|
453
|
+
</label>
|
|
454
|
+
)}
|
|
447
455
|
{filterStyle === 'tab' && !mobileFilterStyle && Tabs}
|
|
448
456
|
{filterStyle === 'pill' && !mobileFilterStyle && Pills}
|
|
449
457
|
{filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
|
|
@@ -458,7 +466,10 @@ const Filters = (props: FilterProps) => {
|
|
|
458
466
|
)}
|
|
459
467
|
{filterStyle === 'nested-dropdown' && (
|
|
460
468
|
<NestedDropdown
|
|
461
|
-
|
|
469
|
+
activeGroup={(singleFilter.active as string) || (singleFilter.queuedActive || [])[0]}
|
|
470
|
+
activeSubGroup={(singleFilter.subGrouping?.active as string) || (singleFilter.queuedActive || [])[1]}
|
|
471
|
+
filterIndex={outerIndex}
|
|
472
|
+
options={getNestedOptions(singleFilter)}
|
|
462
473
|
listLabel={label}
|
|
463
474
|
handleSelectedItems={value => changeFilterActive(outerIndex, value)}
|
|
464
475
|
/>
|
|
@@ -479,26 +490,32 @@ const Filters = (props: FilterProps) => {
|
|
|
479
490
|
}
|
|
480
491
|
|
|
481
492
|
if (visualizationConfig?.filters?.length === 0) return
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
type === 'map' ? general.headerColor :
|
|
487
|
-
|
|
493
|
+
|
|
494
|
+
const getClasses = () => {
|
|
495
|
+
const { visualizationType, legend } = visualizationConfig || {}
|
|
496
|
+
const baseClass = 'filters-section'
|
|
497
|
+
const conditionalClass = type === 'map' ? general.headerColor : visualizationType === 'Spark Line' ? null : theme
|
|
498
|
+
const legendClass = legend && !legend.hide && legend.position === 'top' ? 'mb-0' : null
|
|
499
|
+
|
|
500
|
+
return [baseClass, conditionalClass, legendClass].filter(Boolean)
|
|
501
|
+
}
|
|
502
|
+
|
|
488
503
|
return (
|
|
489
|
-
<section className={
|
|
504
|
+
<section className={getClasses().join(' ')}>
|
|
490
505
|
<p className='filters-section__intro-text'>
|
|
491
|
-
{filters?.some(
|
|
506
|
+
{filters?.some(filter => filter.active && filter.columnName) ? filterConstants.introText : ''}{' '}
|
|
492
507
|
{visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
|
|
493
508
|
</p>
|
|
494
|
-
<div className='filters-section__wrapper'>
|
|
509
|
+
<div className='d-flex flex-wrap w-100 filters-section__wrapper'>
|
|
495
510
|
{' '}
|
|
496
511
|
<>
|
|
497
512
|
<Style />
|
|
498
513
|
{filterBehavior === 'Apply Button' ? (
|
|
499
514
|
<div className='filters-section__buttons'>
|
|
500
515
|
<Button
|
|
501
|
-
onClick={
|
|
516
|
+
onClick={e => {
|
|
517
|
+
handleApplyButton(filters)
|
|
518
|
+
}}
|
|
502
519
|
disabled={!showApplyButton}
|
|
503
520
|
className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
|
|
504
521
|
>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VIZ_FILTER_STYLE } from '../Filters'
|
|
2
|
+
import { SharedFilter } from '@cdc/dashboard/src/types/SharedFilter'
|
|
3
|
+
|
|
4
|
+
export const applyQueuedActive = (sharedFilter: SharedFilter) => {
|
|
5
|
+
if (sharedFilter.filterStyle === VIZ_FILTER_STYLE.nestedDropdown) {
|
|
6
|
+
sharedFilter.active = sharedFilter.queuedActive[0]
|
|
7
|
+
sharedFilter.subGrouping.active = sharedFilter.queuedActive[1]
|
|
8
|
+
} else {
|
|
9
|
+
sharedFilter.active = sharedFilter.queuedActive
|
|
10
|
+
}
|
|
11
|
+
delete sharedFilter.queuedActive
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SubGrouping } from '../../../types/VizFilter'
|
|
2
|
+
import { NestedOptions, ValueTextPair } from '../../NestedDropdown/nestedDropdownHelpers'
|
|
3
|
+
|
|
4
|
+
type GetOptionsMemoParams = {
|
|
5
|
+
orderedValues?: string[]
|
|
6
|
+
values: string[]
|
|
7
|
+
subGrouping: SubGrouping
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getNestedOptions = ({ orderedValues, values, subGrouping }: GetOptionsMemoParams): NestedOptions => {
|
|
11
|
+
// keep custom ordered value order
|
|
12
|
+
const filteredValues = orderedValues?.length
|
|
13
|
+
? orderedValues.filter(orderedValue => values.includes(orderedValue))
|
|
14
|
+
: values
|
|
15
|
+
const v: NestedOptions = filteredValues.map<[ValueTextPair, ValueTextPair[]]>(value => {
|
|
16
|
+
if (!subGrouping) return [[value], []]
|
|
17
|
+
const { orderedValues, values: filteredSubValues } = subGrouping.valuesLookup[value]
|
|
18
|
+
// keep custom subFilter order
|
|
19
|
+
const subFilterValues =
|
|
20
|
+
orderedValues?.filter(orderedValue => filteredSubValues.includes(orderedValue)) || filteredSubValues
|
|
21
|
+
const structuredNestedDropdownData: [ValueTextPair, ValueTextPair[]] = [
|
|
22
|
+
[value],
|
|
23
|
+
subFilterValues.map(subValue => [subValue])
|
|
24
|
+
]
|
|
25
|
+
return structuredNestedDropdownData
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return v
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const handleSorting = singleFilter => {
|
|
4
|
+
const singleFilterValues = _.cloneDeep(singleFilter.values)
|
|
5
|
+
if (singleFilter.order === 'cust' && singleFilter.filterStyle !== 'nested-dropdown') {
|
|
6
|
+
singleFilter.values = singleFilter.orderedValues?.length ? singleFilter.orderedValues : singleFilterValues
|
|
7
|
+
return singleFilter
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const sort = (a, b) => {
|
|
11
|
+
const asc = singleFilter.order !== 'desc'
|
|
12
|
+
return String(asc ? a : b).localeCompare(String(asc ? b : a), 'en', { numeric: true })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
singleFilter.values = singleFilterValues.sort(sort)
|
|
16
|
+
|
|
17
|
+
return singleFilter
|
|
18
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { applyQueuedActive } from '../applyQueuedActive'
|
|
3
|
+
import { VIZ_FILTER_STYLE } from '../../Filters'
|
|
4
|
+
import { SharedFilter } from '@cdc/dashboard/src/types/SharedFilter'
|
|
5
|
+
|
|
6
|
+
describe('applyQueuedActive', () => {
|
|
7
|
+
it('should apply queuedActive to active and subGrouping.active for nestedDropdown filter style', () => {
|
|
8
|
+
const sharedFilter: SharedFilter = {
|
|
9
|
+
filterStyle: VIZ_FILTER_STYLE.nestedDropdown,
|
|
10
|
+
queuedActive: ['activeValue', 'subActiveValue'],
|
|
11
|
+
active: null,
|
|
12
|
+
subGrouping: {
|
|
13
|
+
active: null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
applyQueuedActive(sharedFilter)
|
|
18
|
+
|
|
19
|
+
expect(sharedFilter.active).toBe('activeValue')
|
|
20
|
+
expect(sharedFilter.subGrouping.active).toBe('subActiveValue')
|
|
21
|
+
expect(sharedFilter.queuedActive).toBeUndefined()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should apply queuedActive to active for non-nestedDropdown filter style', () => {
|
|
25
|
+
const sharedFilter: SharedFilter = {
|
|
26
|
+
filterStyle: 'someOtherStyle',
|
|
27
|
+
queuedActive: 'activeValue',
|
|
28
|
+
active: null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
applyQueuedActive(sharedFilter)
|
|
32
|
+
|
|
33
|
+
expect(sharedFilter.active).toBe('activeValue')
|
|
34
|
+
expect(sharedFilter.queuedActive).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should handle empty queuedActive for non-nestedDropdown filter style', () => {
|
|
38
|
+
const sharedFilter: SharedFilter = {
|
|
39
|
+
filterStyle: 'someOtherStyle',
|
|
40
|
+
queuedActive: null,
|
|
41
|
+
active: null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
applyQueuedActive(sharedFilter)
|
|
45
|
+
|
|
46
|
+
expect(sharedFilter.active).toBeNull()
|
|
47
|
+
expect(sharedFilter.queuedActive).toBeUndefined()
|
|
48
|
+
})
|
|
49
|
+
})
|