@cdc/dashboard 4.26.1 → 4.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cdcdashboard-8NmHlKRI.es.js +15 -0
  2. package/dist/cdcdashboard-BPoPzKPz.es.js +6 -0
  3. package/dist/cdcdashboard-Cf9_fbQf.es.js +6 -0
  4. package/dist/{cdcdashboard-dgT_1dIT.es.js → cdcdashboard-DQ00cQCm.es.js} +1 -20
  5. package/dist/cdcdashboard-jiQQPkty.es.js +6 -0
  6. package/dist/cdcdashboard.js +59394 -65183
  7. package/examples/default.json +522 -133
  8. package/examples/nested-dropdown.json +6985 -0
  9. package/examples/private/abc.json +467 -0
  10. package/examples/private/dash.json +12696 -0
  11. package/examples/private/npcr.json +1 -0
  12. package/examples/private/test.json +125407 -0
  13. package/examples/private/timeline-data.json +4994 -0
  14. package/examples/private/timeline.json +1708 -0
  15. package/examples/test-api-filter-reset.json +8 -4
  16. package/examples/tp5-gauges.json +196 -0
  17. package/examples/tp5-test.json +266 -0
  18. package/index.html +1 -29
  19. package/package.json +38 -40
  20. package/src/CdcDashboardComponent.tsx +0 -3
  21. package/src/_stories/Dashboard.DataSetup.stories.tsx +2 -1
  22. package/src/_stories/Dashboard.stories.tsx +27 -5
  23. package/src/_stories/_mock/dashboard-line-chart-angles.json +1030 -0
  24. package/src/_stories/_mock/tp5-test.json +267 -0
  25. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -0
  26. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +5 -1
  27. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +5 -1
  28. package/src/components/VisualizationRow.tsx +30 -25
  29. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -17
  30. package/src/helpers/addValuesToDashboardFilters.ts +17 -11
  31. package/src/helpers/apiFilterHelpers.ts +28 -32
  32. package/src/helpers/tests/addValuesToDashboardFilters.test.ts +141 -44
  33. package/src/helpers/tests/apiFilterHelpers.test.ts +523 -420
  34. package/src/scss/main.scss +8 -5
  35. package/vite.config.js +7 -1
  36. package/dist/cdcdashboard-BnB1QM5d.es.js +0 -361528
  37. package/dist/cdcdashboard-Ct2SB0vL.es.js +0 -231049
  38. package/dist/cdcdashboard-D6CG2-Hb.es.js +0 -39377
  39. package/dist/cdcdashboard-MXgURbdZ.es.js +0 -39194
