@cdc/dashboard 4.24.10 → 4.24.12-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 (58) hide show
  1. package/dist/cdcdashboard.js +51165 -49100
  2. package/examples/ed-visits-county-file.json +141 -357
  3. package/examples/private/DEV-10120.json +1294 -0
  4. package/examples/private/DEV-9199.json +606 -0
  5. package/examples/private/DEV-9644.json +20092 -0
  6. package/examples/private/DEV-9684.json +2135 -0
  7. package/examples/private/DEV-9989.json +229 -0
  8. package/examples/private/art-dashboard.json +18174 -0
  9. package/examples/private/art-scratch.json +2406 -0
  10. package/examples/private/dashboard-config-ehdi.json +29915 -0
  11. package/examples/private/dashboard-margins.js +15 -0
  12. package/examples/private/dataset.json +1452 -0
  13. package/examples/private/ehdi-data.json +29502 -0
  14. package/examples/private/gaza-issue.json +1214 -0
  15. package/examples/private/workforce.json +2041 -0
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +43 -29
  18. package/src/CdcDashboardComponent.tsx +91 -52
  19. package/src/DashboardContext.tsx +2 -0
  20. package/src/_stories/Dashboard.stories.tsx +8 -0
  21. package/src/_stories/_mock/api-filter-error.json +55 -0
  22. package/src/_stories/_mock/group-pivot-filter.json +10 -5
  23. package/src/components/CollapsibleVisualizationRow.tsx +8 -2
  24. package/src/components/DashboardFilters/DashboardFilters.tsx +121 -58
  25. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +3 -1
  26. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +54 -50
  27. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +13 -7
  28. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
  29. package/src/components/DashboardFilters/dashboardfilter.styles.css +27 -0
  30. package/src/components/Grid.tsx +1 -1
  31. package/src/components/Header/Header.tsx +71 -10
  32. package/src/components/Header/index.scss +0 -5
  33. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
  34. package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
  35. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
  36. package/src/components/Row.tsx +59 -13
  37. package/src/components/VisualizationRow.tsx +30 -22
  38. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
  39. package/src/components/Widget.tsx +23 -1
  40. package/src/data/initial-state.js +2 -1
  41. package/src/helpers/addValuesToDashboardFilters.ts +4 -2
  42. package/src/helpers/apiFilterHelpers.ts +55 -20
  43. package/src/helpers/changeFilterActive.ts +3 -0
  44. package/src/helpers/filterData.ts +1 -1
  45. package/src/helpers/getVizRowColumnLocator.ts +1 -0
  46. package/src/helpers/loadAPIFilters.ts +32 -10
  47. package/src/helpers/reloadURLHelpers.ts +9 -2
  48. package/src/helpers/shouldLoadAllFilters.ts +30 -0
  49. package/src/helpers/tests/apiFilterHelpers.test.ts +85 -4
  50. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +10 -4
  51. package/src/helpers/tests/reloadURLHelpers.test.ts +11 -5
  52. package/src/helpers/tests/shouldLoadAllFilters.test.ts +117 -0
  53. package/src/scss/editor-panel.scss +0 -3
  54. package/src/scss/grid.scss +22 -23
  55. package/src/scss/main.scss +0 -27
  56. package/src/store/dashboard.reducer.ts +9 -2
  57. package/src/store/errorMessage/errorMessage.actions.ts +7 -0
  58. package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
@@ -2,22 +2,31 @@ import React from 'react'
2
2
  import MultiSelect from '@cdc/core/components/MultiSelect'
3
3
  import { SharedFilter } from '../../types/SharedFilter'
4
4
  import { APIFilterDropdowns, DropdownOptions } from './DashboardFiltersWrapper'
5
- import NestedDropdown from '../../../../core/components/NestedDropdown/NestedDropdown'
6
5
  import { FILTER_STYLE } from '../../types/FilterStyles'
7
6
  import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
7
+ import NestedDropdown from '@cdc/core/components/NestedDropdown'
8
+ import { MouseEventHandler } from 'react'
9
+ import Loader from '@cdc/core/components/Loader'
10
+ import _ from 'lodash'
8
11
 
