@cdc/dashboard 1.1.4 → 9.22.9
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.
- package/dist/cdcdashboard.js +40 -28
- package/examples/default-filter-control.json +175 -0
- package/examples/default-multi-dataset.json +498 -0
- package/examples/private/chart-issue.json +3467 -0
- package/examples/private/no-issue.json +3467 -0
- package/examples/private/totals-two.json +104 -0
- package/examples/private/totals.json +103 -0
- package/examples/temp-example-data.json +130 -0
- package/package.json +9 -8
- package/src/CdcDashboard.js +16 -1
- package/src/CdcDashboard.jsx +668 -0
- package/src/{context.tsx → ConfigContext.js} +0 -0
- package/src/components/{Column.js → Column.jsx} +9 -7
- package/src/components/DataTable.tsx +6 -9
- package/src/components/EditorPanel.js +200 -44
- package/src/components/{Grid.js → Grid.jsx} +5 -4
- package/src/components/Header.jsx +242 -0
- package/src/components/Row.js +1 -0
- package/src/components/Row.jsx +181 -0
- package/src/components/Row.jsx~HEAD +212 -0
- package/src/components/Widget.js +6 -2
- package/src/components/Widget.jsx +191 -0
- package/src/index.html +5 -4
- package/src/scss/editor-panel.scss +0 -1
- package/src/scss/grid.scss +61 -14
- package/src/scss/main.scss +67 -6
- package/src/components/Header.js +0 -15
- package/src/images/icon-close.svg +0 -1
- package/src/images/icon-code.svg +0 -3
- package/src/images/icon-down.svg +0 -1
- package/src/images/icon-edit.svg +0 -3
- package/src/images/icon-grid.svg +0 -4
- package/src/images/icon-move.svg +0 -8
- package/src/images/icon-up.svg +0 -1
- package/src/images/warning.svg +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import { useDrop } from 'react-dnd'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
5
|
import Widget from './Widget'
|
|
6
6
|
|
|
7
7
|
const Column = ({ data, rowIdx, colIdx }) => {
|
|
8
|
-
const { visualizations } = useContext(
|
|
8
|
+
const { visualizations } = useContext(ConfigContext)
|
|
9
9
|
|
|
10
10
|
const [ { isOver, canDrop }, drop ] = useDrop(() => ({
|
|
11
11
|
accept: 'vis-widget',
|
|
@@ -28,19 +28,21 @@ const Column = ({ data, rowIdx, colIdx }) => {
|
|
|
28
28
|
'column-size--' + data.width,
|
|
29
29
|
]
|
|
30
30
|
|
|
31
|
-
if(isOver && canDrop) {
|
|
31
|
+
if (isOver && canDrop) {
|
|
32
32
|
classNames.push('column--drop')
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if(widget) {
|
|
35
|
+
if (widget) {
|
|
36
36
|
classNames.push('column--populated')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<div className={classNames.join(' ')} ref={drop}>
|
|
41
|
-
{widget ?
|
|
41
|
+
{widget ?
|
|
42
|
+
<Widget data={{ rowIdx, colIdx, ...widget }} type={widget.visualizationType ?? widget.general?.geoType}/> :
|
|
43
|
+
<p className="builder-column__text">Drag and drop <br/> visualization</p>}
|
|
42
44
|
</div>
|
|
43
45
|
)
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
export default Column
|
|
48
|
+
export default Column
|
|
@@ -15,11 +15,9 @@ import { Base64 } from 'js-base64';
|
|
|
15
15
|
|
|
16
16
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
export default function DataTable(props) {
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { data, config } = useContext<any>(Context);
|
|
20
|
+
const { data, datasetKey, config } = props;
|
|
23
21
|
|
|
24
22
|
const [tableExpanded, setTableExpanded] = useState<boolean>(config.table ? config.table.expanded : false);
|
|
25
23
|
const [accessibilityLabel, setAccessibilityLabel] = useState('');
|
|
@@ -46,7 +44,7 @@ export default function DataTable() {
|
|
|
46
44
|
aria-label="Download this data in a CSV file format."
|
|
47
45
|
className={`btn btn-download no-border`}
|
|
48
46
|
>
|
|
49
|
-
Download Data (CSV)
|
|
47
|
+
Download {datasetKey ? `"${datasetKey}"` : 'Data'} (CSV)
|
|
50
48
|
</a>
|
|
51
49
|
)
|
|
52
50
|
});
|
|
@@ -60,9 +58,7 @@ export default function DataTable() {
|
|
|
60
58
|
return [{
|
|
61
59
|
Header : 'No Data Found'
|
|
62
60
|
}];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
else {
|
|
61
|
+
} else {
|
|
66
62
|
Object.keys(data[0]).map((key) => {
|
|
67
63
|
const newCol = {
|
|
68
64
|
Header: key,
|
|
@@ -125,12 +121,13 @@ export default function DataTable() {
|
|
|
125
121
|
<ErrorBoundary component="DataTable">
|
|
126
122
|
<section className={`data-table-container`} aria-label={accessibilityLabel}>
|
|
127
123
|
<div
|
|
124
|
+
role="button"
|
|
128
125
|
className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
|
|
129
126
|
onClick={() => { setTableExpanded(!tableExpanded); }}
|
|
130
127
|
tabIndex={0}
|
|
131
128
|
onKeyDown={(e) => { if (e.keyCode === 13) { setTableExpanded(!tableExpanded); } }}
|
|
132
129
|
>
|
|
133
|
-
{config.table.label}
|
|
130
|
+
{config.table.label}{datasetKey ? `: ${datasetKey}` : ''}
|
|
134
131
|
</div>
|
|
135
132
|
<div className="table-container">
|
|
136
133
|
<table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!tableExpanded} {...getTableProps()}>
|
|
@@ -268,87 +268,243 @@ const EditorPanel = memo(() => {
|
|
|
268
268
|
|
|
269
269
|
return (
|
|
270
270
|
<ErrorBoundary component="EditorPanel">
|
|
271
|
-
{config.runtime && config.runtime.editorErrorMessage && <Error />
|
|
272
|
-
<button
|
|
273
|
-
|
|
271
|
+
{config.runtime && config.runtime.editorErrorMessage && <Error />}
|
|
272
|
+
<button
|
|
273
|
+
className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`}
|
|
274
|
+
title={displayPanel ? `Collapse Editor` : `Expand Editor`}
|
|
275
|
+
onClick={() => setDisplayPanel(!displayPanel)}
|
|
276
|
+
></button>
|
|
277
|
+
<section
|
|
278
|
+
className={
|
|
279
|
+
displayPanel ? "editor-panel cove" : "hidden editor-panel cove"
|
|
280
|
+
}
|
|
281
|
+
>
|
|
274
282
|
<div className="heading-2">Configure</div>
|
|
275
283
|
<section className="form-container">
|
|
276
284
|
<form>
|
|
277
285
|
<Accordion allowZeroExpanded={true}>
|
|
278
|
-
<AccordionItem>
|
|
286
|
+
<AccordionItem>
|
|
287
|
+
{" "}
|
|
288
|
+
{/* General */}
|
|
279
289
|
<AccordionItemHeading>
|
|
280
|
-
<AccordionItemButton>
|
|
281
|
-
General
|
|
282
|
-
</AccordionItemButton>
|
|
290
|
+
<AccordionItemButton>General</AccordionItemButton>
|
|
283
291
|
</AccordionItemHeading>
|
|
284
292
|
<AccordionItemPanel>
|
|
285
|
-
<TextField
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
<TextField
|
|
294
|
+
value={config.dashboard.title}
|
|
295
|
+
section="dashboard"
|
|
296
|
+
fieldName="title"
|
|
297
|
+
label="Title"
|
|
298
|
+
updateField={updateField}
|
|
299
|
+
/>
|
|
300
|
+
|
|
301
|
+
<TextField
|
|
302
|
+
value={"Super Title"}
|
|
303
|
+
updateField={updateField}
|
|
304
|
+
section="general"
|
|
305
|
+
fieldName="superTitle"
|
|
306
|
+
label="Super Title"
|
|
307
|
+
placeholder="Super Title"
|
|
308
|
+
tooltip={
|
|
309
|
+
<Tooltip style={{ textTransform: "none" }}>
|
|
310
|
+
<Tooltip.Target>
|
|
311
|
+
<Icon
|
|
312
|
+
display="question"
|
|
313
|
+
style={{ marginLeft: "0.5rem" }}
|
|
314
|
+
/>
|
|
315
|
+
</Tooltip.Target>
|
|
316
|
+
<Tooltip.Content>
|
|
317
|
+
<p>Super Title</p>
|
|
318
|
+
</Tooltip.Content>
|
|
319
|
+
</Tooltip>
|
|
320
|
+
}
|
|
321
|
+
/>
|
|
322
|
+
|
|
323
|
+
<TextField
|
|
324
|
+
type="textarea"
|
|
325
|
+
value={"Intro Text"}
|
|
326
|
+
updateField={updateField}
|
|
327
|
+
section="general"
|
|
328
|
+
fieldName="introText"
|
|
329
|
+
label="Intro Text"
|
|
330
|
+
tooltip={
|
|
331
|
+
<Tooltip style={{ textTransform: "none" }}>
|
|
332
|
+
<Tooltip.Target>
|
|
333
|
+
<Icon
|
|
334
|
+
display="question"
|
|
335
|
+
style={{ marginLeft: "0.5rem" }}
|
|
336
|
+
/>
|
|
337
|
+
</Tooltip.Target>
|
|
338
|
+
<Tooltip.Content>
|
|
339
|
+
<p>Intro Text</p>
|
|
340
|
+
</Tooltip.Content>
|
|
341
|
+
</Tooltip>
|
|
342
|
+
}
|
|
343
|
+
/>
|
|
344
|
+
|
|
345
|
+
<TextField
|
|
346
|
+
type="textarea"
|
|
347
|
+
value={config.dashboard.description}
|
|
348
|
+
section="dashboard"
|
|
349
|
+
fieldName="description"
|
|
350
|
+
label="Description"
|
|
351
|
+
updateField={updateField}
|
|
352
|
+
tooltip={
|
|
353
|
+
<Tooltip style={{ textTransform: "none" }}>
|
|
354
|
+
<Tooltip.Target>
|
|
355
|
+
<Icon
|
|
356
|
+
display="question"
|
|
357
|
+
style={{ marginLeft: "0.5rem" }}
|
|
358
|
+
/>
|
|
359
|
+
</Tooltip.Target>
|
|
360
|
+
<Tooltip.Content>
|
|
361
|
+
<p>
|
|
362
|
+
Enter supporting text to display below the data
|
|
363
|
+
visualization, if applicable. The following HTML
|
|
364
|
+
tags are supported: strong, em, sup, and sub.
|
|
365
|
+
</p>
|
|
366
|
+
</Tooltip.Content>
|
|
367
|
+
</Tooltip>
|
|
368
|
+
}
|
|
369
|
+
/>
|
|
370
|
+
|
|
371
|
+
<TextField
|
|
372
|
+
type="textarea"
|
|
373
|
+
value={"Foootnotes"}
|
|
374
|
+
updateField={updateField}
|
|
375
|
+
section="general"
|
|
376
|
+
fieldName="footnotes"
|
|
377
|
+
label="Footnotes"
|
|
378
|
+
tooltip={
|
|
379
|
+
<Tooltip style={{ textTransform: "none" }}>
|
|
380
|
+
<Tooltip.Target>
|
|
381
|
+
<Icon
|
|
382
|
+
display="question"
|
|
383
|
+
style={{ marginLeft: "0.5rem" }}
|
|
384
|
+
/>
|
|
385
|
+
</Tooltip.Target>
|
|
386
|
+
<Tooltip.Content>
|
|
387
|
+
<p>Footnotes</p>
|
|
388
|
+
</Tooltip.Content>
|
|
389
|
+
</Tooltip>
|
|
390
|
+
}
|
|
391
|
+
/>
|
|
294
392
|
</AccordionItemPanel>
|
|
295
393
|
</AccordionItem>
|
|
296
394
|
<AccordionItem>
|
|
297
395
|
<AccordionItemHeading>
|
|
298
|
-
<AccordionItemButton>
|
|
299
|
-
Filters
|
|
300
|
-
</AccordionItemButton>
|
|
396
|
+
<AccordionItemButton>Filters</AccordionItemButton>
|
|
301
397
|
</AccordionItemHeading>
|
|
302
398
|
<AccordionItemPanel>
|
|
303
399
|
<ul className="filters-list">
|
|
304
|
-
{config.dashboard.filters &&
|
|
305
|
-
|
|
306
|
-
|
|
400
|
+
{config.dashboard.filters &&
|
|
401
|
+
config.dashboard.filters.map((filter, index) => (
|
|
402
|
+
<fieldset
|
|
403
|
+
className="edit-block"
|
|
404
|
+
key={filter.columnName + index}
|
|
405
|
+
>
|
|
406
|
+
<button
|
|
407
|
+
type="button"
|
|
408
|
+
className="remove-column"
|
|
409
|
+
onClick={() => {
|
|
410
|
+
removeFilter(index);
|
|
411
|
+
}}
|
|
412
|
+
>
|
|
413
|
+
Remove
|
|
414
|
+
</button>
|
|
307
415
|
<label>
|
|
308
|
-
<span className="edit-label column-heading">
|
|
309
|
-
|
|
416
|
+
<span className="edit-label column-heading">
|
|
417
|
+
Filter
|
|
418
|
+
</span>
|
|
419
|
+
<select
|
|
420
|
+
value={filter.columnName}
|
|
421
|
+
onChange={(e) => {
|
|
422
|
+
updateFilterProp(
|
|
423
|
+
"columnName",
|
|
424
|
+
index,
|
|
425
|
+
e.target.value
|
|
426
|
+
);
|
|
427
|
+
}}
|
|
428
|
+
>
|
|
310
429
|
<option value="">- Select Option -</option>
|
|
311
430
|
{getColumns().map((dataKey) => (
|
|
312
|
-
<option value={dataKey} key={dataKey}>
|
|
431
|
+
<option value={dataKey} key={dataKey}>
|
|
432
|
+
{dataKey}
|
|
433
|
+
</option>
|
|
313
434
|
))}
|
|
314
435
|
</select>
|
|
315
436
|
</label>
|
|
316
437
|
<label>
|
|
317
|
-
<span className="edit-label column-heading">
|
|
318
|
-
|
|
438
|
+
<span className="edit-label column-heading">
|
|
439
|
+
Label
|
|
440
|
+
</span>
|
|
441
|
+
<input
|
|
442
|
+
type="text"
|
|
443
|
+
value={filter.label}
|
|
444
|
+
onChange={(e) => {
|
|
445
|
+
updateFilterProp(
|
|
446
|
+
"label",
|
|
447
|
+
index,
|
|
448
|
+
e.target.value
|
|
449
|
+
);
|
|
450
|
+
}}
|
|
451
|
+
/>
|
|
319
452
|
</label>
|
|
320
453
|
</fieldset>
|
|
321
|
-
)
|
|
322
|
-
)}
|
|
454
|
+
))}
|
|
323
455
|
</ul>
|
|
324
456
|
|
|
325
|
-
<button
|
|
457
|
+
<button
|
|
458
|
+
type="button"
|
|
459
|
+
onClick={addNewFilter}
|
|
460
|
+
className="btn btn-primary"
|
|
461
|
+
>
|
|
462
|
+
Add Filter
|
|
463
|
+
</button>
|
|
326
464
|
</AccordionItemPanel>
|
|
327
465
|
</AccordionItem>
|
|
328
466
|
<AccordionItem>
|
|
329
467
|
<AccordionItemHeading>
|
|
330
|
-
<AccordionItemButton>
|
|
331
|
-
Data Table
|
|
332
|
-
</AccordionItemButton>
|
|
468
|
+
<AccordionItemButton>Data Table</AccordionItemButton>
|
|
333
469
|
</AccordionItemHeading>
|
|
334
470
|
<AccordionItemPanel>
|
|
335
|
-
<CheckBox
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
471
|
+
<CheckBox
|
|
472
|
+
value={config.table.show}
|
|
473
|
+
section="table"
|
|
474
|
+
fieldName="show"
|
|
475
|
+
label="Show Table"
|
|
476
|
+
updateField={updateField}
|
|
477
|
+
/>
|
|
478
|
+
<CheckBox
|
|
479
|
+
value={config.table.expanded}
|
|
480
|
+
section="table"
|
|
481
|
+
fieldName="expanded"
|
|
482
|
+
label="Expanded by Default"
|
|
483
|
+
updateField={updateField}
|
|
484
|
+
/>
|
|
485
|
+
<CheckBox
|
|
486
|
+
value={config.table.download}
|
|
487
|
+
section="table"
|
|
488
|
+
fieldName="download"
|
|
489
|
+
label="Display Download Button"
|
|
490
|
+
updateField={updateField}
|
|
491
|
+
/>
|
|
492
|
+
<TextField
|
|
493
|
+
value={config.table.label}
|
|
494
|
+
section="table"
|
|
495
|
+
fieldName="label"
|
|
496
|
+
label="Label"
|
|
497
|
+
updateField={updateField}
|
|
498
|
+
/>
|
|
339
499
|
</AccordionItemPanel>
|
|
340
500
|
</AccordionItem>
|
|
341
|
-
|
|
501
|
+
</Accordion>
|
|
342
502
|
</form>
|
|
343
503
|
</section>
|
|
344
|
-
<ReactTooltip
|
|
345
|
-
html={true}
|
|
346
|
-
multiline={true}
|
|
347
|
-
className="helper-tooltip"
|
|
348
|
-
/>
|
|
504
|
+
<ReactTooltip html={true} multiline={true} className="helper-tooltip" />
|
|
349
505
|
</section>
|
|
350
506
|
</ErrorBoundary>
|
|
351
|
-
)
|
|
507
|
+
);
|
|
352
508
|
})
|
|
353
509
|
|
|
354
510
|
export default EditorPanel;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import Row from './Row'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
5
|
|
|
6
6
|
const Grid = () => {
|
|
7
|
-
const { rows, config, updateConfig } = useContext(
|
|
7
|
+
const { rows, config, updateConfig } = useContext(ConfigContext)
|
|
8
8
|
|
|
9
9
|
const addRow = () => {
|
|
10
10
|
updateConfig({
|
|
11
11
|
...config,
|
|
12
12
|
rows: [
|
|
13
13
|
...rows,
|
|
14
|
-
[{width: 12}, {}, {}]
|
|
14
|
+
[ { width: 12 }, { equalHeight: false }, {}, {} ]
|
|
15
|
+
//[{width: 12}, {}, {}] ],
|
|
15
16
|
],
|
|
16
17
|
uuid: Date.now()
|
|
17
18
|
})
|
|
@@ -25,4 +26,4 @@ const Grid = () => {
|
|
|
25
26
|
)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export default Grid
|
|
29
|
+
export default Grid
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React, { useState, useEffect, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
import ConfigContext from '../ConfigContext'
|
|
4
|
+
|
|
5
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
6
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
7
|
+
|
|
8
|
+
const Header = ({setPreview, tabSelected, setTabSelected, back, subEditor = null}) => {
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
config,
|
|
12
|
+
updateConfig,
|
|
13
|
+
setParentConfig
|
|
14
|
+
} = useContext(ConfigContext);
|
|
15
|
+
|
|
16
|
+
const { overlay } = useGlobalContext()
|
|
17
|
+
|
|
18
|
+
const [ columns, setColumns ] = useState([])
|
|
19
|
+
|
|
20
|
+
const changeConfigValue = (parentObj, key, value) => {
|
|
21
|
+
|
|
22
|
+
let newConfig = {...config};
|
|
23
|
+
if(!newConfig[parentObj]) newConfig[parentObj] = {};
|
|
24
|
+
newConfig[parentObj][key] = value;
|
|
25
|
+
|
|
26
|
+
updateConfig(newConfig)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const setTab = (index) => {
|
|
30
|
+
setTabSelected(index)
|
|
31
|
+
if(index === 3){
|
|
32
|
+
setPreview(true)
|
|
33
|
+
} else {
|
|
34
|
+
setPreview(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const addNewFilter = () => {
|
|
39
|
+
let dashboardConfig = {...config.dashboard};
|
|
40
|
+
|
|
41
|
+
dashboardConfig.sharedFilters = dashboardConfig.sharedFilters || [];
|
|
42
|
+
|
|
43
|
+
dashboardConfig.sharedFilters.push({key: 'Dashboard Filter ' + (dashboardConfig.sharedFilters.length + 1), values: []});
|
|
44
|
+
|
|
45
|
+
updateConfig({...config, dashboard: dashboardConfig});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const removeFilter = (index) => {
|
|
50
|
+
let dashboardConfig = {...config.dashboard};
|
|
51
|
+
|
|
52
|
+
dashboardConfig.sharedFilters.splice(index, 1);
|
|
53
|
+
|
|
54
|
+
updateConfig({...config, dashboard: dashboardConfig});
|
|
55
|
+
|
|
56
|
+
overlay?.actions.toggleOverlay();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const convertStateToConfig = (type = "JSON") => {
|
|
60
|
+
let strippedState = JSON.parse(JSON.stringify(config))
|
|
61
|
+
delete strippedState.newViz
|
|
62
|
+
delete strippedState.runtime
|
|
63
|
+
|
|
64
|
+
if(type === "JSON") {
|
|
65
|
+
return JSON.stringify( strippedState )
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return strippedState
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const parsedData = convertStateToConfig()
|
|
73
|
+
|
|
74
|
+
// Emit the data in a regular JS event so it can be consumed by anything.
|
|
75
|
+
const event = new CustomEvent('updateVizConfig', { detail: parsedData})
|
|
76
|
+
|
|
77
|
+
window.dispatchEvent(event)
|
|
78
|
+
|
|
79
|
+
// Pass up to Editor if needed
|
|
80
|
+
if(setParentConfig) {
|
|
81
|
+
const newConfig = convertStateToConfig("object")
|
|
82
|
+
setParentConfig(newConfig)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [config]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const runSetColumns = async () => {
|
|
90
|
+
let columns = {};
|
|
91
|
+
let dataKeys = Object.keys(config.datasets);
|
|
92
|
+
|
|
93
|
+
for(let i = 0; i < dataKeys.length; i++){
|
|
94
|
+
if(!config.datasets[dataKeys[i]].data && config.datasets[dataKeys[i]].dataUrl){
|
|
95
|
+
config.datasets[dataKeys[i]].data = await fetchRemoteData(config.datasets[dataKeys[i]].dataUrl);
|
|
96
|
+
if(config.datasets[dataKeys[i]].dataDescription) {
|
|
97
|
+
try {
|
|
98
|
+
config.datasets[dataKeys[i]].data = transform.autoStandardize(config.datasets[dataKeys[i]].data);
|
|
99
|
+
config.datasets[dataKeys[i]].data = transform.developerStandardize(config.datasets[dataKeys[i]].data, config.datasets[dataKey].dataDescription);
|
|
100
|
+
} catch(e) {
|
|
101
|
+
//Data not able to be standardized, leave as is
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if(config.datasets[dataKeys[i]].data) {
|
|
107
|
+
config.datasets[dataKeys[i]].data.map(row => {
|
|
108
|
+
Object.keys(row).forEach(columnName => columns[columnName] = true)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setColumns(Object.keys(columns))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
runSetColumns()
|
|
117
|
+
}, [config.datasets]);
|
|
118
|
+
|
|
119
|
+
const filterModal = (filter, index) => {
|
|
120
|
+
|
|
121
|
+
const saveChanges = () => {
|
|
122
|
+
let tempConfig = {...config.dashboard};
|
|
123
|
+
tempConfig.sharedFilters[index] = filter;
|
|
124
|
+
|
|
125
|
+
updateConfig({...config, dashboard: tempConfig});
|
|
126
|
+
|
|
127
|
+
overlay?.actions.toggleOverlay()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const updateFilterProp = (name, index, value) => {
|
|
131
|
+
let newFilter = {...filter};
|
|
132
|
+
|
|
133
|
+
newFilter[name] = value;
|
|
134
|
+
|
|
135
|
+
overlay?.actions.openOverlay(filterModal(newFilter, index))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const addFilterUsedBy = (filter, index, value) => {
|
|
139
|
+
if(!filter.usedBy) filter.usedBy = [];
|
|
140
|
+
filter.usedBy.push(value);
|
|
141
|
+
updateFilterProp('usedBy', index, filter.usedBy);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const removeFilterUsedBy = (filter, index, value) => {
|
|
145
|
+
let usedByIndex = filter.usedBy.indexOf(value);
|
|
146
|
+
if(usedByIndex !== -1){
|
|
147
|
+
filter.usedBy.splice(usedByIndex, 1);
|
|
148
|
+
updateFilterProp('usedBy', index, filter.usedBy);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Modal>
|
|
155
|
+
<Modal.Content>
|
|
156
|
+
<h2>Dashboard Filter Settings</h2>
|
|
157
|
+
<fieldset className="shared-filter-modal" key={filter.columnName + index}>
|
|
158
|
+
<button type="button" className="btn btn-primary remove-column" onClick={() => {removeFilter(index)}}>Remove Filter</button>
|
|
159
|
+
<label>
|
|
160
|
+
<span className="edit-label column-heading">Filter: </span>
|
|
161
|
+
<select value={filter.columnName} onChange={(e) => {updateFilterProp('columnName', index, e.target.value)}}>
|
|
162
|
+
<option value="">- Select Option -</option>
|
|
163
|
+
{columns.map((dataKey) => (
|
|
164
|
+
<option value={dataKey} key={`filter-column-select-item-${dataKey}`}>{dataKey}</option>
|
|
165
|
+
))}
|
|
166
|
+
</select>
|
|
167
|
+
</label>
|
|
168
|
+
<label>
|
|
169
|
+
<span className="edit-label column-heading">Label: </span>
|
|
170
|
+
<input type="text" value={filter.key} onChange={(e) => {updateFilterProp('key', index, e.target.value)}}/>
|
|
171
|
+
</label>
|
|
172
|
+
<label>
|
|
173
|
+
<span className="edit-label column-heading">Show Dropdown</span>
|
|
174
|
+
<input type="checkbox" defaultChecked={filter.showDropdown === true} onChange={(e) => {updateFilterProp('showDropdown', index, !filter.showDropdown)}}/>
|
|
175
|
+
</label>
|
|
176
|
+
<label>
|
|
177
|
+
<span className="edit-label column-heading">Set By: </span>
|
|
178
|
+
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
|
|
179
|
+
<option value="">- Select Option -</option>
|
|
180
|
+
{Object.keys(config.visualizations).map((vizKey) => (
|
|
181
|
+
<option value={vizKey} key={`set-by-select-item-${vizKey}`}>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</option>
|
|
182
|
+
))}
|
|
183
|
+
</select>
|
|
184
|
+
</label>
|
|
185
|
+
<label>
|
|
186
|
+
<span className="edit-label column-heading">Used By:</span>
|
|
187
|
+
<ul>
|
|
188
|
+
{filter.usedBy && filter.usedBy.map(vizKey => (
|
|
189
|
+
<li key={`used-by-list-item-${vizKey}`}><span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</span> <button onClick={(e) => {e.preventDefault();removeFilterUsedBy(filter, index, vizKey)}}>X</button></li>
|
|
190
|
+
))}
|
|
191
|
+
</ul>
|
|
192
|
+
<select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
|
|
193
|
+
<option value="">- Select Option -</option>
|
|
194
|
+
{Object.keys(config.visualizations).filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter).map((vizKey) => (
|
|
195
|
+
<option value={vizKey} key={`used-by-select-item-${vizKey}`}>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</option>
|
|
196
|
+
))}
|
|
197
|
+
</select>
|
|
198
|
+
</label>
|
|
199
|
+
</fieldset>
|
|
200
|
+
<button type="button" className="btn btn-primary" style={{display: 'inline-block', 'margin-right': '1em'}} onClick={overlay?.actions.toggleOverlay}>Cancel</button>
|
|
201
|
+
<button type="button" className="btn btn-primary" style={{display: 'inline-block'}} onClick={saveChanges}>Save</button>
|
|
202
|
+
</Modal.Content>
|
|
203
|
+
</Modal>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div aria-level="2" role="heading" className={`editor-heading${subEditor ? ' sub-dashboard-viz' : ''}`}>
|
|
209
|
+
{subEditor ? <div className="heading-1 back-to" onClick={back} style={{cursor: 'pointer'}}><span>←</span> Back to Dashboard</div> : <div className="heading-1">Dashboard Editor<br/>{<input type="text" placeholder="Enter Dashboard Name Here" defaultValue={config.dashboard.title} onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}/>}</div>}
|
|
210
|
+
{!subEditor && <div>
|
|
211
|
+
<ul className="toggle-bar">
|
|
212
|
+
<li className={tabSelected === 0 ? 'active' : 'inactive'} onClick={() => {setTab(0)}}>Dashboard Description</li>
|
|
213
|
+
<li className={tabSelected === 1 ? 'active' : 'inactive'} onClick={() => {setTab(1)}}>Dashboard Filters</li>
|
|
214
|
+
<li className={tabSelected === 2 ? 'active' : 'inactive'} onClick={() => {setTab(2)}}>Data Table Settings</li>
|
|
215
|
+
<li className={tabSelected === 3 ? 'active' : 'inactive'} onClick={() => {setTab(3)}}>Dashboard Preview</li>
|
|
216
|
+
</ul>
|
|
217
|
+
<div className="heading-body">
|
|
218
|
+
{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)} />}
|
|
219
|
+
{tabSelected === 1 && (
|
|
220
|
+
<>
|
|
221
|
+
{config.dashboard.sharedFilters && config.dashboard.sharedFilters.map((sharedFilter, index) => (
|
|
222
|
+
<span className="shared-filter-button" key={`shared-filter-${sharedFilter.key}`}>
|
|
223
|
+
<a href="#" onClick={(e) => {e.preventDefault(); overlay?.actions.openOverlay(filterModal(sharedFilter, index))}}>{sharedFilter.key}</a>
|
|
224
|
+
<button onClick={() => removeFilter(index)}>X</button>
|
|
225
|
+
</span>
|
|
226
|
+
))}
|
|
227
|
+
<button onClick={addNewFilter}>Add New Filter</button></>
|
|
228
|
+
)}
|
|
229
|
+
{tabSelected === 2 && (
|
|
230
|
+
<>
|
|
231
|
+
<label>Show Table</label><input type="checkbox" defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
|
|
232
|
+
<label>Expanded by Default</label><input type="checkbox" defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
|
|
233
|
+
<label>Display Download Button</label><input type="checkbox" defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>}
|
|
238
|
+
</div>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default Header
|
package/src/components/Row.js
CHANGED