@cdc/dashboard 4.23.11 → 4.24.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 (45) hide show
  1. package/dist/cdcdashboard.js +109007 -98738
  2. package/examples/DEV-6574.json +2224 -0
  3. package/examples/filters/Alabama.json +72 -0
  4. package/examples/filters/Alaska.json +1737 -0
  5. package/examples/filters/Arkansas.json +4713 -0
  6. package/examples/filters/California.json +212 -0
  7. package/examples/filters/Colorado.json +1500 -0
  8. package/examples/filters/Connecticut.json +559 -0
  9. package/examples/filters/Delaware.json +63 -0
  10. package/examples/filters/DistrictofColumbia.json +63 -0
  11. package/examples/filters/Florida.json +4217 -0
  12. package/examples/filters/States.json +146 -0
  13. package/examples/test.json +752 -0
  14. package/examples/zika.json +2274 -0
  15. package/index.html +5 -3
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +124 -963
  18. package/src/CdcDashboardComponent.tsx +903 -0
  19. package/src/_stories/Dashboard.stories.tsx +2 -2
  20. package/src/components/Column.tsx +15 -12
  21. package/src/components/Header/Header.tsx +694 -0
  22. package/src/components/Header/index.tsx +1 -676
  23. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +106 -0
  24. package/src/components/MultiConfigTabs/MultiTabs.tsx +30 -0
  25. package/src/components/MultiConfigTabs/index.tsx +8 -0
  26. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +32 -0
  27. package/src/components/Widget.tsx +25 -9
  28. package/src/helpers/filterData.ts +73 -73
  29. package/src/helpers/generateValuesForFilter.ts +25 -29
  30. package/src/helpers/getUpdateConfig.ts +6 -2
  31. package/src/helpers/processData.ts +13 -0
  32. package/src/helpers/processDataLegacy.ts +14 -0
  33. package/src/{index.jsx → index.tsx} +2 -2
  34. package/src/scss/editor-panel.scss +14 -11
  35. package/src/scss/grid.scss +4 -6
  36. package/src/scss/main.scss +2 -8
  37. package/src/store/dashboard.actions.ts +10 -4
  38. package/src/store/dashboard.reducer.ts +74 -3
  39. package/src/types/ConfigRow.ts +6 -0
  40. package/src/types/Dashboard.ts +11 -0
  41. package/src/types/DashboardConfig.ts +23 -0
  42. package/src/types/InitialState.ts +10 -0
  43. package/src/types/MultiDashboard.ts +11 -0
  44. package/src/types/SharedFilter.ts +31 -20
  45. package/src/types/Config.ts +0 -27
