@cdc/dashboard 4.25.3 → 4.25.6-1

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 (63) hide show
  1. package/Dynamic_Data.md +79 -0
  2. package/Override_Data.md +39 -0
  3. package/dist/cdcdashboard.js +76052 -77984
  4. package/examples/legend-issue-data.json +1874 -0
  5. package/examples/legend-issue.json +749 -0
  6. package/examples/map.json +628 -0
  7. package/examples/special-classes.json +54340 -0
  8. package/index.html +1 -26
  9. package/package.json +10 -15
  10. package/src/CdcDashboardComponent.tsx +65 -216
  11. package/src/_stories/Dashboard.stories.tsx +2 -0
  12. package/src/_stories/_mock/api-filter-map.json +43 -1
  13. package/src/components/CollapsibleVisualizationRow.tsx +4 -6
  14. package/src/components/DashboardEditors.tsx +143 -0
  15. package/src/components/DashboardFilters/DashboardFilters.tsx +205 -205
  16. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +286 -287
  17. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +129 -0
  18. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +680 -652
  19. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +198 -198
  20. package/src/components/DataDesignerModal.tsx +33 -14
  21. package/src/components/Header/Header.tsx +7 -9
  22. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +3 -0
  23. package/src/components/Row.tsx +2 -24
  24. package/src/components/VisualizationRow.tsx +191 -214
  25. package/src/helpers/getVizConfig.ts +93 -80
  26. package/src/helpers/getVizRowColumnLocator.ts +0 -1
  27. package/src/helpers/reloadURLHelpers.ts +11 -6
  28. package/src/helpers/shouldLoadAllFilters.ts +30 -30
  29. package/src/index.tsx +2 -1
  30. package/src/scss/main.scss +0 -5
  31. package/src/store/dashboard.actions.ts +61 -62
  32. package/src/store/dashboard.reducer.ts +15 -11
  33. package/src/types/ConfigRow.ts +0 -1
  34. package/src/types/Dashboard.ts +1 -1
  35. package/src/types/DashboardConfig.ts +1 -1
  36. package/src/types/SharedFilter.ts +2 -0
  37. package/examples/private/DEV-10120.json +0 -1294
  38. package/examples/private/DEV-10527.json +0 -845
  39. package/examples/private/DEV-10586.json +0 -54319
  40. package/examples/private/DEV-10856.json +0 -54319
  41. package/examples/private/DEV-9199.json +0 -606
  42. package/examples/private/DEV-9644.json +0 -20092
  43. package/examples/private/DEV-9684.json +0 -2135
  44. package/examples/private/DEV-9932.json +0 -95
  45. package/examples/private/DEV-9989.json +0 -229
  46. package/examples/private/art-dashboard.json +0 -18174
  47. package/examples/private/art-scratch.json +0 -2406
  48. package/examples/private/bird-flu-2.json +0 -440
  49. package/examples/private/bird-flu.json +0 -413
  50. package/examples/private/dashboard-config-ehdi.json +0 -29915
  51. package/examples/private/dashboard-map-filter.json +0 -815
  52. package/examples/private/dashboard-margins.js +0 -15
  53. package/examples/private/dataset.json +0 -1452
  54. package/examples/private/dev-10856-2.json +0 -1348
  55. package/examples/private/ehdi-data.json +0 -29502
  56. package/examples/private/exposure-source-h5-data.csv +0 -26
  57. package/examples/private/fatal-data.csv +0 -3159
  58. package/examples/private/feelings.json +0 -1
  59. package/examples/private/gaza-issue.json +0 -1214
  60. package/examples/private/markup.json +0 -115
  61. package/examples/private/nhis.json +0 -1792
  62. package/examples/private/workforce.json +0 -2041
  63. package/src/types/DataSet.ts +0 -11