9
12
  type DashboardFilterProps = {
10
13
  show: number[]
11
14
  filters: SharedFilter[]
12
15
  apiFilterDropdowns: APIFilterDropdowns
13
16
  handleOnChange: (index: number, value: string | string[]) => void
17
+ showSubmit: boolean
18
+ applyFilters: MouseEventHandler<HTMLButtonElement>
19
+ applyFiltersButtonText?: string
14
20
  }
15
21
 
16
22
  const DashboardFilters: React.FC<DashboardFilterProps> = ({
17
23
  show,
18
24
  filters: sharedFilters,
19
25
  apiFilterDropdowns,
20
- handleOnChange
26
+ handleOnChange,
27
+ showSubmit,
28
+ applyFilters,
29
+ applyFiltersButtonText
21
30
  }) => {
22
31
  const nullVal = (filter: SharedFilter) => {
23
32
  const val = filter.queuedActive || filter.active
@@ -38,29 +47,34 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
38
47
  }
39
48
 
40
49
  return (
41
- <>
50
+ <form className='d-flex flex-wrap'>
42
51
  {sharedFilters.map((filter, filterIndex) => {
43
52
  const urlFilterType = filter.type === 'urlfilter'
53
+ const label = filter.key
44
54
 
45
55
  if (
46
56
  (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
47
57
  (show && !show.includes(filterIndex))
48
58
  )
49
- return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
59
+ return <React.Fragment key={`${label}-filtersection-${filterIndex}-option`} />
50
60
  const values: JSX.Element[] = []
51
61
 
52
- if (filter.resetLabel) {
53
- values.push(
54
- <option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
55
- {filter.resetLabel}
56
- </option>
57
- )
58
- }
59
-
60
62
  const _key = filter.apiFilter?.apiEndpoint
63
+ const loading = apiFilterDropdowns[_key] === null
61
64
 
62
65
  const multiValues: { value; label }[] = []
66
+ const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
67
+ ([key, data]) => [
68
+ [key, key], // Main option: [value, text]
69
+ Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
70
+ ]
71
+ )
63
72
 
73
+ const activeSubGroupValue = _.get(
74
+ filter?.subGrouping?.valuesLookup,
75
+ [filter?.active as string, 'values', 0],
76
+ null // Default to null if the path is invalid
77
+ )
64
78
  if (_key && apiFilterDropdowns[_key]) {
65
79
  // URL Filter
66
80
  if (filter.filterStyle !== FILTER_STYLE.nestedDropdown) {
@@ -77,59 +91,108 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
77
91
  // Data Filter
78
92
  filter.values?.forEach((filterOption, index) => {
79
93
  const labeledOpt = filter.labels && filter.labels[filterOption]
80
- values.push(
81
- <option key={`${filter.key}-option-${index}`} value={filterOption}>
82
- {labeledOpt || filterOption}
83
- </option>
84
- )
94
+ const resetLabelHasMatch = (filterOption || labeledOpt) === filter.resetLabel
95
+
96
+ if (!resetLabelHasMatch) {
97
+ values.push(
98
+ <option key={`${filter.key}-option-${index}`} value={filterOption}>
99
+ {labeledOpt || filterOption}
100
+ </option>
101
+ )
102
+ } else {
103
+ // add label to the front of list if it matches with reset label
104
+ values.unshift(
105
+ <option key={`${filter.key}-option-${index}`} value={filterOption}>
106
+ {labeledOpt || filterOption}
107
+ </option>
108
+ )
109
+ }
110
+
85
111
  multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
86
112
  })
87
113
  }
88
114
 
89
- return filter.filterStyle === FILTER_STYLE.multiSelect ? (
90
- <MultiSelect
91
- key={`${filter.key}-filtersection-${filterIndex}`}
92
- label={filter.key}
93
- options={multiValues}
94
- fieldName={filterIndex}
95
- updateField={updateField}
96
- selected={filter.active as string[]}
97
- limit={filter.selectLimit || 5}
98
- />
99
- ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
100
- <NestedDropdown
101
- key={`${filter.key}-filtersection-${filterIndex}`}
102
- activeGroup={filter.active as string}
103
- activeSubGroup={filter.subGrouping?.active}
104
- options={getNestedDropdownOptions(apiFilterDropdowns[_key])}
105
- listLabel={filter.key}
106
- handleSelectedItems={value => updateField(null, null, filterIndex, value)}
107
- />
108
- ) : (
109
- <div className='cove-dashboard-filters' key={`${filter.key}-filtersection-${filterIndex}`}>
110
- <section className='dashboard-filters-section'>
111
- <label htmlFor={`filter-${filterIndex}`}>{filter.key}</label>
112
- <select
113
- id={`filter-${filterIndex}`}
114
- className='filter-select'
115
- data-index='0'
116
- value={filter.queuedActive || filter.active}
117
- onChange={val => {
118
- handleOnChange(filterIndex, val.target.value)
119
- }}
120
- >
121
- {nullVal(filter) && !filter.resetLabel && (
122
- <option value='' key='select'>
123
- {'-Select-'}
124
- </option>
125
- )}
126
- {values}
127
- </select>
128
- </section>
115
+ const isDisabled = !values.length
116
+ // push reset label only if it does not includes in filter values options
117
+ if (filter.resetLabel && !filter.values.includes(filter.resetLabel)) {
118
+ values.unshift(
119
+ <option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
120
+ {filter.resetLabel}
121
+ </option>
122
+ )
123
+ }
124
+
125
+ const formGroupClass = `form-group mr-3 mb-1${loading ? ' loading-filter' : ''}`
126
+
127
+ return (
128
+ <div className={formGroupClass} key={`${label}-filtersection-${filterIndex}`}>
129
+ {label && (
130
+ <label className='font-weight-bold mt-1 mb-0' htmlFor={`filter-${filterIndex}`}>
131
+ {label}
132
+ </label>
133
+ )}
134
+ {filter.filterStyle === FILTER_STYLE.multiSelect ? (
135
+ <MultiSelect
136
+ label={label}
137
+ options={multiValues}
138
+ fieldName={filterIndex}
139
+ updateField={updateField}
140
+ selected={filter.active as string[]}
141
+ limit={filter.selectLimit || 5}
142
+ loading={loading}
143
+ />
144
+ ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
145
+ <NestedDropdown
146
+ activeGroup={filter.active as string}
147
+ activeSubGroup={_key ? filter.subGrouping?.active : activeSubGroupValue}
148
+ filterIndex={filterIndex}
149
+ options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
150
+ listLabel={label}
151
+ handleSelectedItems={value => updateField(null, null, filterIndex, value)}
152
+ loading={loading}
153
+ />
154
+ ) : (
155
+ <>
156
+ <select
157
+ id={`filter-${filterIndex}`}
158
+ className='cove-form-select'
159
+ data-index='0'
160
+ value={loading ? 'Loading...' : filter.queuedActive || filter.active}
161
+ onChange={val => {
162
+ handleOnChange(filterIndex, val.target.value)
163
+ }}
164
+ disabled={loading || isDisabled}
165
+ >
166
+ {loading && <option value='Loading...'>Loading...</option>}
167
+ {nullVal(filter) && (
168
+ <option key={`select`} value=''>
169
+ {filter.resetLabel || '- Select -'}
170
+ </option>
171
+ )}
172
+ {values}
173
+ </select>
174
+ {loading && <Loader spinnerType={'text-secondary'} />}
175
+ </>
176
+ )}
129
177
  </div>
130
178
  )
131
179
  })}
132
- </>
180
+ {showSubmit && (
181
+ <button
182
+ className='btn btn-primary mb-1'
183
+ onClick={applyFilters}
184
+ disabled={show.some(filterIndex => {
185
+ const emptyFilterValues = [undefined, '', '- Select -']
186
+ return (
187
+ emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
188
+ emptyFilterValues.includes(sharedFilters[filterIndex].active)
189
+ )
190
+ })}
191
+ >
192
+ {applyFiltersButtonText || 'GO!'}
193
+ </button>
194
+ )}
195
+ </form>
133
196
  )
134
197
  }
135
198
 
@@ -274,7 +274,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
274
274
  </select>
275
275
  </label>
276
276
  ) : (
277
- <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width'>
277
+
278
+ <button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
279
+
278
280
  Add Existing Dashboard Filter
279
281
  </button>
280
282
  )}