@@ -0,0 +1,694 @@
1
+ import React, { useState, useEffect, useContext } from 'react'
2
+
3
+ import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
4
+
5
+ // types
6
+ import { type APIFilter } from '../../types/APIFilter'
7
+ import { type SharedFilter } from '../../types/SharedFilter'
8
+ import { type DashboardConfig as Config } from '../../types/DashboardConfig'
9
+
10
+ import { DataTransform } from '@cdc/core/helpers/DataTransform'
11
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
12
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
13
+ import Modal from '@cdc/core/components/ui/Modal'
14
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
15
+ import Icon from '@cdc/core/components/ui/Icon'
16
+ import Select from '@cdc/core/components/ui/Select'
17
+ import Button from '@cdc/core/components/elements/Button'
18
+
19
+ import './index.scss'
20
+ import MultiConfigTabs from '../MultiConfigTabs'
21
+
22
+ type HeaderProps = {
23
+ setPreview?: any
24
+ back?: any
25
+ subEditor?: any
26
+ visualizationKey?: string
27
+ }
28
+
29
+ export const FilterBehavior = {
30
+ Apply: 'Apply Button',
31
+ OnChange: 'Filter Change'
32
+ }
33
+
34
+ const Header = (props: HeaderProps) => {
35
+ const { setPreview, visualizationKey, subEditor } = props
36
+ const { config, setParentConfig, tabSelected } = useContext(DashboardContext)
37
+ if (!config) return null
38
+ const dispatch = useContext(DashboardDispatchContext)
39
+ const setTabSelected = (payload: number) => dispatch({ type: 'SET_TAB_SELECTED', payload })
40
+ const back = () => {
41
+ if (!visualizationKey) return
42
+ const newConfig: Config = { ...config } as Config
43
+ newConfig.visualizations[visualizationKey].editing = false
44
+ dispatch({ type: 'SET_CONFIG', payload: newConfig })
45
+ }
46
+
47
+ const { overlay } = useGlobalContext()
48
+
49
+ const [columns, setColumns] = useState<string[]>([])
50
+
51
+ const transform = new DataTransform()
52
+
53
+ const changeConfigValue = (parentObj, key, value) => {
54
+ let newConfig = { ...config }
55
+ if (!newConfig[parentObj]) newConfig[parentObj] = {}
56
+ newConfig[parentObj][key] = value
57
+ dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
58
+ }
59
+
60
+ const setTab = index => {
61
+ setTabSelected(index)
62
+ if (index === 3) {
63
+ setPreview(true)
64
+ } else {
65
+ setPreview(false)
66
+ }
67
+ }
68
+
69
+ const addNewFilter = () => {
70
+ let dashboardConfig = { ...config.dashboard }
71
+
72
+ dashboardConfig.sharedFilters = dashboardConfig.sharedFilters || []
73
+ const newFilter: SharedFilter = { key: 'Dashboard Filter ' + (dashboardConfig.sharedFilters.length + 1) }
74
+ dashboardConfig.sharedFilters.push(newFilter)
75
+
76
+ dispatch({ type: 'UPDATE_CONFIG', payload: [{ ...config, dashboard: dashboardConfig }] })
77
+ }
78
+
79
+ const removeFilter = index => {
80
+ let dashboardConfig = { ...config.dashboard }
81
+ let visualizations = { ...config.visualizations }
82
+
83
+ dashboardConfig.sharedFilters?.splice(index, 1)
84
+
85
+ Object.keys(visualizations).forEach(vizKey => {
86
+ if (visualizations[vizKey].visualizationType === 'filter-dropdowns' && visualizations[vizKey].hide && visualizations[vizKey].hide.length > 0) {
87
+ if (visualizations[vizKey].hide.indexOf(index) !== -1) {
88
+ visualizations[vizKey].hide.splice(visualizations[vizKey].hide.indexOf(index), 1)
89
+ }
90
+ visualizations[vizKey].hide.forEach((hideIndex, i) => {
91
+ if (hideIndex > index) {
92
+ visualizations[vizKey].hide[i] = hideIndex - 1
93
+ }
94
+ })
95
+ }
96
+ })
97
+
98
+ // Ensures URL filters refresh after filter removal
99
+ if (dashboardConfig.datasets) {
100
+ Object.keys(dashboardConfig.datasets).forEach(datasetKey => {
101
+ dashboardConfig.datasets![datasetKey].runtimeDataUrl = ''
102
+ })
103
+ }
104
+
105
+ const newConfig = { ...config, visualizations, dashboard: dashboardConfig }
106
+ dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
107
+
108
+ overlay?.actions.toggleOverlay()
109
+ }
110
+
111
+ const convertStateToConfig = (type = 'JSON') => {
112
+ let strippedState = JSON.parse(JSON.stringify(config))
113
+ delete strippedState.newViz
114
+ delete strippedState.runtime
115
+
116
+ if (type === 'JSON') {
117
+ return JSON.stringify(strippedState)
118
+ }
119
+
120
+ return strippedState
121
+ }
122
+
123
+ useEffect(() => {
124
+ const parsedData = convertStateToConfig()
125
+
126
+ // Emit the data in a regular JS event so it can be consumed by anything.
127
+ const event = new CustomEvent('updateVizConfig', { detail: parsedData })
128
+
129
+ window.dispatchEvent(event)
130
+
131
+ // Pass up to Editor if needed
132
+ if (setParentConfig) {
133
+ const newConfig = convertStateToConfig('object')
134
+ setParentConfig(newConfig)
135
+ }
136
+
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps
138
+ }, [config])
139
+
140
+ useEffect(() => {
141
+ const runSetColumns = async () => {
142
+ if (!config) return
143
+ if (config.filterBehavior === FilterBehavior.Apply) return
144
+ let columns = {}
145
+ let dataKeys = Object.keys(config.datasets)
146
+
147
+ for (let i = 0; i < dataKeys.length; i++) {
148
+ let _dataSet = config.datasets[dataKeys[i]]
149
+ if (!_dataSet.data && _dataSet.dataUrl) {
150
+ config.datasets[dataKeys[i]].data = await fetchRemoteData(config.datasets[dataKeys[i]].dataUrl)
151
+ _dataSet = config.datasets[dataKeys[i]]
152
+ if (_dataSet.dataDescription) {
153
+ try {
154
+ config.datasets[dataKeys[i]].data = transform.autoStandardize(_dataSet.data)
155
+ _dataSet = config.datasets[dataKeys[i]]
156
+ config.datasets[dataKeys[i]].data = transform.developerStandardize(_dataSet.data, _dataSet.dataDescription)
157
+ _dataSet = config.datasets[dataKeys[i]]
158
+ } catch (e) {
159
+ //Data not able to be standardized, leave as is
160
+ }
161
+ }
162
+ }
163
+
164
+ if (_dataSet.data) {
165
+ config.datasets[dataKeys[i]].data.forEach(row => {
166
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
167
+ })
168
+ }
169
+ }
170
+
171
+ setColumns(Object.keys(columns))
172
+ }
173
+
174
+ runSetColumns()
175
+ }, [config.datasets])
176
+
177
+ const filterModal = (filter: SharedFilter, index) => {
178
+ const saveChanges = () => {
179
+ let tempConfig = { ...config.dashboard }
180
+ tempConfig.sharedFilters[index] = filter
181
+
182
+ dispatch({ type: 'UPDATE_CONFIG', payload: [{ ...config, dashboard: tempConfig }] })
183
+ overlay?.actions.toggleOverlay()
184
+ }
185
+
186
+ const updateFilterProp = (name, index, value) => {
187
+ // @TODO this should be refactored into a reducer function.
188
+ // it's unsafe to directly set objects w/o guardrails
189
+ let newFilter = { ...filter }
190
+
191
+ newFilter[name] = value
192
+
193
+ console.log('newFilter', newFilter)
194
+
195
+ overlay?.actions.openOverlay(filterModal(newFilter, index))
196
+ }
197
+
198
+ const addFilterUsedBy = (filter, index, value) => {
199
+ if (!filter.usedBy) filter.usedBy = []
200
+ filter.usedBy.push(value)
201
+ updateFilterProp('usedBy', index, filter.usedBy)
202
+ }
203
+
204
+ const removeFilterUsedBy = (filter, index, value) => {
205
+ let usedByIndex = filter.usedBy.indexOf(value)
206
+ if (usedByIndex !== -1) {
207
+ filter.usedBy.splice(usedByIndex, 1)
208
+ updateFilterProp('usedBy', index, filter.usedBy)
209
+ }
210
+ }
211
+
212
+ const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
213
+ const _filter = filter.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
214
+ const newAPIFilter: APIFilter = { ..._filter, [key]: value }
215
+ overlay?.actions.openOverlay(filterModal({ ...filter, apiFilter: newAPIFilter }, index))
216
+ }
217
+
218
+ return (
219
+ <Modal>
220
+ <Modal.Content>
221
+ <h2 className='shared-filter-modal__title'>Dashboard Filter Settings</h2>
222
+ <fieldset className='shared-filter-modal shared-filter-modal__fieldset' key={filter.columnName + index}>
223
+ <label>
224
+ <span className='edit-label column-heading'>Filter Type: </span>
225
+ <select defaultValue={filter.type || ''} onChange={e => updateFilterProp('type', index, e.target.value)}>
226
+ <option value=''>- Select Option -</option>
227
+ <option value='urlfilter'>URL</option>
228
+ <option value='datafilter'>Data</option>
229
+ </select>
230
+ </label>
231
+ {filter.type === 'urlfilter' && (
232
+ <>
233
+ <label>
234
+ <span className='edit-label column-heading'>Label: </span>
235
+ <input
236
+ type='text'
237
+ value={filter.key}
238
+ onChange={e => {
239
+ updateFilterProp('key', index, e.target.value)
240
+ }}
241
+ />
242
+ </label>
243
+ {config.filterBehavior !== FilterBehavior.Apply && (
244
+ <>
245
+ <label>
246
+ <span className='edit-label column-heading'>URL to Filter: </span>
247
+ <select defaultValue={filter.datasetKey || ''} onChange={e => updateFilterProp('datasetKey', index, e.target.value)}>
248
+ <option value=''>- Select Option -</option>
249
+ {Object.keys(config.datasets).map(datasetKey => {
250
+ if (config.datasets[datasetKey].dataUrl) {
251
+ return (
252
+ <option key={datasetKey} value={datasetKey}>
253
+ {config.datasets[datasetKey].dataUrl}
254
+ </option>
255
+ )
256
+ }
257
+ return null
258
+ })}
259
+ </select>
260
+ </label>
261
+ <label>
262
+ <span className='edit-label column-heading'>Filter By: </span>
263
+ <select defaultValue={filter.filterBy || ''} onChange={e => updateFilterProp('filterBy', index, e.target.value)}>
264
+ <option value=''>- Select Option -</option>
265
+ <option key={'query-string'} value={'Query String'}>
266
+ Query String
267
+ </option>
268
+ <option key={'file-name'} value={'File Name'}>
269
+ File Name
270
+ </option>
271
+ </select>
272
+ </label>
273
+ {filter.filterBy === 'File Name' && (
274
+ <>
275
+ <label>
276
+ <span className='edit-label column-heading'>
277
+ File Name:
278
+ <Tooltip style={{ textTransform: 'none' }}>
279
+ <Tooltip.Target>
280
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
281
+ </Tooltip.Target>
282
+ <Tooltip.Content>
283
+ <p>{`Add \${query}\ to replace the filename with the active dropdown value.`}</p>
284
+ </Tooltip.Content>
285
+ </Tooltip>
286
+ </span>
287
+
288
+ <input type='text' defaultValue={filter.fileName || ''} onChange={e => updateFilterProp('fileName', index, e.target.value)} />
289
+ </label>
290
+
291
+ <label>
292
+ <span className='edit-label column-heading'>
293
+ White Space Replacments
294
+ <Tooltip style={{ textTransform: 'none' }}>
295
+ <Tooltip.Target>
296
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
297
+ </Tooltip.Target>
298
+ <Tooltip.Content>
299
+ <p>{`Set how whitespace characters will be handled in the file request`}</p>
300
+ </Tooltip.Content>
301
+ </Tooltip>
302
+ </span>
303
+ <select defaultValue={filter.whitespaceReplacement || 'Keep Spaces'} onChange={e => updateFilterProp('whitespaceReplacement', index, e.target.value)}>
304
+ <option key={'remove-spaces'} value={'Remove Spaces'}>
305
+ Remove Spaces
306
+ </option>
307
+ <option key={'replace-with-underscore'} value={'Replace With Underscore'}>
308
+ Replace With Underscore
309
+ </option>
310
+ <option key={'keep-spaces'} value={'Keep Spaces'}>
311
+ Keep Spaces
312
+ </option>
313
+ </select>
314
+ </label>
315
+ </>
316
+ )}
317
+ </>
318
+ )}
319
+ {filter.filterBy === 'Query String' && (
320
+ <label>
321
+ <span className='edit-label column-heading'>Query string parameter</span> <input type='text' defaultValue={filter.queryParameter} onChange={e => updateFilterProp('queryParameter', index, e.target.value)} />
322
+ </label>
323
+ )}
324
+ <label>
325
+ <span className='edit-label column-heading'>Filter API Endpoint: </span>
326
+ <input
327
+ type='text'
328
+ value={filter.apiFilter?.apiEndpoint}
329
+ onChange={e => {
330
+ updateAPIFilter('apiEndpoint', e.target.value)
331
+ }}
332
+ />
333
+ </label>
334
+ <label>
335
+ <span className='edit-label column-heading'>
336
+ Option Text Selector:
337
+ <Tooltip style={{ textTransform: 'none' }}>
338
+ <Tooltip.Target>
339
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
340
+ </Tooltip.Target>
341
+ <Tooltip.Content>
342
+ <p>Text to use in the html option element</p>
343
+ </Tooltip.Content>
344
+ </Tooltip>
345
+ </span>
346
+ <input
347
+ type='text'
348
+ value={filter.apiFilter?.textSelector}
349
+ onChange={e => {
350
+ updateAPIFilter('textSelector', e.target.value)
351
+ }}
352
+ />
353
+ </label>
354
+ <label>
355
+ <span className='edit-label column-heading'>
356
+ Option Value Selector:
357
+ <Tooltip style={{ textTransform: 'none' }}>
358
+ <Tooltip.Target>
359
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
360
+ </Tooltip.Target>
361
+ <Tooltip.Content>
362
+ <p>Value to use in the html option element</p>
363
+ </Tooltip.Content>
364
+ </Tooltip>
365
+ </span>
366
+ <input
367
+ type='text'
368
+ value={filter.apiFilter?.valueSelector}
369
+ onChange={e => {
370
+ updateAPIFilter('valueSelector', e.target.value)
371
+ }}
372
+ />
373
+ </label>
374
+ <label>
375
+ <span className='edit-label column-heading'>Parent Filter: </span>
376
+ <select
377
+ value={filter.parents || []}
378
+ onChange={e => {
379
+ updateFilterProp('parents', index, e.target.value)
380
+ }}
381
+ >
382
+ <option value=''>Select a filter</option>
383
+ {config.dashboard.sharedFilters &&
384
+ config.dashboard.sharedFilters.map(sharedFilter => {
385
+ if (sharedFilter.key !== filter.key && sharedFilter.type !== 'datafilter') {
386
+ return <option value={sharedFilter.key}>{sharedFilter.key}</option>
387
+ }
388
+ })}
389
+ </select>
390
+ </label>
391
+ <label>
392
+ <span className='edit-label column-heading'>Auto Load: </span>
393
+ <input
394
+ type='checkbox'
395
+ checked={filter.apiFilter?.autoLoad}
396
+ onChange={e => {
397
+ updateAPIFilter('autoLoad', !filter.apiFilter?.autoLoad)
398
+ }}
399
+ />
400
+ </label>
401
+ <label>
402
+ <span className='edit-label column-heading'>Default Value: </span>
403
+ <input
404
+ type='text'
405
+ value={filter.apiFilter?.defaultValue}
406
+ onChange={e => {
407
+ updateAPIFilter('defaultValue', e.target.value)
408
+ }}
409
+ />
410
+ </label>
411
+ </>
412
+ )}
413
+ {filter.type === 'datafilter' && (
414
+ <>
415
+ <label>
416
+ <span className='edit-label column-heading'>Filter: </span>
417
+ <select
418
+ value={filter.columnName}
419
+ onChange={e => {
420
+ updateFilterProp('columnName', index, e.target.value)
421
+ }}
422
+ >
423
+ <option value=''>- Select Option -</option>
424
+ {columns.map(dataKey => (
425
+ <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
426
+ {dataKey}
427
+ </option>
428
+ ))}
429
+ </select>
430
+ </label>
431
+ <label>
432
+ <span className='edit-label column-heading'>Label: </span>
433
+ <input
434
+ type='text'
435
+ value={filter.key}
436
+ onChange={e => {
437
+ updateFilterProp('key', index, e.target.value)
438
+ }}
439
+ />
440
+ </label>
441
+ <label>
442
+ <span className='edit-label column-heading'>Show Dropdown</span>
443
+ <input
444
+ type='checkbox'
445
+ defaultChecked={filter.showDropdown === true}
446
+ onChange={e => {
447
+ updateFilterProp('showDropdown', index, !filter.showDropdown)
448
+ }}
449
+ />
450
+ </label>
451
+ <label>
452
+ <span className='edit-label column-heading'>Set By: </span>
453
+ <select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
454
+ <option value=''>- Select Option -</option>
455
+ {Object.keys(config.visualizations).map(vizKey => (
456
+ <option value={vizKey} key={`set-by-select-item-${vizKey}`}>
457
+ {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
458
+ </option>
459
+ ))}
460
+ </select>
461
+ </label>
462
+ <label>
463
+ <span className='edit-label column-heading'>Used By: </span>
464
+ <ul>
465
+ {filter.usedBy &&
466
+ filter.usedBy.map(vizKey => (
467
+ <li key={`used-by-list-item-${vizKey}`}>
468
+ <span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
469
+ <button
470
+ onClick={e => {
471
+ e.preventDefault()
472
+ removeFilterUsedBy(filter, index, vizKey)
473
+ }}
474
+ >
475
+ X
476
+ </button>
477
+ </li>
478
+ ))}
479
+ </ul>
480
+ <select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
481
+ <option value=''>- Select Option -</option>
482
+ {Object.keys(config.visualizations)
483
+ .filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
484
+ .map(vizKey => (
485
+ <option value={vizKey} key={`used-by-select-item-${vizKey}`}>
486
+ {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
487
+ </option>
488
+ ))}
489
+ </select>
490
+ </label>
491
+ <label>
492
+ <span className='edit-label column-heading'>Reset Label: </span>
493
+ <input
494
+ type='text'
495
+ value={filter.resetLabel || ''}
496
+ onChange={e => {
497
+ updateFilterProp('resetLabel', index, e.target.value)
498
+ }}
499
+ />
500
+ </label>
501
+ <label>
502
+ <span className='edit-label column-heading'>Parent Filter: </span>
503
+ <select
504
+ value={filter.parents || []}
505
+ onChange={e => {
506
+ updateFilterProp('parents', index, e.target.value)
507
+ }}
508
+ >
509
+ <option value=''>Select a filter</option>
510
+ {config.dashboard.sharedFilters &&
511
+ config.dashboard.sharedFilters.map(sharedFilter => {
512
+ if (sharedFilter.key !== filter.key) {
513
+ return <option>{sharedFilter.key}</option>
514
+ }
515
+ })}
516
+ </select>
517
+ </label>
518
+ </>
519
+ )}
520
+ </fieldset>
521
+
522
+ <Button
523
+ className='btn--remove warn'
524
+ onClick={() => {
525
+ removeFilter(index)
526
+ }}
527
+ >
528
+ Remove Filter
529
+ </Button>
530
+
531
+ <div className='shared-filter-modal__right-buttons'>
532
+ <Button className='btn--cancel muted' style={{ display: 'inline-block', marginRight: '1em' }} onClick={overlay?.actions.toggleOverlay}>
533
+ Cancel
534
+ </Button>
535
+
536
+ <Button type='button' className='btn--submit success' style={{ display: 'inline-block' }} onClick={saveChanges}>
537
+ Save
538
+ </Button>
539
+ </div>
540
+ </Modal.Content>
541
+ </Modal>
542
+ )
543
+ }
544
+
545
+ const handleCheck = e => {
546
+ const { checked } = e.currentTarget
547
+ if (checked) {
548
+ dispatch({ type: 'INITIALIZE_MULTIDASHBOARDS' })
549
+ }
550
+ }
551
+
552
+ const multiInitialized = !!config.multiDashboards
553
+
554
+ return (
555
+ <div aria-level={2} role='heading' className={`editor-heading${subEditor ? ' sub-dashboard-viz' : ''}`}>
556
+ {subEditor ? (
557
+ <div className='heading-1 back-to' onClick={back} style={{ cursor: 'pointer' }}>
558
+ <span>&#8592;</span> Back to Dashboard
559
+ </div>
560
+ ) : (
561
+ <div className='heading-1'>
562
+ Dashboard Editor{' '}
563
+ <span className='small'>
564
+ <input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make multidashboard
565
+ </span>
566
+ <br />
567
+ {<input type='text' placeholder='Enter Dashboard Name Here' defaultValue={config.dashboard?.title} onChange={e => changeConfigValue('dashboard', 'title', e.target.value)} />}
568
+ </div>
569
+ )}
570
+ {!subEditor && (
571
+ <div className='toggle-bar__wrapper'>
572
+ <MultiConfigTabs isEditor />
573
+ <ul className='toggle-bar'>
574
+ <li
575
+ className={tabSelected === 0 ? 'active' : 'inactive'}
576
+ onClick={() => {
577
+ setTab(0)
578
+ }}
579
+ >
580
+ Dashboard Description
581
+ </li>
582
+ <li
583
+ className={tabSelected === 1 ? 'active' : 'inactive'}
584
+ onClick={() => {
585
+ setTab(1)
586
+ }}
587
+ >
588
+ Dashboard Filters
589
+ </li>
590
+ <li
591
+ className={tabSelected === 2 ? 'active' : 'inactive'}
592
+ onClick={() => {
593
+ setTab(2)
594
+ }}
595
+ >
596
+ Data Table Settings
597
+ </li>
598
+ <li
599
+ className={tabSelected === 3 ? 'active' : 'inactive'}
600
+ onClick={() => {
601
+ setTab(3)
602
+ }}
603
+ >
604
+ Dashboard Preview
605
+ </li>
606
+ </ul>
607
+ <div className='heading-body'>
608
+ {tabSelected === 0 && <input type='text' className='description-input' placeholder='Type a dashboard description here.' defaultValue={config.dashboard?.description} onChange={e => changeConfigValue('dashboard', 'description', e.target.value)} />}
609
+ {tabSelected === 1 && (
610
+ <>
611
+ {config.dashboard.sharedFilters &&
612
+ config.dashboard.sharedFilters.map((sharedFilter, index) => (
613
+ <span className='shared-filter-button' key={`shared-filter-${sharedFilter.key}`}>
614
+ <a
615
+ href='#'
616
+ onClick={e => {
617
+ e.preventDefault()
618
+ overlay?.actions.openOverlay(filterModal(sharedFilter, index))
619
+ }}
620
+ >
621
+ {sharedFilter.key}
622
+ </a>
623
+ <button onClick={() => removeFilter(index)}>X</button>
624
+ </span>
625
+ ))}
626
+ <button onClick={addNewFilter}>Add New Filter</button>
627
+
628
+ <Select
629
+ value={config.filterBehavior}
630
+ fieldName='filterBehavior'
631
+ label='Filter Behavior'
632
+ initial='- Select Option -'
633
+ onchange={e => {
634
+ const newConfig = { ...config, filterBehavior: e.target.value }
635
+ dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
636
+ }}
637
+ options={Object.values(FilterBehavior)}
638
+ tooltip={
639
+ <Tooltip style={{ textTransform: 'none' }}>
640
+ <Tooltip.Target>
641
+ <Icon display='question' color='' style={{ marginLeft: '0.5rem' }} />
642
+ </Tooltip.Target>
643
+ <Tooltip.Content>
644
+ <p>The Apply Button option changes the visualization when the user clicks "apply". The Filter Change option immediately changes the visualization when the selection is changed.</p>
645
+ </Tooltip.Content>
646
+ </Tooltip>
647
+ }
648
+ />
649
+ </>
650
+ )}
651
+ {tabSelected === 2 && (
652
+ <>
653
+ <div className='wrap'>
654
+ <label>
655
+ <input type='checkbox' defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
656
+ Show Data Table(s)
657
+ </label>
658
+ <br />
659
+
660
+ <label>
661
+ <input type='checkbox' defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
662
+ Expanded by Default
663
+ </label>
664
+ <br />
665
+ </div>
666
+
667
+ <div className='wrap'>
668
+ <label>
669
+ <input type='checkbox' defaultChecked={config.table.limitHeight} onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)} />
670
+ Limit Table Height
671
+ </label>
672
+ {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)} />}
673
+ </div>
674
+
675
+ <div className='wrap'>
676
+ <label>
677
+ <input type='checkbox' defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
678
+ Show Download CSV Link
679
+ </label>
680
+ <label>
681
+ <input type='checkbox' defaultChecked={config.table.showDownloadUrl} onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)} />
682
+ Show URL to Automatically Updated Data
683
+ </label>
684
+ </div>
685
+ </>
686
+ )}
687
+ </div>
688
+ </div>
689
+ )}
690
+ </div>
691
+ )
692
+ }
693
+
694
+ export default Header