@cdc/dashboard 1.1.2 → 4.22.10
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 +159 -48
- package/examples/default-filter-control.json +175 -0
- package/examples/default-multi-dataset.json +498 -0
- package/examples/default.json +36 -348
- 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/examples/test-example.json +1 -0
- package/package.json +14 -8
- package/src/CdcDashboard.js +279 -156
- package/src/CdcDashboard.jsx +697 -0
- package/src/{context.tsx → ConfigContext.js} +0 -0
- package/src/components/{Column.js → Column.jsx} +9 -7
- package/src/components/DataTable.tsx +57 -55
- package/src/components/EditorPanel.js +207 -45
- package/src/components/{Grid.js → Grid.jsx} +5 -4
- package/src/components/Header.jsx +246 -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 +24 -5
- package/src/components/Widget.jsx +206 -0
- package/src/index.html +31 -25
- package/src/scss/editor-panel.scss +53 -49
- package/src/scss/grid.scss +60 -14
- package/src/scss/main.scss +77 -9
- package/src/components/Header.js +0 -15
- package/src/images/icon-close.svg +0 -1
- package/src/images/icon-down.svg +0 -1
- package/src/images/icon-edit.svg +0 -1
- package/src/images/icon-move.svg +0 -8
- package/src/images/icon-up.svg +0 -1
- package/src/images/warning.svg +0 -1
package/src/CdcDashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
// IE11
|
|
4
4
|
import 'core-js/stable'
|
|
@@ -8,44 +8,61 @@ import ResizeObserver from 'resize-observer-polyfill'
|
|
|
8
8
|
import { DndProvider } from 'react-dnd'
|
|
9
9
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
|
10
10
|
|
|
11
|
-
import parse from 'html-react-parser'
|
|
11
|
+
import parse from 'html-react-parser'
|
|
12
12
|
|
|
13
|
-
import Loading from '@cdc/core/components/Loading'
|
|
14
|
-
import DataTransform from '@cdc/core/components/DataTransform'
|
|
15
|
-
import getViewport from '@cdc/core/helpers/getViewport'
|
|
13
|
+
import Loading from '@cdc/core/components/Loading'
|
|
14
|
+
import DataTransform from '@cdc/core/components/DataTransform'
|
|
15
|
+
import getViewport from '@cdc/core/helpers/getViewport'
|
|
16
16
|
|
|
17
|
-
import CdcMap from '@cdc/map'
|
|
18
|
-
import CdcChart from '@cdc/chart'
|
|
19
|
-
import CdcDataBite from '@cdc/data-bite'
|
|
17
|
+
import CdcMap from '@cdc/map'
|
|
18
|
+
import CdcChart from '@cdc/chart'
|
|
19
|
+
import CdcDataBite from '@cdc/data-bite'
|
|
20
|
+
import CdcWaffleChart from '@cdc/waffle-chart'
|
|
21
|
+
import CdcMarkupInclude from '@cdc/markup-include'
|
|
22
|
+
import FilteredText from '@cdc/filtered-text';
|
|
20
23
|
|
|
21
|
-
import EditorPanel from './components/EditorPanel'
|
|
22
|
-
import Grid from './components/Grid'
|
|
23
|
-
import Header from './components/Header'
|
|
24
|
-
import Context from './context'
|
|
25
|
-
import defaults from './data/initial-state'
|
|
26
|
-
import Widget from './components/Widget'
|
|
27
|
-
import DataTable from './components/DataTable'
|
|
24
|
+
import EditorPanel from './components/EditorPanel'
|
|
25
|
+
import Grid from './components/Grid'
|
|
26
|
+
import Header from './components/Header'
|
|
27
|
+
import Context from './context'
|
|
28
|
+
import defaults from './data/initial-state'
|
|
29
|
+
import Widget from './components/Widget'
|
|
30
|
+
import DataTable from './components/DataTable'
|
|
28
31
|
|
|
29
|
-
import '
|
|
32
|
+
import Papa from 'papaparse'
|
|
33
|
+
|
|
34
|
+
import './scss/main.scss'
|
|
35
|
+
|
|
36
|
+
import { publish } from '@cdc/core/helpers/events'
|
|
37
|
+
import cacheBustingString from '@cdc/core/helpers/cacheBustingString'
|
|
30
38
|
|
|
31
39
|
const addVisualization = (type, subType) => {
|
|
32
40
|
let newVisualizationConfig = {
|
|
33
41
|
newViz: true,
|
|
34
42
|
uid: type + Date.now(),
|
|
35
43
|
type
|
|
36
|
-
}
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
switch(type) {
|
|
46
|
+
switch (type) {
|
|
39
47
|
case 'chart':
|
|
40
|
-
newVisualizationConfig.visualizationType = subType
|
|
41
|
-
break
|
|
48
|
+
newVisualizationConfig.visualizationType = subType
|
|
49
|
+
break
|
|
42
50
|
case 'map':
|
|
43
|
-
newVisualizationConfig.general = {}
|
|
44
|
-
newVisualizationConfig.general.geoType = subType
|
|
45
|
-
break
|
|
51
|
+
newVisualizationConfig.general = {}
|
|
52
|
+
newVisualizationConfig.general.geoType = subType
|
|
53
|
+
break
|
|
46
54
|
case 'data-bite':
|
|
47
|
-
newVisualizationConfig.visualizationType = type
|
|
48
|
-
break
|
|
55
|
+
newVisualizationConfig.visualizationType = type
|
|
56
|
+
break
|
|
57
|
+
case 'waffle-chart':
|
|
58
|
+
newVisualizationConfig.visualizationType = type
|
|
59
|
+
break
|
|
60
|
+
case 'markup-include':
|
|
61
|
+
newVisualizationConfig.visualizationType = type
|
|
62
|
+
break
|
|
63
|
+
case 'filtered-text':
|
|
64
|
+
newVisualizationConfig.visualizationType = type
|
|
65
|
+
break
|
|
49
66
|
}
|
|
50
67
|
|
|
51
68
|
return newVisualizationConfig
|
|
@@ -53,176 +70,252 @@ const addVisualization = (type, subType) => {
|
|
|
53
70
|
|
|
54
71
|
const VisualizationsPanel = () => (
|
|
55
72
|
<div className="visualizations-panel">
|
|
56
|
-
<p style={{fontSize: '14px'}}>Click and drag an item onto the grid to add it to your dashboard.</p>
|
|
73
|
+
<p style={{ fontSize: '14px' }}>Click and drag an item onto the grid to add it to your dashboard.</p>
|
|
57
74
|
<span className="subheading-3">Chart</span>
|
|
58
75
|
<div className="drag-grid">
|
|
59
|
-
<Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"
|
|
60
|
-
<Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"
|
|
61
|
-
<Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"
|
|
76
|
+
<Widget addVisualization={() => addVisualization('chart', 'Bar')} type="Bar"/>
|
|
77
|
+
<Widget addVisualization={() => addVisualization('chart', 'Line')} type="Line"/>
|
|
78
|
+
<Widget addVisualization={() => addVisualization('chart', 'Pie')} type="Pie"/>
|
|
62
79
|
</div>
|
|
63
80
|
<span className="subheading-3">Map</span>
|
|
64
81
|
<div className="drag-grid">
|
|
65
|
-
<Widget addVisualization={() => addVisualization('map', 'us')} type="us"
|
|
66
|
-
<Widget addVisualization={() => addVisualization('map', 'world')} type="world"
|
|
82
|
+
<Widget addVisualization={() => addVisualization('map', 'us')} type="us"/>
|
|
83
|
+
<Widget addVisualization={() => addVisualization('map', 'world')} type="world"/>
|
|
84
|
+
<Widget addVisualization={() => addVisualization('map', 'single-state')} type="single-state"/>
|
|
67
85
|
</div>
|
|
68
86
|
<span className="subheading-3">Misc.</span>
|
|
69
87
|
<div className="drag-grid">
|
|
70
|
-
<Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"
|
|
88
|
+
<Widget addVisualization={() => addVisualization('data-bite', '')} type="data-bite"/>
|
|
89
|
+
<Widget addVisualization={() => addVisualization('waffle-chart', '')} type="waffle-chart"/>
|
|
90
|
+
<Widget addVisualization={() => addVisualization('markup-include', '')} type="markup-include"/>
|
|
91
|
+
<Widget addVisualization={() => addVisualization('filtered-text', '')} type="filtered-text"/>
|
|
71
92
|
</div>
|
|
72
93
|
</div>
|
|
73
94
|
)
|
|
74
95
|
|
|
75
96
|
export default function CdcDashboard(
|
|
76
|
-
{ configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig }
|
|
97
|
+
{ configUrl = '', config: configObj = undefined, isEditor = false, setConfig: setParentConfig, hostname }
|
|
77
98
|
) {
|
|
78
99
|
|
|
79
|
-
const transform = new DataTransform()
|
|
100
|
+
const transform = new DataTransform()
|
|
80
101
|
|
|
81
|
-
const [config, setConfig] = useState(configObj
|
|
102
|
+
const [ config, setConfig ] = useState(configObj)
|
|
82
103
|
|
|
83
|
-
const [data, setData] = useState([])
|
|
104
|
+
const [ data, setData ] = useState([])
|
|
84
105
|
|
|
85
|
-
const [filteredData, setFilteredData] = useState()
|
|
106
|
+
const [ filteredData, setFilteredData ] = useState()
|
|
86
107
|
|
|
87
|
-
const [loading, setLoading] = useState(true)
|
|
108
|
+
const [ loading, setLoading ] = useState(true)
|
|
88
109
|
|
|
89
|
-
const [preview, setPreview] = useState(false)
|
|
110
|
+
const [ preview, setPreview ] = useState(false)
|
|
90
111
|
|
|
91
|
-
const [currentViewport, setCurrentViewport] = useState('lg')
|
|
112
|
+
const [ currentViewport, setCurrentViewport ] = useState('lg')
|
|
92
113
|
|
|
93
|
-
const
|
|
114
|
+
const [ coveLoadedHasRan, setCoveLoadedHasRan ] = useState(false)
|
|
94
115
|
|
|
95
|
-
const
|
|
96
|
-
let response = configObj || await (await fetch(configUrl)).json();
|
|
116
|
+
const [ container, setContainer ] = useState()
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
const { title, description } = config ? (config.dashboard || config) : {}
|
|
119
|
+
|
|
120
|
+
// Supports JSON or CSV
|
|
121
|
+
const fetchRemoteData = async (url) => {
|
|
122
|
+
try {
|
|
123
|
+
const urlObj = new URL(url)
|
|
124
|
+
const regex = /(?:\.([^.]+))?$/
|
|
100
125
|
|
|
101
|
-
|
|
102
|
-
const dataString = await fetch(response.dataUrl);
|
|
126
|
+
let data = []
|
|
103
127
|
|
|
104
|
-
|
|
128
|
+
const ext = (regex.exec(urlObj.pathname)[1])
|
|
129
|
+
if ('csv' === ext) {
|
|
130
|
+
data = await fetch(url)
|
|
131
|
+
.then(response => response.text())
|
|
132
|
+
.then(responseText => {
|
|
133
|
+
const parsedCsv = Papa.parse(responseText, {
|
|
134
|
+
header: true,
|
|
135
|
+
dynamicTyping: true,
|
|
136
|
+
skipEmptyLines: true
|
|
137
|
+
})
|
|
138
|
+
return parsedCsv.data
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if ('json' === ext) {
|
|
143
|
+
data = await fetch(url)
|
|
144
|
+
.then(response => response.json())
|
|
145
|
+
}
|
|
105
146
|
|
|
147
|
+
return data
|
|
148
|
+
} catch {
|
|
149
|
+
// If we can't parse it, still attempt to fetch it
|
|
106
150
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
} catch
|
|
110
|
-
|
|
151
|
+
let response = await (await fetch(configUrl)).json()
|
|
152
|
+
return response
|
|
153
|
+
} catch {
|
|
154
|
+
console.error(`Cannot parse URL: ${url}`)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
const loadConfig = async (configObj) => {
|
|
162
|
+
// Set loading flag
|
|
163
|
+
if (!loading) setLoading(true)
|
|
164
|
+
|
|
165
|
+
let newState = configObj || await (await fetch(configUrl)).json()
|
|
166
|
+
|
|
167
|
+
// If a dataUrl property exists, always pull from that.
|
|
168
|
+
if (newState.dataUrl) {
|
|
169
|
+
|
|
170
|
+
if (newState.dataUrl[0] === '/') {
|
|
171
|
+
newState.dataUrl = 'https://' + hostname + newState.dataUrl
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let newData = await fetchRemoteData(newState.dataUrl + `?v=${cacheBustingString()}`)
|
|
175
|
+
|
|
176
|
+
if (newData && newState.dataDescription) {
|
|
177
|
+
newData = transform.autoStandardize(newData)
|
|
178
|
+
newData = transform.developerStandardize(newData, newState.dataDescription)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (newData) {
|
|
182
|
+
newState.data = newData
|
|
111
183
|
}
|
|
112
184
|
}
|
|
113
185
|
|
|
114
|
-
|
|
186
|
+
// If data is included through a URL, fetch that and store
|
|
187
|
+
let data = newState.formattedData || newState.data || {}
|
|
188
|
+
|
|
189
|
+
setData(data)
|
|
115
190
|
|
|
116
|
-
let newConfig = {...defaults, ...
|
|
191
|
+
let newConfig = { ...defaults, ...newState }
|
|
117
192
|
|
|
118
|
-
updateConfig(newConfig, data)
|
|
193
|
+
updateConfig(newConfig, data)
|
|
119
194
|
|
|
120
|
-
setLoading(false)
|
|
195
|
+
setLoading(false)
|
|
121
196
|
}
|
|
122
197
|
|
|
123
198
|
const filterData = (filters, data) => {
|
|
124
|
-
let filteredData = []
|
|
199
|
+
let filteredData = []
|
|
125
200
|
|
|
126
201
|
data.forEach((row) => {
|
|
127
|
-
let add = true
|
|
202
|
+
let add = true
|
|
128
203
|
|
|
129
204
|
filters.forEach((filter) => {
|
|
130
|
-
if(row[filter.columnName] !== filter.active) {
|
|
131
|
-
add = false
|
|
205
|
+
if (row[filter.columnName] !== filter.active) {
|
|
206
|
+
add = false
|
|
132
207
|
}
|
|
133
|
-
})
|
|
208
|
+
})
|
|
134
209
|
|
|
135
|
-
if(add) filteredData.push(row)
|
|
136
|
-
})
|
|
210
|
+
if (add) filteredData.push(row)
|
|
211
|
+
})
|
|
137
212
|
|
|
138
|
-
return filteredData
|
|
213
|
+
return filteredData
|
|
139
214
|
}
|
|
140
215
|
|
|
141
216
|
// Gets filer values from dataset
|
|
142
217
|
const generateValuesForFilter = (columnName, data = this.state.data) => {
|
|
143
|
-
const values = []
|
|
218
|
+
const values = []
|
|
144
219
|
|
|
145
|
-
data.forEach(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
})
|
|
220
|
+
data.forEach((row) => {
|
|
221
|
+
const value = row[columnName]
|
|
222
|
+
if (value && false === values.includes(value)) {
|
|
223
|
+
values.push(value)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
151
226
|
|
|
152
|
-
return values
|
|
227
|
+
return values
|
|
153
228
|
}
|
|
154
229
|
|
|
155
|
-
|
|
230
|
+
function isEmpty(obj) {
|
|
231
|
+
return Object.keys(obj).length === 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const updateConfig = (newConfig, dataOverride = null) => {
|
|
156
235
|
// After data is grabbed, loop through and generate filter column values if there are any
|
|
157
236
|
if (newConfig.dashboard.filters) {
|
|
158
|
-
const filterList = []
|
|
237
|
+
const filterList = []
|
|
159
238
|
|
|
160
239
|
newConfig.dashboard.filters.forEach((filter) => {
|
|
161
|
-
|
|
162
|
-
})
|
|
240
|
+
filterList.push(filter.columnName)
|
|
241
|
+
})
|
|
163
242
|
|
|
164
243
|
filterList.forEach((filter, index) => {
|
|
165
|
-
|
|
244
|
+
const filterValues = generateValuesForFilter(filter, (dataOverride || data))
|
|
245
|
+
|
|
246
|
+
if (newConfig.dashboard.filters[index].order === 'asc') {
|
|
247
|
+
filterValues.sort()
|
|
248
|
+
}
|
|
249
|
+
if (newConfig.dashboard.filters[index].order === 'desc') {
|
|
250
|
+
filterValues.sort().reverse()
|
|
251
|
+
}
|
|
166
252
|
|
|
167
|
-
|
|
253
|
+
newConfig.dashboard.filters[index].values = filterValues
|
|
168
254
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
})
|
|
255
|
+
// Initial filter should be active
|
|
256
|
+
newConfig.dashboard.filters[index].active = filterValues[0]
|
|
257
|
+
})
|
|
172
258
|
|
|
173
|
-
setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)))
|
|
259
|
+
setFilteredData(filterData(newConfig.dashboard.filters, (dataOverride || data)))
|
|
174
260
|
}
|
|
175
261
|
|
|
176
262
|
//Enforce default values that need to be calculated at runtime
|
|
177
|
-
newConfig.runtime = {}
|
|
263
|
+
newConfig.runtime = {}
|
|
178
264
|
|
|
179
|
-
setConfig(newConfig)
|
|
180
|
-
}
|
|
265
|
+
setConfig(newConfig)
|
|
266
|
+
}
|
|
181
267
|
|
|
182
268
|
// Load data when component first mounts
|
|
183
269
|
useEffect(() => {
|
|
184
|
-
loadConfig()
|
|
185
|
-
}, [])
|
|
270
|
+
loadConfig(config)
|
|
271
|
+
}, [])
|
|
186
272
|
|
|
187
273
|
// Pass up to <CdcEditor /> if it exists when config state changes
|
|
188
274
|
useEffect(() => {
|
|
189
|
-
if(setParentConfig && isEditor) {
|
|
190
|
-
setParentConfig(config)
|
|
275
|
+
if (setParentConfig && isEditor) {
|
|
276
|
+
setParentConfig(config)
|
|
191
277
|
}
|
|
192
|
-
}, [config])
|
|
278
|
+
}, [ config ])
|
|
279
|
+
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
if (config && !coveLoadedHasRan && container) {
|
|
282
|
+
publish('cove_loaded', { config: config })
|
|
283
|
+
setCoveLoadedHasRan(true)
|
|
284
|
+
}
|
|
285
|
+
}, [config, container]);
|
|
193
286
|
|
|
194
287
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
195
|
-
let updatedConfig = {...config}
|
|
288
|
+
let updatedConfig = { ...config }
|
|
196
289
|
|
|
197
|
-
updatedConfig.visualizations[visualizationKey] = newConfig
|
|
198
|
-
setConfig(updatedConfig)
|
|
199
|
-
}
|
|
290
|
+
updatedConfig.visualizations[visualizationKey] = newConfig
|
|
291
|
+
setConfig(updatedConfig)
|
|
292
|
+
}
|
|
200
293
|
|
|
201
294
|
const Filters = () => {
|
|
202
295
|
const changeFilterActive = (index, value) => {
|
|
203
|
-
let dashboardConfig = {...config.dashboard}
|
|
296
|
+
let dashboardConfig = { ...config.dashboard }
|
|
204
297
|
|
|
205
|
-
dashboardConfig.filters[index].active = value
|
|
298
|
+
dashboardConfig.filters[index].active = value
|
|
206
299
|
|
|
207
|
-
setConfig({...config, dashboard: dashboardConfig})
|
|
300
|
+
setConfig({ ...config, dashboard: dashboardConfig })
|
|
208
301
|
|
|
209
|
-
setFilteredData(filterData(dashboardConfig.filters, data))
|
|
210
|
-
}
|
|
302
|
+
setFilteredData(filterData(dashboardConfig.filters, data))
|
|
303
|
+
}
|
|
211
304
|
|
|
212
305
|
const announceChange = (text) => {
|
|
213
306
|
|
|
214
|
-
}
|
|
307
|
+
}
|
|
215
308
|
|
|
216
309
|
return config.dashboard.filters.map((singleFilter, index) => {
|
|
217
|
-
const values = []
|
|
310
|
+
const values = []
|
|
218
311
|
|
|
219
312
|
singleFilter.values.forEach((filterOption, index) => {
|
|
220
313
|
values.push(<option
|
|
221
314
|
key={index}
|
|
222
315
|
value={filterOption}
|
|
223
316
|
>{filterOption}
|
|
224
|
-
</option>)
|
|
225
|
-
})
|
|
317
|
+
</option>)
|
|
318
|
+
})
|
|
226
319
|
|
|
227
320
|
return (
|
|
228
321
|
<section className="dashboard-filters-section" key={index}>
|
|
@@ -233,50 +326,52 @@ export default function CdcDashboard(
|
|
|
233
326
|
data-index="0"
|
|
234
327
|
value={singleFilter.active}
|
|
235
328
|
onChange={(val) => {
|
|
236
|
-
changeFilterActive(index, val.target.value)
|
|
237
|
-
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
329
|
+
changeFilterActive(index, val.target.value)
|
|
330
|
+
announceChange(`Filter ${singleFilter.label} value has been changed to ${val.target.value}, please reference the data table to see updated values.`)
|
|
238
331
|
}}
|
|
239
332
|
>
|
|
240
333
|
{values}
|
|
241
334
|
</select>
|
|
242
335
|
</section>
|
|
243
|
-
)
|
|
244
|
-
})
|
|
336
|
+
)
|
|
337
|
+
})
|
|
338
|
+
|
|
245
339
|
}
|
|
246
340
|
|
|
247
341
|
const resizeObserver = new ResizeObserver(entries => {
|
|
248
342
|
for (let entry of entries) {
|
|
249
|
-
|
|
343
|
+
let newViewport = getViewport(entry.contentRect.width)
|
|
250
344
|
|
|
251
|
-
|
|
345
|
+
setCurrentViewport(newViewport)
|
|
252
346
|
}
|
|
253
|
-
})
|
|
347
|
+
})
|
|
254
348
|
|
|
255
349
|
const outerContainerRef = useCallback(node => {
|
|
256
350
|
if (node !== null) {
|
|
257
|
-
|
|
351
|
+
resizeObserver.observe(node)
|
|
258
352
|
}
|
|
259
|
-
|
|
353
|
+
setContainer(node)
|
|
354
|
+
}, [])
|
|
260
355
|
|
|
261
356
|
// Prevent render if loading
|
|
262
|
-
if(loading) return <Loading
|
|
357
|
+
if (loading) return <Loading/>
|
|
263
358
|
|
|
264
359
|
let body = null
|
|
265
360
|
|
|
266
361
|
// Editor mode
|
|
267
|
-
if(isEditor && !preview) {
|
|
268
|
-
let subVisualizationEditing = false
|
|
362
|
+
if (isEditor && !preview) {
|
|
363
|
+
let subVisualizationEditing = false
|
|
269
364
|
|
|
270
365
|
Object.keys(config.visualizations).forEach(visualizationKey => {
|
|
271
|
-
let visualizationConfig = config.visualizations[visualizationKey]
|
|
366
|
+
let visualizationConfig = config.visualizations[visualizationKey]
|
|
272
367
|
|
|
273
|
-
visualizationConfig.data = filteredData || data
|
|
368
|
+
visualizationConfig.data = filteredData || data
|
|
274
369
|
|
|
275
|
-
|
|
276
|
-
subVisualizationEditing = true
|
|
370
|
+
if (visualizationConfig.editing) {
|
|
371
|
+
subVisualizationEditing = true
|
|
277
372
|
|
|
278
373
|
const back = () => {
|
|
279
|
-
const newConfig = {...config}
|
|
374
|
+
const newConfig = { ...config }
|
|
280
375
|
|
|
281
376
|
delete newConfig.visualizations[visualizationKey].editing
|
|
282
377
|
|
|
@@ -285,28 +380,37 @@ export default function CdcDashboard(
|
|
|
285
380
|
|
|
286
381
|
const updateConfig = (newConfig) => updateChildConfig(visualizationKey, newConfig)
|
|
287
382
|
|
|
288
|
-
switch(visualizationConfig.type){
|
|
383
|
+
switch (visualizationConfig.type) {
|
|
289
384
|
case 'chart':
|
|
290
|
-
body = <><Header back={back} subEditor="Chart"
|
|
291
|
-
break
|
|
292
|
-
case 'map':
|
|
293
|
-
body = <><Header back={back} subEditor="Map"
|
|
294
|
-
break
|
|
385
|
+
body = <><Header back={back} subEditor="Chart"/><CdcChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
386
|
+
break
|
|
387
|
+
case 'map':
|
|
388
|
+
body = <><Header back={back} subEditor="Map"/><CdcMap key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
389
|
+
break
|
|
295
390
|
case 'data-bite':
|
|
296
|
-
visualizationConfig = {...visualizationConfig, newViz: true}
|
|
297
|
-
body = <><Header back={back} subEditor="Data Bite"
|
|
298
|
-
break
|
|
391
|
+
visualizationConfig = { ...visualizationConfig, newViz: true }
|
|
392
|
+
body = <><Header back={back} subEditor="Data Bite"/><CdcDataBite key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
393
|
+
break
|
|
394
|
+
case 'waffle-chart':
|
|
395
|
+
body = <><Header back={back} subEditor="Waffle Chart"/><CdcWaffleChart key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
396
|
+
break
|
|
397
|
+
case 'markup-include':
|
|
398
|
+
body = <><Header back={back} subEditor="Markup Include"/><CdcMarkupInclude key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
399
|
+
break
|
|
400
|
+
case 'filtered-text':
|
|
401
|
+
body = <><Header back={back} subEditor="Filtered Text"/><FilteredText key={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true}/></>
|
|
402
|
+
break
|
|
299
403
|
}
|
|
300
404
|
}
|
|
301
|
-
})
|
|
405
|
+
})
|
|
302
406
|
|
|
303
|
-
if(!subVisualizationEditing){
|
|
407
|
+
if (!subVisualizationEditing) {
|
|
304
408
|
body = (
|
|
305
409
|
<DndProvider backend={HTML5Backend}>
|
|
306
|
-
<Header preview={preview} setPreview={setPreview}
|
|
410
|
+
<Header preview={preview} setPreview={setPreview}/>
|
|
307
411
|
<div className="layout-container">
|
|
308
|
-
<VisualizationsPanel
|
|
309
|
-
<Grid
|
|
412
|
+
<VisualizationsPanel/>
|
|
413
|
+
<Grid/>
|
|
310
414
|
</div>
|
|
311
415
|
</DndProvider>
|
|
312
416
|
)
|
|
@@ -314,42 +418,61 @@ export default function CdcDashboard(
|
|
|
314
418
|
} else {
|
|
315
419
|
body = (
|
|
316
420
|
<>
|
|
317
|
-
{isEditor && <Header preview={preview} setPreview={setPreview}
|
|
318
|
-
{isEditor && <EditorPanel
|
|
421
|
+
{isEditor && <Header preview={preview} setPreview={setPreview}/>}
|
|
422
|
+
{isEditor && <EditorPanel/>}
|
|
319
423
|
<div className="cdc-dashboard-inner-container">
|
|
320
424
|
{/* Title */}
|
|
321
425
|
{title && <div role="heading" className={`dashboard-title ${config.dashboard.theme ?? 'theme-blue'}`}>{title}</div>}
|
|
322
426
|
|
|
323
427
|
{/* Filters */}
|
|
324
|
-
{config.dashboard.filters &&
|
|
428
|
+
{config.dashboard.filters &&
|
|
429
|
+
<div className="cove-dashboard-filters">
|
|
430
|
+
<Filters />
|
|
431
|
+
</div>
|
|
432
|
+
}
|
|
325
433
|
|
|
326
434
|
{/* Visualizations */}
|
|
327
|
-
{config.rows && config.rows.map(row => {
|
|
435
|
+
{config.rows && config.rows.map((row, index) => {
|
|
328
436
|
// Empty check
|
|
329
|
-
if(row.filter(col => col.widget).length === 0) return null
|
|
437
|
+
if (row.filter(col => col.widget).length === 0) return null
|
|
330
438
|
|
|
331
439
|
return (
|
|
332
|
-
<div className="dashboard-row">
|
|
333
|
-
{row.map(col => {
|
|
334
|
-
if(col.width) {
|
|
335
|
-
if(!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
336
|
-
|
|
337
|
-
let visualizationConfig = config.visualizations[col.widget]
|
|
338
|
-
|
|
339
|
-
visualizationConfig.data = filteredData || data
|
|
340
|
-
|
|
341
|
-
return <div className={`dashboard-col dashboard-col-${col.width}`}>
|
|
342
|
-
{visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
343
|
-
|
|
344
|
-
|
|
440
|
+
<div className="dashboard-row" key={`row__${index}`}>
|
|
441
|
+
{row.map((col, index) => {
|
|
442
|
+
if (col.width) {
|
|
443
|
+
if (!col.widget) return <div className={`dashboard-col dashboard-col-${col.width}`}></div>
|
|
444
|
+
|
|
445
|
+
let visualizationConfig = config.visualizations[col.widget]
|
|
446
|
+
|
|
447
|
+
visualizationConfig.data = filteredData || data
|
|
448
|
+
|
|
449
|
+
return <div className={`dashboard-col dashboard-col-${col.width}`} key={`vis__${index}`}>
|
|
450
|
+
{visualizationConfig.type === 'chart' && <CdcChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
451
|
+
updateChildConfig(col.widget, newConfig)
|
|
452
|
+
}} isDashboard={true}/>}
|
|
453
|
+
{visualizationConfig.type === 'map' && <CdcMap key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
454
|
+
updateChildConfig(col.widget, newConfig)
|
|
455
|
+
}} isDashboard={true}/>}
|
|
456
|
+
{visualizationConfig.type === 'data-bite' && <CdcDataBite key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
457
|
+
updateChildConfig(col.widget, newConfig)
|
|
458
|
+
}} isDashboard={true}/>}
|
|
459
|
+
{visualizationConfig.type === 'waffle-chart' && <CdcWaffleChart key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
460
|
+
updateChildConfig(col.widget, newConfig)
|
|
461
|
+
}} isDashboard={true}/>}
|
|
462
|
+
{visualizationConfig.type === 'markup-include' && <CdcMarkupInclude key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
463
|
+
updateChildConfig(col.widget, newConfig)
|
|
464
|
+
}} isDashboard={true}/>}
|
|
465
|
+
{visualizationConfig.type === 'filtered-text' && <FilteredText key={col.widget} config={visualizationConfig} isEditor={false} setConfig={(newConfig) => {
|
|
466
|
+
updateChildConfig(col.widget, newConfig)
|
|
467
|
+
}} isDashboard={true}/>}
|
|
345
468
|
</div>
|
|
346
469
|
}
|
|
347
470
|
})}
|
|
348
|
-
</div>)
|
|
471
|
+
</div>)
|
|
349
472
|
})}
|
|
350
473
|
|
|
351
474
|
{/* Data Table */}
|
|
352
|
-
{config.table.show && <DataTable
|
|
475
|
+
{config.table.show && <DataTable/>}
|
|
353
476
|
|
|
354
477
|
{/* Description */}
|
|
355
478
|
{description && <div className="subtext">{parse(description)}</div>}
|
|
@@ -369,12 +492,12 @@ export default function CdcDashboard(
|
|
|
369
492
|
setParentConfig,
|
|
370
493
|
setPreview
|
|
371
494
|
}
|
|
372
|
-
|
|
495
|
+
|
|
373
496
|
return (
|
|
374
497
|
<Context.Provider value={contextValues}>
|
|
375
498
|
<div className={`cdc-open-viz-module type-dashboard ${currentViewport}`} ref={outerContainerRef}>
|
|
376
499
|
{body}
|
|
377
500
|
</div>
|
|
378
501
|
</Context.Provider>
|
|
379
|
-
)
|
|
502
|
+
)
|
|
380
503
|
}
|