@@ -41,11 +41,10 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
41
41
  if (viz.type === 'dashboardFilters') return false
42
42
  const vizName = viz.general?.title || viz.title || vizKey
43
43
  nameLookup[vizKey] = vizName
44
- const notAdded = !filter.usedBy || filter.usedBy.indexOf(vizKey) === -1
45
44
  const usesSharedFilter = viz.usesSharedFilter
46
45
  const rowIndex = vizLookup.row
47
46
  const dataConfiguredOnRow = config.rows[rowIndex].dataKey
48
- return filter.setBy !== vizKey && notAdded && !usesSharedFilter && !dataConfiguredOnRow
47
+ return filter.setBy !== vizKey && !usesSharedFilter && !dataConfiguredOnRow
49
48
  })
50
49
  const rowOptions: number[] = []
51
50
 
@@ -363,20 +362,23 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
363
362
  {isNestedDropDown && <APIInputs isSubgroup={true} />}
364
363
 
365
364
  {!!parentFilters.length && (
366
- <MultiSelect
367
- label='Parent Filter(s): '
368
- options={parentFilters.map(key => ({ value: key, label: key }))}
369
- fieldName='parents'
370
- selected={filter.parents}
371
- updateField={(_section, _subsection, _fieldname, newItems) => {
372
- updateFilterProp('parents', newItems)
373
- }}
374
- />
365
+ <label>
366
+ <span className='edit-label column-heading mt-1'>Parent Filter(s): </span>
367
+ <MultiSelect
368
+ label='Parent Filter(s): '
369
+ options={parentFilters.map(key => ({ value: key, label: key }))}
370
+ fieldName='parents'
371
+ selected={filter.parents}
372
+ updateField={(_section, _subsection, _fieldname, newItems) => {
373
+ updateFilterProp('parents', newItems)
374
+ }}
375
+ />
376
+ </label>
375
377
  )}
