@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/dashboard",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.11",
|
|
4
4
|
"description": "React component for combining multiple visualizations into a single dashboard",
|
|
5
5
|
"moduleName": "CdcDashboard",
|
|
6
6
|
"main": "dist/cdcdashboard",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cdc/chart": "^4.24.
|
|
31
|
-
"@cdc/core": "^4.24.
|
|
32
|
-
"@cdc/data-bite": "^4.24.
|
|
33
|
-
"@cdc/filtered-text": "^4.24.
|
|
34
|
-
"@cdc/map": "^4.24.
|
|
35
|
-
"@cdc/markup-include": "^4.24.
|
|
36
|
-
"@cdc/waffle-chart": "^4.24.
|
|
30
|
+
"@cdc/chart": "^4.24.11",
|
|
31
|
+
"@cdc/core": "^4.24.11",
|
|
32
|
+
"@cdc/data-bite": "^4.24.11",
|
|
33
|
+
"@cdc/filtered-text": "^4.24.11",
|
|
34
|
+
"@cdc/map": "^4.24.11",
|
|
35
|
+
"@cdc/markup-include": "^4.24.11",
|
|
36
|
+
"@cdc/waffle-chart": "^4.24.11",
|
|
37
37
|
"html-react-parser": "^3.0.8",
|
|
38
38
|
"js-base64": "^2.5.2",
|
|
39
39
|
"papaparse": "^5.3.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"react": "^18.2.0",
|
|
50
50
|
"react-dom": "^18.2.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "9ab5ee9b2b0ef7321a66a2104be6ce8899ec3808"
|
|
53
53
|
}
|
package/src/CdcDashboard.tsx
CHANGED
|
@@ -14,39 +14,47 @@ import { DashboardConfig } from './types/DashboardConfig'
|
|
|
14
14
|
import { coveUpdateWorker } from '@cdc/core/helpers/coveUpdateWorker'
|
|
15
15
|
import _ from 'lodash'
|
|
16
16
|
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
17
|
+
import { getQueryParams } from '@cdc/core/helpers/queryStringUtils'
|
|
17
18
|
|
|
18
19
|
type MultiDashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
19
20
|
configUrl?: string
|
|
20
21
|
config?: MultiDashboardConfig
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
24
|
+
const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({
|
|
25
|
+
configUrl,
|
|
26
|
+
config: editorConfig,
|
|
27
|
+
isEditor,
|
|
28
|
+
isDebug
|
|
29
|
+
}) => {
|
|
24
30
|
const [initial, setInitial] = useState<InitialState>(undefined)
|
|
25
31
|
|
|
26
|
-
const getSelectedConfig = (config: MultiDashboardConfig
|
|
32
|
+
const getSelectedConfig = (config: MultiDashboardConfig): number | null => {
|
|
27
33
|
if (!config.multiDashboards) return null
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
if (foundConfig > -1) return foundConfig
|
|
34
|
+
// if query parameter, select based on query parameter
|
|
35
|
+
const selectedConfig = getQueryParams()['cove-tab']
|
|
36
|
+
if (selectedConfig !== undefined && Number(selectedConfig) < config.multiDashboards.length) {
|
|
37
|
+
return Number(selectedConfig)
|
|
34
38
|
}
|
|
35
39
|
// else select the first available
|
|
36
40
|
return 0
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
const formatInitialState = (
|
|
43
|
+
const formatInitialState = (
|
|
44
|
+
newConfig: MultiDashboardConfig | DashboardConfig,
|
|
45
|
+
datasets: Record<string, Object[]>
|
|
46
|
+
) => {
|
|
40
47
|
const [config, filteredData] = getUpdateConfig(initialState)(newConfig, datasets)
|
|
41
48
|
const versionedConfig = coveUpdateWorker(config)
|
|
42
49
|
return { ...initialState, config: versionedConfig, filteredData, data: datasets }
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
const loadConfig = async (
|
|
52
|
+
const loadConfig = async () => {
|
|
46
53
|
const _config: MultiDashboardConfig = editorConfig || (await (await fetch(configUrl)).json())
|
|
47
|
-
const selected = getSelectedConfig(_config
|
|
54
|
+
const selected = getSelectedConfig(_config)
|
|
48
55
|
|
|
49
|
-
const { newConfig, datasets } =
|
|
56
|
+
const { newConfig, datasets } =
|
|
57
|
+
selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
|
|
50
58
|
setInitial(formatInitialState(newConfig, datasets))
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -101,7 +109,14 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
101
109
|
newConfig.visualizations[vizKey] = { ...newConfig.visualizations[vizKey], ...newData }
|
|
102
110
|
})
|
|
103
111
|
|
|
104
|
-
const blankFields = {
|
|
112
|
+
const blankFields = {
|
|
113
|
+
data: [],
|
|
114
|
+
dataUrl: '',
|
|
115
|
+
dataFileName: '',
|
|
116
|
+
dataFileSourceType: '',
|
|
117
|
+
dataDescription: {},
|
|
118
|
+
formattedData: []
|
|
119
|
+
}
|
|
105
120
|
newConfig = { ...newConfig, ...blankFields }
|
|
106
121
|
|
|
107
122
|
if (newConfig.dashboard.filters) {
|
|
@@ -122,7 +137,13 @@ const MultiDashboardWrapper: React.FC<MultiDashboardProps> = ({ configUrl, confi
|
|
|
122
137
|
|
|
123
138
|
const loadMultiDashboard = async (multiConfig: MultiDashboardConfig, selectedConfig: number) => {
|
|
124
139
|
const selectedDashboard = multiConfig.multiDashboards[selectedConfig]
|
|
125
|
-
|
|
140
|
+
const newConfig = {
|
|
141
|
+
...defaults,
|
|
142
|
+
...multiConfig,
|
|
143
|
+
...selectedDashboard,
|
|
144
|
+
multiDashboards: multiConfig.multiDashboards,
|
|
145
|
+
activeDashboard: selectedConfig
|
|
146
|
+
} as MultiDashboardConfig
|
|
126
147
|
return await loadData(newConfig)
|
|
127
148
|
}
|
|
128
149
|
|
|
@@ -35,6 +35,7 @@ import './scss/main.scss'
|
|
|
35
35
|
|
|
36
36
|
import VisualizationsPanel from './components/VisualizationsPanel'
|
|
37
37
|
import dashboardReducer from './store/dashboard.reducer'
|
|
38
|
+
import errorMessagesReducer from './store/errorMessage/errorMessage.reducer'
|
|
38
39
|
import { filterData } from './helpers/filterData'
|
|
39
40
|
import { getVizKeys } from './helpers/getVizKeys'
|
|
40
41
|
import Title from '@cdc/core/components/ui/Title'
|
|
@@ -65,6 +66,7 @@ import ExpandCollapseButtons from './components/ExpandCollapseButtons'
|
|
|
65
66
|
import { hasDashboardApplyBehavior } from './helpers/hasDashboardApplyBehavior'
|
|
66
67
|
import { loadAPIFiltersFactory } from './helpers/loadAPIFilters'
|
|
67
68
|
import Loader from '@cdc/core/components/Loader'
|
|
69
|
+
import Alert from '@cdc/core/components/Alert'
|
|
68
70
|
|
|
69
71
|
type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
70
72
|
initialState: InitialState
|
|
@@ -72,6 +74,7 @@ type DashboardProps = Omit<WCMSProps, 'configUrl'> & {
|
|
|
72
74
|
|
|
73
75
|
export default function CdcDashboard({ initialState, isEditor = false, isDebug = false }: DashboardProps) {
|
|
74
76
|
const [state, dispatch] = useReducer(dashboardReducer, initialState)
|
|
77
|
+
const [errorMessages, dispatchErrorMessages] = useReducer(errorMessagesReducer, [])
|
|
75
78
|
const editorContext = useContext(EditorContext)
|
|
76
79
|
const [apiFilterDropdowns, setAPIFilterDropdowns] = useState<APIFilterDropdowns>({})
|
|
77
80
|
const [currentViewport, setCurrentViewport] = useState<ViewPort>('lg')
|
|
@@ -97,7 +100,12 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
97
100
|
.reduce((acc, viz: DashboardFilters) => (viz.autoLoad ? [...acc, ...viz.sharedFilterIndexes] : acc), [])
|
|
98
101
|
}, [state.config.visualizations])
|
|
99
102
|
|
|
100
|
-
const loadAPIFilters = loadAPIFiltersFactory(
|
|
103
|
+
const loadAPIFilters = loadAPIFiltersFactory(
|
|
104
|
+
dispatch,
|
|
105
|
+
dispatchErrorMessages,
|
|
106
|
+
setAPIFilterDropdowns,
|
|
107
|
+
autoLoadFilterIndexes
|
|
108
|
+
)
|
|
101
109
|
|
|
102
110
|
const reloadURLData = async (newFilters?: SharedFilter[]) => {
|
|
103
111
|
const config = _.cloneDeep(state.config)
|
|
@@ -163,20 +171,28 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
163
171
|
)
|
|
164
172
|
|
|
165
173
|
setAPILoading(true)
|
|
166
|
-
await fetchRemoteData(dataUrlFinal)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
await fetchRemoteData(dataUrlFinal)
|
|
175
|
+
.then(responseData => {
|
|
176
|
+
let data: any[] = responseData
|
|
177
|
+
if (responseData && dataset.dataDescription) {
|
|
178
|
+
try {
|
|
179
|
+
data = transform.autoStandardize(data)
|
|
180
|
+
data = transform.developerStandardize(data, dataset.dataDescription)
|
|
181
|
+
} catch (e) {
|
|
182
|
+
//Data not able to be standardized, leave as is
|
|
183
|
+
console.error('Error standardizing data:', e)
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
newDatasets[datasetKey].data = data
|
|
187
|
+
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
188
|
+
newData[datasetKey] = data
|
|
189
|
+
})
|
|
190
|
+
.catch(e => {
|
|
191
|
+
console.error(e)
|
|
192
|
+
newDatasets[datasetKey].data = []
|
|
193
|
+
newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
|
|
194
|
+
newData[datasetKey] = []
|
|
195
|
+
})
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
198
|
}
|
|
@@ -492,6 +508,14 @@ export default function CdcDashboard({ initialState, isEditor = false, isDebug =
|
|
|
492
508
|
{isEditor && <Header />}
|
|
493
509
|
{apiLoading && <Loader fullScreen={true} />}
|
|
494
510
|
<MultiTabs isEditor={isEditor && !isPreview} />
|
|
511
|
+
{errorMessages.map((message, index) => (
|
|
512
|
+
<Alert
|
|
513
|
+
type='danger'
|
|
514
|
+
onDismiss={() => dispatchErrorMessages({ type: 'DISMISS_ERROR_MESSAGE', payload: index })}
|
|
515
|
+
message={message}
|
|
516
|
+
autoDismiss={true}
|
|
517
|
+
/>
|
|
518
|
+
))}
|
|
495
519
|
<Layout.Responsive isEditor={isEditor}>
|
|
496
520
|
<div className={`cdc-dashboard-inner-container${isEditor ? ' is-editor' : ''}`}>
|
|
497
521
|
<Title
|
|
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
2
2
|
import { faker } from '@faker-js/faker'
|
|
3
3
|
import APIFiltersMapData from './_mock/api-filter-map.json'
|
|
4
4
|
import APIFiltersChartData from './_mock/api-filter-chart.json'
|
|
5
|
+
import APIFilterErrorConfig from './_mock/api-filter-error.json'
|
|
5
6
|
import ExampleConfig_1 from './_mock/dashboard-gallery.json'
|
|
6
7
|
import ExampleConfig_2 from './_mock/dashboard-2.json'
|
|
7
8
|
import ExampleConfig_3 from './_mock/dashboard_no_filter.json'
|
|
@@ -66,6 +67,13 @@ export const Dashboard_Filters: Story = {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
export const API_Filter_Error: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
config: APIFilterErrorConfig,
|
|
73
|
+
isEditor: false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
export const StandAloneTable: Story = {
|
|
70
78
|
args: {
|
|
71
79
|
config: StandaloneTable,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dashboard": {
|
|
3
|
+
"theme": "theme-blue",
|
|
4
|
+
"sharedFilters": [
|
|
5
|
+
{
|
|
6
|
+
"key": "New Dashboard Filter 1",
|
|
7
|
+
"showDropdown": true,
|
|
8
|
+
"type": "urlfilter",
|
|
9
|
+
"apiFilter": {
|
|
10
|
+
"apiEndpoint": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_t",
|
|
11
|
+
"valueSelector": "",
|
|
12
|
+
"textSelector": ""
|
|
13
|
+
},
|
|
14
|
+
"tier": 1
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"rows": [{ "columns": [{ "width": 12, "widget": "dashboardFilters1730831317688" }, {}, {}] }],
|
|
19
|
+
"visualizations": {
|
|
20
|
+
"dashboardFilters1730831317688": {
|
|
21
|
+
"filters": [],
|
|
22
|
+
"filterBehavior": "Filter Change",
|
|
23
|
+
"newViz": true,
|
|
24
|
+
"openModal": true,
|
|
25
|
+
"uid": "dashboardFilters1730831317688",
|
|
26
|
+
"type": "dashboardFilters",
|
|
27
|
+
"sharedFilterIndexes": [0],
|
|
28
|
+
"visualizationType": "dashboardFilters"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"table": {
|
|
32
|
+
"label": "Data Table",
|
|
33
|
+
"show": true,
|
|
34
|
+
"showDownloadUrl": false,
|
|
35
|
+
"showDownloadLinkBelow": true,
|
|
36
|
+
"showVertical": true
|
|
37
|
+
},
|
|
38
|
+
"newViz": true,
|
|
39
|
+
"datasets": {
|
|
40
|
+
"https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic": {
|
|
41
|
+
"dataFileSize": 35061,
|
|
42
|
+
"dataFileName": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic",
|
|
43
|
+
"dataFileSourceType": "url",
|
|
44
|
+
"dataFileFormat": "JSON",
|
|
45
|
+
"preview": true,
|
|
46
|
+
"dataUrl": "https://nccd-cove-public-api.apps.ecpaas-dev.cdc.gov/od-public?$datakey=brfss_prevalence_cove_explore_by_topic"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"isResponsiveTicks": false,
|
|
50
|
+
"type": "dashboard",
|
|
51
|
+
"barThickness": "0.37",
|
|
52
|
+
"xAxis": { "type": "categorical", "size": 75, "maxTickRotation": 45, "labelOffset": 0 },
|
|
53
|
+
"runtime": {},
|
|
54
|
+
"version": "4.24.10"
|
|
55
|
+
}
|
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
"valueColumns": ["age", "color"]
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
"columns": {
|
|
33
|
+
"columns": {
|
|
34
|
+
"other": {
|
|
35
|
+
"name": "other",
|
|
36
|
+
"dataTable": false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
34
39
|
"dataFormat": {},
|
|
35
40
|
"visualizationType": "table",
|
|
36
41
|
"dataDescription": {
|
|
@@ -59,10 +64,10 @@
|
|
|
59
64
|
"datasets": {
|
|
60
65
|
"valid-data-chart.csv": {
|
|
61
66
|
"data": [
|
|
62
|
-
{ "name": "John", "age": 25, "color": "blue", "city": "New York" },
|
|
63
|
-
{ "name": "Jane", "age": 27, "color": "red", "city": "New York" },
|
|
64
|
-
{ "name": "Jane", "age": 30, "color": "yellow", "city": "San Francisco" },
|
|
65
|
-
{ "name": "John", "age": 31, "color": "green", "city": "San Francisco" }
|
|
67
|
+
{ "name": "John", "age": 25, "color": "blue", "other": "no", "city": "New York" },
|
|
68
|
+
{ "name": "Jane", "age": 27, "color": "red", "other": "yes", "city": "New York" },
|
|
69
|
+
{ "name": "Jane", "age": 30, "color": "yellow", "other": "no", "city": "San Francisco" },
|
|
70
|
+
{ "name": "John", "age": 31, "color": "green", "other": "yes", "city": "San Francisco" }
|
|
66
71
|
],
|
|
67
72
|
"dataFileSize": 178,
|
|
68
73
|
"dataFileName": "valid-data-chart.csv",
|
|
@@ -2,22 +2,29 @@ import React from 'react'
|
|
|
2
2
|
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
3
3
|
import { SharedFilter } from '../../types/SharedFilter'
|
|
4
4
|
import { APIFilterDropdowns, DropdownOptions } from './DashboardFiltersWrapper'
|
|
5
|
-
import NestedDropdown from '../../../../core/components/NestedDropdown/NestedDropdown'
|
|
6
5
|
import { FILTER_STYLE } from '../../types/FilterStyles'
|
|
7
6
|
import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
|
|
7
|
+
import NestedDropdown from '@cdc/core/components/NestedDropdown'
|
|
8
|
+
import { MouseEventHandler } from 'react'
|
|
8
9
|
|
|
9
10
|
type DashboardFilterProps = {
|
|
10
11
|
show: number[]
|
|
11
12
|
filters: SharedFilter[]
|
|
12
13
|
apiFilterDropdowns: APIFilterDropdowns
|
|
13
14
|
handleOnChange: (index: number, value: string | string[]) => void
|
|
15
|
+
showSubmit: boolean
|
|
16
|
+
applyFilters: MouseEventHandler<HTMLButtonElement>
|
|
17
|
+
applyFiltersButtonText?: string
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
17
21
|
show,
|
|
18
22
|
filters: sharedFilters,
|
|
19
23
|
apiFilterDropdowns,
|
|
20
|
-
handleOnChange
|
|
24
|
+
handleOnChange,
|
|
25
|
+
showSubmit,
|
|
26
|
+
applyFilters,
|
|
27
|
+
applyFiltersButtonText
|
|
21
28
|
}) => {
|
|
22
29
|
const nullVal = (filter: SharedFilter) => {
|
|
23
30
|
const val = filter.queuedActive || filter.active
|
|
@@ -38,10 +45,9 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
return (
|
|
41
|
-
|
|
48
|
+
<form className='d-flex flex-wrap'>
|
|
42
49
|
{sharedFilters.map((filter, filterIndex) => {
|
|
43
50
|
const urlFilterType = filter.type === 'urlfilter'
|
|
44
|
-
|
|
45
51
|
if (
|
|
46
52
|
(!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
|
|
47
53
|
(show && !show.includes(filterIndex))
|
|
@@ -87,49 +93,58 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
|
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
return filter.filterStyle === FILTER_STYLE.multiSelect ? (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
<div className='form-group mr-3 mb-1' key={`${filter.key}-filtersection-${filterIndex}`}>
|
|
97
|
+
<MultiSelect
|
|
98
|
+
label={filter.key}
|
|
99
|
+
options={multiValues}
|
|
100
|
+
fieldName={filterIndex}
|
|
101
|
+
updateField={updateField}
|
|
102
|
+
selected={filter.active as string[]}
|
|
103
|
+
limit={filter.selectLimit || 5}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
99
106
|
) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
<div className='form-group mr-3 mb-1' key={`${filter.key}-filtersection-${filterIndex}`}>
|
|
108
|
+
<NestedDropdown
|
|
109
|
+
activeGroup={filter.active as string}
|
|
110
|
+
activeSubGroup={filter.subGrouping?.active}
|
|
111
|
+
filterIndex={filterIndex}
|
|
112
|
+
options={getNestedDropdownOptions(apiFilterDropdowns[_key])}
|
|
113
|
+
listLabel={filter.key}
|
|
114
|
+
handleSelectedItems={value => updateField(null, null, filterIndex, value)}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
108
117
|
) : (
|
|
109
|
-
<div className='
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
<div className='form-group mr-3 mb-1' key={`${filter.key}-filtersection-${filterIndex}`}>
|
|
119
|
+
<label className='text-capitalize font-weight-bold' htmlFor={`filter-${filterIndex}`}>
|
|
120
|
+
{filter.key}
|
|
121
|
+
</label>
|
|
122
|
+
<select
|
|
123
|
+
id={`filter-${filterIndex}`}
|
|
124
|
+
className='cove-form-select'
|
|
125
|
+
data-index='0'
|
|
126
|
+
value={filter.queuedActive || filter.active}
|
|
127
|
+
onChange={val => {
|
|
128
|
+
handleOnChange(filterIndex, val.target.value)
|
|
129
|
+
}}
|
|
130
|
+
disabled={values.length === 1 && !nullVal(filter)}
|
|
131
|
+
>
|
|
132
|
+
{nullVal(filter) && (
|
|
133
|
+
<option key={`select`} value=''>
|
|
134
|
+
{filter.resetLabel || '- Select -'}
|
|
135
|
+
</option>
|
|
136
|
+
)}
|
|
137
|
+
{values}
|
|
138
|
+
</select>
|
|
129
139
|
</div>
|
|
130
140
|
)
|
|
131
141
|
})}
|
|
132
|
-
|
|
142
|
+
{showSubmit && (
|
|
143
|
+
<button className='btn btn-primary mb-1' onClick={applyFilters}>
|
|
144
|
+
{applyFiltersButtonText || 'GO!'}
|
|
145
|
+
</button>
|
|
146
|
+
)}
|
|
147
|
+
</form>
|
|
133
148
|
)
|
|
134
149
|
}
|
|
135
150
|
|
|
@@ -274,7 +274,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
|
|
|
274
274
|
</select>
|
|
275
275
|
</label>
|
|
276
276
|
) : (
|
|
277
|
-
<button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width'>
|
|
277
|
+
<button onClick={() => setCanAddExisting(true)} className='btn btn-primary full-width mt-2'>
|
|
278
278
|
Add Existing Dashboard Filter
|
|
279
279
|
</button>
|
|
280
280
|
)}
|
|
@@ -13,6 +13,7 @@ import { ViewPort } from '@cdc/core/types/ViewPort'
|
|
|
13
13
|
import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
|
|
14
14
|
import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
|
|
15
15
|
import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
|
|
16
|
+
import './dashboardfilter.styles.css'
|
|
16
17
|
|
|
17
18
|
type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
|
|
18
19
|
|
|
@@ -43,7 +44,8 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
43
44
|
const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns } = state
|
|
44
45
|
const dispatch = useContext(DashboardDispatchContext)
|
|
45
46
|
|
|
46
|
-
const applyFilters =
|
|
47
|
+
const applyFilters = e => {
|
|
48
|
+
e.preventDefault() // prevent form submission
|
|
47
49
|
const dashboardConfig = _.cloneDeep(state.config.dashboard)
|
|
48
50
|
const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
|
|
49
51
|
.filter(v => v.type === 'dashboardFilters')
|
|
@@ -171,7 +173,7 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
171
173
|
{!displayNone && (
|
|
172
174
|
<Layout.Responsive isEditor={isEditor}>
|
|
173
175
|
<div
|
|
174
|
-
className={
|
|
176
|
+
className={`${
|
|
175
177
|
isEditor ? ' is-editor' : ''
|
|
176
178
|
} cove-component__content col-12 cove-dashboard-filters-container`}
|
|
177
179
|
>
|
|
@@ -180,10 +182,10 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
|
|
|
180
182
|
filters={dashboardConfig.dashboard.sharedFilters || []}
|
|
181
183
|
apiFilterDropdowns={apiFilterDropdowns}
|
|
182
184
|
handleOnChange={handleOnChange}
|
|
185
|
+
showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
|
|
186
|
+
applyFilters={applyFilters}
|
|
187
|
+
applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
|
|
183
188
|
/>
|
|
184
|
-
{visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad && (
|
|
185
|
-
<button onClick={applyFilters}>{visualizationConfig.applyFiltersButtonText || 'GO!'}</button>
|
|
186
|
-
)}
|
|
187
189
|
</div>
|
|
188
190
|
</Layout.Responsive>
|
|
189
191
|
)}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import DashboardFilters from '../DashboardFilters'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof DashboardFilters> = {
|
|
5
|
+
title: 'Components/Atoms/Inputs/DashboardFilters',
|
|
6
|
+
component: DashboardFilters
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<typeof DashboardFilters>
|
|
10
|
+
|
|
11
|
+
export const Example_1: Story = {
|
|
12
|
+
args: {
|
|
13
|
+
filters: [
|
|
14
|
+
{ type: 'datafilter', key: 'label here', values: [1, 2, 3, 4] },
|
|
15
|
+
{ type: 'datafilter', key: 'something' }
|
|
16
|
+
],
|
|
17
|
+
handleOnChange: () => {}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.cove-dashboard-filters-container {
|
|
2
|
+
:is(label) {
|
|
3
|
+
margin-bottom: 0;
|
|
4
|
+
margin-top: 0.5rem;
|
|
5
|
+
}
|
|
6
|
+
.btn {
|
|
7
|
+
/* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
|
|
8
|
+
height: calc(1.5em + 0.75rem + 2px);
|
|
9
|
+
align-self: flex-end;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/components/Grid.tsx
CHANGED
|
@@ -24,7 +24,7 @@ const Grid = () => {
|
|
|
24
24
|
{(rows || []).map((row, idx) => (
|
|
25
25
|
<Row row={row} idx={idx} uuid={row.uuid} key={idx} />
|
|
26
26
|
))}
|
|
27
|
-
<button className='btn
|
|
27
|
+
<button className='btn btn-primary col' onClick={addRow}>
|
|
28
28
|
Add Row
|
|
29
29
|
</button>
|
|
30
30
|
</div>
|