@@ -1,198 +1,198 @@
1
- import { useContext, useState } from 'react'
2
- import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
- import Filters from './DashboardFilters'
4
- import { changeFilterActive } from '../../helpers/changeFilterActive'
5
- import _ from 'lodash'
6
- import { FilterBehavior } from '../../helpers/FilterBehavior'
7
- import { getFilteredData } from '../../helpers/getFilteredData'
8
- import { DashboardFilters } from '../../types/DashboardFilters'
9
- import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
10
- import Layout from '@cdc/core/components/Layout'
11
- import DashboardFiltersEditor from './DashboardFiltersEditor'
12
- import { ViewPort } from '@cdc/core/types/ViewPort'
13
- import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
14
- import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
- import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
- import './dashboardfilter.styles.css'
17
- import { updateChildFilters } from '../../helpers/updateChildFilters'
18
-
19
- type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
20
-
21
- export type DropdownOptions = (Record<'value' | 'text', string> & SubOptions)[]
22
-
23
- /** the cached dropdown options for each filter */
24
- export type APIFilterDropdowns = {
25
- // null means still loading
26
- [dropdownsKey: string]: null | DropdownOptions
27
- }
28
-
29
- type DashboardFiltersProps = {
30
- apiFilterDropdowns: APIFilterDropdowns
31
- visualizationConfig: DashboardFilters
32
- isEditor?: boolean
33
- setConfig: (config: DashboardFilters) => void
34
- currentViewport?: ViewPort
35
- }
36
-
37
- const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
38
- apiFilterDropdowns,
39
- visualizationConfig,
40
- setConfig: updateConfig,
41
- currentViewport,
42
- isEditor = false
43
- }) => {
44
- const state = useContext(DashboardContext)
45
- const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
46
- const dispatch = useContext(DashboardDispatchContext)
47
-
48
- const applyFilters = e => {
49
- e.preventDefault() // prevent form submission
50
- const dashboardConfig = _.cloneDeep(state.config.dashboard)
51
- const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
52
- .filter(v => v.type === 'dashboardFilters')
53
- .reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
54
- const allRequiredFiltersSelected = !dashboardConfig.sharedFilters.some((filter, filterIndex) => {
55
- if (nonAutoLoadFilterIndexes.includes(filterIndex)) {
56
- return !filter.active && !filter.queuedActive
57
- } else {
58
- // autoload filters don't need to be selected to apply filters
59
- return false
60
- }
61
- })
62
- if (allRequiredFiltersSelected) {
63
- if (hasDashboardApplyBehavior(state.config.visualizations)) {
64
- const queryParams = getQueryParams()
65
- let needsQueryUpdate = false
66
- dashboardConfig.sharedFilters.forEach(sharedFilter => {
67
- if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
68
- if (
69
- sharedFilter.setByQueryParameter &&
70
- queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
71
- ) {
72
- queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
73
- ? sharedFilter.active.join(',')
74
- : sharedFilter.active
75
- needsQueryUpdate = true
76
- }
77
- })
78
-
79
- if (needsQueryUpdate) {
80
- updateQueryString(queryParams)
81
- }
82
- }
83
- setAPILoading(true)
84
- dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
85
- dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
86
- loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
87
- .then(newFilters => {
88
- reloadURLData(newFilters)
89
- })
90
- .catch(e => {
91
- console.error(e)
92
- })
93
- } else {
94
- // TODO noftify of required fields
95
- }
96
- }
97
-
98
- const handleOnChange = (index: number, value: string | string[]) => {
99
- const newConfig = _.cloneDeep(dashboardConfig)
100
- let [newSharedFilters, changedFilterIndexes] = changeFilterActive(
101
- index,
102
- value,
103
- newConfig.dashboard.sharedFilters,
104
- visualizationConfig
105
- )
106
-
107
- // sets the active filter option that the user just selected.
108
- dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
109
-
110
- if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
111
- const isAutoSelectFilter = visualizationConfig.autoLoad
112
- const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
113
- const apiEndpoints = newSharedFilters.filter(f => f.apiFilter).map(f => f.apiFilter.apiEndpoint)
114
- const loadingFilterMemo = apiFilterHelpers.getLoadingFilterMemo(
115
- apiEndpoints,
116
- apiFilterDropdowns,
117
- changedFilterIndexes
118
- )
119
- if (isAutoSelectFilter && !missingFilterSelections) {
120
- // a dropdown has been selected that doesn't
121
- // require the Go Button
122
- setAPIFilterDropdowns(loadingFilterMemo)
123
- loadAPIFilters(newSharedFilters, loadingFilterMemo).then(filters => {
124
- reloadURLData(filters)
125
- })
126
- } else {
127
- newSharedFilters[index].queuedActive = value
128
- // setData to empty object because we no longer have a data state.
129
- dispatch({ type: 'SET_DATA', payload: {} })
130
- dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
131
- setAPIFilterDropdowns(loadingFilterMemo)
132
- loadAPIFilters(newSharedFilters, loadingFilterMemo)
133
- }
134
- } else {
135
- if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
136
- reloadURLData(newSharedFilters)
137
- } else {
138
- const clonedState = _.cloneDeep(state)
139
- clonedState.config.dashboard.sharedFilters = newSharedFilters
140
- const newFilteredData = getFilteredData(clonedState)
141
- dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
142
- dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
143
- }
144
- }
145
- }
146
- const [displayPanel, setDisplayPanel] = useState(true)
147
- const onBackClick = () => {
148
- setDisplayPanel(!displayPanel)
149
- updateConfig({
150
- ...visualizationConfig,
151
- showEditorPanel: !displayPanel
152
- })
153
- }
154
-
155
- // if all of the filters are hidden filters don't display the VisualizationWrapper
156
- const filters = visualizationConfig?.sharedFilterIndexes
157
- ?.map(Number)
158
- .map(filterIndex => dashboardConfig.dashboard.sharedFilters[filterIndex])
159
-
160
- const displayNone = filters.length ? filters.every(filter => filter.showDropdown === false) : false
161
- if (displayNone && !isEditor) return <></>
162
- return (
163
- <Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
164
- {isEditor && (
165
- <Layout.Sidebar
166
- displayPanel={displayPanel}
167
- isDashboard={true}
168
- title={'Configure Dashboard Filters'}
169
- onBackClick={onBackClick}
170
- >
171
- <DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
172
- </Layout.Sidebar>
173
- )}
174
-
175
- {!displayNone && (
176
- <Layout.Responsive isEditor={isEditor}>
177
- <div
178
- className={`${
179
- isEditor ? ' is-editor' : ''
180
- } cove-component__content col-12 cove-dashboard-filters-container`}
181
- >
182
- <Filters
183
- show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
184
- filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
185
- apiFilterDropdowns={apiFilterDropdowns}
186
- handleOnChange={handleOnChange}
187
- showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
188
- applyFilters={applyFilters}
189
- applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
190
- />
191
- </div>
192
- </Layout.Responsive>
193
- )}
194
- </Layout.VisualizationWrapper>
195
- )
196
- }
197
-
198
- export default DashboardFiltersWrapper
1
+ import { useContext, useState } from 'react'
2
+ import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
3
+ import Filters from './DashboardFilters'
4
+ import { changeFilterActive } from '../../helpers/changeFilterActive'
5
+ import _ from 'lodash'
6
+ import { FilterBehavior } from '../../helpers/FilterBehavior'
7
+ import { getFilteredData } from '../../helpers/getFilteredData'
8
+ import { DashboardFilters } from '../../types/DashboardFilters'
9
+ import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
10
+ import Layout from '@cdc/core/components/Layout'
11
+ import DashboardFiltersEditor from './DashboardFiltersEditor'
12
+ import { ViewPort } from '@cdc/core/types/ViewPort'
13
+ import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
14
+ import * as apiFilterHelpers from '../../helpers/apiFilterHelpers'
15
+ import { applyQueuedActive } from '@cdc/core/components/Filters/helpers/applyQueuedActive'
16
+ import './dashboardfilter.styles.css'
17
+ import { updateChildFilters } from '../../helpers/updateChildFilters'
18
+
19
+ type SubOptions = { subOptions?: Record<'value' | 'text', string>[] }
20
+
21
+ export type DropdownOptions = (Record<'value' | 'text', string> & SubOptions)[]
22
+
23
+ /** the cached dropdown options for each filter */
24
+ export type APIFilterDropdowns = {
25
+ // null means still loading
26
+ [dropdownsKey: string]: null | DropdownOptions
27
+ }
28
+
29
+ type DashboardFiltersProps = {
30
+ apiFilterDropdowns: APIFilterDropdowns
31
+ visualizationConfig: DashboardFilters
32
+ isEditor?: boolean
33
+ setConfig: (config: DashboardFilters) => void
34
+ currentViewport?: ViewPort
35
+ }
36
+
37
+ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
38
+ apiFilterDropdowns,
39
+ visualizationConfig,
40
+ setConfig: updateConfig,
41
+ currentViewport,
42
+ isEditor = false
43
+ }) => {
44
+ const state = useContext(DashboardContext)
45
+ const { config: dashboardConfig, reloadURLData, loadAPIFilters, setAPIFilterDropdowns, setAPILoading } = state
46
+ const dispatch = useContext(DashboardDispatchContext)
47
+
48
+ const applyFilters = e => {
49
+ e.preventDefault() // prevent form submission
50
+ const dashboardConfig = _.cloneDeep(state.config.dashboard)
51
+ const nonAutoLoadFilterIndexes = Object.values(state.config.visualizations)
52
+ .filter(v => v.type === 'dashboardFilters')
53
+ .reduce((acc, viz: DashboardFilters) => (!viz.autoLoad ? [...acc, viz.sharedFilterIndexes] : acc), [])
54
+ const allRequiredFiltersSelected = !dashboardConfig.sharedFilters.some((filter, filterIndex) => {
55
+ if (nonAutoLoadFilterIndexes.includes(filterIndex)) {
56
+ return !filter.active && !filter.queuedActive
57
+ } else {
58
+ // autoload filters don't need to be selected to apply filters
59
+ return false
60
+ }
61
+ })
62
+ if (allRequiredFiltersSelected) {
63
+ if (hasDashboardApplyBehavior(state.config.visualizations)) {
64
+ const queryParams = getQueryParams()
65
+ let needsQueryUpdate = false
66
+ dashboardConfig.sharedFilters.forEach(sharedFilter => {
67
+ if (sharedFilter.queuedActive) applyQueuedActive(sharedFilter)
68
+ if (
69
+ sharedFilter.setByQueryParameter &&
70
+ queryParams[sharedFilter.setByQueryParameter] !== sharedFilter.active
71
+ ) {
72
+ queryParams[sharedFilter.setByQueryParameter] = Array.isArray(sharedFilter.active)
73
+ ? sharedFilter.active.join(',')
74
+ : sharedFilter.active
75
+ needsQueryUpdate = true
76
+ }
77
+ })
78
+
79
+ if (needsQueryUpdate) {
80
+ updateQueryString(queryParams)
81
+ }
82
+ }
83
+ setAPILoading(true)
84
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: dashboardConfig.sharedFilters })
85
+ dispatch({ type: 'SET_FILTERED_DATA', payload: getFilteredData(_.cloneDeep(state)) })
86
+ loadAPIFilters(dashboardConfig.sharedFilters, apiFilterDropdowns)
87
+ .then(newFilters => {
88
+ reloadURLData(newFilters)
89
+ })
90
+ .catch(e => {
91
+ console.error(e)
92
+ })
93
+ } else {
94
+ // TODO noftify of required fields
95
+ }
96
+ }
97
+
98
+ const handleOnChange = (index: number, value: string | string[]) => {
99
+ const newConfig = _.cloneDeep(dashboardConfig)
100
+ let [newSharedFilters, changedFilterIndexes] = changeFilterActive(
101
+ index,
102
+ value,
103
+ newConfig.dashboard.sharedFilters,
104
+ visualizationConfig
105
+ )
106
+
107
+ // sets the active filter option that the user just selected.
108
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
109
+
110
+ if (hasDashboardApplyBehavior(dashboardConfig.visualizations)) {
111
+ const isAutoSelectFilter = visualizationConfig.autoLoad
112
+ const missingFilterSelections = newConfig.dashboard.sharedFilters.some(f => !f.active)
113
+ const apiEndpoints = newSharedFilters.filter(f => f.apiFilter).map(f => f.apiFilter.apiEndpoint)
114
+ const loadingFilterMemo = apiFilterHelpers.getLoadingFilterMemo(
115
+ apiEndpoints,
116
+ apiFilterDropdowns,
117
+ changedFilterIndexes
118
+ )
119
+ if (isAutoSelectFilter && !missingFilterSelections) {
120
+ // a dropdown has been selected that doesn't
121
+ // require the Go Button
122
+ setAPIFilterDropdowns(loadingFilterMemo)
123
+ loadAPIFilters(newSharedFilters, loadingFilterMemo).then(filters => {
124
+ reloadURLData(filters)
125
+ })
126
+ } else {
127
+ newSharedFilters[index].queuedActive = value
128
+ // setData to empty object because we no longer have a data state.
129
+ dispatch({ type: 'SET_DATA', payload: {} })
130
+ dispatch({ type: 'SET_FILTERED_DATA', payload: {} })
131
+ setAPIFilterDropdowns(loadingFilterMemo)
132
+ loadAPIFilters(newSharedFilters, loadingFilterMemo)
133
+ }
134
+ } else {
135
+ if (newSharedFilters[index].type === 'urlfilter' && newSharedFilters[index].apiFilter) {
136
+ reloadURLData(newSharedFilters)
137
+ } else {
138
+ const clonedState = _.cloneDeep(state)
139
+ clonedState.config.dashboard.sharedFilters = newSharedFilters
140
+ const newFilteredData = getFilteredData(clonedState)
141
+ dispatch({ type: 'SET_FILTERED_DATA', payload: newFilteredData })
142
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
143
+ }
144
+ }
145
+ }
146
+ const [displayPanel, setDisplayPanel] = useState(true)
147
+ const onBackClick = () => {
148
+ setDisplayPanel(!displayPanel)
149
+ updateConfig({
150
+ ...visualizationConfig,
151
+ showEditorPanel: !displayPanel
152
+ })
153
+ }
154
+
155
+ // if all of the filters are hidden filters don't display the VisualizationWrapper
156
+ const filters = visualizationConfig?.sharedFilterIndexes
157
+ ?.map(Number)
158
+ .map(filterIndex => dashboardConfig.dashboard.sharedFilters[filterIndex])
159
+
160
+ const displayNone = filters.length ? filters.every(filter => filter.showDropdown === false) : false
161
+ if (displayNone && !isEditor) return <></>
162
+ return (
163
+ <Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
164
+ {isEditor && (
165
+ <Layout.Sidebar
166
+ displayPanel={displayPanel}
167
+ isDashboard={true}
168
+ title={'Configure Dashboard Filters'}
169
+ onBackClick={onBackClick}
170
+ >
171
+ <DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
172
+ </Layout.Sidebar>
173
+ )}
174
+
175
+ {!displayNone && (
176
+ <Layout.Responsive isEditor={isEditor}>
177
+ <div
178
+ className={`${
179
+ isEditor ? ' is-editor' : ''
180
+ } cove-component__content col-12 cove-dashboard-filters-container`}
181
+ >
182
+ <Filters
183
+ show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
184
+ filters={updateChildFilters(dashboardConfig.dashboard.sharedFilters, state.data) || []}
185
+ apiFilterDropdowns={apiFilterDropdowns}
186
+ handleOnChange={handleOnChange}
187
+ showSubmit={visualizationConfig.filterBehavior === FilterBehavior.Apply && !visualizationConfig.autoLoad}
188
+ applyFilters={applyFilters}
189
+ applyFiltersButtonText={visualizationConfig.applyFiltersButtonText}
190
+ />
191
+ </div>
192
+ </Layout.Responsive>
193
+ )}
194
+ </Layout.VisualizationWrapper>
195
+ )
196
+ }
197
+
198
+ export default DashboardFiltersWrapper
@@ -29,20 +29,16 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
29
29
  const [errorMessage, setErrorMessage] = useState('')
