@cdc/dashboard 4.23.11 → 4.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcdashboard.js +109007 -98738
- package/examples/DEV-6574.json +2224 -0
- package/examples/filters/Alabama.json +72 -0
- package/examples/filters/Alaska.json +1737 -0
- package/examples/filters/Arkansas.json +4713 -0
- package/examples/filters/California.json +212 -0
- package/examples/filters/Colorado.json +1500 -0
- package/examples/filters/Connecticut.json +559 -0
- package/examples/filters/Delaware.json +63 -0
- package/examples/filters/DistrictofColumbia.json +63 -0
- package/examples/filters/Florida.json +4217 -0
- package/examples/filters/States.json +146 -0
- package/examples/test.json +752 -0
- package/examples/zika.json +2274 -0
- package/index.html +5 -3
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +124 -963
- package/src/CdcDashboardComponent.tsx +903 -0
- package/src/_stories/Dashboard.stories.tsx +2 -2
- package/src/components/Column.tsx +15 -12
- package/src/components/Header/Header.tsx +694 -0
- package/src/components/Header/index.tsx +1 -676
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +106 -0
- package/src/components/MultiConfigTabs/MultiTabs.tsx +30 -0
- package/src/components/MultiConfigTabs/index.tsx +8 -0
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +32 -0
- package/src/components/Widget.tsx +25 -9
- package/src/helpers/filterData.ts +73 -73
- package/src/helpers/generateValuesForFilter.ts +25 -29
- package/src/helpers/getUpdateConfig.ts +6 -2
- package/src/helpers/processData.ts +13 -0
- package/src/helpers/processDataLegacy.ts +14 -0
- package/src/{index.jsx → index.tsx} +2 -2
- package/src/scss/editor-panel.scss +14 -11
- package/src/scss/grid.scss +4 -6
- package/src/scss/main.scss +2 -8
- package/src/store/dashboard.actions.ts +10 -4
- package/src/store/dashboard.reducer.ts +74 -3
- package/src/types/ConfigRow.ts +6 -0
- package/src/types/Dashboard.ts +11 -0
- package/src/types/DashboardConfig.ts +23 -0
- package/src/types/InitialState.ts +10 -0
- package/src/types/MultiDashboard.ts +11 -0
- package/src/types/SharedFilter.ts +31 -20
- package/src/types/Config.ts +0 -27
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createRef, useContext, useMemo, useState } from 'react'
|
|
2
|
+
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
4
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
5
|
+
import './multiconfigtabs.styles.css'
|
|
6
|
+
|
|
7
|
+
const AreYouSure = deleteCallback => {
|
|
8
|
+
return (
|
|
9
|
+
<Modal>
|
|
10
|
+
<Modal.Content>
|
|
11
|
+
<p>Are you sure you want to delete this dashboard? </p>
|
|
12
|
+
<button className='btn btn-danger' onClick={deleteCallback}>
|
|
13
|
+
DELETE
|
|
14
|
+
</button>
|
|
15
|
+
</Modal.Content>
|
|
16
|
+
</Modal>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
21
|
+
const [editing, setEditing] = useState(false)
|
|
22
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
23
|
+
const { overlay } = useGlobalContext()
|
|
24
|
+
const inputRef = createRef<HTMLInputElement>()
|
|
25
|
+
|
|
26
|
+
const onBlur = () => {
|
|
27
|
+
const newVal = inputRef.current.value
|
|
28
|
+
const sameName = newVal === name
|
|
29
|
+
const blankName = !newVal
|
|
30
|
+
const duplicateName = tabs.includes(newVal)
|
|
31
|
+
if (!sameName && !blankName && !duplicateName) {
|
|
32
|
+
dispatch({ type: 'RENAME_DASHBOARD_TAB', payload: { current: name, new: newVal } })
|
|
33
|
+
}
|
|
34
|
+
setEditing(false)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const onClick = e => {
|
|
38
|
+
// ignore click on delete button
|
|
39
|
+
if (e.target.className === 'remove') return
|
|
40
|
+
if (active) {
|
|
41
|
+
setEditing(true)
|
|
42
|
+
} else {
|
|
43
|
+
handleClick()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const handleRemove = () => {
|
|
48
|
+
const deleteCallback = () => {
|
|
49
|
+
dispatch({ type: 'REMOVE_MULTIDASHBOARD_AT_INDEX', payload: index })
|
|
50
|
+
overlay?.actions.toggleOverlay(false)
|
|
51
|
+
}
|
|
52
|
+
overlay?.actions.openOverlay(AreYouSure(deleteCallback))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleReorder = (index: number, moveTo: -1 | 1) => {
|
|
56
|
+
const newIndex = index + moveTo
|
|
57
|
+
const inbounds = newIndex > -1 && newIndex <= tabs.length - 1
|
|
58
|
+
if (inbounds) {
|
|
59
|
+
dispatch({ type: 'REORDER_MULTIDASHBOARDS', payload: { currentIndex: index, newIndex: index + moveTo } })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const canMoveLeft = index !== 0
|
|
64
|
+
const canMoveRight = index <= tabs.length - 2
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<li className='nav-item'>
|
|
68
|
+
<a className={`edit nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : null} href='#' onClick={onClick}>
|
|
69
|
+
{canMoveLeft && <button onClick={() => handleReorder(index, -1)}>{'<'}</button>}
|
|
70
|
+
{editing ? <input type='text' defaultValue={name} onBlur={onBlur} ref={inputRef} /> : <>{name}</>}
|
|
71
|
+
{canMoveRight && <button onClick={() => handleReorder(index, 1)}>{'>'}</button>}
|
|
72
|
+
<button className='remove' onClick={handleRemove}>
|
|
73
|
+
X
|
|
74
|
+
</button>
|
|
75
|
+
</a>
|
|
76
|
+
</li>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MultiConfigTabs = () => {
|
|
81
|
+
const { config } = useContext(DashboardContext)
|
|
82
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
83
|
+
const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
|
|
84
|
+
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
85
|
+
|
|
86
|
+
const saveAndLoad = (indexToSwitchTo: number) => {
|
|
87
|
+
dispatch({ type: 'SAVE_CURRENT_CHANGES' })
|
|
88
|
+
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!config.multiDashboards) return null
|
|
92
|
+
return (
|
|
93
|
+
<ul className='nav nav-tabs multi-config-tabs'>
|
|
94
|
+
{tabs.map((tab, index) => (
|
|
95
|
+
<Tab key={tab + index} name={tab} tabs={tabs} index={index} handleClick={() => saveAndLoad(index)} active={index === activeTab} />
|
|
96
|
+
))}
|
|
97
|
+
<li className='nav-item'>
|
|
98
|
+
<a className='nav-link add' href='#' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
|
|
99
|
+
+
|
|
100
|
+
</a>
|
|
101
|
+
</li>
|
|
102
|
+
</ul>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default MultiConfigTabs
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react'
|
|
2
|
+
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
|
+
|
|
4
|
+
import './multiconfigtabs.styles.css'
|
|
5
|
+
|
|
6
|
+
const MultiTabs = () => {
|
|
7
|
+
const { config } = useContext(DashboardContext)
|
|
8
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
9
|
+
const tabs = useMemo<string[]>(() => (config.multiDashboards || []).map(({ label }) => label), [config.multiDashboards])
|
|
10
|
+
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
11
|
+
|
|
12
|
+
const load = (indexToSwitchTo: number) => {
|
|
13
|
+
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!config.multiDashboards) return null
|
|
17
|
+
return (
|
|
18
|
+
<ul className='nav nav-tabs multi-config-tabs'>
|
|
19
|
+
{tabs.map((tab, index) => (
|
|
20
|
+
<li className='nav-item'>
|
|
21
|
+
<a className={`nav-link${activeTab === index ? ' active' : ''}`} aria-current={activeTab === index ? 'page' : null} href='#' onClick={() => load(index)}>
|
|
22
|
+
{tab}
|
|
23
|
+
</a>
|
|
24
|
+
</li>
|
|
25
|
+
))}
|
|
26
|
+
</ul>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default MultiTabs
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.multi-config-tabs {
|
|
2
|
+
.nav-link {
|
|
3
|
+
font-weight: 400;
|
|
4
|
+
display: block;
|
|
5
|
+
padding: 0.5rem 1rem;
|
|
6
|
+
:is(button) {
|
|
7
|
+
display: none;
|
|
8
|
+
background: none;
|
|
9
|
+
}
|
|
10
|
+
&:hover {
|
|
11
|
+
:is(button) {
|
|
12
|
+
display: inline-block;
|
|
13
|
+
color: var(--gray);
|
|
14
|
+
&:hover {
|
|
15
|
+
color: var(--black);
|
|
16
|
+
font-weight: bold;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
.remove {
|
|
22
|
+
color: var(--gray);
|
|
23
|
+
&:hover {
|
|
24
|
+
color: var(--black);
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
.add {
|
|
29
|
+
font-weight: 800;
|
|
30
|
+
padding: 0.5rem 1rem;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -10,7 +10,7 @@ import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
|
10
10
|
import DataDesigner from '@cdc/core/components/managers/DataDesigner'
|
|
11
11
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
12
12
|
import Modal from '@cdc/core/components/ui/Modal'
|
|
13
|
-
import { Visualization } from '
|
|
13
|
+
import { Visualization } from '@cdc/core/types/Visualization'
|
|
14
14
|
|
|
15
15
|
const iconHash = {
|
|
16
16
|
'data-bite': <Icon display='databite' base />,
|
|
@@ -87,13 +87,16 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
|
|
|
87
87
|
updateConfig({ ...config, rows, visualizations })
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const [{ isDragging, ...collected }, drag] = useDrag(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const [{ isDragging, ...collected }, drag] = useDrag(
|
|
91
|
+
{
|
|
92
|
+
type: 'vis-widget',
|
|
93
|
+
end: handleWidgetMove,
|
|
94
|
+
collect: monitor => ({
|
|
95
|
+
isDragging: monitor.isDragging()
|
|
96
|
+
})
|
|
97
|
+
},
|
|
98
|
+
[config.activeDashboard, config.rows]
|
|
99
|
+
)
|
|
97
100
|
|
|
98
101
|
const deleteWidget = () => {
|
|
99
102
|
if (!data) return
|
|
@@ -266,6 +269,19 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
|
|
|
266
269
|
}
|
|
267
270
|
}, [data?.openModal])
|
|
268
271
|
|
|
272
|
+
let isConfigurationReady = false;
|
|
273
|
+
if(type === 'markup-include' || type === 'filter-dropdowns'){
|
|
274
|
+
isConfigurationReady = true;
|
|
275
|
+
} else if(data && data.formattedData) {
|
|
276
|
+
isConfigurationReady = true;
|
|
277
|
+
} else if(data && data.dataKey && data.dataDescription && config.datasets[data.dataKey]){
|
|
278
|
+
let formattedDataAttempt = transform.autoStandardize(config.datasets[data.dataKey].data);
|
|
279
|
+
formattedDataAttempt = transform.developerStandardize(formattedDataAttempt, data.dataDescription);
|
|
280
|
+
if(formattedDataAttempt){
|
|
281
|
+
isConfigurationReady = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
269
285
|
return (
|
|
270
286
|
<>
|
|
271
287
|
<div className='widget' ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }} {...collected}>
|
|
@@ -273,7 +289,7 @@ const Widget = ({ data, addVisualization, type }: WidgetProps) => {
|
|
|
273
289
|
<div className='widget__content'>
|
|
274
290
|
{data?.rowIdx !== undefined && (
|
|
275
291
|
<div className='widget-menu'>
|
|
276
|
-
{
|
|
292
|
+
{isConfigurationReady && (
|
|
277
293
|
<button title='Configure Visualization' className='btn btn-configure' onClick={editWidget}>
|
|
278
294
|
{iconHash['tools']}
|
|
279
295
|
</button>
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { SharedFilter } from '../types/SharedFilter'
|
|
2
|
-
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
3
|
-
|
|
4
|
-
const findFilterTier = (filters: SharedFilter[], sharedFilter: SharedFilter) => {
|
|
5
|
-
if (!sharedFilter.parents?.length) {
|
|
6
|
-
return 1
|
|
7
|
-
} else {
|
|
8
|
-
let parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
|
|
9
|
-
if (!parent) return 1
|
|
10
|
-
return 1 + findFilterTier(filters, parent)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const filterData = (filters: SharedFilter[], _data: Object[], filterBehavior) => {
|
|
15
|
-
if (_data) {
|
|
16
|
-
let maxTier = 1
|
|
17
|
-
filters.forEach(sharedFilter => {
|
|
18
|
-
sharedFilter.tier = findFilterTier(filters, sharedFilter)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
filters.forEach(sharedFilter => {
|
|
22
|
-
if (sharedFilter.tier && sharedFilter.tier > maxTier) {
|
|
23
|
-
maxTier = sharedFilter.tier
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
let filteredData = _data
|
|
28
|
-
// TODO triple loop??
|
|
29
|
-
for (let i = 0; i < maxTier; i++) {
|
|
30
|
-
let filteredDataSubTier: any[] = []
|
|
31
|
-
|
|
32
|
-
filteredData.forEach(row => {
|
|
33
|
-
let add = true
|
|
34
|
-
|
|
35
|
-
filters.forEach(filter => {
|
|
36
|
-
// eslint-disable-next-line eqeqeq
|
|
37
|
-
if (filter.type !== 'urlfilter' && ((!filter.tier && i === 0) || filter.tier === i + 1) && filter.active && row[filter.columnName!] != filter.active) {
|
|
38
|
-
add = false
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
if (add) filteredDataSubTier.push(row)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
filters.forEach(sharedFilter => {
|
|
46
|
-
if (sharedFilter.tier && sharedFilter.tier === i + 2) {
|
|
47
|
-
sharedFilter.values = generateValuesForFilter(sharedFilter.columnName, { data: filteredDataSubTier }, filterBehavior)
|
|
48
|
-
if (sharedFilter.values.length > 0 && (!sharedFilter.active || sharedFilter.values.indexOf(sharedFilter.active) === -1)) {
|
|
49
|
-
sharedFilter.active = sharedFilter.values[0]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
filteredData = filteredDataSubTier
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let filteredDataSubTier: any[] = []
|
|
58
|
-
filteredData.forEach(row => {
|
|
59
|
-
let add = true
|
|
60
|
-
|
|
61
|
-
filters.forEach(filter => {
|
|
62
|
-
// eslint-disable-next-line eqeqeq
|
|
63
|
-
if (filter.type !== 'urlfilter' && filter.tier && filter.tier === maxTier - 1 && filter.active && row[filter.columnName!] != filter.active) {
|
|
64
|
-
add = false
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
if (add) filteredDataSubTier.push(row)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return filteredDataSubTier
|
|
72
|
-
}
|
|
73
|
-
}
|
|
1
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
2
|
+
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
3
|
+
|
|
4
|
+
const findFilterTier = (filters: SharedFilter[], sharedFilter: SharedFilter) => {
|
|
5
|
+
if (!sharedFilter.parents?.length) {
|
|
6
|
+
return 1
|
|
7
|
+
} else {
|
|
8
|
+
let parent = filters.find(filter => sharedFilter.parents!.includes(filter.key))
|
|
9
|
+
if (!parent) return 1
|
|
10
|
+
return 1 + findFilterTier(filters, parent)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const filterData = (filters: SharedFilter[], _data: Object[], filterBehavior) => {
|
|
15
|
+
if (_data) {
|
|
16
|
+
let maxTier = 1
|
|
17
|
+
filters.forEach(sharedFilter => {
|
|
18
|
+
sharedFilter.tier = findFilterTier(filters, sharedFilter)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
filters.forEach(sharedFilter => {
|
|
22
|
+
if (sharedFilter.tier && sharedFilter.tier > maxTier) {
|
|
23
|
+
maxTier = sharedFilter.tier
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
let filteredData = _data
|
|
28
|
+
// TODO triple loop??
|
|
29
|
+
for (let i = 0; i < maxTier; i++) {
|
|
30
|
+
let filteredDataSubTier: any[] = []
|
|
31
|
+
|
|
32
|
+
filteredData.forEach(row => {
|
|
33
|
+
let add = true
|
|
34
|
+
|
|
35
|
+
filters.forEach(filter => {
|
|
36
|
+
// eslint-disable-next-line eqeqeq
|
|
37
|
+
if (filter.type !== 'urlfilter' && ((!filter.tier && i === 0) || filter.tier === i + 1) && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
|
|
38
|
+
add = false
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (add) filteredDataSubTier.push(row)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
filters.forEach(sharedFilter => {
|
|
46
|
+
if (sharedFilter.tier && sharedFilter.tier === i + 2) {
|
|
47
|
+
sharedFilter.values = generateValuesForFilter(sharedFilter.columnName, { data: filteredDataSubTier }, filterBehavior)
|
|
48
|
+
if (sharedFilter.values.length > 0 && (!sharedFilter.active || sharedFilter.values.indexOf(sharedFilter.active) === -1)) {
|
|
49
|
+
sharedFilter.active = sharedFilter.values[0]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
filteredData = filteredDataSubTier
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let filteredDataSubTier: any[] = []
|
|
58
|
+
filteredData.forEach(row => {
|
|
59
|
+
let add = true
|
|
60
|
+
|
|
61
|
+
filters.forEach(filter => {
|
|
62
|
+
// eslint-disable-next-line eqeqeq
|
|
63
|
+
if (filter.type !== 'urlfilter' && filter.tier && filter.tier === maxTier - 1 && (filter.queuedActive || filter.active) && row[filter.columnName!] != (filter.queuedActive || filter.active)) {
|
|
64
|
+
add = false
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (add) filteredDataSubTier.push(row)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return filteredDataSubTier
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import { FilterBehavior } from '../components/Header'
|
|
2
|
-
|
|
3
|
-
// Gets filter values from API response
|
|
4
|
-
export const generateValuesForAPIFilter = (columnName, _data): string[] => {
|
|
5
|
-
type Row = { [key: string]: any }
|
|
6
|
-
return Object.values(_data)
|
|
7
|
-
.filter(row => row && !!(row as Row)[columnName])
|
|
8
|
-
.map(row => (row as Row)[columnName])
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Gets filter values from dataset
|
|
12
|
-
export const generateValuesForFilter = (columnName, _data, filterBehavior) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
return values
|
|
29
|
-
}
|
|
1
|
+
import { FilterBehavior } from '../components/Header/Header'
|
|
2
|
+
|
|
3
|
+
// Gets filter values from API response
|
|
4
|
+
export const generateValuesForAPIFilter = (columnName, _data): string[] => {
|
|
5
|
+
type Row = { [key: string]: any }
|
|
6
|
+
return Object.values(_data)
|
|
7
|
+
.filter(row => row && !!(row as Row)[columnName])
|
|
8
|
+
.map(row => (row as Row)[columnName])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Gets filter values from dataset
|
|
12
|
+
export const generateValuesForFilter = (columnName, _data, filterBehavior) => {
|
|
13
|
+
const values: string[] = []
|
|
14
|
+
|
|
15
|
+
Object.keys(_data).forEach(key => {
|
|
16
|
+
_data[key]?.forEach(row => {
|
|
17
|
+
const value = row[columnName]
|
|
18
|
+
if (value && false === values.includes(value)) {
|
|
19
|
+
values.push(value)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return values
|
|
25
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { DashboardState } from '../store/dashboard.reducer'
|
|
2
|
-
import { Config } from '../types/
|
|
2
|
+
import { DashboardConfig as Config, DashboardConfig } from '../types/DashboardConfig'
|
|
3
3
|
import { filterData } from './filterData'
|
|
4
4
|
import { generateValuesForFilter } from './generateValuesForFilter'
|
|
5
5
|
import { getFormattedData } from './getFormattedData'
|
|
6
6
|
import { getVizKeys } from './getVizKeys'
|
|
7
7
|
|
|
8
|
+
type UpdateState = Omit<DashboardState, 'config'> & {
|
|
9
|
+
config?: DashboardConfig
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
export const getUpdateConfig =
|
|
9
|
-
(state:
|
|
13
|
+
(state: UpdateState) =>
|
|
10
14
|
(newConfig, dataOverride?: Object): [Config, Object] => {
|
|
11
15
|
let newFilteredData = {}
|
|
12
16
|
let visualizationKeys = getVizKeys(newConfig)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FilterBehavior } from '../components/Header/Header'
|
|
2
|
+
import { DataSet } from '../types/DataSet'
|
|
3
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
4
|
+
import { getFormattedData } from './getFormattedData'
|
|
5
|
+
|
|
6
|
+
export const processData = async (dataSet: DataSet, filterBehavior) => {
|
|
7
|
+
if (dataSet.dataUrl && filterBehavior !== FilterBehavior.Apply) {
|
|
8
|
+
const dataset = await fetchRemoteData(`${dataSet.dataUrl}`)
|
|
9
|
+
return getFormattedData(dataset, dataSet.dataDescription)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return dataSet.formattedData || dataSet.data
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
2
|
+
import { getFormattedData } from './getFormattedData'
|
|
3
|
+
|
|
4
|
+
export const processDataLegacy = async (response: any) => {
|
|
5
|
+
let dataset = response.formattedData || response.data
|
|
6
|
+
|
|
7
|
+
if (response.dataUrl) {
|
|
8
|
+
dataset = await fetchRemoteData(`${response.dataUrl}`)
|
|
9
|
+
|
|
10
|
+
dataset = getFormattedData(dataset, response.dataDescription)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return dataset
|
|
14
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import MultiDashboardWrapper from './CdcDashboard'
|
|
5
5
|
|
|
6
6
|
let isEditor = window.location.href.includes('editor=true')
|
|
7
7
|
let isDebug = window.location.href.includes('debug=true')
|
|
@@ -10,6 +10,6 @@ let domContainer = document.getElementsByClassName('react-container')[0]
|
|
|
10
10
|
|
|
11
11
|
ReactDOM.createRoot(domContainer).render(
|
|
12
12
|
<React.StrictMode>
|
|
13
|
-
<
|
|
13
|
+
<MultiDashboardWrapper configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} isDebug={isDebug} />
|
|
14
14
|
</React.StrictMode>
|
|
15
15
|
)
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
.editor-panel + .cdc-dashboard-inner-container,
|
|
4
4
|
.editor-heading + .cdc-open-viz-module {
|
|
5
5
|
position: relative;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
min-height: 80vh;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.header-container {
|
|
10
|
+
display: flex;
|
|
11
|
+
position: sticky;
|
|
12
|
+
top: 0;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
width: calc(350px + 1em);
|
|
15
|
+
max-height: 95vh;
|
|
16
|
+
z-index: 3;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
.editor-panel {
|
|
@@ -83,12 +87,12 @@
|
|
|
83
87
|
background: #fff;
|
|
84
88
|
width: $editorWidth;
|
|
85
89
|
overflow-y: overlay;
|
|
86
|
-
position:
|
|
90
|
+
position: absolute;
|
|
87
91
|
z-index: 8;
|
|
88
92
|
display: flex;
|
|
89
93
|
flex-direction: column;
|
|
90
94
|
left: 0;
|
|
91
|
-
top:
|
|
95
|
+
top: 0;
|
|
92
96
|
bottom: 0;
|
|
93
97
|
.accordion__heading {
|
|
94
98
|
background: $lightestGray;
|
|
@@ -587,14 +591,13 @@
|
|
|
587
591
|
color: #000;
|
|
588
592
|
font-size: 1em;
|
|
589
593
|
border: 0;
|
|
590
|
-
position:
|
|
594
|
+
position: absolute;
|
|
591
595
|
z-index: 99;
|
|
592
596
|
transition: 0.1s background;
|
|
593
597
|
cursor: pointer;
|
|
594
598
|
width: 25px;
|
|
595
599
|
height: 25px;
|
|
596
600
|
left: 307px;
|
|
597
|
-
top: calc(3em + 10px);
|
|
598
601
|
box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
|
|
599
602
|
&:before {
|
|
600
603
|
top: 43%;
|
package/src/scss/grid.scss
CHANGED
|
@@ -3,18 +3,17 @@ $red: #f74242;
|
|
|
3
3
|
|
|
4
4
|
.layout-container {
|
|
5
5
|
display: flex;
|
|
6
|
+
margin-top: -80vh;
|
|
7
|
+
margin-left: calc($editorWidth + 1em);
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
.visualizations-panel {
|
|
9
11
|
background-color: #fff;
|
|
10
12
|
padding: 1em;
|
|
11
|
-
width:
|
|
13
|
+
width: $editorWidth;
|
|
12
14
|
border-right: #c7c7c7 1px solid;
|
|
13
|
-
position: fixed;
|
|
14
|
-
top: 6em;
|
|
15
|
-
bottom: 0;
|
|
16
15
|
z-index: 1;
|
|
17
|
-
overflow-y:
|
|
16
|
+
overflow-y: scroll;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
.hidden.editor-panel + .builder-grid {
|
|
@@ -29,7 +28,6 @@ $red: #f74242;
|
|
|
29
28
|
.builder-grid {
|
|
30
29
|
flex-grow: 1;
|
|
31
30
|
padding: 5em 3em 3em;
|
|
32
|
-
margin-left: calc(350px + 1em);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
.column-container {
|
package/src/scss/main.scss
CHANGED
|
@@ -15,11 +15,8 @@
|
|
|
15
15
|
border-bottom: #c7c7c7 1px solid;
|
|
16
16
|
padding: 0 1em;
|
|
17
17
|
display: flex;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
width: 100%;
|
|
21
|
-
z-index: 19;
|
|
22
|
-
top: 0;
|
|
18
|
+
width: 100vw;
|
|
19
|
+
z-index: 5;
|
|
23
20
|
&.sub-dashboard-viz {
|
|
24
21
|
height: 3em;
|
|
25
22
|
}
|
|
@@ -285,9 +282,6 @@
|
|
|
285
282
|
.editor-panel {
|
|
286
283
|
top: 40px;
|
|
287
284
|
}
|
|
288
|
-
.editor-toggle {
|
|
289
|
-
top: 3em;
|
|
290
|
-
}
|
|
291
285
|
}
|
|
292
286
|
}
|
|
293
287
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Config } from '../types/
|
|
2
|
-
|
|
3
|
-
type Action<T, P> = { type: T; payload: P }
|
|
1
|
+
import type { DashboardConfig as Config } from '../types/DashboardConfig'
|
|
2
|
+
import { type Action } from '@cdc/core/types/Action'
|
|
4
3
|
|
|
5
4
|
type SET_CONFIG = Action<'SET_CONFIG', Config>
|
|
6
5
|
type UPDATE_CONFIG = Action<'UPDATE_CONFIG', [Config, Object?]>
|
|
@@ -9,6 +8,13 @@ type SET_LOADING = Action<'SET_LOADING', boolean>
|
|
|
9
8
|
type SET_PREVIEW = Action<'SET_PREVIEW', boolean>
|
|
10
9
|
type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', Object>
|
|
11
10
|
type SET_TAB_SELECTED = Action<'SET_TAB_SELECTED', number>
|
|
11
|
+
type RENAME_DASHBOARD_TAB = Action<'RENAME_DASHBOARD_TAB', { current: string; new: string }>
|
|
12
|
+
type INITIALIZE_MULTIDASHBOARDS = Action<'INITIALIZE_MULTIDASHBOARDS', undefined>
|
|
13
|
+
type REMOVE_MULTIDASHBOARD_AT_INDEX = Action<'REMOVE_MULTIDASHBOARD_AT_INDEX', number>
|
|
14
|
+
type REORDER_MULTIDASHBOARDS = Action<'REORDER_MULTIDASHBOARDS', { currentIndex: number; newIndex: number }>
|
|
15
|
+
type ADD_NEW_DASHBOARD = Action<'ADD_NEW_DASHBOARD', undefined>
|
|
16
|
+
type SAVE_CURRENT_CHANGES = Action<'SAVE_CURRENT_CHANGES', undefined>
|
|
17
|
+
type SWITCH_CONFIG = Action<'SWITCH_CONFIG', number>
|
|
12
18
|
|
|
13
|
-
type DashboardActions = SET_CONFIG | UPDATE_CONFIG | SET_DATA | SET_LOADING | SET_PREVIEW | SET_FILTERED_DATA | SET_TAB_SELECTED
|
|
19
|
+
type DashboardActions = ADD_NEW_DASHBOARD | SET_CONFIG | UPDATE_CONFIG | REMOVE_MULTIDASHBOARD_AT_INDEX | RENAME_DASHBOARD_TAB | REORDER_MULTIDASHBOARDS | SAVE_CURRENT_CHANGES | SET_DATA | SET_LOADING | SET_PREVIEW | SET_FILTERED_DATA | SET_TAB_SELECTED | SWITCH_CONFIG | INITIALIZE_MULTIDASHBOARDS
|
|
14
20
|
export default DashboardActions
|