@cdc/dashboard 4.24.2 → 4.24.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 (38) hide show
  1. package/dist/cdcdashboard.js +98192 -85200
  2. package/examples/sankey.json +5218 -0
  3. package/index.html +3 -2
  4. package/package.json +11 -10
  5. package/src/CdcDashboard.tsx +124 -124
  6. package/src/CdcDashboardComponent.tsx +173 -186
  7. package/src/DashboardContext.tsx +4 -1
  8. package/src/_stories/Dashboard.stories.tsx +27 -5
  9. package/src/_stories/_mock/pivot-filter.json +163 -0
  10. package/src/_stories/_mock/standalone-table.json +122 -0
  11. package/src/_stories/_mock/toggle-example.json +4035 -0
  12. package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
  13. package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
  14. package/src/components/Filters.tsx +88 -0
  15. package/src/components/Header/FilterModal.tsx +480 -0
  16. package/src/components/Header/Header.tsx +25 -465
  17. package/src/components/Row.tsx +28 -17
  18. package/src/components/Toggle/Toggle.tsx +37 -0
  19. package/src/components/Toggle/index.tsx +1 -0
  20. package/src/components/Toggle/toggle-style.css +34 -0
  21. package/src/components/VisualizationsPanel.tsx +13 -3
  22. package/src/components/Widget.tsx +14 -30
  23. package/src/helpers/filterData.ts +72 -49
  24. package/src/helpers/generateValuesForFilter.ts +2 -12
  25. package/src/helpers/getApiFilterKey.ts +5 -0
  26. package/src/helpers/getUpdateConfig.ts +24 -22
  27. package/src/helpers/iconHash.tsx +34 -0
  28. package/src/helpers/tests/filterData.test.ts +149 -0
  29. package/src/images/icon-toggle.svg +1 -0
  30. package/src/scss/grid.scss +1 -1
  31. package/src/scss/main.scss +6 -0
  32. package/src/store/dashboard.actions.ts +19 -2
  33. package/src/store/dashboard.reducer.ts +9 -1
  34. package/src/types/ConfigRow.ts +2 -0
  35. package/src/types/DataSet.ts +7 -7
  36. package/src/types/InitialState.ts +2 -1
  37. package/src/types/SharedFilter.ts +5 -2
  38. package/src/types/Tab.ts +1 -0
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import { Accordion } from 'react-accessible-accordion'
3
+ import Header from '../Header'
4
+ import { Visualization } from '@cdc/core/types/Visualization'
5
+ import { ViewPort } from '@cdc/core/types/ViewPort'
6
+ import './editor-wrapper.style.css'
7
+
8
+ type StandAloneComponentProps = {
9
+ visualizationKey: string
10
+ config: Visualization
11
+ isEditor: boolean
12
+ setConfig: Function
13
+ isDashboard: boolean
14
+ configUrl: string
15
+ setEditing: Function
16
+ hostname: string
17
+ viewport?: ViewPort
18
+ }
19
+
20
+ type EditorProps = {
21
+ component: React.JSXElementConstructor<StandAloneComponentProps>
22
+ type: string
23
+ visualizationKey: string
24
+ visualizationConfig: Visualization
25
+ updateConfig: Function
26
+ viewport?: ViewPort
27
+ }
28
+
29
+ const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ children, visualizationKey, visualizationConfig, type, component: Component, updateConfig, viewport }) => {
30
+ const [displayPanel, setDisplayPanel] = React.useState(true)
31
+ return (
32
+ <>
33
+ <Header visualizationKey={visualizationKey} subEditor={type} />
34
+ <div className='editor-wrapper'>
35
+ <button className={`editor-toggle ${displayPanel ? '' : 'collapsed'}`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel)} />
36
+ <section className={`${displayPanel ? '' : 'hidden'} editor-panel cove`}>
37
+ <div aria-level={2} role='heading' className='heading-2'>
38
+ Configure {type}
39
+ </div>
40
+ <form>
41
+ <Accordion allowZeroExpanded={true}>{children}</Accordion>
42
+ </form>
43
+ </section>
44
+ <div className='preview-wrapper'>
45
+ <Component visualizationKey={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
46
+ </div>
47
+ </div>
48
+ </>
49
+ )
50
+ }
51
+
52
+ export default EditorWrapper
@@ -0,0 +1,13 @@
1
+ .editor-wrapper {
2
+ --editorPanelWidth: 350px;
3
+ position: relative;
4
+ .editor-panel {
5
+ :is(form) {
6
+ border-right: var(--lightGray) 1px solid;
7
+ flex-grow: 1;
8
+ }
9
+ }
10
+ .preview-wrapper {
11
+ padding-left: var(--editorPanelWidth);
12
+ }
13
+ }
@@ -0,0 +1,88 @@
1
+ import MultiSelect from '@cdc/core/components/MultiSelect'
2
+ import { getApiFilterKey } from '../helpers/getApiFilterKey'
3
+ import { SharedFilter } from '../types/SharedFilter'
4
+
5
+ export type DropdownOptions = Record<'value' | 'text', string>[]
6
+
7
+ export type APIFilterDropdowns = {
8
+ // null means still loading
9
+ [filtername: string]: null | DropdownOptions
10
+ }
11
+
12
+ type FilterProps = {
13
+ hide?: number[]
14
+ filters: SharedFilter[]
15
+ apiFilterDropdowns: APIFilterDropdowns
16
+ handleOnChange: Function
17
+ }
18
+
19
+ const Filters: React.FC<FilterProps> = ({ hide, filters, apiFilterDropdowns, handleOnChange }) => {
20
+ const updateField = (_section, _subsection, fieldName, value) => {
21
+ handleOnChange(fieldName, value)
22
+ }
23
+ return (
24
+ <>
25
+ {filters.map((singleFilter, filterIndex) => {
26
+ if ((singleFilter.type !== 'urlfilter' && !singleFilter.showDropdown) || (hide && hide.indexOf(filterIndex) !== -1)) return <></>
27
+ const values: JSX.Element[] = []
28
+ const multiValues = []
29
+ if (singleFilter.resetLabel) {
30
+ values.push(
31
+ <option key={`${singleFilter.resetLabel}-option`} value={singleFilter.resetLabel}>
32
+ {singleFilter.resetLabel}
33
+ </option>
34
+ )
35
+ }
36
+ const _key = singleFilter.apiFilter ? getApiFilterKey(singleFilter.apiFilter) : undefined
37
+ if (_key && apiFilterDropdowns[_key]) {
38
+ // URL Filter
39
+ apiFilterDropdowns[_key].forEach(({ text, value }, index) => {
40
+ values.push(
41
+ <option key={`${value}-option-${index}`} value={value}>
42
+ {text}
43
+ </option>
44
+ )
45
+ })
46
+ } else {
47
+ // Data Filter
48
+ singleFilter.values?.forEach((filterOption, index) => {
49
+ const labeledOpt = singleFilter.labels && singleFilter.labels[filterOption]
50
+ values.push(
51
+ <option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
52
+ {labeledOpt || filterOption}
53
+ </option>
54
+ )
55
+ multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
56
+ })
57
+ }
58
+
59
+ return (
60
+ <div className='cove-dashboard-filters' key={`${singleFilter.key}-filtersection-${filterIndex}`}>
61
+ <section className='dashboard-filters-section'>
62
+ {!singleFilter.pivot ? (
63
+ <>
64
+ <label htmlFor={`filter-${filterIndex}`}>{singleFilter.key}</label>
65
+ <select
66
+ id={`filter-${filterIndex}`}
67
+ className='filter-select'
68
+ data-index='0'
69
+ value={singleFilter.queuedActive || singleFilter.active}
70
+ onChange={val => {
71
+ handleOnChange(filterIndex, val.target.value)
72
+ }}
73
+ >
74
+ {values}
75
+ </select>
76
+ </>
77
+ ) : (
78
+ <MultiSelect label={singleFilter.key} options={multiValues} fieldName={filterIndex} updateField={updateField} selected={singleFilter.active as string[]} limit={singleFilter.selectLimit || 5} />
79
+ )}
80
+ </section>
81
+ </div>
82
+ )
83
+ })}
84
+ </>
85
+ )
86
+ }
87
+
88
+ export default Filters
@@ -0,0 +1,480 @@
1
+ import { useContext, useEffect, useState } from 'react'
2
+ import { MultiDashboardConfig } from '../../types/MultiDashboard'
3
+ import { SharedFilter } from '../../types/SharedFilter'
4
+ import { DashboardDispatchContext } from '../../DashboardContext'
5
+ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
6
+ import { APIFilter } from '../../types/APIFilter'
7
+ import Modal from '@cdc/core/components/ui/Modal'
8
+ import { FilterBehavior } from './Header'
9
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
10
+ import Icon from '@cdc/core/components/ui/Icon'
11
+ import Button from '@cdc/core/components/elements/Button'
12
+ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
13
+ import DataTransform from '@cdc/core/helpers/DataTransform'
14
+
15
+ type ModalProps = {
16
+ config: MultiDashboardConfig
17
+ filterState: SharedFilter
18
+ index: number
19
+ removeFilter: Function
20
+ }
21
+
22
+ const FilterModal: React.FC<ModalProps> = ({ config, filterState, index, removeFilter }) => {
23
+ const { overlay } = useGlobalContext()
24
+ const dispatch = useContext(DashboardDispatchContext)
25
+ const [filter, setFilter] = useState<SharedFilter>(filterState)
26
+ const [columns, setColumns] = useState<string[]>([])
27
+ const transform = new DataTransform()
28
+
29
+ useEffect(() => {
30
+ const runSetColumns = async () => {
31
+ if (config.filterBehavior === FilterBehavior.Apply) return
32
+ let columns = {}
33
+ let dataKeys = Object.keys(config.datasets)
34
+
35
+ for (let i = 0; i < dataKeys.length; i++) {
36
+ let _dataSet = config.datasets[dataKeys[i]]
37
+ if (!_dataSet.data && _dataSet.dataUrl) {
38
+ config.datasets[dataKeys[i]].data = await fetchRemoteData(config.datasets[dataKeys[i]].dataUrl)
39
+ _dataSet = config.datasets[dataKeys[i]]
40
+ if (_dataSet.dataDescription) {
41
+ try {
42
+ config.datasets[dataKeys[i]].data = transform.autoStandardize(_dataSet.data)
43
+ _dataSet = config.datasets[dataKeys[i]]
44
+ config.datasets[dataKeys[i]].data = transform.developerStandardize(_dataSet.data, _dataSet.dataDescription)
45
+ _dataSet = config.datasets[dataKeys[i]]
46
+ } catch (e) {
47
+ //Data not able to be standardized, leave as is
48
+ }
49
+ }
50
+ }
51
+
52
+ if (_dataSet.data) {
53
+ config.datasets[dataKeys[i]].data.forEach(row => {
54
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
55
+ })
56
+ }
57
+ }
58
+
59
+ setColumns(Object.keys(columns))
60
+ }
61
+
62
+ runSetColumns()
63
+ }, [config.datasets])
64
+
65
+ const saveChanges = () => {
66
+ let tempConfig = { ...config.dashboard }
67
+ tempConfig.sharedFilters[index] = filter
68
+
69
+ dispatch({ type: 'UPDATE_CONFIG', payload: [{ ...config, dashboard: tempConfig }] })
70
+ overlay?.actions.toggleOverlay()
71
+ }
72
+
73
+ const updateFilterProp = (name, value) => {
74
+ // @TODO this should be refactored into a reducer function.
75
+ // it's unsafe to directly set objects w/o guardrails
76
+ let newFilter = { ...filter }
77
+
78
+ newFilter[name] = value
79
+
80
+ console.log('newFilter', newFilter)
81
+
82
+ setFilter(newFilter)
83
+ }
84
+
85
+ const addFilterUsedBy = (filter, value) => {
86
+ if (!filter.usedBy) filter.usedBy = []
87
+ filter.usedBy.push(value)
88
+ updateFilterProp('usedBy', filter.usedBy)
89
+ }
90
+
91
+ const removeFilterUsedBy = (filter, value) => {
92
+ let usedByIndex = filter.usedBy.indexOf(value)
93
+ if (usedByIndex !== -1) {
94
+ filter.usedBy.splice(usedByIndex, 1)
95
+ updateFilterProp('usedBy', filter.usedBy)
96
+ }
97
+ }
98
+
99
+ const updateAPIFilter = (key: keyof APIFilter, value: string | boolean) => {
100
+ const _filter = filter.apiFilter || { apiEndpoint: '', valueSelector: '', textSelector: '' }
101
+ const newAPIFilter: APIFilter = { ..._filter, [key]: value }
102
+ setFilter({ ...filter, apiFilter: newAPIFilter })
103
+ }
104
+
105
+ return (
106
+ <Modal>
107
+ <Modal.Content>
108
+ <h2 className='shared-filter-modal__title'>Dashboard Filter Settings</h2>
109
+ <fieldset className='shared-filter-modal shared-filter-modal__fieldset' key={filter.columnName + index}>
110
+ <label>
111
+ <span className='edit-label column-heading'>Filter Type: </span>
112
+ <select defaultValue={filter.type || ''} onChange={e => updateFilterProp('type', e.target.value)}>
113
+ <option value=''>- Select Option -</option>
114
+ <option value='urlfilter'>URL</option>
115
+ <option value='datafilter'>Data</option>
116
+ </select>
117
+ </label>
118
+ {filter.type === 'urlfilter' && (
119
+ <>
120
+ <label>
121
+ <span className='edit-label column-heading'>Label: </span>
122
+ <input
123
+ type='text'
124
+ value={filter.key}
125
+ onChange={e => {
126
+ updateFilterProp('key', e.target.value)
127
+ }}
128
+ />
129
+ </label>
130
+ {config.filterBehavior !== FilterBehavior.Apply && (
131
+ <>
132
+ <label>
133
+ <span className='edit-label column-heading'>URL to Filter: </span>
134
+ <select defaultValue={filter.datasetKey || ''} onChange={e => updateFilterProp('datasetKey', e.target.value)}>
135
+ <option value=''>- Select Option -</option>
136
+ {Object.keys(config.datasets).map(datasetKey => {
137
+ if (config.datasets[datasetKey].dataUrl) {
138
+ return (
139
+ <option key={datasetKey} value={datasetKey}>
140
+ {config.datasets[datasetKey].dataUrl}
141
+ </option>
142
+ )
143
+ }
144
+ return null
145
+ })}
146
+ </select>
147
+ </label>
148
+ <label>
149
+ <span className='edit-label column-heading'>Filter By: </span>
150
+ <select defaultValue={filter.filterBy || ''} onChange={e => updateFilterProp('filterBy', e.target.value)}>
151
+ <option value=''>- Select Option -</option>
152
+ <option key={'query-string'} value={'Query String'}>
153
+ Query String
154
+ </option>
155
+ <option key={'file-name'} value={'File Name'}>
156
+ File Name
157
+ </option>
158
+ </select>
159
+ </label>
160
+ {filter.filterBy === 'File Name' && (
161
+ <>
162
+ <label>
163
+ <span className='edit-label column-heading'>
164
+ File Name:
165
+ <Tooltip style={{ textTransform: 'none' }}>
166
+ <Tooltip.Target>
167
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
168
+ </Tooltip.Target>
169
+ <Tooltip.Content>
170
+ <p>{`Add \${query}\ to replace the filename with the active dropdown value.`}</p>
171
+ </Tooltip.Content>
172
+ </Tooltip>
173
+ </span>
174
+
175
+ <input type='text' defaultValue={filter.fileName || ''} onChange={e => updateFilterProp('fileName', e.target.value)} />
176
+ </label>
177
+
178
+ <label>
179
+ <span className='edit-label column-heading'>
180
+ White Space Replacments
181
+ <Tooltip style={{ textTransform: 'none' }}>
182
+ <Tooltip.Target>
183
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
184
+ </Tooltip.Target>
185
+ <Tooltip.Content>
186
+ <p>{`Set how whitespace characters will be handled in the file request`}</p>
187
+ </Tooltip.Content>
188
+ </Tooltip>
189
+ </span>
190
+ <select defaultValue={filter.whitespaceReplacement || 'Keep Spaces'} onChange={e => updateFilterProp('whitespaceReplacement', e.target.value)}>
191
+ <option key={'remove-spaces'} value={'Remove Spaces'}>
192
+ Remove Spaces
193
+ </option>
194
+ <option key={'replace-with-underscore'} value={'Replace With Underscore'}>
195
+ Replace With Underscore
196
+ </option>
197
+ <option key={'keep-spaces'} value={'Keep Spaces'}>
198
+ Keep Spaces
199
+ </option>
200
+ </select>
201
+ </label>
202
+ </>
203
+ )}
204
+ </>
205
+ )}
206
+ {filter.filterBy === 'Query String' && (
207
+ <label>
208
+ <span className='edit-label column-heading'>Query string parameter</span> <input type='text' defaultValue={filter.queryParameter} onChange={e => updateFilterProp('queryParameter', e.target.value)} />
209
+ </label>
210
+ )}
211
+ <label>
212
+ <span className='edit-label column-heading'>Filter API Endpoint: </span>
213
+ <input
214
+ type='text'
215
+ value={filter.apiFilter?.apiEndpoint}
216
+ onChange={e => {
217
+ updateAPIFilter('apiEndpoint', e.target.value)
218
+ }}
219
+ />
220
+ </label>
221
+ <label>
222
+ <span className='edit-label column-heading'>
223
+ Option Text Selector:
224
+ <Tooltip style={{ textTransform: 'none' }}>
225
+ <Tooltip.Target>
226
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
227
+ </Tooltip.Target>
228
+ <Tooltip.Content>
229
+ <p>Text to use in the html option element</p>
230
+ </Tooltip.Content>
231
+ </Tooltip>
232
+ </span>
233
+ <input
234
+ type='text'
235
+ value={filter.apiFilter?.textSelector}
236
+ onChange={e => {
237
+ updateAPIFilter('textSelector', e.target.value)
238
+ }}
239
+ />
240
+ </label>
241
+ <label>
242
+ <span className='edit-label column-heading'>
243
+ Option Value Selector:
244
+ <Tooltip style={{ textTransform: 'none' }}>
245
+ <Tooltip.Target>
246
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
247
+ </Tooltip.Target>
248
+ <Tooltip.Content>
249
+ <p>Value to use in the html option element</p>
250
+ </Tooltip.Content>
251
+ </Tooltip>
252
+ </span>
253
+ <input
254
+ type='text'
255
+ value={filter.apiFilter?.valueSelector}
256
+ onChange={e => {
257
+ updateAPIFilter('valueSelector', e.target.value)
258
+ }}
259
+ />
260
+ </label>
261
+ <label>
262
+ <span className='edit-label column-heading'>Parent Filter: </span>
263
+ <select
264
+ value={filter.parents || []}
265
+ onChange={e => {
266
+ updateFilterProp('parents', e.target.value)
267
+ }}
268
+ >
269
+ <option value=''>Select a filter</option>
270
+ {config.dashboard.sharedFilters &&
271
+ config.dashboard.sharedFilters.map(sharedFilter => {
272
+ if (sharedFilter.key !== filter.key && sharedFilter.type !== 'datafilter') {
273
+ return <option value={sharedFilter.key}>{sharedFilter.key}</option>
274
+ }
275
+ })}
276
+ </select>
277
+ </label>
278
+ <label>
279
+ <span className='edit-label column-heading'>Auto Load: </span>
280
+ <input
281
+ type='checkbox'
282
+ checked={filter.apiFilter?.autoLoad}
283
+ onChange={e => {
284
+ updateAPIFilter('autoLoad', !filter.apiFilter?.autoLoad)
285
+ }}
286
+ />
287
+ </label>
288
+ <label>
289
+ <span className='edit-label column-heading'>Default Value: </span>
290
+ <input
291
+ type='text'
292
+ value={filter.apiFilter?.defaultValue}
293
+ onChange={e => {
294
+ updateAPIFilter('defaultValue', e.target.value)
295
+ }}
296
+ />
297
+ </label>
298
+ <label>
299
+ <span className='edit-label column-heading'>Default Value Set By Query String Parameter: </span>
300
+ <input
301
+ type='text'
302
+ value={filter.setByQueryParameter || ''}
303
+ onChange={e => {
304
+ updateFilterProp('setByQueryParameter', e.target.value)
305
+ }}
306
+ />
307
+ </label>
308
+ </>
309
+ )}
310
+ {filter.type === 'datafilter' && (
311
+ <>
312
+ <label>
313
+ <span className='edit-label column-heading'>Filter: </span>
314
+ <select
315
+ value={filter.columnName}
316
+ onChange={e => {
317
+ updateFilterProp('columnName', e.target.value)
318
+ }}
319
+ >
320
+ <option value=''>- Select Option -</option>
321
+ {columns.map(dataKey => (
322
+ <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
323
+ {dataKey}
324
+ </option>
325
+ ))}
326
+ </select>
327
+ </label>
328
+ <label>
329
+ <span className='edit-label column-heading'>
330
+ Pivot:{' '}
331
+ <Tooltip style={{ textTransform: 'none' }}>
332
+ <Tooltip.Target>
333
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
334
+ </Tooltip.Target>
335
+ <Tooltip.Content>
336
+ <p>The column whos values will be pivoted under the column selected as the Filter.</p>
337
+ </Tooltip.Content>
338
+ </Tooltip>
339
+ </span>
340
+ <select
341
+ value={filter.pivot}
342
+ onChange={e => {
343
+ updateFilterProp('pivot', e.target.value)
344
+ }}
345
+ >
346
+ <option value=''>- Select Option -</option>
347
+ {columns
348
+ .filter(col => filter.columnName !== col)
349
+ .map(dataKey => (
350
+ <option value={dataKey} key={`filter-column-select-item-${dataKey}`}>
351
+ {dataKey}
352
+ </option>
353
+ ))}
354
+ </select>
355
+ </label>
356
+ <label>
357
+ <span className='edit-label column-heading'>Label: </span>
358
+ <input
359
+ type='text'
360
+ value={filter.key}
361
+ onChange={e => {
362
+ updateFilterProp('key', e.target.value)
363
+ }}
364
+ />
365
+ </label>
366
+ <label>
367
+ <span className='edit-label column-heading'>Show Dropdown</span>
368
+ <input
369
+ type='checkbox'
370
+ defaultChecked={filter.showDropdown === true}
371
+ onChange={e => {
372
+ updateFilterProp('showDropdown', !filter.showDropdown)
373
+ }}
374
+ />
375
+ </label>
376
+ <label>
377
+ <span className='edit-label column-heading'>Set By: </span>
378
+ <select value={filter.setBy} onChange={e => updateFilterProp('setBy', e.target.value)}>
379
+ <option value=''>- Select Option -</option>
380
+ {Object.keys(config.visualizations).map(vizKey => (
381
+ <option value={vizKey} key={`set-by-select-item-${vizKey}`}>
382
+ {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
383
+ </option>
384
+ ))}
385
+ </select>
386
+ </label>
387
+ <label>
388
+ <span className='edit-label column-heading'>Used By: </span>
389
+ <ul>
390
+ {filter.usedBy &&
391
+ filter.usedBy.map(vizKey => (
392
+ <li key={`used-by-list-item-${vizKey}`}>
393
+ <span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}</span>{' '}
394
+ <button
395
+ onClick={e => {
396
+ e.preventDefault()
397
+ removeFilterUsedBy(filter, vizKey)
398
+ }}
399
+ >
400
+ X
401
+ </button>
402
+ </li>
403
+ ))}
404
+ </ul>
405
+ <select onChange={e => addFilterUsedBy(filter, e.target.value)}>
406
+ <option value=''>- Select Option -</option>
407
+ {Object.keys(config.visualizations)
408
+ .filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter)
409
+ .map(vizKey => (
410
+ <option value={vizKey} key={`used-by-select-item-${vizKey}`}>
411
+ {config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : config.visualizations[vizKey].title || vizKey}
412
+ </option>
413
+ ))}
414
+ </select>
415
+ </label>
416
+ <label>
417
+ <span className='edit-label column-heading'>Reset Label: </span>
418
+ <input
419
+ type='text'
420
+ value={filter.resetLabel || ''}
421
+ onChange={e => {
422
+ updateFilterProp('resetLabel', e.target.value)
423
+ }}
424
+ />
425
+ </label>
426
+ <label>
427
+ <span className='edit-label column-heading'>Parent Filter: </span>
428
+ <select
429
+ value={filter.parents || []}
430
+ onChange={e => {
431
+ updateFilterProp('parents', e.target.value)
432
+ }}
433
+ >
434
+ <option value=''>Select a filter</option>
435
+ {config.dashboard.sharedFilters &&
436
+ config.dashboard.sharedFilters.map(sharedFilter => {
437
+ if (sharedFilter.key !== filter.key) {
438
+ return <option>{sharedFilter.key}</option>
439
+ }
440
+ })}
441
+ </select>
442
+ </label>
443
+ <label>
444
+ <span className='edit-label column-heading'>Default Value Set By Query String Parameter: </span>
445
+ <input
446
+ type='text'
447
+ value={filter.setByQueryParameter || ''}
448
+ onChange={e => {
449
+ updateFilterProp('setByQueryParameter', e.target.value)
450
+ }}
451
+ />
452
+ </label>
453
+ </>
454
+ )}
455
+ </fieldset>
456
+
457
+ <Button
458
+ className='btn--remove warn'
459
+ onClick={() => {
460
+ removeFilter()
461
+ }}
462
+ >
463
+ Remove Filter
464
+ </Button>
465
+
466
+ <div className='shared-filter-modal__right-buttons'>
467
+ <Button className='btn--cancel muted' style={{ display: 'inline-block', marginRight: '1em' }} onClick={overlay?.actions.toggleOverlay}>
468
+ Cancel
469
+ </Button>
470
+
471
+ <Button type='button' className='btn--submit success' style={{ display: 'inline-block' }} onClick={saveChanges}>
472
+ Save
473
+ </Button>
474
+ </div>
475
+ </Modal.Content>
476
+ </Modal>
477
+ )
478
+ }
479
+
480
+ export default FilterModal