@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.
Files changed (32) hide show
  1. package/dist/cdcdashboard.js +46738 -44885
  2. package/examples/private/DEV-9644.json +20092 -0
  3. package/package.json +9 -9
  4. package/src/CdcDashboard.tsx +35 -14
  5. package/src/CdcDashboardComponent.tsx +38 -14
  6. package/src/_stories/Dashboard.stories.tsx +8 -0
  7. package/src/_stories/_mock/api-filter-error.json +55 -0
  8. package/src/_stories/_mock/group-pivot-filter.json +10 -5
  9. package/src/components/DashboardFilters/DashboardFilters.tsx +57 -42
  10. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +1 -1
  11. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +7 -5
  12. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +21 -0
  13. package/src/components/DashboardFilters/dashboardfilter.styles.css +11 -0
  14. package/src/components/Grid.tsx +1 -1
  15. package/src/components/Header/Header.tsx +71 -10
  16. package/src/components/Header/index.scss +0 -5
  17. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +28 -6
  18. package/src/components/MultiConfigTabs/MultiTabs.tsx +2 -0
  19. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +4 -11
  20. package/src/components/Row.tsx +59 -13
  21. package/src/components/VisualizationRow.tsx +0 -2
  22. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +0 -1
  23. package/src/components/Widget.tsx +23 -1
  24. package/src/helpers/getVizRowColumnLocator.ts +1 -0
  25. package/src/helpers/loadAPIFilters.ts +7 -2
  26. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +2 -1
  27. package/src/scss/editor-panel.scss +0 -3
  28. package/src/scss/grid.scss +22 -23
  29. package/src/scss/main.scss +0 -27
  30. package/src/store/dashboard.reducer.ts +7 -1
  31. package/src/store/errorMessage/errorMessage.actions.ts +7 -0
  32. 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.10",
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.10",
31
- "@cdc/core": "^4.24.10",
32
- "@cdc/data-bite": "^4.24.10",
33
- "@cdc/filtered-text": "^4.24.10",
34
- "@cdc/map": "^4.24.10",
35
- "@cdc/markup-include": "^4.24.10",
36
- "@cdc/waffle-chart": "^4.24.10",
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": "a4d88d1bc91f596e1b0307d8e25c57ad8c668b75"
52
+ "gitHead": "9ab5ee9b2b0ef7321a66a2104be6ce8899ec3808"
53
53
  }
@@ -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> = ({ configUrl, config: editorConfig, isEditor, isDebug }) => {
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, selectedConfig?: string): number | null => {
32
+ const getSelectedConfig = (config: MultiDashboardConfig): number | null => {
27
33
  if (!config.multiDashboards) return null
28
- // TODO: if query parameter select based on query parameter
29
- if (selectedConfig) {
30
- const foundConfig = Object.values(config.multiDashboards).findIndex(({ label }) => {
31
- return label === selectedConfig
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 = (newConfig: MultiDashboardConfig | DashboardConfig, datasets: Record<string, Object[]>) => {
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 (selectedConfig?: string) => {
52
+ const loadConfig = async () => {
46
53
  const _config: MultiDashboardConfig = editorConfig || (await (await fetch(configUrl)).json())
47
- const selected = getSelectedConfig(_config, selectedConfig)
54
+ const selected = getSelectedConfig(_config)
48
55
 
49
- const { newConfig, datasets } = selected !== null ? await loadMultiDashboard(_config, selected) : await loadSingleDashboard(_config)
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 = { data: [], dataUrl: '', dataFileName: '', dataFileSourceType: '', dataDescription: {}, formattedData: [] }
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
- let newConfig = { ...defaults, ...multiConfig, ...selectedDashboard, multiDashboards: multiConfig.multiDashboards, activeDashboard: selectedConfig } as MultiDashboardConfig
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(dispatch, setAPIFilterDropdowns, autoLoadFilterIndexes)
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).then(responseData => {
167
- let data: any[] = responseData
168
- if (responseData && dataset.dataDescription) {
169
- try {
170
- data = transform.autoStandardize(data)
171
- data = transform.developerStandardize(data, dataset.dataDescription)
172
- } catch (e) {
173
- //Data not able to be standardized, leave as is
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
- newDatasets[datasetKey].data = data
177
- newDatasets[datasetKey].runtimeDataUrl = dataUrlFinal
178
- newData[datasetKey] = data
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
- <MultiSelect
91
- key={`${filter.key}-filtersection-${filterIndex}`}
92
- label={filter.key}
93
- options={multiValues}
94
- fieldName={filterIndex}
95
- updateField={updateField}
96
- selected={filter.active as string[]}
97
- limit={filter.selectLimit || 5}
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
- <NestedDropdown
101
- key={`${filter.key}-filtersection-${filterIndex}`}
102
- activeGroup={filter.active as string}
103
- activeSubGroup={filter.subGrouping?.active}
104
- options={getNestedDropdownOptions(apiFilterDropdowns[_key])}
105
- listLabel={filter.key}
106
- handleSelectedItems={value => updateField(null, null, filterIndex, value)}
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='cove-dashboard-filters' key={`${filter.key}-filtersection-${filterIndex}`}>
110
- <section className='dashboard-filters-section'>
111
- <label htmlFor={`filter-${filterIndex}`}>{filter.key}</label>
112
- <select
113
- id={`filter-${filterIndex}`}
114
- className='filter-select'
115
- data-index='0'
116
- value={filter.queuedActive || filter.active}
117
- onChange={val => {
118
- handleOnChange(filterIndex, val.target.value)
119
- }}
120
- >
121
- {nullVal(filter) && !filter.resetLabel && (
122
- <option value='' key='select'>
123
- {'-Select-'}
124
- </option>
125
- )}
126
- {values}
127
- </select>
128
- </section>
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={`cdc-dashboard-inner-container${
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
+ }
@@ -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 add-row' onClick={addRow}>
27
+ <button className='btn btn-primary col' onClick={addRow}>
28
28
  Add Row
29
29
  </button>
30
30
  </div>