376
378
 
377
- <MultiSelect
378
- label='Used By: (optional)'
379
- tooltip={
379
+ <label>
380
+ <span className='edit-label column-heading mt-1'>
381
+ Used By: (optional)
380
382
  <Tooltip style={{ textTransform: 'none' }}>
381
383
  <Tooltip.Target>
382
384
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
@@ -388,17 +390,19 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
388
390
  </p>
389
391
  </Tooltip.Content>
390
392
  </Tooltip>
391
- }
392
- options={[...usedByOptions, ...(filter.usedBy || [])].map(opt => ({
393
- value: opt,
394
- label: usedByNameLookup[opt]
395
- }))}
396
- fieldName='usedBy'
397
- selected={filter.usedBy}
398
- updateField={(_section, _subsection, _fieldname, newItems) => {
399
- updateFilterProp('usedBy', newItems)
400
- }}
401
- />
393
+ </span>
394
+ <MultiSelect
395
+ options={usedByOptions.map(opt => ({
396
+ value: opt,
397
+ label: usedByNameLookup[opt]
398
+ }))}
399
+ fieldName='usedBy'
400
+ selected={filter.usedBy}
401
+ updateField={(_section, _subsection, _fieldname, newItems) => {
402
+ updateFilterProp('usedBy', newItems)
403
+ }}
404
+ />
405
+ </label>
402
406
 
403
407
  <TextField
404
408
  label='Reset Label: '
