@cdc/dashboard 4.26.1 → 4.26.3

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 (76) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcdashboard-8NmHlKRI.es.js +15 -0
  3. package/dist/cdcdashboard-BPoPzKPz.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-vr9HZwRt.es.js +6 -0
  7. package/dist/cdcdashboard.js +80971 -83096
  8. package/examples/custom/css/respiratory.css +1 -1
  9. package/examples/data/data-with-metadata.json +18 -0
  10. package/examples/default.json +492 -132
  11. package/examples/nested-dropdown.json +6985 -0
  12. package/examples/private/abc.json +467 -0
  13. package/examples/private/dash.json +12696 -0
  14. package/examples/private/inline-markup.json +775 -0
  15. package/examples/private/npcr.json +1 -0
  16. package/examples/private/recent-update.json +1456 -0
  17. package/examples/private/test.json +125407 -0
  18. package/examples/private/timeline-data.json +4994 -0
  19. package/examples/private/timeline.json +1708 -0
  20. package/examples/private/toggle.json +10137 -0
  21. package/examples/test-api-filter-reset.json +8 -4
  22. package/examples/tp5-gauges.json +196 -0
  23. package/examples/tp5-test.json +266 -0
  24. package/index.html +1 -29
  25. package/package.json +38 -40
  26. package/src/CdcDashboard.tsx +2 -1
  27. package/src/CdcDashboardComponent.tsx +47 -30
  28. package/src/_stories/Dashboard.DataSetup.stories.tsx +8 -2
  29. package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
  30. package/src/_stories/Dashboard.stories.tsx +4501 -80
  31. package/src/_stories/_mock/dashboard-line-chart-angles.json +1030 -0
  32. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  33. package/src/_stories/_mock/tp5-test.json +267 -0
  34. package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +10 -4
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +6 -3
  38. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +13 -8
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  40. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  41. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  42. package/src/components/DataDesignerModal.tsx +2 -2
  43. package/src/components/Header/Header.tsx +27 -5
  44. package/src/components/Header/index.scss +1 -1
  45. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  46. package/src/components/Row.tsx +21 -0
  47. package/src/components/Toggle/toggle-style.css +7 -7
  48. package/src/components/VisualizationRow.tsx +42 -29
  49. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -71
  50. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  51. package/src/components/Widget/Widget.tsx +2 -2
  52. package/src/components/Widget/widget.styles.css +12 -12
  53. package/src/data/initial-state.js +1 -1
  54. package/src/helpers/addValuesToDashboardFilters.ts +17 -11
  55. package/src/helpers/addVisualization.ts +71 -0
  56. package/src/helpers/apiFilterHelpers.ts +28 -32
  57. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  58. package/src/helpers/getVizConfig.ts +13 -3
  59. package/src/helpers/iconHash.tsx +45 -36
  60. package/src/helpers/processDataLegacy.ts +19 -14
  61. package/src/helpers/tests/addValuesToDashboardFilters.test.ts +141 -44
  62. package/src/helpers/tests/addVisualization.test.ts +52 -0
  63. package/src/helpers/tests/apiFilterHelpers.test.ts +523 -420
  64. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  65. package/src/scss/editor-panel.scss +1 -1
  66. package/src/scss/main.scss +169 -41
  67. package/src/store/dashboard.reducer.ts +1 -1
  68. package/src/test/CdcDashboard.test.jsx +2 -2
  69. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  70. package/src/types/FilterStyles.ts +2 -1
  71. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  72. package/vite.config.js +7 -1
  73. package/dist/cdcdashboard-BnB1QM5d.es.js +0 -361528
  74. package/dist/cdcdashboard-Ct2SB0vL.es.js +0 -231049
  75. package/dist/cdcdashboard-D6CG2-Hb.es.js +0 -39377
  76. package/dist/cdcdashboard-MXgURbdZ.es.js +0 -39194
@@ -7,7 +7,7 @@ import { FilterBehavior } from '../../helpers/FilterBehavior'
7
7
  import { getFilteredData } from '../../helpers/getFilteredData'