@@ -0,0 +1,267 @@
1
+ {
2
+ "type": "dashboard",
3
+ "title": "TP5 Alignment Test Dashboard",
4
+ "description": "Testing alignment of TP5 Waffles, Data Bites, and Gauges",
5
+ "data": [
6
+ {
7
+ "Category": "Adults",
8
+ "Vaccination Rate": "68.5",
9
+ "Insured Rate": "87.2",
10
+ "Screening Rate": "72.8"
11
+ },
12
+ {
13
+ "Category": "Seniors",
14
+ "Vaccination Rate": "82.3",
15
+ "Insured Rate": "95.1",
16
+ "Screening Rate": "84.6"
17
+ },
18
+ {
19
+ "Category": "Youth",
20
+ "Vaccination Rate": "54.2",
21
+ "Insured Rate": "92.4",
22
+ "Screening Rate": "65.3"
23
+ }
24
+ ],
25
+ "dashboard": {
26
+ "filters": []
27
+ },
28
+ "rows": [
29
+ [
30
+ { "width": 4, "widget": "waffle1" },
31
+ { "width": 4, "widget": "waffle2" },
32
+ { "width": 4, "widget": "waffle3" }
33
+ ],
34
+ [
35
+ { "width": 4, "widget": "bite1" },
36
+ { "width": 4, "widget": "bite2" },
37
+ { "width": 4, "widget": "bite3" }
38
+ ],
39
+ [
40
+ { "width": 4, "widget": "gauge1" },
41
+ { "width": 4, "widget": "gauge2" },
42
+ { "width": 4, "widget": "gauge3" }
43
+ ]
44
+ ],
45
+ "visualizations": {
46
+ "waffle1": {
47
+ "uid": "waffle1",
48
+ "type": "waffle-chart",
49
+ "title": "Vaccination Coverage",
50
+ "visualizationType": "TP5 Waffle",
51
+ "visualizationSubType": "linear",
52
+ "showPercent": true,
53
+ "showDenominator": false,
54
+ "valueDescription": "",
55
+ "content": "of the population is vaccinated against seasonal flu",
56
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
57
+ "dataColumn": "Vaccination Rate",
58
+ "dataFunction": "Mean (Average)",
59
+ "customDenom": false,
60
+ "dataDenom": "100",
61
+ "suffix": "%",
62
+ "roundToPlace": "1",
63
+ "theme": "theme-blue",
64
+ "shape": "square",
65
+ "visual": {
66
+ "whiteBackground": false
67
+ },
68
+ "showTitle": true,
69
+ "overallFontSize": "medium"
70
+ },
71
+ "waffle2": {
72
+ "uid": "waffle2",
73
+ "type": "waffle-chart",
74
+ "title": "Health Insurance Coverage Rate",
75
+ "visualizationType": "TP5 Waffle",
76
+ "visualizationSubType": "linear",
77
+ "showPercent": true,
78
+ "showDenominator": false,
79
+ "valueDescription": "",
80
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
81
+ "subtext": "",
82
+ "dataColumn": "Insured Rate",
83
+ "dataFunction": "Mean (Average)",
84
+ "customDenom": false,
85
+ "dataDenom": "100",
86
+ "suffix": "%",
87
+ "roundToPlace": "1",
88
+ "theme": "theme-teal",
89
+ "shape": "person",
90
+ "visual": {
91
+ "whiteBackground": false
92
+ },
93
+ "showTitle": true,
94
+ "overallFontSize": "medium"
95
+ },
96
+ "waffle3": {
97
+ "uid": "waffle3",
98
+ "type": "waffle-chart",
99
+ "title": "Cancer Screening Completion",
100
+ "visualizationType": "TP5 Waffle",
101
+ "visualizationSubType": "linear",
102
+ "showPercent": true,
103
+ "showDenominator": false,
104
+ "valueDescription": "",
105
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
106
+ "subtext": "Data from National Health Interview Survey 2024",
107
+ "dataColumn": "Screening Rate",
108
+ "dataFunction": "Mean (Average)",
109
+ "customDenom": false,
110
+ "dataDenom": "100",
111
+ "suffix": "%",
112
+ "roundToPlace": "1",
113
+ "theme": "theme-purple",
114
+ "shape": "circle",
115
+ "visual": {
116
+ "whiteBackground": false
117
+ },
118
+ "showTitle": true,
119
+ "overallFontSize": "medium"
120
+ },
121
+ "gauge1": {
122
+ "uid": "gauge1",
123
+ "type": "waffle-chart",
124
+ "title": "Vaccination Coverage",
125
+ "visualizationType": "TP5 Gauge",
126
+ "visualizationSubType": "linear",
127
+ "showPercent": true,
128
+ "showDenominator": false,
129
+ "valueDescription": "",
130
+ "content": "of the population is vaccinated against seasonal flu",
131
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
132
+ "dataColumn": "Vaccination Rate",
133
+ "dataFunction": "Mean (Average)",
134
+ "customDenom": false,
135
+ "dataDenom": "100",
136
+ "suffix": "%",
137
+ "roundToPlace": "1",
138
+ "theme": "theme-blue",
139
+ "gauge": {
140
+ "height": 35,
141
+ "width": "100%"
142
+ },
143
+ "visual": {
144
+ "whiteBackground": false
145
+ },
146
+ "showTitle": true,
147
+ "overallFontSize": "medium"
148
+ },
149
+ "gauge2": {
150
+ "uid": "gauge2",
151
+ "type": "waffle-chart",
152
+ "title": "Health Insurance Coverage Rate",
153
+ "visualizationType": "TP5 Gauge",
154
+ "visualizationSubType": "linear",
155
+ "showPercent": true,
156
+ "showDenominator": false,
157
+ "valueDescription": "",
158
+ "content": "",
159
+ "subtext": "",
160
+ "dataColumn": "Insured Rate",
161
+ "dataFunction": "Mean (Average)",
162
+ "customDenom": false,
163
+ "dataDenom": "100",
164
+ "suffix": "%",
165
+ "roundToPlace": "1",
166
+ "theme": "theme-teal",
167
+ "gauge": {
168
+ "height": 35,
169
+ "width": "100%"
170
+ },
171
+ "visual": {
172
+ "whiteBackground": false
173
+ },
174
+ "showTitle": true,
175
+ "overallFontSize": "medium"
176
+ },
177
+ "gauge3": {
178
+ "uid": "gauge3",
179
+ "type": "waffle-chart",
180
+ "title": "Cancer Screening Completion",
181
+ "visualizationType": "TP5 Gauge",
182
+ "visualizationSubType": "linear",
183
+ "showPercent": true,
184
+ "showDenominator": false,
185
+ "valueDescription": "",
186
+ "content": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
187
+ "subtext": "Data from National Health Interview Survey 2024",
188
+ "dataColumn": "Screening Rate",
189
+ "dataFunction": "Mean (Average)",
190
+ "customDenom": false,
191
+ "dataDenom": "100",
192
+ "suffix": "%",
193
+ "roundToPlace": "1",
194
+ "theme": "theme-purple",
195
+ "gauge": {
196
+ "height": 35,
197
+ "width": "100%"
198
+ },
199
+ "visual": {
200
+ "whiteBackground": false
201
+ },
202
+ "showTitle": true,
203
+ "overallFontSize": "medium"
204
+ },
205
+ "bite1": {
206
+ "uid": "bite1",
207
+ "type": "data-bite",
208
+ "title": "Vaccination Coverage",
209
+ "biteStyle": "tp5",
210
+ "dataColumn": "Vaccination Rate",
211
+ "dataFunction": "Mean (Average)",
212
+ "biteBody": "of the population is vaccinated against seasonal flu",
213
+ "subtext": "Based on 2024 CDC surveillance data across all age groups",
214
+ "dataFormat": {
215
+ "roundToPlace": 1,
216
+ "commas": true,
217
+ "prefix": "",
218
+ "suffix": "%"
219
+ },
220
+ "theme": "theme-blue",
221
+ "visual": {
222
+ "hideBackgroundColor": false
223
+ }
224
+ },
225
+ "bite2": {
226
+ "uid": "bite2",
227
+ "type": "data-bite",
228
+ "title": "Health Insurance Coverage Rate",
229
+ "biteStyle": "tp5",
230
+ "dataColumn": "Insured Rate",
231
+ "dataFunction": "Mean (Average)",
232
+ "biteBody": "",
233
+ "subtext": "",
234
+ "dataFormat": {
235
+ "roundToPlace": 1,
236
+ "commas": true,
237
+ "prefix": "",
238
+ "suffix": "%"
239
+ },
240
+ "theme": "theme-teal",
241
+ "visual": {
242
+ "hideBackgroundColor": false
243
+ }
244
+ },
245
+ "bite3": {
246
+ "uid": "bite3",
247
+ "type": "data-bite",
248
+ "title": "Cancer Screening Completion",
249
+ "biteStyle": "tp5",
250
+ "dataColumn": "Screening Rate",
251
+ "dataFunction": "Mean (Average)",
252
+ "biteBody": "completed recommended cancer screenings including mammography, colonoscopy, and cervical cancer screening",
253
+ "subtext": "Data from National Health Interview Survey 2024",
254
+ "dataFormat": {
255
+ "roundToPlace": 1,
256
+ "commas": true,
257
+ "prefix": "",
258
+ "suffix": "%"
259
+ },
260
+ "theme": "theme-purple",
261
+ "visual": {
262
+ "hideBackgroundColor": false
263
+ }
264
+ }
265
+ }
266
+ }
267
+
@@ -52,6 +52,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
52
52
 