30
30
  const [loadingAPIData, setLoadingAPIData] = useState(false)
31
31
 
32
+ const isUpdatingVisualization = useMemo(() => {
33
+ return !!vizKey && !useRow
34
+ }, [vizKey, useRow])
35
+
32
36
  const configureData = useMemo(() => {
33
- if (vizKey && !useRow) {
37
+ if (isUpdatingVisualization) {
34
38
  return config.visualizations[vizKey]
35
39
  }
36
40
  return config.rows[rowIndex]
37
- }, [config.visualizations, config.rows, vizKey, rowIndex, useRow])
38
-
39
- const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
40
- if (vizKey && !useRow) {
41
- dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
42
- } else {
43
- dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
44
- }
45
- }
41
+ }, [config.visualizations, config.rows, rowIndex, isUpdatingVisualization])
46
42
 
47
43
  const fetchData = async datasetKey => {
48
44
  const { data, dataUrl } = config.datasets[datasetKey]
@@ -65,10 +61,32 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
65
61
  return newData
66
62
  }
67
63
 
68
- const changeDataset = async ({ target: { value } }) => {
69
- const newData = await fetchData(value)
70
- const newConfigureData = { dataDescription: {}, formattedData: undefined, dataKey: value, data: newData }
64
+ const updateConfigureData = (newConfigureData: Partial<ConfigureData>) => {
65
+ if (isUpdatingVisualization) {
66
+ dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey, configureData: newConfigureData } })
67
+ } else {
68
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: newConfigureData } })
69
+ }
70
+ }
71
71
 
