@cdc/dashboard 4.24.10 → 4.24.11
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 +46738 -44885
- package/examples/private/DEV-9644.json +20092 -0
- package/package.json +9 -9
- package/src/CdcDashboard.tsx +35 -14
- package/src/CdcDashboardComponent.tsx +38 -14
- package/src/_stories/Dashboard.stories.tsx +8 -0
- package/src/_stories/_mock/api-filter-error.json +55 -0
- package/src/_stories/_mock/group-pivot-filter.json +10 -5
- package/src/components/DashboardFilters/DashboardFilters.tsx +57 -42
- package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +1 -1
- package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +7 -5
- package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
- package/src/components/DashboardFilters/dashboardfilter.styles.css +11 -0
- package/src/components/Grid.tsx +1 -1
- package/src/components/Header/Header.tsx +71 -10
- package/src/components/Header/index.scss +0 -5
- package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
- package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
- package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
- package/src/components/Row.tsx +59 -13
- package/src/components/VisualizationRow.tsx +0 -2
- package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
- package/src/components/Widget.tsx +23 -1
- package/src/helpers/getVizRowColumnLocator.ts +1 -0
- package/src/helpers/loadAPIFilters.ts +7 -2
- package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +2 -1
- package/src/scss/editor-panel.scss +0 -3
- package/src/scss/grid.scss +22 -23
- package/src/scss/main.scss +0 -27
- package/src/store/dashboard.reducer.ts +7 -1
- package/src/store/errorMessage/errorMessage.actions.ts +7 -0
- package/src/store/errorMessage/errorMessage.reducer.ts +24 -0
|
@@ -6,6 +6,7 @@ import './index.scss'
|
|
|
6
6
|
import MultiConfigTabs from '../MultiConfigTabs'
|
|
7
7
|
import { Tab } from '../../types/Tab'
|
|
8
8
|
import _ from 'lodash'
|
|
9
|
+
import { getVizRowColumnLocator } from '../../helpers/getVizRowColumnLocator'
|
|
9
10
|
|
|
10
11
|
type HeaderProps = {
|
|
11
12
|
back?: any
|
|
@@ -16,7 +17,7 @@ type HeaderProps = {
|
|
|
16
17
|
const Header = (props: HeaderProps) => {
|
|
17
18
|
const tabs: Tab[] = ['Dashboard Description', 'Data Table Settings', 'Dashboard Preview']
|
|
18
19
|
const { visualizationKey, subEditor } = props
|
|
19
|
-
const { config, setParentConfig, tabSelected } = useContext(DashboardContext)
|
|
20
|
+
const { config, setParentConfig, tabSelected, data } = useContext(DashboardContext)
|
|
20
21
|
if (!config) return null
|
|
21
22
|
const dispatch = useContext(DashboardDispatchContext)
|
|
22
23
|
const back = () => {
|
|
@@ -24,6 +25,22 @@ const Header = (props: HeaderProps) => {
|
|
|
24
25
|
const newConfig = _.cloneDeep(config)
|
|
25
26
|
newConfig.visualizations[visualizationKey].editing = false
|
|
26
27
|
dispatch({ type: 'SET_CONFIG', payload: newConfig })
|
|
28
|
+
|
|
29
|
+
// the Widget component will do a data fetch if no data is available for the visualization
|
|
30
|
+
// this is intended to help visualization developers.
|
|
31
|
+
type SampleData = Record<string, { sample: boolean }> & Object[]
|
|
32
|
+
if (Object.values(data).some((d: SampleData) => d.sample)) {
|
|
33
|
+
const sampleDataRemoved = Object.keys(data).reduce((acc, key) => {
|
|
34
|
+
if ((data[key] as SampleData).sample) {
|
|
35
|
+
acc[key] = []
|
|
36
|
+
} else {
|
|
37
|
+
acc[key] = data[key]
|
|
38
|
+
}
|
|
39
|
+
return acc
|
|
40
|
+
}, {})
|
|
41
|
+
|
|
42
|
+
dispatch({ type: 'SET_DATA', payload: sampleDataRemoved })
|
|
43
|
+
}
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
const changeConfigValue = (parentObj, key, value) => {
|
|
@@ -81,10 +98,18 @@ const Header = (props: HeaderProps) => {
|
|
|
81
98
|
<div className='heading-1'>
|
|
82
99
|
Dashboard Editor{' '}
|
|
83
100
|
<span className='small'>
|
|
84
|
-
<input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make
|
|
101
|
+
<input type='checkbox' onChange={handleCheck} checked={multiInitialized} disabled={multiInitialized} /> make
|
|
102
|
+
multidashboard
|
|
85
103
|
</span>
|
|
86
104
|
<br />
|
|
87
|
-
{
|
|
105
|
+
{
|
|
106
|
+
<input
|
|
107
|
+
type='text'
|
|
108
|
+
placeholder='Enter Dashboard Name Here'
|
|
109
|
+
defaultValue={config.dashboard?.title}
|
|
110
|
+
onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
|
|
111
|
+
/>
|
|
112
|
+
}
|
|
88
113
|
</div>
|
|
89
114
|
)}
|
|
90
115
|
{!subEditor && (
|
|
@@ -106,18 +131,34 @@ const Header = (props: HeaderProps) => {
|
|
|
106
131
|
})}
|
|
107
132
|
</ul>
|
|
108
133
|
<div className='heading-body'>
|
|
109
|
-
{tabSelected === 'Dashboard Description' &&
|
|
134
|
+
{tabSelected === 'Dashboard Description' && (
|
|
135
|
+
<input
|
|
136
|
+
type='text'
|
|
137
|
+
className='description-input'
|
|
138
|
+
placeholder='Type a dashboard description here.'
|
|
139
|
+
defaultValue={config.dashboard?.description}
|
|
140
|
+
onChange={e => changeConfigValue('dashboard', 'description', e.target.value)}
|
|
141
|
+
/>
|
|
142
|
+
)}
|
|
110
143
|
{tabSelected === 'Data Table Settings' && (
|
|
111
144
|
<>
|
|
112
145
|
<div className='wrap'>
|
|
113
146
|
<label>
|
|
114
|
-
<input
|
|
147
|
+
<input
|
|
148
|
+
type='checkbox'
|
|
149
|
+
defaultChecked={config.table.show}
|
|
150
|
+
onChange={e => changeConfigValue('table', 'show', e.target.checked)}
|
|
151
|
+
/>
|
|
115
152
|
Show Data Table(s)
|
|
116
153
|
</label>
|
|
117
154
|
<br />
|
|
118
155
|
|
|
119
156
|
<label>
|
|
120
|
-
<input
|
|
157
|
+
<input
|
|
158
|
+
type='checkbox'
|
|
159
|
+
defaultChecked={config.table.expanded}
|
|
160
|
+
onChange={e => changeConfigValue('table', 'expanded', e.target.checked)}
|
|
161
|
+
/>
|
|
121
162
|
Expanded by Default
|
|
122
163
|
</label>
|
|
123
164
|
<br />
|
|
@@ -125,19 +166,39 @@ const Header = (props: HeaderProps) => {
|
|
|
125
166
|
|
|
126
167
|
<div className='wrap'>
|
|
127
168
|
<label>
|
|
128
|
-
<input
|
|
169
|
+
<input
|
|
170
|
+
type='checkbox'
|
|
171
|
+
defaultChecked={config.table.limitHeight}
|
|
172
|
+
onChange={e => changeConfigValue('table', 'limitHeight', e.target.checked)}
|
|
173
|
+
/>
|
|
129
174
|
Limit Table Height
|
|
130
175
|
</label>
|
|
131
|
-
{config.table.limitHeight &&
|
|
176
|
+
{config.table.limitHeight && (
|
|
177
|
+
<input
|
|
178
|
+
className='table-height-input'
|
|
179
|
+
type='text'
|
|
180
|
+
placeholder='Height (px)'
|
|
181
|
+
defaultValue={config.table.height}
|
|
182
|
+
onChange={e => changeConfigValue('table', 'height', e.target.value)}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
132
185
|
</div>
|
|
133
186
|
|
|
134
187
|
<div className='wrap'>
|
|
135
188
|
<label>
|
|
136
|
-
<input
|
|
189
|
+
<input
|
|
190
|
+
type='checkbox'
|
|
191
|
+
defaultChecked={config.table.download}
|
|
192
|
+
onChange={e => changeConfigValue('table', 'download', e.target.checked)}
|
|
193
|
+
/>
|
|
137
194
|
Show Download CSV Link
|
|
138
195
|
</label>
|
|
139
196
|
<label>
|
|
140
|
-
<input
|
|
197
|
+
<input
|
|
198
|
+
type='checkbox'
|
|
199
|
+
defaultChecked={config.table.showDownloadUrl}
|
|
200
|
+
onChange={e => changeConfigValue('table', 'showDownloadUrl', e.target.checked)}
|
|
201
|
+
/>
|
|
141
202
|
Show URL to Automatically Updated Data
|
|
142
203
|
</label>
|
|
143
204
|
</div>
|
|
@@ -66,8 +66,16 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
68
|
<li className='nav-item d-flex mt-0'>
|
|
69
|
-
{canMoveLeft && editing &&
|
|
70
|
-
|
|
69
|
+
{canMoveLeft && editing && (
|
|
70
|
+
<button className='border-0' onClick={() => handleReorder(index, -1)}>
|
|
71
|
+
{'<'}
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
<div
|
|
75
|
+
className={`edit nav-link${active ? ' active' : ''}`}
|
|
76
|
+
aria-current={active ? 'page' : null}
|
|
77
|
+
onClick={onClick}
|
|
78
|
+
>
|
|
71
79
|
{editing ? (
|
|
72
80
|
<div className='d-flex'>
|
|
73
81
|
<input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
|
|
@@ -78,13 +86,17 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
78
86
|
) : (
|
|
79
87
|
<>
|
|
80
88
|
{name}
|
|
81
|
-
<button className='
|
|
89
|
+
<button className='btn btn-danger border-0 ml-1' onClick={handleRemove}>
|
|
82
90
|
X
|
|
83
91
|
</button>
|
|
84
92
|
</>
|
|
85
93
|
)}
|
|
86
94
|
</div>
|
|
87
|
-
{canMoveRight && editing &&
|
|
95
|
+
{canMoveRight && editing && (
|
|
96
|
+
<button className='border-0' onClick={() => handleReorder(index, 1)}>
|
|
97
|
+
{'>'}
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
88
100
|
</li>
|
|
89
101
|
)
|
|
90
102
|
}
|
|
@@ -92,7 +104,10 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
|
|
|
92
104
|
const MultiConfigTabs = () => {
|
|
93
105
|
const { config } = useContext(DashboardContext)
|
|
94
106
|
const dispatch = useContext(DashboardDispatchContext)
|
|
95
|
-
const tabs = useMemo<string[]>(
|
|
107
|
+
const tabs = useMemo<string[]>(
|
|
108
|
+
() => (config.multiDashboards || []).map(({ label }) => label),
|
|
109
|
+
[config.multiDashboards]
|
|
110
|
+
)
|
|
96
111
|
const activeTab = useMemo<number>(() => config.activeDashboard, [config.activeDashboard])
|
|
97
112
|
|
|
98
113
|
const saveAndLoad = (indexToSwitchTo: number) => {
|
|
@@ -104,7 +119,14 @@ const MultiConfigTabs = () => {
|
|
|
104
119
|
return (
|
|
105
120
|
<ul className='nav nav-tabs multi-config-tabs'>
|
|
106
121
|
{tabs.map((tab, index) => (
|
|
107
|
-
<Tab
|
|
122
|
+
<Tab
|
|
123
|
+
key={tab + index}
|
|
124
|
+
name={tab}
|
|
125
|
+
tabs={tabs}
|
|
126
|
+
index={index}
|
|
127
|
+
handleClick={() => saveAndLoad(index)}
|
|
128
|
+
active={index === activeTab}
|
|
129
|
+
/>
|
|
108
130
|
))}
|
|
109
131
|
<li className='nav-item'>
|
|
110
132
|
<button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
|
|
@@ -2,6 +2,7 @@ import { useContext, useMemo } from 'react'
|
|
|
2
2
|
import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
|
|
3
3
|
|
|
4
4
|
import './multiconfigtabs.styles.css'
|
|
5
|
+
import { updateQueryParam } from '@cdc/core/helpers/queryStringUtils'
|
|
5
6
|
|
|
6
7
|
const MultiTabs = () => {
|
|
7
8
|
const { config } = useContext(DashboardContext)
|
|
@@ -15,6 +16,7 @@ const MultiTabs = () => {
|
|
|
15
16
|
const load = (indexToSwitchTo: number, e) => {
|
|
16
17
|
e.preventDefault() // some form wrapper is causing this to act as a submit button
|
|
17
18
|
dispatch({ type: 'SWITCH_CONFIG', payload: indexToSwitchTo })
|
|
19
|
+
updateQueryParam('cove-tab', indexToSwitchTo)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
if (!config.multiDashboards) return null
|
|
@@ -13,11 +13,6 @@
|
|
|
13
13
|
&:hover {
|
|
14
14
|
:is(button) {
|
|
15
15
|
display: inline-block;
|
|
16
|
-
color: var(--gray);
|
|
17
|
-
&:hover {
|
|
18
|
-
color: var(--black);
|
|
19
|
-
font-weight: bold;
|
|
20
|
-
}
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
background: var(--quaternary);
|
|
@@ -32,12 +27,10 @@
|
|
|
32
27
|
color: white;
|
|
33
28
|
font-weight: bold;
|
|
34
29
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
color: var(--black);
|
|
40
|
-
font-weight: bold;
|
|
30
|
+
.btn-danger {
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
padding: 0px 5px;
|
|
33
|
+
font-size: inherit;
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
36
|
.add {
|
package/src/components/Row.tsx
CHANGED
|
@@ -122,39 +122,82 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
const layoutList = [
|
|
125
|
-
<li
|
|
125
|
+
<li
|
|
126
|
+
className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
127
|
+
onClick={() => setRowLayout([12])}
|
|
128
|
+
key='12'
|
|
129
|
+
title='1 Column'
|
|
130
|
+
>
|
|
126
131
|
<OneColIcon />
|
|
127
132
|
</li>,
|
|
128
|
-
<li
|
|
133
|
+
<li
|
|
134
|
+
className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
135
|
+
onClick={() => setRowLayout([6, 6])}
|
|
136
|
+
key='66'
|
|
137
|
+
title='2 Columns'
|
|
138
|
+
>
|
|
129
139
|
<TwoColIcon />
|
|
130
140
|
</li>,
|
|
131
|
-
<li
|
|
141
|
+
<li
|
|
142
|
+
className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
143
|
+
onClick={() => setRowLayout([4, 4, 4])}
|
|
144
|
+
key='444'
|
|
145
|
+
title='3 Columns'
|
|
146
|
+
>
|
|
132
147
|
<ThreeColIcon />
|
|
133
148
|
</li>,
|
|
134
|
-
<li
|
|
149
|
+
<li
|
|
150
|
+
className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
151
|
+
onClick={() => setRowLayout([4, 8])}
|
|
152
|
+
key='48'
|
|
153
|
+
title='2 Columns'
|
|
154
|
+
>
|
|
135
155
|
<FourEightColIcon />
|
|
136
156
|
</li>,
|
|
137
|
-
<li
|
|
157
|
+
<li
|
|
158
|
+
className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
159
|
+
onClick={() => setRowLayout([8, 4])}
|
|
160
|
+
key='84'
|
|
161
|
+
title='2 Columns'
|
|
162
|
+
>
|
|
138
163
|
<EightFourColIcon />
|
|
139
164
|
</li>,
|
|
140
|
-
<li
|
|
165
|
+
<li
|
|
166
|
+
className={curr === 'toggle' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
167
|
+
onClick={() => setRowLayout([12, 12, 12], true)}
|
|
168
|
+
key='toggle'
|
|
169
|
+
title='Toggle between up to three visualizations'
|
|
170
|
+
>
|
|
141
171
|
<ToggleIcon />
|
|
142
172
|
</li>
|
|
143
173
|
]
|
|
144
174
|
|
|
145
175
|
return (
|
|
146
176
|
<nav className='row-menu'>
|
|
147
|
-
<
|
|
148
|
-
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
149
|
-
</div>
|
|
177
|
+
<ul className='row-menu__flyout'>{layoutList}</ul>
|
|
150
178
|
<div className='spacer'></div>
|
|
151
|
-
<button
|
|
179
|
+
<button
|
|
180
|
+
className={`btn btn-primary row-menu__btn border-0`}
|
|
181
|
+
title='Move Row Up'
|
|
182
|
+
onClick={() => moveRow('up')}
|
|
183
|
+
disabled={rowIdx === 0}
|
|
184
|
+
>
|
|
152
185
|
<Icon display='caretUp' color='#fff' size={25} />
|
|
153
186
|
</button>
|
|
154
|
-
<button
|
|
187
|
+
<button
|
|
188
|
+
className={'btn btn-primary row-menu__btn border-0'}
|
|
189
|
+
title='Move Row Down'
|
|
190
|
+
onClick={() => moveRow('down')}
|
|
191
|
+
disabled={rowIdx + 1 === rows.length}
|
|
192
|
+
>
|
|
155
193
|
<Icon display='caretDown' color='#fff' size={25} />
|
|
156
194
|
</button>
|
|
157
|
-
<button
|
|
195
|
+
<button
|
|
196
|
+
className={'btn btn-danger row-menu__btn row-menu__btn--remove border-0'}
|
|
197
|
+
title='Delete Row'
|
|
198
|
+
onClick={deleteRow}
|
|
199
|
+
disabled={rowIdx === 0 && rows.length === 1}
|
|
200
|
+
>
|
|
158
201
|
<Icon display='close' color='#fff' size={25} />
|
|
159
202
|
</button>
|
|
160
203
|
</nav>
|
|
@@ -177,7 +220,10 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
|
|
|
177
220
|
visualizationType: type,
|
|
178
221
|
editing: true
|
|
179
222
|
}
|
|
180
|
-
dispatch({
|
|
223
|
+
dispatch({
|
|
224
|
+
type: 'ADD_FOOTNOTE',
|
|
225
|
+
payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization }
|
|
226
|
+
})
|
|
181
227
|
} else {
|
|
182
228
|
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
|
|
183
229
|
}
|
|
@@ -119,7 +119,6 @@ const VisualizationsPanel = () => {
|
|
|
119
119
|
<Widget addVisualization={() => addVisualization('dashboardFilters', '')} type='dashboardFilters' />
|
|
120
120
|
<Widget addVisualization={() => addVisualization('table', '')} type='table' />
|
|
121
121
|
</div>
|
|
122
|
-
<span className='subheading-3'>Advanced</span>
|
|
123
122
|
<AdvancedEditor
|
|
124
123
|
loadConfig={loadConfig}
|
|
125
124
|
config={config}
|
|
@@ -5,6 +5,7 @@ import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
|
5
5
|
import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
|
|
6
6
|
|
|
7
7
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
8
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
8
9
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
10
|
import { AnyVisualization } from '@cdc/core/types/Visualization'
|
|
10
11
|
import { iconHash } from '../helpers/iconHash'
|
|
@@ -39,7 +40,7 @@ type WidgetProps = {
|
|
|
39
40
|
|
|
40
41
|
const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
41
42
|
const { overlay } = useGlobalContext()
|
|
42
|
-
const { config } = useContext(DashboardContext)
|
|
43
|
+
const { config, data } = useContext(DashboardContext)
|
|
43
44
|
const dispatch = useContext(DashboardDispatchContext)
|
|
44
45
|
|
|
45
46
|
const transform = new DataTransform()
|
|
@@ -79,9 +80,30 @@ const Widget = ({ widgetConfig, addVisualization, type }: WidgetProps) => {
|
|
|
79
80
|
})
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
const changeDataLimit = (dataUrl, limit) => {
|
|
84
|
+
const url = new URL(dataUrl)
|
|
85
|
+
url.searchParams.set('$limit', limit)
|
|
86
|
+
// Replace encoded $ with actual $ for the URL
|
|
87
|
+
return url.href.replace(/%24/g, '$')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const loadSampleData = () => {
|
|
91
|
+
const dataKey = config.rows[widgetConfig.rowIdx]?.dataKey || widgetConfig?.dataKey
|
|
92
|
+
const dataset = config.datasets[dataKey]
|
|
93
|
+
const _data = data[dataset.dataUrl]
|
|
94
|
+
if (_data && !_data.length) {
|
|
95
|
+
const url = changeDataLimit(dataset.dataUrl, 100)
|
|
96
|
+
fetchRemoteData(url).then(responseData => {
|
|
97
|
+
responseData.sample = true
|
|
98
|
+
dispatch({ type: 'SET_DATA', payload: { ...data, [dataKey]: responseData } })
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
const editWidget = () => {
|
|
83
104
|
if (!widgetConfig) return
|
|
84
105
|
dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: widgetConfig.uid, configureData: { editing: true } } })
|
|
106
|
+
loadSampleData()
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
let isConfigurationReady = false
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfigRow } from '../types/ConfigRow'
|
|
2
2
|
|
|
3
|
+
// returns a dictionary of widget names and their corresponding row and column index
|
|
3
4
|
export const getVizRowColumnLocator = (rows: ConfigRow[]): Record<string, { row: number; column: number }> =>
|
|
4
5
|
rows.reduce((acc, curr, index) => {
|
|
5
6
|
curr.columns?.forEach((column, columnIndex) => {
|
|
@@ -6,6 +6,7 @@ import { APIFilter } from '../types/APIFilter'
|
|
|
6
6
|
|
|
7
7
|
export const loadAPIFiltersFactory = (
|
|
8
8
|
dispatch: Function,
|
|
9
|
+
dispatchErrorMessages: Function,
|
|
9
10
|
setAPIFilterDropdowns: Function,
|
|
10
11
|
autoLoadFilterIndexes: number[]
|
|
11
12
|
) => {
|
|
@@ -36,7 +37,6 @@ export const loadAPIFiltersFactory = (
|
|
|
36
37
|
.then(data => {
|
|
37
38
|
if (!Array.isArray(data)) {
|
|
38
39
|
console.error('COVE only supports response data in the shape Array<Object>')
|
|
39
|
-
return
|
|
40
40
|
}
|
|
41
41
|
const [_key, index] = toFetch[endpoint]
|
|
42
42
|
const apiFilter = filterLookup.get(_key) as APIFilter
|
|
@@ -51,7 +51,12 @@ export const loadAPIFiltersFactory = (
|
|
|
51
51
|
)
|
|
52
52
|
sharedFilters[index] = newDefaultSelectedFilter
|
|
53
53
|
})
|
|
54
|
-
.catch(
|
|
54
|
+
.catch(() => {
|
|
55
|
+
dispatchErrorMessages({
|
|
56
|
+
type: 'ADD_ERROR_MESSAGE',
|
|
57
|
+
payload: 'There was a problem returning data. Please try again.'
|
|
58
|
+
})
|
|
59
|
+
})
|
|
55
60
|
.finally(() => {
|
|
56
61
|
resolve()
|
|
57
62
|
})
|
|
@@ -66,6 +66,7 @@ global.fetch = fetch
|
|
|
66
66
|
|
|
67
67
|
describe('loadAPIFiltersFactory', () => {
|
|
68
68
|
const dispatch = vi.fn()
|
|
69
|
+
const dispatchErrorMessages = vi.fn()
|
|
69
70
|
const setAPIFilterDropdowns = vi.fn()
|
|
70
71
|
const apiFilterDropdowns = {
|
|
71
72
|
'cdc.gov/filters/Sex': [
|
|
@@ -76,7 +77,7 @@ describe('loadAPIFiltersFactory', () => {
|
|
|
76
77
|
afterEach(() => {
|
|
77
78
|
vi.restoreAllMocks()
|
|
78
79
|
})
|
|
79
|
-
const loadAPIFilters = loadAPIFiltersFactory(dispatch, setAPIFilterDropdowns, [2])
|
|
80
|
+
const loadAPIFilters = loadAPIFiltersFactory(dispatch, dispatchErrorMessages, setAPIFilterDropdowns, [2])
|
|
80
81
|
it('creates a function', () => {
|
|
81
82
|
expect(typeof loadAPIFilters).toEqual('function')
|
|
82
83
|
})
|
package/src/scss/grid.scss
CHANGED
|
@@ -51,27 +51,25 @@ $red: #f74242;
|
|
|
51
51
|
margin-top: 2em;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.row-menu__btn:hover .row-menu__flyout {
|
|
55
|
-
transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
56
|
-
width: 180px;
|
|
57
|
-
|
|
58
|
-
li {
|
|
59
|
-
display: flex;
|
|
60
|
-
}
|
|
61
54
|
|
|
62
|
-
li + li {
|
|
63
|
-
margin-left: 0.3em;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
55
|
|
|
67
56
|
.row-menu__flyout {
|
|
57
|
+
background-color: var(--blue);
|
|
58
|
+
$blue: #005eaa;
|
|
59
|
+
background-color: #c2c2c2;
|
|
60
|
+
border-radius: 0.2em 0.2em 0 0;
|
|
61
|
+
outline: none;
|
|
62
|
+
|
|
63
|
+
padding: 0.2em 0.3em;
|
|
64
|
+
fill: #fff;
|
|
68
65
|
list-style: none;
|
|
69
66
|
display: flex;
|
|
70
67
|
justify-content: flex-start;
|
|
71
68
|
overflow: hidden;
|
|
69
|
+
transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
72
70
|
transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
73
71
|
z-index: 1;
|
|
74
|
-
width:
|
|
72
|
+
width: 35px;
|
|
75
73
|
|
|
76
74
|
li:not(.current) {
|
|
77
75
|
display: none;
|
|
@@ -89,6 +87,18 @@ $red: #f74242;
|
|
|
89
87
|
.row-menu__list--item {
|
|
90
88
|
display: flex;
|
|
91
89
|
}
|
|
90
|
+
&:hover {
|
|
91
|
+
transition: width 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
92
|
+
width: 180px;
|
|
93
|
+
background-color: lighten($blue, 8%);
|
|
94
|
+
li {
|
|
95
|
+
display: flex;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
li + li {
|
|
99
|
+
margin-left: 0.3em;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
.row-menu__btn {
|
|
@@ -96,7 +106,6 @@ $red: #f74242;
|
|
|
96
106
|
border-radius: 0.2em 0.2em 0 0;
|
|
97
107
|
outline: none;
|
|
98
108
|
transition: background-color 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
99
|
-
cursor: pointer;
|
|
100
109
|
padding: 0.2em 0.3em;
|
|
101
110
|
display: flex;
|
|
102
111
|
fill: #fff;
|
|
@@ -294,11 +303,6 @@ $red: #f74242;
|
|
|
294
303
|
}
|
|
295
304
|
}
|
|
296
305
|
|
|
297
|
-
.btn.add-row {
|
|
298
|
-
font-size: 1.1em;
|
|
299
|
-
width: 100%;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
306
|
.btn--fluid {
|
|
303
307
|
@extend .btn;
|
|
304
308
|
width: 100%;
|
|
@@ -359,11 +363,6 @@ $red: #f74242;
|
|
|
359
363
|
|
|
360
364
|
&:hover {
|
|
361
365
|
.row-menu .row-menu__btn {
|
|
362
|
-
background-color: var(--blue);
|
|
363
|
-
$blue: #005eaa;
|
|
364
|
-
&:hover {
|
|
365
|
-
background-color: lighten($blue, 8%);
|
|
366
|
-
}
|
|
367
366
|
|
|
368
367
|
&.row-menu__btn--edit {
|
|
369
368
|
background-color: transparent;
|
package/src/scss/main.scss
CHANGED
|
@@ -138,21 +138,6 @@
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
.btn {
|
|
141
|
-
background: #005eaa;
|
|
142
|
-
color: #fff;
|
|
143
|
-
border: 0;
|
|
144
|
-
padding: 0.4em 0.8em;
|
|
145
|
-
font-size: 0.9em;
|
|
146
|
-
display: block;
|
|
147
|
-
border-radius: 5px;
|
|
148
|
-
transition: 0.1s all;
|
|
149
|
-
cursor: pointer;
|
|
150
|
-
|
|
151
|
-
&[disabled] {
|
|
152
|
-
opacity: 0.5;
|
|
153
|
-
z-index: -1;
|
|
154
|
-
position: relative;
|
|
155
|
-
}
|
|
156
141
|
|
|
157
142
|
// Expand and Collapse Buttons for Multiviz Dashboard
|
|
158
143
|
&.expand-collapse-buttons {
|
|
@@ -260,15 +245,6 @@
|
|
|
260
245
|
width: 100%;
|
|
261
246
|
}
|
|
262
247
|
|
|
263
|
-
.cove-dashboard-filters-container {
|
|
264
|
-
z-index: 5;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.cove-dashboard-filters {
|
|
268
|
-
display: inline-flex;
|
|
269
|
-
margin: 1em;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
248
|
@include breakpointClass(md) {
|
|
273
249
|
.dashboard-row {
|
|
274
250
|
flex-direction: row;
|
|
@@ -301,9 +277,6 @@
|
|
|
301
277
|
}
|
|
302
278
|
}
|
|
303
279
|
|
|
304
|
-
.dashboard-filters-section {
|
|
305
|
-
margin: 0 0 1em;
|
|
306
|
-
}
|
|
307
280
|
.builder-grid .editor-heading {
|
|
308
281
|
position: relative;
|
|
309
282
|
right: -2em;
|
|
@@ -105,8 +105,14 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
|
|
|
105
105
|
_.remove(newMultiDashboards, (_, index) => {
|
|
106
106
|
return index === action.payload
|
|
107
107
|
})
|
|
108
|
+
const config = {
|
|
109
|
+
...state.config,
|
|
110
|
+
multiDashboards: newMultiDashboards,
|
|
111
|
+
...newMultiDashboards[0],
|
|
112
|
+
activeDashboard: 0
|
|
113
|
+
}
|
|
108
114
|
if (newMultiDashboards.length === 0) return { ...state, config: _.omit(state.config, 'multiDashboards') }
|
|
109
|
-
return applyMultiDashboards(state, newMultiDashboards)
|
|
115
|
+
return applyMultiDashboards({ ...state, config }, newMultiDashboards)
|
|
110
116
|
}
|
|
111
117
|
case 'RENAME_DASHBOARD_TAB': {
|
|
112
118
|
const newMultiDashboards = state.config.multiDashboards.map(dashboard => {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Action } from '@cdc/core/types/Action'
|
|
2
|
+
|
|
3
|
+
type ADD_ERROR_MESSAGE = Action<'ADD_ERROR_MESSAGE', string>
|
|
4
|
+
type DISMISS_ERROR_MESSAGE = Action<'DISMISS_ERROR_MESSAGE', number>
|
|
5
|
+
|
|
6
|
+
type errorMessagesActions = ADD_ERROR_MESSAGE | DISMISS_ERROR_MESSAGE
|
|
7
|
+
export default errorMessagesActions
|