53
53
  const openControls = useState({})
54
54
  const [canAddExisting, setCanAddExisting] = useState(false)
55
+ const [isNestedDragHovered, setIsNestedDragHovered] = useState(false)
55
56
 
56
57
  const updateFilterProp = (prop: string, index: number, value) => {
57
58
  const newSharedFilters = _.cloneDeep(sharedFilters)
@@ -255,6 +256,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
255
256
  key={filter.key + index}
256
257
  draggableId={`filter-${filter.key}-${index}`}
257
258
  index={filterIndex}
259
+ isDragDisabled={isNestedDragHovered}
258
260
  >
259
261
  {(provided, snapshot) => (
260
262
  <div
@@ -296,6 +298,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
296
298
  toggleNestedQueryParameters(index, checked)
297
299
  }}
298
300
  config={config}
301
+ onNestedDragAreaHover={setIsNestedDragHovered}
299
302
  />
300
303
  </FieldSetWrapper>
301
304
  </div>
@@ -27,6 +27,7 @@ type FilterEditorProps = {
27
27
  filterIndex: number
28
28
  updateFilterProp: (name: keyof SharedFilter, value: any) => void
29
29
  toggleNestedQueryParameters: (checked: boolean) => void
30
+ onNestedDragAreaHover?: (isHovering: boolean) => void
30
31
  }
31
32
 
32
33
  const FilterEditor: React.FC<FilterEditorProps> = ({
@@ -34,7 +35,8 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
34
35
  filterIndex,
35
36
  config,
36
37
  updateFilterProp,
37
- toggleNestedQueryParameters
38
+ toggleNestedQueryParameters,
39
+ onNestedDragAreaHover
38
40
  }) => {
39
41
  const [columns, setColumns] = useState<string[]>([])
40
42
  const [dataFiltersLoading, setDataFiltersLoading] = useState(false)
@@ -546,6 +548,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
546
548
  values.splice(index2, 0, removed)
547
549
  updateFilterProp('orderedValues', values)
548
550
  }}