72
+ const removeDatasetsFromVisualizations = () => {
73
+ const columnVisualizations = config.rows[rowIndex].columns.map(column => column.widget).filter(Boolean)
74
+ columnVisualizations.forEach(currentVisualizationKey => {
75
+ dispatch({ type: 'RESET_VISUALIZATION', payload: { vizKey: currentVisualizationKey } })
76
+ })
77
+ }
78
+
79
+ const changeDataset = async ({ target: { value } }) => {
80
+ if (!isUpdatingVisualization) removeDatasetsFromVisualizations()
81
+ const newData = value === '' ? {} : await fetchData(value)
82
+ const newConfigureData = {
83
+ dataDescription: {
84
+ horizontal: false
85
+ },
86
+ formattedData: undefined,
87
+ dataKey: value,
88
+ data: newData
89
+ } as ConfigureData
72
90
  updateConfigureData(newConfigureData)
73
91
  }
74
92
 
@@ -112,12 +130,13 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
112
130
  Object.keys(config.datasets).map(datasetKey => <option key={datasetKey}>{datasetKey}</option>)}
113
131
  </select>
114
132
  {vizKey && (
133
+ // only shows for visualizations
115
134
  <CheckBox
116
135
  label='Apply To Row'
117
136
  value={useRow}
118
137
  updateField={(section, subsection, fieldName, value) => {
119
138
  setUseRow(value)
120
- changeDataset({ target: { value: '' } })
139
+ changeDataset({ target: { value: configureData.dataKey } })
121
140
  }}
122
141
  />
123
142
  )}
