@cdc/dashboard 4.24.2 → 4.24.4
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 +128512 -99417
- package/examples/chart-data.json +5409 -0
- package/examples/full-dash-test.json +14643 -0
- package/examples/full-dashboard.json +10036 -0
- package/examples/sankey.json +5218 -0
- package/index.html +4 -3
- package/package.json +11 -10
- package/src/CdcDashboard.tsx +129 -124
- package/src/CdcDashboardComponent.tsx +316 -441
- package/src/DashboardContext.tsx +4 -1
- package/src/_stories/Dashboard.stories.tsx +79 -36
- package/src/_stories/_mock/api-filter-chart.json +11 -35
- package/src/_stories/_mock/api-filter-map.json +17 -31
- package/src/_stories/_mock/dashboard-gallery.json +523 -534
- package/src/_stories/_mock/multi-viz.json +378 -0
- package/src/_stories/_mock/pivot-filter.json +161 -0
- package/src/_stories/_mock/standalone-table.json +122 -0
- package/src/_stories/_mock/toggle-example.json +4035 -0
- package/src/components/DataDesignerModal.tsx +145 -0
- package/src/components/EditorWrapper/EditorWrapper.tsx +52 -0
- package/src/components/EditorWrapper/editor-wrapper.style.css +13 -0
- package/src/components/Filters.tsx +88 -0
- package/src/components/Grid.tsx +3 -1
- package/src/components/Header/FilterModal.tsx +506 -0
- package/src/components/Header/Header.tsx +25 -465
- package/src/components/Row.tsx +65 -29
- package/src/components/Toggle/Toggle.tsx +36 -0
- package/src/components/Toggle/index.tsx +1 -0
- package/src/components/Toggle/toggle-style.css +34 -0
- package/src/components/VisualizationRow.tsx +174 -0
- package/src/components/VisualizationsPanel.tsx +13 -3
- package/src/components/Widget.tsx +28 -126
- package/src/helpers/filterData.ts +75 -50
- package/src/helpers/generateValuesForFilter.ts +2 -12
- package/src/helpers/getApiFilterKey.ts +5 -0
- package/src/helpers/getFilteredData.ts +39 -0
- package/src/helpers/getUpdateConfig.ts +39 -22
- package/src/helpers/getVizConfig.ts +31 -0
- package/src/helpers/getVizRowColumnLocator.ts +9 -0
- package/src/helpers/iconHash.tsx +34 -0
- package/src/helpers/tests/filterData.test.ts +149 -0
- package/src/images/icon-toggle.svg +1 -0
- package/src/scss/grid.scss +10 -3
- package/src/scss/main.scss +11 -0
- package/src/store/dashboard.actions.ts +35 -3
- package/src/store/dashboard.reducer.ts +33 -2
- package/src/types/APIFilter.ts +4 -5
- package/src/types/ConfigRow.ts +13 -2
- package/src/types/DataSet.ts +11 -8
- package/src/types/InitialState.ts +2 -1
- package/src/types/SharedFilter.ts +6 -3
- package/src/types/Tab.ts +1 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
2
|
+
import DataDesigner from '@cdc/core/components/managers/DataDesigner'
|
|
3
|
+
import { useContext, useMemo, useState } from 'react'
|
|
4
|
+
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
5
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
6
|
+
import { CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
|
|
7
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
10
|
+
import DataTransform from '@cdc/core/helpers/DataTransform'
|
|
11
|
+
import { ConfigureData } from '@cdc/core/types/ConfigureData'
|
|
12
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
13
|
+
import InputSelect from '@cdc/core/components/inputs/InputSelect'
|
|
14
|
+
|
|
15
|
+
type DataDesignerModalProps = {
|
|
16
|
+
rowIndex: number
|
|
17
|
+
vizKey?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, rowIndex }) => {
|
|
21
|
+
const { config } = useContext(DashboardContext)
|
|
22
|
+
const { overlay } = useGlobalContext()
|
|
23
|
+
const transform = new DataTransform()
|
|
24
|
+
const dispatch = useContext(DashboardDispatchContext)
|
|
25
|
+
const [canContinue, setCanContinue] = useState(false)
|
|
26
|
+
const [useRow, setUseRow] = useState(!vizKey)
|
|
27
|
+
const [multiViz, setMultiViz] = useState(!!config.rows[rowIndex].multiVizColumn)
|
|
28
|
+
|
|
29
|
+
const configureData = useMemo(() => {
|
|
30
|
+
if (vizKey && !useRow) {
|
|
31
|
+
return config.visualizations[vizKey]
|
|
32
|
+
}
|
|
33
|
+
return config.rows[rowIndex]
|
|
34
|
+
}, [config.visualizations, config.rows, vizKey, rowIndex, useRow])
|
|
35
|
+
|
|
36
|
+
const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
|
|
37
|
+
if (vizKey && !useRow) {
|
|
38
|
+
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
|
|
39
|
+
} else {
|
|
40
|
+
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const changeDataset = ({ target: { value } }) => {
|
|
45
|
+
const newConfigureData = { dataDescription: {}, formattedData: undefined, dataKey: value }
|
|
46
|
+
|
|
47
|
+
updateConfigureData(newConfigureData)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const updateDescriptionProp = async (key, value) => {
|
|
51
|
+
const datasetKey = configureData.dataKey
|
|
52
|
+
const { data, dataUrl } = config.datasets[datasetKey]
|
|
53
|
+
let newData = data
|
|
54
|
+
if (!data && dataUrl) {
|
|
55
|
+
newData = await fetchRemoteData(dataUrl)
|
|
56
|
+
newData = transform.autoStandardize(newData)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const dataDescription = { ...configureData.dataDescription, [key]: value }
|
|
60
|
+
|
|
61
|
+
const newConfigureData = { data: newData, dataDescription, formattedData: transform.developerStandardize(newData, dataDescription) }
|
|
62
|
+
|
|
63
|
+
updateConfigureData(newConfigureData)
|
|
64
|
+
setCanContinue(true)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const setMultiVizColumn = (column: string) => {
|
|
68
|
+
if (column !== '') {
|
|
69
|
+
dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { multiVizColumn: column } } })
|
|
70
|
+
setCanContinue(true)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Modal>
|
|
76
|
+
<Modal.Content>
|
|
77
|
+
<div className='dataset-selector-container'>
|
|
78
|
+
Select a dataset:
|
|
79
|
+
<select className='dataset-selector' value={configureData.dataKey || ''} onChange={changeDataset}>
|
|
80
|
+
<option value=''>Select a dataset</option>
|
|
81
|
+
{config.datasets && Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
|
|
82
|
+
</select>
|
|
83
|
+
{vizKey && (
|
|
84
|
+
<CheckBox
|
|
85
|
+
label='Apply To Row'
|
|
86
|
+
value={useRow}
|
|
87
|
+
updateField={(section, subsection, fieldName, value) => {
|
|
88
|
+
setUseRow(value)
|
|
89
|
+
changeDataset({ target: { value: '' } })
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
{configureData.dataKey && (
|
|
95
|
+
<DataDesigner
|
|
96
|
+
{...{
|
|
97
|
+
configureData,
|
|
98
|
+
visualizationKey: vizKey,
|
|
99
|
+
updateDescriptionProp
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
{useRow && !!configureData.dataKey ? (
|
|
104
|
+
!multiViz ? (
|
|
105
|
+
<CheckBox
|
|
106
|
+
label='Configure Multiple Visualizations'
|
|
107
|
+
value={multiViz}
|
|
108
|
+
tooltip={
|
|
109
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
110
|
+
<Tooltip.Target>
|
|
111
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
112
|
+
</Tooltip.Target>
|
|
113
|
+
<Tooltip.Content>
|
|
114
|
+
<p>You can select a column where for each unique value in the column the configuration for the row will be repeated in to the data preview.</p>
|
|
115
|
+
</Tooltip.Content>
|
|
116
|
+
</Tooltip>
|
|
117
|
+
}
|
|
118
|
+
updateField={(section, subsection, fieldName, value) => {
|
|
119
|
+
if (canContinue && value === true) setCanContinue(false)
|
|
120
|
+
setMultiViz(value)
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
) : (
|
|
124
|
+
<InputSelect
|
|
125
|
+
options={Object.keys(config.datasets[configureData.dataKey]?.data[0] || {})}
|
|
126
|
+
value={config.rows[rowIndex].multiVizColumn}
|
|
127
|
+
label='Multi-Visualization Column'
|
|
128
|
+
initial='--Select--'
|
|
129
|
+
fieldName=''
|
|
130
|
+
updateField={(section, subsection, fieldName, value) => setMultiVizColumn(value)}
|
|
131
|
+
required
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
) : (
|
|
135
|
+
<></>
|
|
136
|
+
)}
|
|
137
|
+
{canContinue && (
|
|
138
|
+
<button style={{ margin: '1em', display: 'block' }} className='cove-button' onClick={() => overlay?.actions.toggleOverlay()}>
|
|
139
|
+
Continue
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
</Modal.Content>
|
|
143
|
+
</Modal>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Accordion } from 'react-accessible-accordion'
|
|
3
|
+
import Header from '../Header'
|
|
4
|
+
import { Visualization } from '@cdc/core/types/Visualization'
|
|
5
|
+
import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
6
|
+
import './editor-wrapper.style.css'
|
|
7
|
+
|
|
8
|
+
type StandAloneComponentProps = {
|
|
9
|
+
visualizationKey: string
|
|
10
|
+
config: Visualization
|
|
11
|
+
isEditor: boolean
|
|
12
|
+
setConfig: Function
|
|
13
|
+
isDashboard: boolean
|
|
14
|
+
configUrl: string
|
|
15
|
+
setEditing: Function
|
|
16
|
+
hostname: string
|
|
17
|
+
viewport?: ViewPort
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type EditorProps = {
|
|
21
|
+
component: React.JSXElementConstructor<StandAloneComponentProps>
|
|
22
|
+
type: string
|
|
23
|
+
visualizationKey: string
|
|
24
|
+
visualizationConfig: Visualization
|
|
25
|
+
updateConfig: Function
|
|
26
|
+
viewport?: ViewPort
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ children, visualizationKey, visualizationConfig, type, component: Component, updateConfig, viewport }) => {
|
|
30
|
+
const [displayPanel, setDisplayPanel] = React.useState(true)
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<Header visualizationKey={visualizationKey} subEditor={type} />
|
|
34
|
+
<div className='editor-wrapper'>
|
|
35
|
+
<button className={`editor-toggle ${displayPanel ? '' : 'collapsed'}`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel)} />
|
|
36
|
+
<section className={`${displayPanel ? '' : 'hidden'} editor-panel cove`}>
|
|
37
|
+
<div aria-level={2} role='heading' className='heading-2'>
|
|
38
|
+
Configure {type}
|
|
39
|
+
</div>
|
|
40
|
+
<form>
|
|
41
|
+
<Accordion allowZeroExpanded={true}>{children}</Accordion>
|
|
42
|
+
</form>
|
|
43
|
+
</section>
|
|
44
|
+
<div className='preview-wrapper'>
|
|
45
|
+
<Component visualizationKey={visualizationKey} config={visualizationConfig} isEditor={true} setConfig={updateConfig} isDashboard={true} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default EditorWrapper
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
2
|
+
import { getApiFilterKey } from '../helpers/getApiFilterKey'
|
|
3
|
+
import { SharedFilter } from '../types/SharedFilter'
|
|
4
|
+
|
|
5
|
+
export type DropdownOptions = Record<'value' | 'text', string>[]
|
|
6
|
+
|
|
7
|
+
export type APIFilterDropdowns = {
|
|
8
|
+
// null means still loading
|
|
9
|
+
[filtername: string]: null | DropdownOptions
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type FilterProps = {
|
|
13
|
+
hide?: number[]
|
|
14
|
+
filters: SharedFilter[]
|
|
15
|
+
apiFilterDropdowns: APIFilterDropdowns
|
|
16
|
+
handleOnChange: Function
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Filters: React.FC<FilterProps> = ({ hide, filters, apiFilterDropdowns, handleOnChange }) => {
|
|
20
|
+
const updateField = (_section, _subsection, fieldName, value) => {
|
|
21
|
+
handleOnChange(fieldName, value)
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{filters.map((singleFilter, filterIndex) => {
|
|
26
|
+
if ((singleFilter.type !== 'urlfilter' && !singleFilter.showDropdown) || (hide && hide.indexOf(filterIndex) !== -1)) return <></>
|
|
27
|
+
const values: JSX.Element[] = []
|
|
28
|
+
const multiValues = []
|
|
29
|
+
if (singleFilter.resetLabel) {
|
|
30
|
+
values.push(
|
|
31
|
+
<option key={`${singleFilter.resetLabel}-option`} value={singleFilter.resetLabel}>
|
|
32
|
+
{singleFilter.resetLabel}
|
|
33
|
+
</option>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
const _key = singleFilter.apiFilter ? getApiFilterKey(singleFilter.apiFilter) : undefined
|
|
37
|
+
if (_key && apiFilterDropdowns[_key]) {
|
|
38
|
+
// URL Filter
|
|
39
|
+
apiFilterDropdowns[_key].forEach(({ text, value }, index) => {
|
|
40
|
+
values.push(
|
|
41
|
+
<option key={`${value}-option-${index}`} value={value}>
|
|
42
|
+
{text}
|
|
43
|
+
</option>
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
} else {
|
|
47
|
+
// Data Filter
|
|
48
|
+
singleFilter.values?.forEach((filterOption, index) => {
|
|
49
|
+
const labeledOpt = singleFilter.labels && singleFilter.labels[filterOption]
|
|
50
|
+
values.push(
|
|
51
|
+
<option key={`${singleFilter.key}-option-${index}`} value={filterOption}>
|
|
52
|
+
{labeledOpt || filterOption}
|
|
53
|
+
</option>
|
|
54
|
+
)
|
|
55
|
+
multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className='cove-dashboard-filters' key={`${singleFilter.key}-filtersection-${filterIndex}`}>
|
|
61
|
+
<section className='dashboard-filters-section'>
|
|
62
|
+
{!singleFilter.pivot ? (
|
|
63
|
+
<>
|
|
64
|
+
<label htmlFor={`filter-${filterIndex}`}>{singleFilter.key}</label>
|
|
65
|
+
<select
|
|
66
|
+
id={`filter-${filterIndex}`}
|
|
67
|
+
className='filter-select'
|
|
68
|
+
data-index='0'
|
|
69
|
+
value={singleFilter.queuedActive || singleFilter.active}
|
|
70
|
+
onChange={val => {
|
|
71
|
+
handleOnChange(filterIndex, val.target.value)
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{values}
|
|
75
|
+
</select>
|
|
76
|
+
</>
|
|
77
|
+
) : (
|
|
78
|
+
<MultiSelect label={singleFilter.key} options={multiValues} fieldName={filterIndex} updateField={updateField} selected={singleFilter.active as string[]} limit={singleFilter.selectLimit || 5} />
|
|
79
|
+
)}
|
|
80
|
+
</section>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
})}
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default Filters
|
package/src/components/Grid.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React, { useContext } from 'react'
|
|
|
2
2
|
import Row from './Row'
|
|
3
3
|
|
|
4
4
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
5
|
+
import { ConfigRow } from '../types/ConfigRow'
|
|
5
6
|
|
|
6
7
|
const Grid = () => {
|
|
7
8
|
const { config } = useContext(DashboardContext)
|
|
@@ -10,9 +11,10 @@ const Grid = () => {
|
|
|
10
11
|
const dispatch = useContext(DashboardDispatchContext)
|
|
11
12
|
const updateConfig = config => dispatch({ type: 'UPDATE_CONFIG', payload: [config] })
|
|
12
13
|
const addRow = () => {
|
|
14
|
+
const blankRow: Partial<ConfigRow> = { columns: [{ width: 12 }] }
|
|
13
15
|
updateConfig({
|
|
14
16
|
...config,
|
|
15
|
-
rows: [...rows,
|
|
17
|
+
rows: [...rows, blankRow],
|
|
16
18
|
uuid: Date.now()
|
|
17
19
|
})
|
|
18
20
|
}
|