551
+ onNestedDragAreaHover={onNestedDragAreaHover}
549
552
  />
550
553
  )}
551
554
 
@@ -569,6 +572,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
569
572
  }}
570
573
  isDashboard={true}
571
574
  config={config}
575
+ onNestedDragAreaHover={onNestedDragAreaHover}
572
576
  />
573
577
  <label>
574
578
  <input
@@ -12,13 +12,15 @@ type NestedDropDownEditorDashboardProps = {
12
12
  filter: SharedFilter
13
13
  isDashboard: boolean
14
14
  updateFilterProp: Function
15
+ onNestedDragAreaHover?: (isHovering: boolean) => void
15
16
  }
16
17
 
17
18
  const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
18
19
  filter,
19
20
  config,
20
21
  isDashboard = false,
21
- updateFilterProp
22
+ updateFilterProp,
23
+ onNestedDragAreaHover
22
24
  }) => {
23
25
  const subGrouping = filter?.subGrouping
24
26
 
@@ -270,6 +272,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
270
272
  <FilterOrder
271
273
  orderedValues={filter.orderedValues || filter.values}
272
274
  handleFilterOrder={handleGroupingCustomOrder}
275
+ onNestedDragAreaHover={onNestedDragAreaHover}
273
276
  />
274
277
  )}
275
278
  </div>
@@ -298,6 +301,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
298
301
  handleFilterOrder={(sourceIndex, destinationIndex) => {
299
302
  handleSubGroupingCustomOrder(sourceIndex, destinationIndex, orderedSubGroupValues, groupName)
300
303
  }}
304
+ onNestedDragAreaHover={onNestedDragAreaHover}
301
305
  />
302
306
  </div>
303
307
  )
@@ -103,50 +103,55 @@ const VisualizationRow: React.FC<VizRowProps> = ({
103
103
  }
104
104
  }, [toggledRow, row.toggle])
105
105
 