@@ -9,7 +9,7 @@ import _ from 'lodash'
9
9
 
10
10
  type HeaderProps = {
11
11
  back?: any
12
- subEditor?: any
12
+ subEditor?: boolean
13
13
  visualizationKey?: string
14
14
  }
15
15
 
@@ -96,14 +96,12 @@ const Header = (props: HeaderProps) => {
96
96
  multidashboard
97
97
  </span>
98
98
  <br />
99
- {
100
- <input
101
- type='text'
102
- placeholder='Enter Dashboard Name Here'
103
- defaultValue={config.dashboard?.title}
104
- onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
105
- />
106
- }
99
+ <input
100
+ type='text'
101
+ placeholder='Enter Dashboard Name Here'
102
+ defaultValue={config.dashboard?.title}
103
+ onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}
104
+ />
107
105
  </div>
108
106
  )}
109
107
  {!subEditor && (
@@ -4,6 +4,9 @@
4
4
  font-weight: 400;
5
5
  display: block;
6
6
  padding: 0.5rem 1rem;
7
+ @media (max-width: 480px) {
8
+ padding: 0.5rem 0.8rem;
9
+ }
7
10
  color: var(--primary);
8
11
  border-color: var(--lightGray);
9
12
  :is(button) {
@@ -1,4 +1,4 @@
1
- import React, { useContext, useMemo, useState } from 'react'
1
+ import React, { useContext, useMemo } from 'react'
2
2
 
3
3
  import { DashboardContext, DashboardDispatchContext } from '../DashboardContext'
4
4
 
@@ -215,30 +215,11 @@ type RowProps = { row: ConfigRow; idx: number; uuid: number | string }
215
215
 
216
216
  const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
217
217
  const { overlay } = useGlobalContext()
218
- const dispatch = useContext(DashboardDispatchContext)
219
-
220
- const configureFootnotes = () => {
221
- if (!row.footnotesId) {
222
- const type = 'footnotes'
223
- const uid = type + Date.now()
224
- const newVisualizationConfig = {
225
- uid,
226
- type,
227
- visualizationType: type,
228
- editing: true
229
- }
230
- dispatch({
231
- type: 'ADD_FOOTNOTE',
232
- payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization }
233
- })
234
- } else {
235
- dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
236
- }
237
- }
238
218
  return (
239
219
  <>
240
220
  <div className='builder-row' data-row-id={rowIdx}>
241
221
  <RowMenu rowIdx={rowIdx} />
222
+ <span className='ms-2 mt-n3'>Row - {rowIdx + 1}</span>
242
223
  <button
243
224
  title='Configure Data'
244
225
  className='btn btn-configure-row'
@@ -261,9 +242,6 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
261
242
  />
262
243
  ))}
263
244
  </div>
264
- <button className='btn btn-primary footnotes' onClick={configureFootnotes}>
265
- {row.footnotesId ? 'Edit' : 'Add'} Footnotes
266
- </button>
267
245
  </div>
268
246
  </>
269
247
  )