@cdc/dashboard 4.24.5 → 4.24.9-1
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 +147572 -128223
- package/examples/custom/css/respiratory.css +236 -0
- package/examples/custom/js/respiratory.js +242 -0
- package/examples/default-multi-dataset-shared-filter.json +1729 -0
- package/examples/ed-visits-county-file.json +618 -0
- package/examples/filtered-dash.json +6 -21
- package/examples/single-state-dashboard-filters.json +421 -0
- package/examples/state-level.json +90136 -0
- package/examples/state-points.json +10474 -0
- package/examples/test-file.json +147 -0
- package/examples/testing.json +94456 -0
- package/index.html +25 -4
- package/package.json +12 -11
- package/src/CdcDashboard.tsx +5 -1
- package/src/CdcDashboardComponent.tsx +250 -327
- package/src/DashboardContext.tsx +15 -1
- package/src/_stories/Dashboard.stories.tsx +158 -40
- package/src/_stories/_mock/api-filter-chart.json +11 -35
- package/src/_stories/_mock/api-filter-map.json +17 -31
- package/src/_stories/_mock/bump-chart.json +3554 -0
- package/src/_stories/_mock/methodology.json +412 -0
- package/src/_stories/_mock/methodologyAPI.ts +90 -0
- package/src/_stories/_mock/multi-viz.json +3 -4
- package/src/_stories/_mock/pivot-filter.json +14 -12
- package/src/_stories/_mock/single-state-dashboard-filters.json +390 -0
- package/src/components/CollapsibleVisualizationRow.tsx +44 -0
- package/src/components/Column.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFilters.tsx +102 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +218 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +48 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +477 -0
- package/src/components/DashboardFilters/DashboardFiltersEditor/index.ts +1 -0
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +191 -0
- package/src/components/DashboardFilters/index.ts +3 -0
- package/src/components/DataDesignerModal.tsx +9 -9
- package/src/components/ExpandCollapseButtons.tsx +20 -0
- package/src/components/Header/Header.tsx +1 -102
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +24 -12
- package/src/components/Row.tsx +52 -19
- package/src/components/Toggle/Toggle.tsx +2 -4
- package/src/components/VisualizationRow.tsx +169 -30
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +116 -0
- package/src/components/VisualizationsPanel/index.ts +1 -0
- package/src/components/VisualizationsPanel/visualizations-panel-styles.css +12 -0
- package/src/components/Widget.tsx +27 -90
- package/src/helpers/FilterBehavior.ts +4 -0
- package/src/helpers/addValuesToDashboardFilters.ts +49 -0
- package/src/helpers/apiFilterHelpers.ts +103 -0
- package/src/helpers/changeFilterActive.ts +39 -0
- package/src/helpers/filterData.ts +10 -48
- package/src/helpers/generateValuesForFilter.ts +1 -1
- package/src/helpers/getAutoLoadVisualization.ts +11 -0
- package/src/helpers/getFilteredData.ts +7 -5
- package/src/helpers/getVizConfig.ts +23 -2
- package/src/helpers/getVizRowColumnLocator.ts +2 -1
- package/src/helpers/hasDashboardApplyBehavior.ts +5 -0
- package/src/helpers/iconHash.tsx +5 -3
- package/src/helpers/loadAPIFilters.ts +74 -0
- package/src/helpers/mapDataToConfig.ts +29 -0
- package/src/helpers/processData.ts +2 -3
- package/src/helpers/reloadURLHelpers.ts +102 -0
- package/src/helpers/tests/addValuesToDashboardFilters.test.ts +44 -0
- package/src/helpers/tests/apiFilterHelpers.test.ts +155 -0
- package/src/helpers/tests/filterData.test.ts +1 -93
- package/src/helpers/tests/getFilteredData.test.ts +86 -0
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +220 -0
- package/src/helpers/tests/reloadURLHelpers.test.ts +232 -0
- package/src/scss/editor-panel.scss +1 -1
- package/src/scss/grid.scss +34 -27
- package/src/scss/main.scss +41 -3
- package/src/scss/variables.scss +4 -0
- package/src/store/dashboard.actions.ts +12 -4
- package/src/store/dashboard.reducer.ts +30 -4
- package/src/types/APIFilter.ts +1 -5
- package/src/types/ConfigRow.ts +2 -0
- package/src/types/Dashboard.ts +1 -1
- package/src/types/DashboardConfig.ts +2 -4
- package/src/types/DashboardFilters.ts +7 -0
- package/src/types/InitialState.ts +1 -1
- package/src/types/MultiDashboard.ts +2 -2
- package/src/types/SharedFilter.ts +4 -6
- package/src/types/Tab.ts +1 -1
- package/src/components/Filters.tsx +0 -88
- package/src/components/Header/FilterModal.tsx +0 -510
- package/src/components/VisualizationsPanel.tsx +0 -95
- package/src/helpers/getApiFilterKey.ts +0 -5
|
@@ -18,7 +18,6 @@ import OverlayFrame from '@cdc/core/components/ui/OverlayFrame'
|
|
|
18
18
|
import Loading from '@cdc/core/components/Loading'
|
|
19
19
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
20
20
|
import getViewport from '@cdc/core/helpers/getViewport'
|
|
21
|
-
import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
|
|
22
21
|
|
|
23
22
|
import CdcMap from '@cdc/map'
|
|
24
23
|
import CdcChart from '@cdc/chart'
|
|
@@ -28,14 +27,12 @@ import CdcMarkupInclude from '@cdc/markup-include'
|
|
|
28
27
|
import CdcFilteredText from '@cdc/filtered-text'
|
|
29
28
|
|
|
30
29
|
import Grid from './components/Grid'
|
|
31
|
-
import Header
|
|
30
|
+
import Header from './components/Header/Header'
|
|
32
31
|
import DataTable from '@cdc/core/components/DataTable'
|
|
33
32
|
import MediaControls from '@cdc/core/components/MediaControls'
|
|
34
33
|
|
|
35
34
|
import './scss/main.scss'
|
|
36
35
|
import '@cdc/core/styles/v2/main.scss'
|
|
37
|
-
import { gatherQueryParams } from '@cdc/core/helpers/gatherQueryParams'
|
|
38
|
-
import { capitalizeSplitAndJoin } from '@cdc/core/helpers/cove/string'
|
|
39
36
|
|
|
40
37
|
import VisualizationsPanel from './components/VisualizationsPanel'
|
|
41
38
|
import dashboardReducer from './store/dashboard.reducer'
|
|
@@ -47,14 +44,12 @@ import { type TableConfig } from '@cdc/core/components/DataTable/types/TableConf
|
|
|
47
44
|
// types
|
|
48
45
|
import { type SharedFilter } from './types/SharedFilter'
|
|
49
46
|
import { type APIFilter } from './types/APIFilter'
|
|
50
|
-
import { type Visualization } from '@cdc/core/types/Visualization'
|
|
51
47
|
import { type WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
52
48
|
import { type InitialState } from './types/InitialState'
|
|
53
49
|
import MultiTabs from './components/MultiConfigTabs'
|
|
54
50
|
import _ from 'lodash'
|
|
55
51
|
import EditorContext from '../../editor/src/ConfigContext'
|
|
56
|
-
import {
|
|
57
|
-
import Filters, { APIFilterDropdowns, DropdownOptions } from './components/Filters'
|
|
52
|
+
import { APIFilterDropdowns, DropdownOptions } from './components/DashboardFilters'
|
|
58
53
|
import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
|
|
59
54
|
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
60
55
|
import VisualizationRow from './components/VisualizationRow'
|
|
@@ -62,6 +57,14 @@ import { getVizConfig } from './helpers/getVizConfig'
|
|
|
62
57
|
import { getFilteredData } from './helpers/getFilteredData'
|
|
63
58
|
import { getVizRowColumnLocator } from './helpers/getVizRowColumnLocator'
|
|
64
59
|
import Layout from '@cdc/core/components/Layout'
|
|
60
|
+
import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAlone'
|
|
61
|
+
import * as reloadURLHelpers from './helpers/reloadURLHelpers'
|
|
62
|
+
import { addValuesToDashboardFilters } from './helpers/addValuesToDashboardFilters'
|
|
63
|
+
import { DashboardFilters } from './types/DashboardFilters'
|
|
64
|
+
import DashboardSharedFilters from './components/DashboardFilters'
|
|
65
|
+
import ExpandCollapseButtons from './components/ExpandCollapseButtons'
|
|
66
|
+
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
67
|
+
import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
|
|
65
68
|
|
|
66
69
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
67
70
|
initialState: InitialState
|
|
@@ -73,13 +76,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
73
76
|
const [apiFilterDropdowns, setAPIFilterDropdowns] = useState<APIFilterDropdowns>({})
|
|
74
77
|
const [currentViewport, setCurrentViewport] = useState<ViewPort>('lg')
|
|
75
78
|
const [imageId] = useState(`cove-${Math.random().toString(16).slice(-4)}`)
|
|
79
|
+
const [allExpanded, setAllExpanded] = useState(true)
|
|
76
80
|
|
|
77
81
|
const isPreview = state.tabSelected === 'Dashboard Preview'
|
|
78
|
-
const replacements = {
|
|
79
|
-
'Remove Spaces': '',
|
|
80
|
-
'Keep Spaces': ' ',
|
|
81
|
-
'Replace With Underscore': '_'
|
|
82
|
-
}
|
|
83
82
|
|
|
84
83
|
const inNoDataState = useMemo(() => {
|
|
85
84
|
const vals = Object.values(state.data)
|
|
@@ -89,121 +88,42 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
89
88
|
|
|
90
89
|
const vizRowColumnLocator = getVizRowColumnLocator(state.config.rows)
|
|
91
90
|
|
|
92
|
-
const getAutoLoadVisualization = (): Visualization | undefined => {
|
|
93
|
-
const autoLoadViz = Object.values(state.config.visualizations).filter(vis => {
|
|
94
|
-
return vis.autoLoad && vis.type === 'filter-dropdowns'
|
|
95
|
-
})
|
|
96
|
-
if (autoLoadViz.length === 0) return
|
|
97
|
-
if (autoLoadViz.length > 1) throw new Error('Only one filter row can be autoloaded')
|
|
98
|
-
return autoLoadViz[0]
|
|
99
|
-
}
|
|
100
|
-
|
|
101
91
|
const transform = new DataTransform()
|
|
102
92
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// we don't want to auto load it
|
|
109
|
-
return
|
|
110
|
-
} else {
|
|
111
|
-
const sharedFilter = state.config.dashboard.sharedFilters[sharedFilterIndex]
|
|
112
|
-
if (sharedFilter.active) return // a value has already been selected.
|
|
113
|
-
const filterParents = state.config.dashboard.sharedFilters.filter(f => sharedFilter.parents?.includes(f.key))
|
|
114
|
-
const notAllParentFiltersSelected = filterParents.some(p => !p.active)
|
|
115
|
-
if (filterParents && notAllParentFiltersSelected) return
|
|
116
|
-
const defaultFilterDropdown = filterDropdowns.find(({ value }) => value === sharedFilter.apiFilter!.defaultValue)
|
|
117
|
-
let defaultValue = defaultFilterDropdown?.value || filterDropdowns[0].value
|
|
118
|
-
changeFilterActive(sharedFilterIndex, defaultValue)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
93
|
+
const autoLoadFilterIndexes = useMemo(() => {
|
|
94
|
+
return Object.values(state.config.visualizations)
|
|
95
|
+
.filter(v => v.type === 'dashboardFilters')
|
|
96
|
+
.reduce((acc, viz: DashboardFilters) => (viz.autoLoad ? [...acc, ...viz.sharedFilterIndexes] : acc), [])
|
|
97
|
+
}, [state.config.visualizations])
|
|
121
98
|
|
|
122
|
-
const loadAPIFilters =
|
|
123
|
-
if (state.config.dashboard.sharedFilters) {
|
|
124
|
-
const sharedAPIFilters = state.config.dashboard.sharedFilters.filter(f => f.apiFilter)
|
|
125
|
-
const loadingFilterMemo: APIFilterDropdowns = sharedAPIFilters.reduce((acc, curr) => {
|
|
126
|
-
const _key = getApiFilterKey(curr.apiFilter!)
|
|
127
|
-
if (apiFilterDropdowns[_key] != null) return acc // don't overwrite fetched data.
|
|
128
|
-
acc[_key] = null
|
|
129
|
-
return acc
|
|
130
|
-
}, {})
|
|
131
|
-
setAPIFilterDropdowns({ ...apiFilterDropdowns, ...loadingFilterMemo })
|
|
132
|
-
const filterLookup = new Map(sharedAPIFilters.map(filter => [getApiFilterKey(filter.apiFilter!), filter.apiFilter!]))
|
|
133
|
-
const getParentParams = (childFilter: SharedFilter): Record<'key' | 'value', string>[] | null => {
|
|
134
|
-
const _parents = sharedAPIFilters.filter(parentFilter => childFilter.parents?.includes(parentFilter.key))
|
|
135
|
-
if (!_parents.length) return null
|
|
136
|
-
return _parents.map(({ queryParameter, queuedActive }) => ({ key: queryParameter || '', value: queuedActive || '' }))
|
|
137
|
-
}
|
|
138
|
-
const getFilterValues = (data: Object | Array<Object>, apiFilter: APIFilter): DropdownOptions => {
|
|
139
|
-
const { textSelector, valueSelector, heirarchyLookup } = apiFilter
|
|
140
|
-
if (heirarchyLookup) {
|
|
141
|
-
const heirarchy = heirarchyLookup!.split('.')
|
|
142
|
-
const selector = heirarchy.shift() // pop first element
|
|
143
|
-
return getFilterValues(selector ? data[selector] : data, { ...apiFilter, heirarchyLookup: heirarchy.join('.') })
|
|
144
|
-
}
|
|
145
|
-
if (!Array.isArray(data)) throw new Error('the filter data has requires a heirarchy path to access the filter values, This should be in the format key.subkey.subsubkey')
|
|
146
|
-
return data.map(v => ({ text: v[textSelector], value: v[valueSelector] }))
|
|
147
|
-
}
|
|
148
|
-
state.config.dashboard.sharedFilters.forEach(async (filter, index) => {
|
|
149
|
-
if (!filter.apiFilter) return
|
|
150
|
-
const baseEndpoint = filter.apiFilter.apiEndpoint
|
|
151
|
-
const _key = getApiFilterKey(filter.apiFilter)
|
|
152
|
-
const params = getParentParams(filter)
|
|
153
|
-
const notAllParentsSelected = params?.some(({ value }) => value === '')
|
|
154
|
-
if (notAllParentsSelected) return // don't send request for dependent children filter options
|
|
155
|
-
if (apiFilterDropdowns[_key] && !params && filter.filterBy === 'Query String') return // don't reload filter unless it's a child
|
|
156
|
-
const endpoint = baseEndpoint + (params ? gatherQueryParams(params) : '')
|
|
157
|
-
|
|
158
|
-
fetch(endpoint)
|
|
159
|
-
.then(resp => resp.json())
|
|
160
|
-
.then(data => {
|
|
161
|
-
const apiFilter = filterLookup.get(_key) as APIFilter
|
|
162
|
-
const _filterValues = getFilterValues(data, apiFilter)
|
|
163
|
-
setAPIFilterDropdowns(dropdowns => ({ ...dropdowns, [_key]: _filterValues }))
|
|
164
|
-
setAutoLoadDefaultValue(index, _filterValues)
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
}
|
|
99
|
+
const loadAPIFilters = loadAPIFiltersFactory(dispatch, setAPIFilterDropdowns, autoLoadFilterIndexes)
|
|
169
100
|
|
|
170
|
-
const reloadURLData = async () => {
|
|
171
|
-
const
|
|
101
|
+
const reloadURLData = async (newFilters?: SharedFilter[]) => {
|
|
102
|
+
const config = _.cloneDeep(state.config)
|
|
172
103
|
if (!config.datasets) return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
104
|
+
const filters = newFilters || config.dashboard.sharedFilters
|
|
105
|
+
const datasetKeys = Object.keys(config.datasets)
|
|
106
|
+
|
|
107
|
+
const newData = _.cloneDeep(state.data)
|
|
108
|
+
const newDatasets = _.cloneDeep(config.datasets)
|
|
109
|
+
let dataWasFetched = false
|
|
177
110
|
let newFileName = ''
|
|
178
111
|
|
|
179
112
|
for (let i = 0; i < datasetKeys.length; i++) {
|
|
180
113
|
const datasetKey = datasetKeys[i]
|
|
181
114
|
const dataset = config.datasets[datasetKey]
|
|
182
|
-
|
|
115
|
+
|
|
183
116
|
if (dataset.dataUrl && filters) {
|
|
184
117
|
const dataUrl = new URL(dataset.runtimeDataUrl || dataset.dataUrl, window.location.origin)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let isUpdateNeeded = false
|
|
188
|
-
|
|
118
|
+
const currentQSParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
|
|
119
|
+
const updatedQSParams = {}
|
|
189
120
|
filters.forEach(filter => {
|
|
190
|
-
|
|
191
|
-
|
|
121
|
+
if (
|
|
122
|
+
filter.type === 'urlfilter' &&
|
|
123
|
+
reloadURLHelpers.filterUsedByDataUrl(filter, datasetKey, config.visualizations)
|
|
124
|
+
) {
|
|
192
125
|
if (filter.filterBy === 'File Name') {
|
|
193
|
-
|
|
194
|
-
if (filter.datasetKey === datasetKey) {
|
|
195
|
-
if (filter.fileName) {
|
|
196
|
-
// if a file name is found, ie, state_${query}, use that, ie. state_activeFilter.json
|
|
197
|
-
newFileName = capitalizeSplitAndJoin.call(String(filter.fileName), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces'])
|
|
198
|
-
} else {
|
|
199
|
-
// if no file name is entered use the default active filter. ie. /activeFilter.json
|
|
200
|
-
newFileName = filter.active
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (newFileName?.includes('${query}')) {
|
|
205
|
-
newFileName = newFileName.replace('${query}', capitalizeSplitAndJoin.call(String(filter.active), ' ', replacements[filter.whitespaceReplacement ?? 'Keep Spaces']))
|
|
206
|
-
}
|
|
126
|
+
newFileName = reloadURLHelpers.getNewFileName(newFileName, filter, datasetKey)
|
|
207
127
|
}
|
|
208
128
|
|
|
209
129
|
if (!!filter.queryParameter) {
|
|
@@ -213,63 +133,53 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
213
133
|
updatedQSParams[filter.queryParameter] = filter.active
|
|
214
134
|
}
|
|
215
135
|
}
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
136
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
137
|
+
if (filter.apiFilter) {
|
|
138
|
+
updatedQSParams[filter.apiFilter.valueSelector] = filter.active
|
|
139
|
+
}
|
|
222
140
|
}
|
|
223
141
|
})
|
|
224
142
|
|
|
225
|
-
if (isUpdateNeeded) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
143
|
+
if (!!newFilters || reloadURLHelpers.isUpdateNeeded(filters, currentQSParams, updatedQSParams)) {
|
|
144
|
+
dataWasFetched = true
|
|
145
|
+
const dataUrlFinal = reloadURLHelpers.getDataURL(
|
|
146
|
+
{ ...currentQSParams, ...updatedQSParams },
|
|
147
|
+
dataUrl,
|
|
148
|
+
newFileName
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
await fetchRemoteData(dataUrlFinal).then(responseData => {
|
|
152
|
+
let data: any[] = responseData
|
|
153
|
+
if (responseData && dataset.dataDescription) {
|
|
154
|
+
try {
|
|
155
|
+
data = transform.autoStandardize(data)
|
|
156
|
+
data = transform.developerStandardize(data, dataset.dataDescription)
|
|
157
|
+
} catch (e) {
|
|
158
|
+
//Data not able to be standardized, leave as is
|
|
159
|
+
}
|
|
230
160
|
}
|
|
161
|
+
newDatasets[datasetKey].data = data
|
|
162
|
+
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
163
|
+
newData[datasetKey] = data
|
|
231
164
|
})
|
|
232
|
-
const _params = Object.keys(updatedQSParams).map(key => ({ key, value: updatedQSParams[key] }))
|
|
233
|
-
let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${gatherQueryParams(_params)}`
|
|
234
|
-
|
|
235
|
-
if (newFileName !== '') {
|
|
236
|
-
let fileExtension = dataUrl.pathname.split('.').pop()
|
|
237
|
-
let pathWithoutFilename = dataUrl.pathname.substring(0, dataUrl.pathname.lastIndexOf('/'))
|
|
238
|
-
dataUrlFinal = `${dataUrl.origin}${pathWithoutFilename}/${newFileName}.${fileExtension}${gatherQueryParams(_params)}`
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
let newDataset = await fetchRemoteData(`${dataUrlFinal}`)
|
|
242
|
-
|
|
243
|
-
if (newDataset && dataset.dataDescription) {
|
|
244
|
-
try {
|
|
245
|
-
newDataset = transform.autoStandardize(newDataset)
|
|
246
|
-
newDataset = transform.developerStandardize(newDataset, dataset.dataDescription)
|
|
247
|
-
} catch (e) {
|
|
248
|
-
//Data not able to be standardized, leave as is
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
newDatasets[datasetKey].data = newDataset
|
|
252
|
-
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
253
|
-
newData[datasetKey] = newDataset
|
|
254
165
|
}
|
|
255
166
|
}
|
|
256
167
|
}
|
|
257
168
|
|
|
258
|
-
if (
|
|
169
|
+
if (dataWasFetched) {
|
|
259
170
|
dispatch({ type: 'SET_DATA', payload: newData })
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
dispatch({ type: '
|
|
272
|
-
dispatch({ type: 'SET_CONFIG', payload: { ...config, datasets: newDatasets, visualizations } })
|
|
171
|
+
const filtersWithNewValues = addValuesToDashboardFilters(filters, newData)
|
|
172
|
+
const dashboardConfig = newFilters
|
|
173
|
+
? { ...config.dashboard, sharedFilters: filtersWithNewValues }
|
|
174
|
+
: config.dashboard
|
|
175
|
+
const filteredData = getFilteredData(
|
|
176
|
+
{ ...state, config: { ...state.config, dashboard: dashboardConfig } },
|
|
177
|
+
{},
|
|
178
|
+
newData
|
|
179
|
+
)
|
|
180
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: filteredData })
|
|
181
|
+
const visualizations = reloadURLHelpers.getVisualizationsWithFormattedData(config.visualizations, newData)
|
|
182
|
+
dispatch({ type: 'SET_CONFIG', payload: { dashboard: dashboardConfig, datasets: newDatasets, visualizations } })
|
|
273
183
|
}
|
|
274
184
|
}
|
|
275
185
|
|
|
@@ -284,8 +194,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
284
194
|
}
|
|
285
195
|
|
|
286
196
|
const setSharedFilter = (key, datum) => {
|
|
287
|
-
const { config } = state
|
|
288
|
-
let newConfig = { ...config }
|
|
197
|
+
const { config: newConfig, filteredData } = _.cloneDeep(state)
|
|
289
198
|
|
|
290
199
|
for (let i = 0; i < newConfig.dashboard.sharedFilters.length; i++) {
|
|
291
200
|
const filter = newConfig.dashboard.sharedFilters[i]
|
|
@@ -297,32 +206,36 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
297
206
|
}
|
|
298
207
|
}
|
|
299
208
|
|
|
300
|
-
const newFilteredData = getFilteredData(state,
|
|
209
|
+
const newFilteredData = getFilteredData({ ...state, config: newConfig }, filteredData)
|
|
301
210
|
|
|
302
211
|
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
303
212
|
dispatch({ type: 'SET_CONFIG', payload: newConfig })
|
|
213
|
+
dispatch({ type: 'SET_SHARED_FILTERS', payload: newConfig.dashboard.sharedFilters })
|
|
304
214
|
}
|
|
305
215
|
|
|
306
216
|
useEffect(() => {
|
|
307
|
-
if (
|
|
217
|
+
if (isEditor && !isPreview) return
|
|
308
218
|
const { config } = state
|
|
309
|
-
if (config.
|
|
219
|
+
if (!hasDashboardApplyBehavior(config.visualizations)) {
|
|
310
220
|
reloadURLData()
|
|
311
221
|
}
|
|
312
|
-
|
|
313
|
-
|
|
222
|
+
|
|
223
|
+
const sharedFiltersWithValues = addValuesToDashboardFilters(config.dashboard.sharedFilters, state.data)
|
|
224
|
+
loadAPIFilters(sharedFiltersWithValues, apiFilterDropdowns)
|
|
225
|
+
updateFilteredData(sharedFiltersWithValues)
|
|
226
|
+
}, [isEditor, isPreview, state.config?.activeDashboard])
|
|
314
227
|
|
|
315
228
|
const updateChildConfig = (visualizationKey, newConfig) => {
|
|
316
|
-
const
|
|
317
|
-
|
|
229
|
+
const config = _.cloneDeep(state.config)
|
|
230
|
+
const updatedConfig = _.pick(config, ['visualizations', 'multiDashboards'])
|
|
318
231
|
updatedConfig.visualizations[visualizationKey] = newConfig
|
|
319
232
|
updatedConfig.visualizations[visualizationKey].formattedData = config.visualizations[visualizationKey].formattedData
|
|
320
233
|
if (config.multiDashboards) {
|
|
321
234
|
const activeDashboard = config.activeDashboard
|
|
322
235
|
const multiDashboards = [...config.multiDashboards]
|
|
323
236
|
const label = multiDashboards[activeDashboard].label
|
|
324
|
-
const toSave = _.pick(
|
|
325
|
-
multiDashboards[activeDashboard] =
|
|
237
|
+
const toSave = { label, visualizations: updatedConfig.visualizations, ..._.pick(config, ['dashboard', 'rows']) }
|
|
238
|
+
multiDashboards[activeDashboard] = toSave
|
|
326
239
|
updatedConfig.multiDashboards = multiDashboards
|
|
327
240
|
}
|
|
328
241
|
|
|
@@ -333,103 +246,11 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
333
246
|
}
|
|
334
247
|
}
|
|
335
248
|
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const queryParams = getQueryParams()
|
|
342
|
-
let needsQueryUpdate = false
|
|
343
|
-
state.config.dashboard.sharedFilters.forEach((sharedFilter, index) => {
|
|
344
|
-
if (sharedFilter.queuedActive) {
|
|
345
|
-
dashboardConfig.sharedFilters[index].active = sharedFilter.queuedActive
|
|
346
|
-
delete dashboardConfig.sharedFilters[index].queuedActive
|
|
347
|
-
|
|
348
|
-
if (sharedFilter.setByQueryParameter && queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active) {
|
|
349
|
-
queryParams[sharedFilter.setByQueryParameter] = sharedFilter.active
|
|
350
|
-
needsQueryUpdate = true
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
if (needsQueryUpdate) {
|
|
356
|
-
updateQueryString(queryParams)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
dispatch({ type: 'SET_CONFIG', payload: { ...state.config, dashboard: dashboardConfig } })
|
|
361
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(state) })
|
|
362
|
-
reloadURLData()
|
|
363
|
-
} else {
|
|
364
|
-
// TODO noftify of required fields
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const changeFilterActive = (index: number, value: string | string[]) => {
|
|
369
|
-
const { config } = state
|
|
370
|
-
let dashboardConfig = { ...config.dashboard }
|
|
371
|
-
let filterActive = dashboardConfig.sharedFilters[index]
|
|
372
|
-
|
|
373
|
-
if (config.filterBehavior !== FilterBehavior.Apply) {
|
|
374
|
-
dashboardConfig.sharedFilters[index].active = value
|
|
375
|
-
|
|
376
|
-
const queryParams = getQueryParams()
|
|
377
|
-
if (filterActive.setByQueryParameter && queryParams[filterActive.setByQueryParameter] !== filterActive.active) {
|
|
378
|
-
queryParams[filterActive.setByQueryParameter] = filterActive.active
|
|
379
|
-
updateQueryString(queryParams)
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
if (Array.isArray(value)) throw Error(`Cannot set active values on urlfilters. expected: ${JSON.stringify(value)} to be a single value.`)
|
|
383
|
-
dashboardConfig.sharedFilters[index].queuedActive = value
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
dispatch({ type: 'SET_CONFIG', payload: { ...config, dashboard: dashboardConfig } })
|
|
387
|
-
if (config.filterBehavior !== FilterBehavior.Apply) {
|
|
388
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(state) })
|
|
389
|
-
reloadURLData()
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const handleOnChange = (index: number, value: string | string[]) => {
|
|
394
|
-
const { config } = state
|
|
395
|
-
changeFilterActive(index, value)
|
|
396
|
-
if (config.filterBehavior === FilterBehavior.Apply) {
|
|
397
|
-
const autoLoadViz = getAutoLoadVisualization()
|
|
398
|
-
if (!autoLoadViz) return // nothing left to do for regular filter behavior.
|
|
399
|
-
const isAutoSelectFilter = !autoLoadViz.hide.includes(index)
|
|
400
|
-
const missingFilterSelections = config.dashboard.sharedFilters.some(f => !f.active)
|
|
401
|
-
if (isAutoSelectFilter && !missingFilterSelections) {
|
|
402
|
-
// a dropdown has been selected that doesn't
|
|
403
|
-
// require the Go Button
|
|
404
|
-
reloadURLData()
|
|
405
|
-
} else {
|
|
406
|
-
// A parent filter was selected, reset filters by:
|
|
407
|
-
// set auto select filter dropdowns to null
|
|
408
|
-
const autoSelectFilters = config.dashboard.sharedFilters.filter((_, _index) => !autoLoadViz?.hide.includes(_index))
|
|
409
|
-
const dropdownFilterKeys = autoSelectFilters.map(filter => getApiFilterKey(filter.apiFilter!))
|
|
410
|
-
const newApiDropdowns = { ...apiFilterDropdowns }
|
|
411
|
-
dropdownFilterKeys.forEach(key => (newApiDropdowns[key] = null))
|
|
412
|
-
setAPIFilterDropdowns(newApiDropdowns)
|
|
413
|
-
// remove active from sharedFilters that are autoLoading
|
|
414
|
-
const dashboardConfig = { ...config.dashboard }
|
|
415
|
-
if (config.filterBehavior !== FilterBehavior.Apply) {
|
|
416
|
-
dashboardConfig.sharedFilters[index].active = value
|
|
417
|
-
} else {
|
|
418
|
-
if (Array.isArray(value)) throw Error(`Cannot set active values on urlfilters. expected: ${JSON.stringify(value)} to be a single value.`)
|
|
419
|
-
dashboardConfig.sharedFilters[index].queuedActive = value
|
|
420
|
-
}
|
|
421
|
-
const newSharedFilters = config.dashboard.sharedFilters.map((filter, _index) => {
|
|
422
|
-
const _isAutoSelectFilter = !autoLoadViz?.hide.includes(_index)
|
|
423
|
-
if (_isAutoSelectFilter) filter.active = ''
|
|
424
|
-
return filter
|
|
425
|
-
})
|
|
426
|
-
const _newConfig = { ...config, dashboard: { ...config.dashboard, sharedFilters: newSharedFilters } }
|
|
427
|
-
dispatch({ type: 'SET_CONFIG', payload: _newConfig })
|
|
428
|
-
// setData to empty object because we no longer have a data state.
|
|
429
|
-
dispatch({ type: 'SET_DATA', payload: {} })
|
|
430
|
-
dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
|
|
431
|
-
}
|
|
432
|
-
}
|
|
249
|
+
const updateFilteredData = (sharedFilters = undefined) => {
|
|
250
|
+
const clonedState = _.cloneDeep(state)
|
|
251
|
+
if (sharedFilters) clonedState.config.dashboard.sharedFilters = sharedFilters
|
|
252
|
+
const newFilteredData = getFilteredData(clonedState)
|
|
253
|
+
dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
|
|
433
254
|
}
|
|
434
255
|
|
|
435
256
|
const resizeObserver = new ResizeObserver(entries => {
|
|
@@ -446,18 +267,9 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
446
267
|
}
|
|
447
268
|
}, [])
|
|
448
269
|
|
|
449
|
-
const setPreview = shouldPreview => dispatch({ type: 'SET_PREVIEW', payload: shouldPreview })
|
|
450
|
-
|
|
451
270
|
// Prevent render if loading
|
|
452
271
|
if (state.loading) return <Loading />
|
|
453
272
|
|
|
454
|
-
const GoButton = ({ autoLoad }: { autoLoad?: boolean }) => {
|
|
455
|
-
if (state.config.filterBehavior === FilterBehavior.Apply && !autoLoad) {
|
|
456
|
-
return <button onClick={applyFilters}>GO!</button>
|
|
457
|
-
}
|
|
458
|
-
return null
|
|
459
|
-
}
|
|
460
|
-
|
|
461
273
|
let body: JSX.Element | null = null
|
|
462
274
|
// Editor mode
|
|
463
275
|
if (isEditor && !isPreview) {
|
|
@@ -465,16 +277,29 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
465
277
|
|
|
466
278
|
getVizKeys(state.config).forEach(visualizationKey => {
|
|
467
279
|
const rowNumber = vizRowColumnLocator[visualizationKey]?.row
|
|
468
|
-
const visualizationConfig = getVizConfig(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
280
|
+
const visualizationConfig = getVizConfig(
|
|
281
|
+
visualizationKey,
|
|
282
|
+
rowNumber,
|
|
283
|
+
state.config,
|
|
284
|
+
state.data,
|
|
285
|
+
state.filteredData
|
|
286
|
+
)
|
|
287
|
+
visualizationConfig.uid = visualizationKey
|
|
288
|
+
if (visualizationConfig.type === 'footnotes') visualizationConfig.formattedData = undefined
|
|
289
|
+
const setsSharedFilter =
|
|
290
|
+
state.config.dashboard.sharedFilters &&
|
|
291
|
+
state.config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === visualizationKey).length > 0
|
|
292
|
+
const setSharedFilterValue = setsSharedFilter
|
|
293
|
+
? state.config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === visualizationKey)[0].active
|
|
294
|
+
: undefined
|
|
472
295
|
|
|
473
296
|
if (visualizationConfig.editing) {
|
|
474
297
|
subVisualizationEditing = true
|
|
475
298
|
|
|
476
299
|
const _updateConfig = newConfig => {
|
|
477
|
-
let dataCorrectedConfig = visualizationConfig.originalFormattedData
|
|
300
|
+
let dataCorrectedConfig = visualizationConfig.originalFormattedData
|
|
301
|
+
? { ...newConfig, formattedData: visualizationConfig.originalFormattedData }
|
|
302
|
+
: newConfig
|
|
478
303
|
updateChildConfig(visualizationKey, dataCorrectedConfig)
|
|
479
304
|
}
|
|
480
305
|
|
|
@@ -524,7 +349,13 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
524
349
|
body = (
|
|
525
350
|
<>
|
|
526
351
|
<Header visualizationKey={visualizationKey} subEditor='Data Bite' />
|
|
527
|
-
<CdcDataBite
|
|
352
|
+
<CdcDataBite
|
|
353
|
+
key={visualizationKey}
|
|
354
|
+
config={{ ...visualizationConfig, newViz: true }}
|
|
355
|
+
isEditor={true}
|
|
356
|
+
setConfig={_updateConfig}
|
|
357
|
+
isDashboard={true}
|
|
358
|
+
/>
|
|
528
359
|
</>
|
|
529
360
|
)
|
|
530
361
|
break
|
|
@@ -532,7 +363,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
532
363
|
body = (
|
|
533
364
|
<>
|
|
534
365
|
<Header visualizationKey={visualizationKey} subEditor='Waffle Chart' />
|
|
535
|
-
<CdcWaffleChart
|
|
366
|
+
<CdcWaffleChart
|
|
367
|
+
key={visualizationKey}
|
|
368
|
+
config={visualizationConfig}
|
|
369
|
+
isEditor={true}
|
|
370
|
+
setConfig={_updateConfig}
|
|
371
|
+
isDashboard={true}
|
|
372
|
+
configUrl={undefined}
|
|
373
|
+
/>
|
|
536
374
|
</>
|
|
537
375
|
)
|
|
538
376
|
break
|
|
@@ -540,7 +378,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
540
378
|
body = (
|
|
541
379
|
<>
|
|
542
380
|
<Header visualizationKey={visualizationKey} subEditor='Markup Include' />
|
|
543
|
-
<CdcMarkupInclude
|
|
381
|
+
<CdcMarkupInclude
|
|
382
|
+
key={visualizationKey}
|
|
383
|
+
config={visualizationConfig}
|
|
384
|
+
isEditor={true}
|
|
385
|
+
setConfig={_updateConfig}
|
|
386
|
+
isDashboard={true}
|
|
387
|
+
configUrl={undefined}
|
|
388
|
+
/>
|
|
544
389
|
</>
|
|
545
390
|
)
|
|
546
391
|
break
|
|
@@ -548,17 +393,28 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
548
393
|
body = (
|
|
549
394
|
<>
|
|
550
395
|
<Header visualizationKey={visualizationKey} subEditor='Filtered Text' />
|
|
551
|
-
<CdcFilteredText
|
|
396
|
+
<CdcFilteredText
|
|
397
|
+
key={visualizationKey}
|
|
398
|
+
config={visualizationConfig}
|
|
399
|
+
isEditor={true}
|
|
400
|
+
setConfig={_updateConfig}
|
|
401
|
+
isDashboard={true}
|
|
402
|
+
configUrl={undefined}
|
|
403
|
+
/>
|
|
552
404
|
</>
|
|
553
405
|
)
|
|
554
406
|
break
|
|
555
|
-
case '
|
|
407
|
+
case 'dashboardFilters':
|
|
556
408
|
const hideFilter = visualizationConfig.autoLoad && inNoDataState
|
|
557
409
|
body = !hideFilter ? (
|
|
558
410
|
<>
|
|
559
411
|
<Header visualizationKey={visualizationKey} subEditor='Filter Dropdowns' />
|
|
560
|
-
<
|
|
561
|
-
|
|
412
|
+
<DashboardSharedFilters
|
|
413
|
+
isEditor={true}
|
|
414
|
+
visualizationConfig={visualizationConfig}
|
|
415
|
+
apiFilterDropdowns={apiFilterDropdowns}
|
|
416
|
+
setConfig={_updateConfig}
|
|
417
|
+
/>
|
|
562
418
|
</>
|
|
563
419
|
) : (
|
|
564
420
|
<></>
|
|
@@ -568,10 +424,27 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
568
424
|
body = (
|
|
569
425
|
<>
|
|
570
426
|
<Header visualizationKey={visualizationKey} subEditor='Table' />
|
|
571
|
-
<DataTableStandAlone
|
|
427
|
+
<DataTableStandAlone
|
|
428
|
+
visualizationKey={visualizationKey}
|
|
429
|
+
config={visualizationConfig}
|
|
430
|
+
isEditor={true}
|
|
431
|
+
updateConfig={_updateConfig}
|
|
432
|
+
/>
|
|
572
433
|
</>
|
|
573
434
|
)
|
|
574
435
|
break
|
|
436
|
+
case 'footnotes':
|
|
437
|
+
body = (
|
|
438
|
+
<>
|
|
439
|
+
<Header visualizationKey={visualizationKey} subEditor='Footnotes' />
|
|
440
|
+
<FootnotesStandAlone
|
|
441
|
+
visualizationKey={visualizationKey}
|
|
442
|
+
config={{ ...visualizationConfig, datasets: state.config.datasets }}
|
|
443
|
+
isEditor={true}
|
|
444
|
+
updateConfig={_updateConfig}
|
|
445
|
+
/>
|
|
446
|
+
</>
|
|
447
|
+
)
|
|
575
448
|
default:
|
|
576
449
|
body = <></>
|
|
577
450
|
break
|
|
@@ -584,7 +457,7 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
584
457
|
<DndProvider backend={HTML5Backend}>
|
|
585
458
|
<div className='header-container'>
|
|
586
459
|
<Header />
|
|
587
|
-
<VisualizationsPanel
|
|
460
|
+
<VisualizationsPanel />
|
|
588
461
|
</div>
|
|
589
462
|
|
|
590
463
|
<div className='layout-container'>
|
|
@@ -596,31 +469,27 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
596
469
|
} else {
|
|
597
470
|
const { config } = state
|
|
598
471
|
const { title, description } = config.dashboard || {}
|
|
472
|
+
|
|
599
473
|
body = (
|
|
600
474
|
<>
|
|
601
475
|
{isEditor && <Header />}
|
|
602
476
|
<MultiTabs isEditor={isEditor && !isPreview} />
|
|
603
477
|
<Layout.Responsive isEditor={isEditor}>
|
|
604
478
|
<div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
|
|
605
|
-
<Title
|
|
479
|
+
<Title
|
|
480
|
+
title={title}
|
|
481
|
+
isDashboard={true}
|
|
482
|
+
classes={[`dashboard-title`, `${config.dashboard.theme ?? 'theme-blue'}`]}
|
|
483
|
+
/>
|
|
606
484
|
{/* Description */}
|
|
607
485
|
{description && <div className='subtext'>{parse(description)}</div>}
|
|
608
|
-
|
|
609
|
-
{/* Filters */}
|
|
610
|
-
{config.dashboard.sharedFilters && Object.values(config.visualizations || {}).filter(viz => viz.visualizationType === 'filter-dropdowns').length === 0 && (
|
|
611
|
-
<>
|
|
612
|
-
<Filters filters={state.config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
|
|
613
|
-
<GoButton />
|
|
614
|
-
</>
|
|
615
|
-
)}
|
|
616
|
-
|
|
617
486
|
{/* Visualizations */}
|
|
618
487
|
{config.rows &&
|
|
619
488
|
config.rows
|
|
620
489
|
.filter(row => row.columns.filter(col => col.widget).length !== 0)
|
|
621
490
|
.map((row, index) => {
|
|
622
491
|
if (row.multiVizColumn && (isPreview || !isEditor)) {
|
|
623
|
-
const filteredData = getFilteredData(state)
|
|
492
|
+
const filteredData = getFilteredData(state, _.cloneDeep(state.data))
|
|
624
493
|
const data = filteredData[index] ?? row.formattedData
|
|
625
494
|
const dataGroups = {}
|
|
626
495
|
data.forEach(d => {
|
|
@@ -628,36 +497,68 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
628
497
|
if (!dataGroups[groupKey]) dataGroups[groupKey] = []
|
|
629
498
|
dataGroups[groupKey].push(d)
|
|
630
499
|
})
|
|
631
|
-
return
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
<
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
500
|
+
return (
|
|
501
|
+
<>
|
|
502
|
+
{/* Expand/Collapse All */}
|
|
503
|
+
{row.expandCollapseAllButtons === true && (
|
|
504
|
+
<ExpandCollapseButtons setAllExpanded={setAllExpanded} />
|
|
505
|
+
)}
|
|
506
|
+
{Object.keys(dataGroups).map(groupName => {
|
|
507
|
+
const dataValue = dataGroups[groupName]
|
|
508
|
+
return (
|
|
509
|
+
<VisualizationRow
|
|
510
|
+
key={`row__${index}__${groupName}`}
|
|
511
|
+
allExpanded={allExpanded}
|
|
512
|
+
filteredDataOverride={dataValue}
|
|
513
|
+
groupName={groupName}
|
|
514
|
+
row={row}
|
|
515
|
+
rowIndex={index}
|
|
516
|
+
setSharedFilter={setSharedFilter}
|
|
517
|
+
updateChildConfig={updateChildConfig}
|
|
518
|
+
apiFilterDropdowns={apiFilterDropdowns}
|
|
519
|
+
currentViewport={currentViewport}
|
|
520
|
+
/>
|
|
521
|
+
)
|
|
522
|
+
})}
|
|
523
|
+
</>
|
|
524
|
+
)
|
|
650
525
|
} else {
|
|
651
526
|
return (
|
|
652
|
-
<VisualizationRow
|
|
527
|
+
<VisualizationRow
|
|
528
|
+
key={`row__${index}`}
|
|
529
|
+
allExpanded={false}
|
|
530
|
+
groupName={''}
|
|
531
|
+
row={row}
|
|
532
|
+
rowIndex={index}
|
|
533
|
+
setSharedFilter={setSharedFilter}
|
|
534
|
+
updateChildConfig={updateChildConfig}
|
|
535
|
+
apiFilterDropdowns={apiFilterDropdowns}
|
|
536
|
+
currentViewport={currentViewport}
|
|
537
|
+
/>
|
|
653
538
|
)
|
|
654
539
|
}
|
|
655
540
|
})}
|
|
656
541
|
|
|
657
542
|
{/* Image or PDF Inserts */}
|
|
658
543
|
<section className='download-buttons'>
|
|
659
|
-
{config.table?.downloadImageButton &&
|
|
660
|
-
|
|
544
|
+
{config.table?.downloadImageButton && (
|
|
545
|
+
<MediaControls.Button
|
|
546
|
+
title='Download Dashboard as Image'
|
|
547
|
+
type='image'
|
|
548
|
+
state={config}
|
|
549
|
+
text='Download Dashboard Image'
|
|
550
|
+
elementToCapture={imageId}
|
|
551
|
+
/>
|
|
552
|
+
)}
|
|
553
|
+
{config.table?.downloadPdfButton && (
|
|
554
|
+
<MediaControls.Button
|
|
555
|
+
title='Download Dashboard as PDF'
|
|
556
|
+
type='pdf'
|
|
557
|
+
state={config}
|
|
558
|
+
text='Download Dashboard PDF'
|
|
559
|
+
elementToCapture={imageId}
|
|
560
|
+
/>
|
|
561
|
+
)}
|
|
661
562
|
</section>
|
|
662
563
|
|
|
663
564
|
{/* Data Table */}
|
|
@@ -702,14 +603,26 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
702
603
|
})
|
|
703
604
|
|
|
704
605
|
//Applys any applicable filters to the Table
|
|
705
|
-
const filteredTableData =
|
|
606
|
+
const filteredTableData =
|
|
607
|
+
applicableFilters.length > 0
|
|
608
|
+
? filterData(applicableFilters, config.datasets[datasetKey].data)
|
|
609
|
+
: undefined
|
|
706
610
|
return (
|
|
707
|
-
<div
|
|
611
|
+
<div
|
|
612
|
+
className='multi-table-container'
|
|
613
|
+
id={`data-table-${datasetKey}`}
|
|
614
|
+
key={`data-table-${datasetKey}`}
|
|
615
|
+
>
|
|
708
616
|
<DataTable
|
|
709
617
|
config={config as TableConfig}
|
|
710
618
|
dataConfig={config.datasets[datasetKey]}
|
|
711
619
|
rawData={config.datasets[datasetKey].data?.[0]?.tableData || config.datasets[datasetKey].data}
|
|
712
|
-
runtimeData={
|
|
620
|
+
runtimeData={
|
|
621
|
+
config.datasets[datasetKey].data?.[0]?.tableData ||
|
|
622
|
+
filteredTableData ||
|
|
623
|
+
config.datasets[datasetKey].data ||
|
|
624
|
+
[]
|
|
625
|
+
}
|
|
713
626
|
expandDataTable={config.table.expanded}
|
|
714
627
|
tableTitle={datasetKey}
|
|
715
628
|
viewport={currentViewport}
|
|
@@ -728,7 +641,17 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
728
641
|
|
|
729
642
|
return (
|
|
730
643
|
<GlobalContextProvider>
|
|
731
|
-
<DashboardContext.Provider
|
|
644
|
+
<DashboardContext.Provider
|
|
645
|
+
value={{
|
|
646
|
+
...state,
|
|
647
|
+
setParentConfig: editorContext.setTempConfig,
|
|
648
|
+
outerContainerRef,
|
|
649
|
+
isDebug,
|
|
650
|
+
loadAPIFilters,
|
|
651
|
+
setAPIFilterDropdowns,
|
|
652
|
+
reloadURLData
|
|
653
|
+
}}
|
|
654
|
+
>
|
|
732
655
|
<DashboardDispatchContext.Provider value={dispatch}>
|
|
733
656
|
<div className={dashboardContainerClasses.join(' ')} ref={outerContainerRef} data-download-id={imageId}>
|
|
734
657
|
{body}
|