106
- // Equalize TP5 data bite title heights in the same row
107
- useEffect(() => {
108
- const rowElement = document.querySelector(`[data-row-index="${index}"]`)
109
- if (!rowElement) return
106
+ const setupTP5MinHeightEqualizer = (rowElement: Element, itemSelector: string) => {
107
+ const items = Array.from(rowElement.querySelectorAll(itemSelector)) as HTMLElement[]
108
+ if (items.length <= 1) return undefined
110
109
 
111
- const tp5Titles = Array.from(rowElement.querySelectorAll('.bite__style--tp5 .cdc-callout__heading'))
112
- if (tp5Titles.length <= 1) return // No need to equalize if there's only one or none
113
-
114
- const equalizeTP5Titles = () => {
115
- // Reset heights first
116
- tp5Titles.forEach((title: HTMLElement) => {
117
- title.style.minHeight = ''
110
+ const equalizeHeights = () => {
111
+ items.forEach(item => {
112
+ item.style.minHeight = ''
118
113
  })
119
114
 
120
- // Calculate max height after reset
121
115
  let maxHeight = 0
122
- tp5Titles.forEach((title: HTMLElement) => {
123
- const height = title.offsetHeight
116
+ items.forEach(item => {
117
+ const height = item.offsetHeight
124
118
  if (height > maxHeight) maxHeight = height
125
119
  })
126
120
 
127
- // Apply max height to all titles
128
121
  if (maxHeight > 0) {
129
- tp5Titles.forEach((title: HTMLElement) => {
130
- title.style.minHeight = `${maxHeight}px`
122
+ items.forEach(item => {
123
+ item.style.minHeight = `${maxHeight}px`
131
124
  })
132
125
  }
133
126
  }
134
127
 
135
- // Initial equalization
136
- equalizeTP5Titles()
128
+ equalizeHeights()
137
129
 
138
- // Use ResizeObserver to watch for size changes in any of the titles
139
130
  const resizeObserver = new ResizeObserver(() => {
140
- equalizeTP5Titles()
131
+ equalizeHeights()
141
132
  })
142
133
 
143
- // Observe all titles
144
- tp5Titles.forEach(title => {
145
- resizeObserver.observe(title as Element)
134
+ items.forEach(item => {
135
+ resizeObserver.observe(item)
146
136
  })
147
137
 
138
+ return () => resizeObserver.disconnect()
139
+ }
140
+
141
+ // Equalize TP5 callout title heights and TP5 gauge message blocks for like visualizations in the same row
142
+ useEffect(() => {
143
+ const rowElement = document.querySelector(`[data-row-index="${index}"]`)
144
+ if (!rowElement) return
145
+
146
+ const cleanups = [
147
+ setupTP5MinHeightEqualizer(rowElement, '.bite__style--tp5 .cdc-callout__heading'),
148
+ setupTP5MinHeightEqualizer(rowElement, '.waffle__style--tp5 .cdc-callout__heading'),
149
+ setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cdc-callout__heading'),
150
+ setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cove-gauge-chart__content')
151
+ ].filter(Boolean) as Array<() => void>
152
+
148
153
  return () => {
149
- resizeObserver.disconnect()
154
+ cleanups.forEach(cleanup => cleanup())
150
155
  }
151
156
  }, [index, row, config, filteredDataOverride])
152
157
 
@@ -46,24 +46,7 @@ const addVisualization = (type, subType) => {
46
46
  newVisualizationConfig.visualizationType = type
47
47
  break
48
48
  case 'markup-include':
49
- newVisualizationConfig.contentEditor = {
50
- inlineHTML: '<h2>Inline HTML</h2>',
51
- markupVariables: [],
52
- showHeader: true,
53
- srcUrl: '#example',
54
- useInlineHTML: true
55
- }
56
- newVisualizationConfig.theme = 'theme-blue'
57
- newVisualizationConfig.visual = {
58
- border: false,
59
- accent: false,
60
- background: false,
61
- hideBackgroundColor: false,
62
- borderColorTheme: false
63
- }
64
- newVisualizationConfig.showEditorPanel = true
65
49
  newVisualizationConfig.visualizationType = type
66
-
67
50
  break
68
51
  case 'dashboardFilters': {
69
52
  newVisualizationConfig.sharedFilterIndexes = []
@@ -58,8 +58,7 @@ export const addValuesToDashboardFilters = (
58
58
  const active: string[] = Array.isArray(filterCopy.active) ? filterCopy.active : [filterCopy.active]
59
59
  filterCopy.active = active.filter(val => defaultValues.includes(val))
60
60
  } else {
61
- // Initialize active from defaultValue if not already set
62
- // OR if defaultValue exists, always use it (overrides stale active from saved config)
61
+ // Use defaultValue if set, otherwise keep existing active or use first value
63
62
  if (filterCopy.defaultValue) {
64
63
  filterCopy.active = filterCopy.defaultValue
65
64
  } else if (!filterCopy.active) {
@@ -82,17 +81,24 @@ export const addValuesToDashboardFilters = (
82
81
  }
83
82
  const queryStringFilterValue = getQueryStringFilterValue(subGroupingFilter)
84
83
  const groupActive = groupName || filterCopy.values[0]
85
- const defaultSubValue = filterCopy.subGrouping.valuesLookup[groupActive as string]?.values[0]
84
+ const currentGroupValues = filterCopy.subGrouping.valuesLookup[groupActive as string]?.values || []
85
+ const defaultSubValue = currentGroupValues[0]
86
86
 
87
- // Priority order: query string > existing active > configured default > first available value
88
- let activeValue = queryStringFilterValue || filterCopy.subGrouping.active
87
+ // Priority order: query string > configured default > existing active > first available value
88
+ let activeValue: string | undefined
89
89
 
90
- // If we have a configured default value and it exists in the current group's options, use it
91
- if (!activeValue && filterCopy.subGrouping.defaultValue) {
92
- const currentGroupValues = filterCopy.subGrouping.valuesLookup[groupActive as string]?.values || []
93
- if (currentGroupValues.includes(filterCopy.subGrouping.defaultValue)) {
94
- activeValue = filterCopy.subGrouping.defaultValue
95
- }
90
+ if (queryStringFilterValue && currentGroupValues.includes(queryStringFilterValue)) {
91
+ // 1. Query string parameter takes highest priority (only if valid for the current group)
92
+ activeValue = queryStringFilterValue
93
+ } else if (
94
+ filterCopy.subGrouping.defaultValue &&
95
+ currentGroupValues.includes(filterCopy.subGrouping.defaultValue)
96
+ ) {
97
+ // 2. Use configured defaultValue if it exists and is valid for the current group
98
+ activeValue = filterCopy.subGrouping.defaultValue
99
+ } else if (filterCopy.subGrouping.active && currentGroupValues.includes(filterCopy.subGrouping.active)) {
100
+ // 3. Keep existing active value if it's valid for the current group
101
+ activeValue = filterCopy.subGrouping.active
96
102
  }
97
103
 
98
104
  filterCopy.subGrouping.active = activeValue || defaultSubValue
@@ -119,35 +119,31 @@ export const getToFetch = (
119
119
  }
120
120
 
121
121
  export const setActiveNestedDropdown = (dropdownOptions, sharedFilter) => {
122
- const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
123
- const defaultValue = dropdownOptions[0]?.value
124
- const subDefaultValue = dropdownOptions[0]?.subOptions[0].value
125
- const subDefaultQueryParamValue = getQueryParam(sharedFilter?.subGrouping.setByQueryParameter)
126
- if (!sharedFilter.active) {
127
- sharedFilter.active = defaultQueryParamValue || defaultValue
128
- sharedFilter.subGrouping.active = subDefaultQueryParamValue || subDefaultValue
129
- } else {
130
- const currentOption = dropdownOptions.find(option => option.value === sharedFilter.active)
131
- sharedFilter.active = currentOption ? currentOption.value : defaultValue
132
- if (currentOption) {
133
- const currentSubOption = currentOption.subOptions.find(option => option.value === sharedFilter.subGrouping.active)
134
- sharedFilter.subGrouping.active = currentSubOption?.value || subDefaultValue
135
- } else {
136
- sharedFilter.subGrouping.active = subDefaultValue
137
- }
138
- }
122
+ const queryValue = getQueryParam(sharedFilter?.setByQueryParameter)
123
+ const subQueryValue = getQueryParam(sharedFilter?.subGrouping?.setByQueryParameter)
124
+
125
+ // Priority: query string > configured defaultValue > existing active (if valid) > first option
126
+ // Note: use loose equality here to match values across possible string/number differences
127
+ const validActive = dropdownOptions.find(option => option.value == sharedFilter.active)
128
+ sharedFilter.active =
129
+ queryValue || sharedFilter.defaultValue || (validActive ? sharedFilter.active : dropdownOptions[0]?.value)
130
+
131
+ const options = dropdownOptions.find(option => option.value == sharedFilter.active)?.subOptions || []
132
+ const validSubActive = options.find(o => o.value == sharedFilter.subGrouping?.active)
133
+ sharedFilter.subGrouping.active =
134
+ subQueryValue ||
135
+ sharedFilter.subGrouping?.defaultValue ||
136
+ (validSubActive ? sharedFilter.subGrouping.active : options[0]?.value)
139
137
  }
140
138
 
141
139
  export const setActiveMultiDropdown = (dropdownOptions, sharedFilter) => {
142
- const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
143
- const multiDefaultQueryParamValue = Array.isArray(defaultQueryParamValue)
144
- ? defaultQueryParamValue
145
- : defaultQueryParamValue?.split(',')
146
- const multiDefaultValue = defaultQueryParamValue ? multiDefaultQueryParamValue : [dropdownOptions[0]?.value]
140
+ const queryValue = getQueryParam(sharedFilter?.setByQueryParameter)
141
+ const queryValues = Array.isArray(queryValue) ? queryValue : queryValue?.split(',')
142
+ const defaultValues = queryValue ? queryValues : [dropdownOptions[0]?.value]
147
143
  const currentOption = (Array.isArray(sharedFilter.active) ? sharedFilter.active : []).filter(activeVal =>
148
144
  dropdownOptions.find(option => option.value === activeVal)
149
145
  )
150
- sharedFilter.active = currentOption.length ? currentOption : multiDefaultValue
146
+ sharedFilter.active = currentOption.length ? currentOption : defaultValues
151
147
  }
152
148
 
153
149
  export const setAutoLoadDefaultValue = (
@@ -158,20 +154,20 @@ export const setAutoLoadDefaultValue = (
158
154
  ): SharedFilter => {
159
155
  const sharedFiltersCopy = _.cloneDeep(sharedFilters)
160
156
  const sharedFilter = _.cloneDeep(sharedFiltersCopy[sharedFilterIndex])
161
- const defaultQueryParamValue = getQueryParam(sharedFilter?.setByQueryParameter)
162
- const hasQueryParameter = sharedFilter.setByQueryParameter ? defaultQueryParamValue !== undefined : false
157
+ const queryValue = getQueryParam(sharedFilter?.setByQueryParameter)
158
+ const hasQuery = sharedFilter.setByQueryParameter ? queryValue !== undefined : false
163
159
  if (!autoLoadFilterIndexes.length || !dropdownOptions?.length) {
164
- if (hasQueryParameter && sharedFilter.apiFilter) {
160
+ if (hasQuery && sharedFilter.apiFilter) {
165
161
  const subQueryValue = getQueryParam(sharedFilter.subGrouping?.setByQueryParameter)
166
- const isNestedDropdown = subQueryValue !== undefined
167
- sharedFilter.queuedActive = isNestedDropdown ? [defaultQueryParamValue, subQueryValue] : defaultQueryParamValue
162
+ const isNested = subQueryValue !== undefined
163
+ sharedFilter.queuedActive = isNested ? [queryValue, subQueryValue] : queryValue
168
164
  }
169
165
  return sharedFilter // no autoLoading happening
170
166
  }
171
- if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQueryParameter) {
167
+ if (autoLoadFilterIndexes.includes(sharedFilterIndex) || hasQuery) {
172
168
  const filterParents = sharedFiltersCopy.filter(f => sharedFilter.parents?.includes(f.key))
173
- const notAllParentFiltersSelected = filterParents.some(p => !(p.active || p.queuedActive))
174
- if (filterParents && notAllParentFiltersSelected) return sharedFilter
169
+ const missingParents = filterParents.some(p => !(p.active || p.queuedActive))
170
+ if (missingParents) return sharedFilter
175
171
  if (sharedFilter.filterStyle === FILTER_STYLE.multiSelect) {
176
172
  setActiveMultiDropdown(dropdownOptions, sharedFilter)
177
173
  } else if (sharedFilter.filterStyle === FILTER_STYLE.nestedDropdown) {
@@ -179,7 +175,7 @@ export const setAutoLoadDefaultValue = (
179
175
  } else {
180
176
  const defaultValue = dropdownOptions[0]?.value
181
177
  if (!sharedFilter.active) {
182
- sharedFilter.active = defaultQueryParamValue || defaultValue
178
+ sharedFilter.active = queryValue ?? defaultValue
183
179
  } else {
184
180
  const currentOption = dropdownOptions.find(option => option.value == sharedFilter.active) // loose equality required: 2017 should equal '2017'
185
181
  sharedFilter.active = currentOption ? currentOption.value : defaultValue