@@ -473,31 +477,31 @@ const FilterEditor: React.FC<FilterEditorProps> = ({ filter, config, updateFilte
473
477
  </select>
474
478
  </label>
475
479
  <label>
476
- <span className='edit-label column-heading'>Used By: </span>
477
- <ul>
478
- {filter.usedBy &&
479
- filter.usedBy.map(opt => (
480
- <li key={`used-by-list-item-${opt}`}>
481
- <span>{usedByNameLookup[opt] || opt}</span>{' '}
482
- <button
483
- onClick={e => {
484
- e.preventDefault()
485
- removeFilterUsedBy(filter, opt)
486
- }}
487
- >
488
- X
489
- </button>
490
- </li>
491
- ))}
492
- </ul>
493
- <select value='' onChange={e => addFilterUsedBy(filter, e.target.value)}>
494
- <option value=''>- Select Option -</option>
495
- {usedByOptions.map(opt => (
496
- <option value={opt} key={`used-by-select-item-${opt}`}>
497
- {usedByNameLookup[opt] || opt}
498
- </option>
499
- ))}
500
- </select>
480
+ <span className='edit-label column-heading mt-1'>
481
+ Used By: (optional)
482
+ <Tooltip style={{ textTransform: 'none' }}>
483
+ <Tooltip.Target>
484
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
485
+ </Tooltip.Target>
486
+ <Tooltip.Content>
487
+ <p>
488
+ Select if you would like specific visualizations or rows to use this filter. Otherwise the
489
+ filter will be added to all api requests.
490
+ </p>
491
+ </Tooltip.Content>
492
+ </Tooltip>
493
+ </span>
494
+ <MultiSelect
495
+ options={usedByOptions.map(opt => ({
496
+ value: opt,
497
+ label: usedByNameLookup[opt]
498
+ }))}
499
+ fieldName='usedBy'
500
+ selected={filter.usedBy}
501
+ updateField={(_section, _subsection, _fieldname, newItems) => {
502
+ updateFilterProp('usedBy', newItems)
503
+ }}
504
+ />
501
505
  </label>
502
506
  <TextField
503
507
  label='Reset Label: '
@@ -13,6 +13,7 @@ import { ViewPort } from '@cdc/core/types/ViewPort'
13
13
  import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
14
14
  import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
15
  import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
+ import './dashboardfilter.styles.css'
16
17
 
17
18
  type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
18
19
 
@@ -30,6 +31,7 @@ type DashboardFiltersProps = {
30
31
  isEditor?: boolean
31
32
  setConfig: (config: DashboardFilters) => void
32
33
  currentViewport?: ViewPort
34
+ setAPILoading: (loading: boolean) => void
33
35
  }
34
36
 
35
37
  const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
@@ -40,10 +42,11 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
40
42
  isEditor = false
41
43
  }) => {
42
44
  const state = useContext(DashboardContext)
43
- const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns } = state
45
+ const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
44
46
  const dispatch = useContext(DashboardDispatchContext)
45
47
 
46
- const applyFilters = () => {
48
+ const applyFilters = e => {
49
+ e.preventDefault() // prevent form submission
47
50
  const dashboardConfig = _.cloneDeep(state.config.dashboard)
48
51
  const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
49
52
  .filter(v => v.type === 'dashboardFilters')
@@ -79,7 +82,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
79
82
  updateQueryString(queryParams)
80
83
  }
81
84
  }
82
-
85
+ setAPILoading(true)
83
86
  dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
84
87
  dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
85
88
  loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
@@ -103,6 +106,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
103
106
  visualizationConfig
104
107
  )
105
108
 
109
+ // sets the active filter option that the user just selected.
110
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
111
+
106
112
  if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
107
113
  const isAutoSelectFilter = visualizationConfig.autoLoad
108
114
  const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