8
8
  import { DashboardFilters } from '../../types/DashboardFilters'
9
9
  import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
10
- import Layout from '@cdc/core/components/Layout'
10
+ import { VisualizationWrapper, Sidebar, Responsive } from '@cdc/core/components/Layout'
11
11
  import DashboardFiltersEditor from './DashboardFiltersEditor'
12
12
  import { ViewPort } from '@cdc/core/types/ViewPort'
13
13
  import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
@@ -302,24 +302,24 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
302
302
  const displayNone = filters?.length ? filters.every(filter => filter.showDropdown === false) : false
303
303
  if (displayNone && !isEditor) return <></>
304
304
  return (
305
- <Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
305
+ <VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
306
306
  {isEditor && (
307
- <Layout.Sidebar
307
+ <Sidebar
308
308
  displayPanel={displayPanel}
309
309
  isDashboard={true}
310
310
  title={'Configure Dashboard Filters'}
311
311
  onBackClick={onBackClick}
312
312
  >
313
313
  <DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
314
- </Layout.Sidebar>
314
+ </Sidebar>
315
315
  )}
316
316
 
317
317
  {!displayNone && (
318
- <Layout.Responsive isEditor={isEditor}>
318
+ <Responsive isEditor={isEditor}>
319
319
  <div
320
320
  className={`${
321
321
  isEditor ? ' is-editor' : ''
322
- } cove-component__content col-12 cove-dashboard-filters-container`}
322
+ } cove-visualization__inner cove-visualization__body col-12 cove-dashboard-filters-container`}
323
323
  >
324
324
  <Filters
325
325
  show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
@@ -337,9 +337,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
337
337
  }
338
338
  />
339
339
  </div>
340
- </Layout.Responsive>
340
+ </Responsive>
341
341
  )}
342
- </Layout.VisualizationWrapper>
342
+ </VisualizationWrapper>
343
343
  )
344
344
  }
345
345
 
@@ -7,7 +7,7 @@ const meta: Meta<typeof DashboardFilters> = {
7
7
  component: DashboardFilters,
8
8
  decorators: [
9
9
  Story => (
10
- <div className='cdc-open-viz-module type-dashboard'>
10
+ <div className='cove-visualization type-dashboard'>
11
11
  <Story />
12
12
  </div>
13
13
  )
@@ -4,18 +4,18 @@
4
4
  font-weight: 700;
5
5
  }
6
6
  .btn {
7
+ align-self: flex-end;
7
8
  /* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
8
9
  height: calc(1.5em + 0.75rem + 2px);
9
- align-self: flex-end;
10
10
  }
11
11
  .loading-filter {
12
12
  position: relative;
13
13
  .spinner-border {
14
+ height: 1.5rem;
14
15
  position: absolute;
15
- top: 55%;
16
16
  right: 10%;
17
+ top: 55%;
17
18
  width: 1.5rem;
18
- height: 1.5rem;
19
19
  }
20
20
  }
21
21
  :is(select):disabled {
@@ -59,8 +59,8 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
59
59
  if (dataSetChanged || noCachedData) {
60
60
  setLoadingAPIData(true)
61
61
  try {
62
- newData = await fetchRemoteData(dataUrl)
63
- newData = transform.autoStandardize(newData)
62
+ const result = await fetchRemoteData(dataUrl)
63
+ newData = transform.autoStandardize(result.data)
64
64
  } catch (e) {
65
65
  setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
66
66
  }
@@ -176,7 +176,6 @@ const Header = (props: HeaderProps) => {
176
176
  Show Data Table(s)
177
177
  </label>
178
178
  <br />
179
-
180
179
  <label>
181
180
  <input
182
181
  type='checkbox'
@@ -185,7 +184,6 @@ const Header = (props: HeaderProps) => {
185
184
  />
186
185
  Expanded by Default
187
186
  </label>
188
- <br />
189
187
  </div>
190
188
 
191
189
  <div className='wrap'>
@@ -206,9 +204,6 @@ const Header = (props: HeaderProps) => {
206
204
  onChange={e => changeConfigValue('table', 'height', e.target.value)}
207
205
  />
208
206
  )}
209
- </div>
210
-
211
- <div className='wrap'>
212
207
  <label>
213
208
  <input
214
209
  type='checkbox'
@@ -217,6 +212,17 @@ const Header = (props: HeaderProps) => {
217
212
  />
218
213
  Show Download CSV Link
219
214
  </label>
215
+ {config.table.download && (
216
+ <input
217
+ type='text'
218
+ placeholder='Customize label'
219
+ defaultValue={config.table.downloadDataLabel}
220
+ onChange={e => changeConfigValue('table', 'downloadDataLabel', e.target.value)}
221
+ />
222
+ )}
223
+ </div>
224
+
225
+ <div className='wrap'>
220
226
  <label>
221
227
  <input
222
228
  type='checkbox'
@@ -225,6 +231,22 @@ const Header = (props: HeaderProps) => {
225
231
  />
226
232
  Show URL to Automatically Updated Data
227
233
  </label>
234
+ <label>
235
+ <input
236
+ type='checkbox'
237
+ defaultChecked={config.table.downloadImageButton}
238
+ onChange={e => changeConfigValue('table', 'downloadImageButton', e.target.checked)}
239
+ />
240
+ Show Download Image Button
241
+ </label>
242
+ {config.table.downloadImageButton && (
243
+ <input
244
+ type='text'
245
+ placeholder='Customize label'
246
+ defaultValue={config.table.downloadImageLabel}
247
+ onChange={e => changeConfigValue('table', 'downloadImageLabel', e.target.value)}
248
+ />
249
+ )}
228
250
  </div>
229
251
  </>
230
252
  )}
@@ -1,4 +1,4 @@
1
- .cdc-open-viz-module .shared-filter-modal,
1
+ .cove-visualization .shared-filter-modal,
2
2
  .cove .shared-filter-modal,
3
3
  .shared-filter-modal {
4
4
  &__title {
@@ -1,17 +1,17 @@
1
1
  .multi-config-tabs {
2
2
  .nav-link {
3
+ border-color: var(--lightGray);
3
4
  border-radius: 6px 6px 0 0;
4
- font-weight: 400;
5
+ color: var(--primary);
5
6
  display: block;
7
+ font-weight: 400;
6
8
  padding: 0.5rem 1rem;
7
9
  @media (max-width: 480px) {
8
10
  padding: 0.5rem 0.8rem;
9
11
  }
10
- color: var(--primary);
11
- border-color: var(--lightGray);
12
12
  :is(button) {
13
- display: none;
14
13
  background: none;
14
+ display: none;
15
15
  }
16
16
  &:hover {
17
17
  :is(button) {
@@ -31,9 +31,9 @@
31
31
  font-weight: bold;
32
32
  }
33
33
  .btn-danger {
34
- text-decoration: none;
35
- padding: 0px 5px;
36
34
  font-size: inherit;
35
+ padding: 0px 5px;
36
+ text-decoration: none;
37
37
  }
38
38
  }
39
39
  .add {
@@ -70,6 +70,12 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
70
70
  updateConfig({ ...config, rows: newRows })
71
71
  }
72
72
 
73
+ const toggleEqualHeight = () => {
74
+ const newRows = _.cloneDeep(rows)
75
+ newRows[rowIdx].equalHeight = !newRows[rowIdx].equalHeight
76
+ updateConfig({ ...config, rows: newRows })
77
+ }
78
+
73
79
  const moveRow = (dir = 'down') => {
74
80
  if (rowIdx === rows.length - 1 && dir === 'down') return
75
81
 
@@ -179,9 +185,24 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
179
185
  </li>
180
186
  ]
181
187
 
188
+ const isMultiColumn = curr !== '12' && curr !== 'toggle'
189
+
182
190
  return (
183
191
  <nav className='row-menu'>
184
192
  <ul className='row-menu__flyout'>{layoutList}</ul>
193
+ {isMultiColumn && (
194
+ <button
195
+ className={`btn row-menu__btn border-0${row.equalHeight ? ' btn-primary' : ''}`}
196
+ title={row.equalHeight ? 'Disable Equal Height Rows' : 'Enable Equal Height Rows'}
197
+ onClick={toggleEqualHeight}
198
+ >
199
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='25' height='20' fill='#fff'>
200
+ <rect x='1' y='2' width='9' height='14' rx='1' />
201
+ <rect x='14' y='2' width='9' height='14' rx='1' />
202
+ <line x1='0' y1='19' x2='24' y2='19' stroke='#fff' strokeWidth='2' strokeDasharray='3 2' />
203
+ </svg>
204
+ </button>
205
+ )}
185
206
  <div className='spacer'></div>
186
207
  <button
187
208
  className={`btn btn-primary row-menu__btn border-0`}
@@ -1,10 +1,10 @@
1
- .cdc-open-viz-module {
1
+ .cove-visualization {
2
2
  --border: 1px solid var(--lightGray);
3
3
  .toggle-component {
4
4
  display: flex;
5
5
  justify-content: right;
6
- width: 100%;
7
6
  margin-bottom: 15px;
7
+ width: 100%;
8
8
  :first-child:is(div) {
9
9
  border: var(--border);
10
10
  border-radius: 5px 0 0 5px;
@@ -14,18 +14,18 @@
14
14
  border-radius: 0 5px 5px 0;
15
15
  }
16
16
  :is(div) {
17
- border-top: var(--border);
17
+ background-color: var(--white);
18
18
  border-bottom: var(--border);
19
- padding: 7px 15px;
19
+ border-top: var(--border);
20
+ color: var(--primary);
21
+ cursor: pointer;
20
22
  display: inline;
21
23
  float: right;
22
- cursor: pointer;
24
+ padding: 7px 15px;
23
25
  &.selected {
24
26
  background-color: var(--primary);
25
27
  color: white;
26
28
  }
27
- background-color: var(--white);
28
- color: var(--primary);
29
29
  :is(svg) {
30
30
  height: 25px;
31
31
  }
@@ -1,7 +1,7 @@
1
1
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
2
2
  import React, { useContext, useEffect, useMemo, useState } from 'react'
3
3
  import Toggle from './Toggle'
4
- import _ from 'lodash'
4
+ import cloneDeep from 'lodash/cloneDeep'
5
5
  import { ConfigRow } from '../types/ConfigRow'
6
6
  import CdcDataBite from '@cdc/data-bite/src/CdcDataBite'
7
7
  import CdcMap from '@cdc/map/src/CdcMapComponent'
@@ -103,53 +103,65 @@ 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
110
-
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
106
+ const setupTP5MinHeightEqualizer = (rowElement: Element, itemSelector: string) => {
107
+ const items = Array.from(rowElement.querySelectorAll(itemSelector)) as HTMLElement[]
108
+ if (items.length <= 1) return undefined
113
109
 
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
+ if (!row.equalHeight) return
144
+
145
+ const rowElement = document.querySelector(`[data-row-index="${index}"]`)
146
+ if (!rowElement) return
147
+
148
+ const cleanups = [
149
+ setupTP5MinHeightEqualizer(rowElement, '.bite__style--tp5 .cdc-callout__heading'),
150
+ setupTP5MinHeightEqualizer(rowElement, '.waffle__style--tp5 .cdc-callout__heading'),
151
+ setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cdc-callout__heading'),
152
+ setupTP5MinHeightEqualizer(rowElement, '.gauge__style--tp5 .cove-gauge-chart__content')
153
+ ].filter(Boolean) as Array<() => void>
154
+
148
155
  return () => {
149
- resizeObserver.disconnect()
156
+ cleanups.forEach(cleanup => cleanup())
150
157
  }
151
158
  }, [index, row, config, filteredDataOverride])
152
159
 
160
+ const isFilterRow = row.columns.some(
161
+ col => col.widget && config.visualizations[col.widget]?.type === 'dashboardFilters'
162
+ )
163
+ const needsEqualHeight = !!row.equalHeight && !isFilterRow
164
+
153
165
  const show = useMemo(() => {
154
166
  if (row.toggle) {
155
167
  return row.columns.map((col, i) => i === toggledRow)
@@ -196,7 +208,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
196
208
  )}
197
209
  {Object.keys(dataGroups).map(groupName => {
198
210
  const dataValue = dataGroups[groupName]
199
- const _row = _.cloneDeep(row) // clone the row to avoid mutating the original row
211
+ const _row = cloneDeep(row) // clone the row to avoid mutating the original row
200
212
  const originalMultiVizColumn = _row.multiVizColumn // store original value before clearing
201
213
  _row.multiVizColumn = undefined // reset the multiVizColumn to avoid passing it to the child components
202
214
  _row.originalMultiVizColumn = originalMultiVizColumn // store for footnote filtering
@@ -224,7 +236,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
224
236
 
225
237
  return (
226
238
  <div
227
- className={`row${row.equalHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
239
+ className={`row${needsEqualHeight ? ' equal-height' : ''}${row.toggle ? ' toggle' : ''}`}
228
240
  key={`row__${index}`}
229
241
  data-row-index={index}
230
242
  >
@@ -347,6 +359,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
347
359
  updateChildConfig(col.widget, newConfig)
348
360
  }}
349
361
  isDashboard={true}
362
+ isEditor={config.editing === true}
350
363
  interactionLabel={interactionLabel}
351
364
  />
352
365
  )}
@@ -358,7 +371,7 @@ const VisualizationRow: React.FC<VizRowProps> = ({
358
371
  updateChildConfig(col.widget, newConfig)
359
372
  }}
360
373
  isDashboard={true}
361
- interactionLabel={link}
374
+ interactionLabel={interactionLabel}
362
375
  />
363
376
  )}
364
377
  {type === 'markup-include' && (
@@ -1,83 +1,13 @@
1
1
  import { useContext, useState } from 'react'
2
- import type { AnyVisualization } from '@cdc/core/types/Visualization'
3
2
  import Widget from '../Widget/Widget'
4
3
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
5
- import { Table } from '@cdc/core/types/Table'
6
4
  import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
5
+ import { addVisualization } from '../../helpers/addVisualization'
7
6
  import { mapDataToConfig } from '../../helpers/mapDataToConfig'
8
7
  import './visualizations-panel-styles.css'
9
8
  import { MultiDashboardConfig } from '../../types/MultiDashboard'
10
9
  import { stripConfig } from '../../helpers/formatConfigBeforeSave'
11
10
 
12
- const addVisualization = (type, subType) => {
13
- const modalWillOpen = type !== 'markup-include'
14
- const newVisualizationConfig: Partial<AnyVisualization> = {
15
- filters: [],
16
- filterBehavior: 'Filter Change',
17
- newViz: type !== 'table',
18
- openModal: modalWillOpen,
19
- uid: type + Date.now(),
20
- type
21
- }
22
-
23
- switch (type) {
24
- case 'chart':
25
- newVisualizationConfig.visualizationType = subType
26
- break
27
- case 'map':
28
- newVisualizationConfig.general = {}
29
- newVisualizationConfig.general.geoType = subType
30
- break
31
- case 'data-bite' || 'waffle-chart' || 'filtered-text':
32
- newVisualizationConfig.visualizationType = type
33
- break
34
- case 'table':
35
- const tableConfig: Table = {
36
- label: 'Data Table',
37
- show: true,
38
- showDownloadUrl: false,
39
- showVertical: true,
40
- expanded: true,
41
- collapsible: true
42
- }
43
- newVisualizationConfig.table = tableConfig
44
- newVisualizationConfig.columns = {}
45
- newVisualizationConfig.dataFormat = {}
46
- newVisualizationConfig.visualizationType = type
47
- break
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
- newVisualizationConfig.visualizationType = type
66
-
67
- break
68
- case 'dashboardFilters': {
69
- newVisualizationConfig.sharedFilterIndexes = []
70
- newVisualizationConfig.visualizationType = type
71
- break
72
- }
73
- default:
74
- newVisualizationConfig.visualizationType = type
75
- break
76
- }
77
-
78
- return newVisualizationConfig
79
- }
80
-
81
11
  const VisualizationsPanel = () => {
82
12
  const [advancedEditing, setAdvancedEditing] = useState(false)
83
13
  const { config, isEditor } = useContext(DashboardContext)
@@ -1,10 +1,10 @@
1
1
  .visualizations-panel {
2
2
  background-color: #fff;
3
+ border-right: #c7c7c7 1px solid;
4
+ overflow-y: scroll;
3
5
  padding: 1em;
4
6
  width: var(--editorWidth);
5
- border-right: #c7c7c7 1px solid;
6
7
  z-index: 1;
7
- overflow-y: scroll;
8
8
 
9
9
  &.advanced-editor {
10
10
  width: 50vw;
@@ -105,11 +105,11 @@ const Widget = ({
105
105
  const url = changeDataLimit(dataset.dataUrl, 100)
106
106
  if (url) {
107
107
  fetchRemoteData(url)
108
- .then(responseData => {
108
+ .then(({ data: responseData }) => {
109
109
  // this sample data is temporary.
110
110
  // the HEADER component removes the data when you toggle to the main viz panel.
111
111
  // data will be cached only when it's loaded via dashboard preview.
112
- responseData.sample = true
112
+ ;(responseData as any).sample = true
113
113
  dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
114
114
  })
115
115
  .catch(error => {
@@ -1,13 +1,13 @@
1
1
  .widget__toggle-title {
2
- display: flex;
3
- flex-direction: column;
4
- justify-content: center;
5
2
  align-items: center;
6
- width: 100%;
7
- height: 25%;
8
3
  background-color: #005eaa;
9
4
  color: white;
5
+ display: flex;
6
+ flex-direction: column;
7
+ height: 25%;
8
+ justify-content: center;
10
9
  padding: 5px;
10
+ width: 100%;
11
11
  }
12
12
 
13
13
  .widget__edit-title-icon {
@@ -19,37 +19,37 @@
19
19
  }
20
20
 
21
21
  .widget--toggle .widget__content .widget-menu + svg {
22
+ flex-grow: unset;
22
23
  height: 35px !important;
23
24
  width: 35px !important;
24
- flex-grow: unset;
25
25
  }
26
26
 
27
27
  .widget-menu {
28
28
  position: absolute;
29
- top: 6px;
30
29
  right: 6px;
30
+ top: 6px;
31
31
  }
32
32
 
33
33
  .widget-menu {
34
- display: flex;
35
34
  align-items: center;
35
+ display: flex;
36
36
  justify-content: space-between;
37
37
  }
38
38
 
39
39
  .btn-configure {
40
40
  background: none;
41
- width: 20px;
42
41
  height: 20px;
43
- padding: 0;
44
42
  margin: 0 5px;
43
+ padding: 0;
44
+ width: 20px;
45
45
  }
46
46
 
47
47
  .widget-menu-item {
48
+ cursor: pointer;
48
49
  display: block;
49
- width: 20px;
50
50
  height: 20px;
51
- cursor: pointer;
52
51
  user-select: none;
52
+ width: 20px;
53
53
  }
54
54
 
55
55
  .widget-menu-item svg {
@@ -4,7 +4,7 @@ export default {
4
4
  titleStyle: 'small',
5
5
  sharedFilters: []
6
6
  },
7
- rows: [[{ width: 12 }, {}, {}]],
7
+ rows: [{ columns: [{ width: 12 }] }],
8
8
  visualizations: {},
9
9
  table: {
10
10
  label: 'Data Table',
@@ -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