@@ -171,7 +177,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
171
177
  {!displayNone && (
172
178
  <Layout.Responsive isEditor={isEditor}>
173
179
  <div
174
- className={`cdc-dashboard-inner-container${
180
+ className={`${
175
181
  isEditor ? ' is-editor' : ''
176
182
  } cove-component__content col-12 cove-dashboard-filters-container`}
177
183
  >
@@ -180,10 +186,10 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
180
186
  filters={dashboardConfig.dashboard.sharedFilters || []}
181
187
  apiFilterDropdowns={apiFilterDropdowns}
182
188
  handleOnChange={handleOnChange}
189
+ showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
190
+ applyFilters={applyFilters}
191
+ applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
183
192
  />
184
- {visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad && (
185
- <button onClick={applyFilters}>{visualizationConfig.applyFiltersButtonText || 'GO!'}</button>
186
- )}
187
193
  </div>
188
194
  </Layout.Responsive>
189
195
  )}
@@ -0,0 +1,21 @@
1
+ import { Meta, StoryObj } from '@storybook/react'
2
+ import DashboardFilters from '../DashboardFilters'
3
+
4
+ const meta: Meta<typeof DashboardFilters> = {
5
+ title: 'Components/Atoms/Inputs/DashboardFilters',
6
+ component: DashboardFilters
7
+ }
8
+
9
+ type Story = StoryObj<typeof DashboardFilters>
10
+
11
+ export const Example_1: Story = {
12
+ args: {
13
+ filters: [
14
+ { type: 'datafilter', key: 'label here', values: [1, 2, 3, 4] },
15
+ { type: 'datafilter', key: 'something' }
16
+ ],
17
+ handleOnChange: () => {}
18
+ }
19
+ }
20
+
21
+ export default meta
@@ -0,0 +1,27 @@
1
+ .cove-dashboard-filters-container {
2
+ :is(label) {
3
+ margin-bottom: 0;
4
+ margin-top: 0.5rem;
5
+ }
6
+ .btn {
7
+ /* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
8
+ height: calc(1.5em + 0.75rem + 2px);
9
+ align-self: flex-end;
10
+ }
11
+ .loading-filter {
12
+ position: relative;
13
+ .spinner-border {
14
+ position: absolute;
15
+ top: 55%;
16
+ right: 10%;
17
+ width: 1.5rem;
18
+ height: 1.5rem;
19
+ }
20
+ }
21
+ :is(select):disabled {
22
+ background-color: var(--lightestGray);
23
+ & > :is(option) {
24
+ color: var(--darkGray);
25
+ }
26
+ }
27
+ }
@@ -24,7 +24,7 @@ const Grid = () => {
24
24
  {(rows || []).map((row, idx) => (
25
25
  <Row row={row} idx={idx} uuid={row.uuid} key={idx} />
26
26
  ))}
27
- <button className='btn add-row' onClick={addRow}>
27
+ <button className='btn btn-primary col' onClick={addRow}>
28
28
  Add Row
29
29
  </button>
30
30
  </div>
@@ -6,6 +6,7 @@ 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'
9
10
 
10
11
  type HeaderProps = {
11
12
  back?: any
@@ -16,7 +17,7 @@ type HeaderProps = {
16
17
  const Header = (props: HeaderProps) => {
17
18
  const tabs: Tab[] = ['Dashboard Description', 'Data Table Settings', 'Dashboard Preview']
18
19
  const { visualizationKey, subEditor } = props
19
- const { config, setParentConfig, tabSelected } = useContext(DashboardContext)
20
+ const { config, setParentConfig, tabSelected, data } = useContext(DashboardContext)
20
21
  if (!config) return null
21
22
  const dispatch = useContext(DashboardDispatchContext)
22
23
  const back = () => {
@@ -24,6 +25,22 @@ const Header = (props: HeaderProps) => {
24
25
  const newConfig = _.cloneDeep(config)
25
26
  newConfig.visualizations[visualizationKey].editing = false
26
27
  dispatch({ type: 'SET_CONFIG', payload: newConfig })
28
+
29
+ // the Widget component will do a data fetch if no data is available for the visualization
30
+ // this is intended to help visualization developers.
31
+ type SampleData = Record<string, { sample: boolean }> & Object[]
32
+ if (Object.values(data).some((d: SampleData) => d.sample)) {
33
+ const sampleDataRemoved = Object.keys(data).reduce((acc, key) => {
34
+ if ((data[key] as SampleData).sample) {
35
+ acc[key] = []
36
+ } else {
37
+ acc[key] = data[key]
38
+ }
39
+ return acc
40
+ }, {})
41
+
42
+ dispatch({ type: 'SET_DATA', payload: sampleDataRemoved })
43
+ }
27
44
  }
28
45
 
29
46
  const changeConfigValue = (parentObj, key, value) => {
@@ -81,10 +98,18 @@ const Header = (props: HeaderProps) => {
81
98
  <div className='heading-1'>
82
99
  Dashboard Editor{' '}
83
100
  <span className='small'>
84
- <input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make multidashboard
101
+ <input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make
102
+ multidashboard
85
103
  </span>
86
104
  <br />
87
- {<input type='text' placeholder='Enter Dashboard Name Here' defaultValue={config.dashboard?.title} onChange={e => changeConfigValue('dashboard', 'title', e.target.value)} />}
105
+ {
106
+ <input
107
+ type='text'
108
+ placeholder='Enter Dashboard Name Here'
109
+ defaultValue={config.dashboard?.title}
110
+ onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
111
+ />
112
+ }
88
113
  </div>
89
114
  )}
90
115
  {!subEditor && (
@@ -106,18 +131,34 @@ const Header = (props: HeaderProps) => {
106
131
  })}
107
132
  </ul>
108
133
  <div className='heading-body'>
109
- {tabSelected === 'Dashboard Description' && <input type='text' className='description-input' placeholder='Type a dashboard description here.' defaultValue={config.dashboard?.description} onChange={e => changeConfigValue('dashboard', 'description', e.target.value)} />}
134
+ {tabSelected === 'Dashboard Description' && (
135
+ <input
136
+ type='text'
137
+ className='description-input'
138
+ placeholder='Type a dashboard description here.'
139
+ defaultValue={config.dashboard?.description}
140
+ onChange={e => changeConfigValue('dashboard', 'description', e.target.value)}
141
+ />
142
+ )}
110
143
  {tabSelected === 'Data Table Settings' && (
111
144
  <>
112
145
  <div className='wrap'>
113
146
  <label>
114
- <input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
147
+ <input
148
+ type='checkbox'
149
+ defaultChecked={config.table.show}
150
+ onChange={e => changeConfigValue('table', 'show', e.target.checked)}
151
+ />
115
152
  Show Data Table(s)
116
153
  </label>
117
154
  <br />
118
155
 
119
156
  <label>
120
- <input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
157
+ <input
158
+ type='checkbox'
159
+ defaultChecked={config.table.expanded}
160
+ onChange={e => changeConfigValue('table', 'expanded', e.target.checked)}
161
+ />
121
162
  Expanded by Default
122
163
  </label>
123
164
  <br />
@@ -125,19 +166,39 @@ const Header = (props: HeaderProps) => {
125
166
 
126
167
  <div className='wrap'>
127
168
  <label>
128
- <input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
169
+ <input
170
+ type='checkbox'
171
+ defaultChecked={config.table.limitHeight}
172
+ onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)}
173
+ />
129
174
  Limit Table Height
130
175
  </label>
131
- {config.table.limitHeight && <input className='table-height-input' type='text' placeholder='Height (px)' defaultValue={config.table.height} onChange={e => changeConfigValue('table', 'height', e.target.value)} />}
176
+ {config.table.limitHeight && (
177
+ <input
178
+ className='table-height-input'
179
+ type='text'
180
+ placeholder='Height (px)'
181
+ defaultValue={config.table.height}
182
+ onChange={e => changeConfigValue('table', 'height', e.target.value)}
183
+ />
184
+ )}
132
185
  </div>
133
186
 
134
187
  <div className='wrap'>
135
188
  <label>
136
- <input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
189
+ <input
190
+ type='checkbox'
191
+ defaultChecked={config.table.download}
192
+ onChange={e => changeConfigValue('table', 'download', e.target.checked)}
193
+ />
137
194
  Show Download CSV Link
138
195
  </label>
139
196
  <label>
140
- <input type='checkbox' defaultChecked={config.table.showDownloadUrl} onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)} />
197
+ <input
198
+ type='checkbox'
199
+ defaultChecked={config.table.showDownloadUrl}
200
+ onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)}
201
+ />
141
202
  Show URL to Automatically Updated Data
142
203
  </label>
143
204
  </div>