@cdc/dashboard 4.24.5 → 4.24.9-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 (86) hide show
  1. package/dist/cdcdashboard.js +147572 -128223
  2. package/examples/custom/css/respiratory.css +236 -0
  3. package/examples/custom/js/respiratory.js +242 -0
  4. package/examples/default-multi-dataset-shared-filter.json +1729 -0
  5. package/examples/ed-visits-county-file.json +618 -0
  6. package/examples/filtered-dash.json +6 -21
  7. package/examples/single-state-dashboard-filters.json +421 -0
  8. package/examples/state-level.json +90136 -0
  9. package/examples/state-points.json +10474 -0
  10. package/examples/test-file.json +147 -0
  11. package/examples/testing.json +94456 -0
  12. package/index.html +25 -4
  13. package/package.json +12 -11
  14. package/src/CdcDashboard.tsx +5 -1
  15. package/src/CdcDashboardComponent.tsx +250 -327
  16. package/src/DashboardContext.tsx +15 -1
  17. package/src/_stories/Dashboard.stories.tsx +158 -40
  18. package/src/_stories/_mock/api-filter-chart.json +11 -35
  19. package/src/_stories/_mock/api-filter-map.json +17 -31
  20. package/src/_stories/_mock/bump-chart.json +3554 -0
  21. package/src/_stories/_mock/methodology.json +412 -0
  22. package/src/_stories/_mock/methodologyAPI.ts +90 -0
  23. package/src/_stories/_mock/multi-viz.json +3 -4
  24. package/src/_stories/_mock/pivot-filter.json +14 -12
  25. package/src/_stories/_mock/single-state-dashboard-filters.json +390 -0
  26. package/src/components/CollapsibleVisualizationRow.tsx +44 -0
  27. package/src/components/Column.tsx +1 -1
  28. package/src/components/DashboardFilters/DashboardFilters.tsx +102 -0
  29. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +218 -0
  30. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +48 -0
  31. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +477 -0
  32. package/src/components/DashboardFilters/DashboardFiltersEditor/index.ts +1 -0
  33. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +191 -0
  34. package/src/components/DashboardFilters/index.ts +3 -0
  35. package/src/components/DataDesignerModal.tsx +9 -9
  36. package/src/components/ExpandCollapseButtons.tsx +20 -0
  37. package/src/components/Header/Header.tsx +1 -102
  38. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +24 -12
  39. package/src/components/Row.tsx +52 -19
  40. package/src/components/Toggle/Toggle.tsx +2 -4
  41. package/src/components/VisualizationRow.tsx +169 -30
  42. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +116 -0
  43. package/src/components/VisualizationsPanel/index.ts +1 -0
  44. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +12 -0
  45. package/src/components/Widget.tsx +27 -90
  46. package/src/helpers/FilterBehavior.ts +4 -0
  47. package/src/helpers/addValuesToDashboardFilters.ts +49 -0
  48. package/src/helpers/apiFilterHelpers.ts +103 -0
  49. package/src/helpers/changeFilterActive.ts +39 -0
  50. package/src/helpers/filterData.ts +10 -48
  51. package/src/helpers/generateValuesForFilter.ts +1 -1
  52. package/src/helpers/getAutoLoadVisualization.ts +11 -0
  53. package/src/helpers/getFilteredData.ts +7 -5
  54. package/src/helpers/getVizConfig.ts +23 -2
  55. package/src/helpers/getVizRowColumnLocator.ts +2 -1
  56. package/src/helpers/hasDashboardApplyBehavior.ts +5 -0
  57. package/src/helpers/iconHash.tsx +5 -3
  58. package/src/helpers/loadAPIFilters.ts +74 -0
  59. package/src/helpers/mapDataToConfig.ts +29 -0
  60. package/src/helpers/processData.ts +2 -3
  61. package/src/helpers/reloadURLHelpers.ts +102 -0
  62. package/src/helpers/tests/addValuesToDashboardFilters.test.ts +44 -0
  63. package/src/helpers/tests/apiFilterHelpers.test.ts +155 -0
  64. package/src/helpers/tests/filterData.test.ts +1 -93
  65. package/src/helpers/tests/getFilteredData.test.ts +86 -0
  66. package/src/helpers/tests/loadAPIFiltersWrapper.test.ts +220 -0
  67. package/src/helpers/tests/reloadURLHelpers.test.ts +232 -0
  68. package/src/scss/editor-panel.scss +1 -1
  69. package/src/scss/grid.scss +34 -27
  70. package/src/scss/main.scss +41 -3
  71. package/src/scss/variables.scss +4 -0
  72. package/src/store/dashboard.actions.ts +12 -4
  73. package/src/store/dashboard.reducer.ts +30 -4
  74. package/src/types/APIFilter.ts +1 -5
  75. package/src/types/ConfigRow.ts +2 -0
  76. package/src/types/Dashboard.ts +1 -1
  77. package/src/types/DashboardConfig.ts +2 -4
  78. package/src/types/DashboardFilters.ts +7 -0
  79. package/src/types/InitialState.ts +1 -1
  80. package/src/types/MultiDashboard.ts +2 -2
  81. package/src/types/SharedFilter.ts +4 -6
  82. package/src/types/Tab.ts +1 -1
  83. package/src/components/Filters.tsx +0 -88
  84. package/src/components/Header/FilterModal.tsx +0 -510
  85. package/src/components/VisualizationsPanel.tsx +0 -95
  86. package/src/helpers/getApiFilterKey.ts +0 -5
@@ -71,6 +71,11 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
71
71
  }
72
72
  }
73
73
 
74
+ const setExpandCollapseAllButtons = (selection: boolean) => {
75
+ dispatch({ type: 'UPDATE_ROW', payload: { rowIndex, rowData: { expandCollapseAllButtons: selection } } })
76
+ setCanContinue(true)
77
+ }
78
+
74
79
  return (
75
80
  <Modal>
76
81
  <Modal.Content>
@@ -121,15 +126,10 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
121
126
  }}
122
127
  />
123
128
  ) : (
124
- <InputSelect
125
- options={Object.keys(config.datasets[configureData.dataKey]?.data[0] || {})}
126
- value={config.rows[rowIndex].multiVizColumn}
127
- label='Multi-Visualization Column'
128
- initial='--Select--'
129
- fieldName=''
130
- updateField={(section, subsection, fieldName, value) => setMultiVizColumn(value)}
131
- required
132
- />
129
+ <>
130
+ <InputSelect options={Object.keys(config.datasets[configureData.dataKey]?.data[0] || {})} value={config.rows[rowIndex].multiVizColumn} label='Multi-Visualization Column' initial='--Select--' updateField={(section, subsection, fieldName, value) => setMultiVizColumn(value)} required />
131
+ <CheckBox value={config.rows[rowIndex].expandCollapseAllButtons} label=' Add Expand/Collapse All buttons' fieldName='' updateField={(section, subsection, fieldName, value) => setExpandCollapseAllButtons(value)} />
132
+ </>
133
133
  )
134
134
  ) : (
135
135
  <></>
@@ -0,0 +1,20 @@
1
+ type ExpandCollapseButtonsProps = {
2
+ setAllExpanded: Function
3
+ }
4
+
5
+ const ExpandCollapseButtons: React.FC<ExpandCollapseButtonsProps> = ({ setAllExpanded }) => {
6
+ return (
7
+ <div className='d-block '>
8
+ <div className='d-flex flex-row-reverse mb-2'>
9
+ <button className='btn expand-collapse-buttons' onClick={() => setAllExpanded(false)}>
10
+ - Collapse All
11
+ </button>
12
+ <button className='btn expand-collapse-buttons mr-2' onClick={() => setAllExpanded(true)}>
13
+ + Expand All
14
+ </button>
15
+ </div>
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export default ExpandCollapseButtons
@@ -2,19 +2,9 @@ import { useEffect, useContext } from 'react'
2
2
 
3
3
  import { DashboardContext, DashboardDispatchContext } from '../../DashboardContext'
4
4
 
5
- // types
6
- import { type SharedFilter } from '../../types/SharedFilter'
7
- import { type DashboardConfig as Config } from '../../types/DashboardConfig'
8
- import { useGlobalContext } from '@cdc/core/components/GlobalContext'
9
-
10
- import Tooltip from '@cdc/core/components/ui/Tooltip'
11
- import Icon from '@cdc/core/components/ui/Icon'
12
- import Select from '@cdc/core/components/ui/Select'
13
-
14
5
  import './index.scss'
15
6
  import MultiConfigTabs from '../MultiConfigTabs'
16
7
  import { Tab } from '../../types/Tab'
17
- import FilterModal from './FilterModal'
18
8
  import _ from 'lodash'
19
9
 
20
10
  type HeaderProps = {
@@ -23,13 +13,8 @@ type HeaderProps = {
23
13
  visualizationKey?: string
24
14
  }
25
15
 
26
- export const FilterBehavior = {
27
- Apply: 'Apply Button',
28
- OnChange: 'Filter Change'
29
- }
30
-
31
16
  const Header = (props: HeaderProps) => {
32
- const tabs: Tab[] = ['Dashboard Description', 'Dashboard Filters', 'Data Table Settings', 'Dashboard Preview']
17
+ const tabs: Tab[] = ['Dashboard Description', 'Data Table Settings', 'Dashboard Preview']
33
18
  const { visualizationKey, subEditor } = props
34
19
  const { config, setParentConfig, tabSelected } = useContext(DashboardContext)
35
20
  if (!config) return null
@@ -41,8 +26,6 @@ const Header = (props: HeaderProps) => {
41
26
  dispatch({ type: 'SET_CONFIG', payload: newConfig })
42
27
  }
43
28
 
44
- const { overlay } = useGlobalContext()
45
-
46
29
  const changeConfigValue = (parentObj, key, value) => {
47
30
  let newConfig = { ...config }
48
31
  if (!newConfig[parentObj]) newConfig[parentObj] = {}
@@ -50,48 +33,6 @@ const Header = (props: HeaderProps) => {
50
33
  dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
51
34
  }
52
35
 
53
- const addNewFilter = () => {
54
- let dashboardConfig = { ...config.dashboard }
55
-
56
- dashboardConfig.sharedFilters = dashboardConfig.sharedFilters || []
57
- const newFilter: SharedFilter = { key: 'Dashboard Filter ' + (dashboardConfig.sharedFilters.length + 1) }
58
- dashboardConfig.sharedFilters.push(newFilter)
59
-
60
- dispatch({ type: 'UPDATE_CONFIG', payload: [{ ...config, dashboard: dashboardConfig }] })
61
- }
62
-
63
- const removeFilter = index => {
64
- let dashboardConfig = { ...config.dashboard }
65
- let visualizations = { ...config.visualizations }
66
-
67
- dashboardConfig.sharedFilters?.splice(index, 1)
68
-
69
- Object.keys(visualizations).forEach(vizKey => {
70
- if (visualizations[vizKey].visualizationType === 'filter-dropdowns' && visualizations[vizKey].hide && visualizations[vizKey].hide.length > 0) {
71
- if (visualizations[vizKey].hide.indexOf(index) !== -1) {
72
- visualizations[vizKey].hide.splice(visualizations[vizKey].hide.indexOf(index), 1)
73
- }
74
- visualizations[vizKey].hide.forEach((hideIndex, i) => {
75
- if (hideIndex > index) {
76
- visualizations[vizKey].hide[i] = hideIndex - 1
77
- }
78
- })
79
- }
80
- })
81
-
82
- // Ensures URL filters refresh after filter removal
83
- if (dashboardConfig.datasets) {
84
- Object.keys(dashboardConfig.datasets).forEach(datasetKey => {
85
- dashboardConfig.datasets![datasetKey].runtimeDataUrl = ''
86
- })
87
- }
88
-
89
- const newConfig = { ...config, visualizations, dashboard: dashboardConfig }
90
- dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
91
-
92
- overlay?.actions.toggleOverlay()
93
- }
94
-
95
36
  const convertStateToConfig = (type = 'JSON') => {
96
37
  let strippedState = JSON.parse(JSON.stringify(config))
97
38
  delete strippedState.newViz
@@ -166,48 +107,6 @@ const Header = (props: HeaderProps) => {
166
107
  </ul>
167
108
  <div className='heading-body'>
168
109
  {tabSelected === 'Dashboard Description' && <input type='text' className='description-input' placeholder='Type a dashboard description here.' defaultValue={config.dashboard?.description} onChange={e => changeConfigValue('dashboard', 'description', e.target.value)} />}
169
- {tabSelected === 'Dashboard Filters' && (
170
- <>
171
- {config.dashboard.sharedFilters &&
172
- config.dashboard.sharedFilters.map((sharedFilter, index) => (
173
- <span className='shared-filter-button' key={`shared-filter-${sharedFilter.key}`}>
174
- <a
175
- href='#'
176
- onClick={e => {
177
- e.preventDefault()
178
- overlay?.actions.openOverlay(<FilterModal index={index} config={config} filterState={sharedFilter} removeFilter={removeFilter} />)
179
- }}
180
- >
181
- {sharedFilter.key}
182
- </a>
183
- <button onClick={() => removeFilter(index)}>X</button>
184
- </span>
185
- ))}
186
- <button onClick={addNewFilter}>Add New Filter</button>
187
-
188
- <Select
189
- value={config.filterBehavior}
190
- fieldName='filterBehavior'
191
- label='Filter Behavior'
192
- initial='- Select Option -'
193
- onchange={e => {
194
- const newConfig = { ...config, filterBehavior: e.target.value }
195
- dispatch({ type: 'UPDATE_CONFIG', payload: [newConfig] })
196
- }}
197
- options={Object.values(FilterBehavior)}
198
- tooltip={
199
- <Tooltip style={{ textTransform: 'none' }}>
200
- <Tooltip.Target>
201
- <Icon display='question' color='' style={{ marginLeft: '0.5rem' }} />
202
- </Tooltip.Target>
203
- <Tooltip.Content>
204
- <p>The Apply Button option changes the visualization when the user clicks "apply". The Filter Change option immediately changes the visualization when the selection is changed.</p>
205
- </Tooltip.Content>
206
- </Tooltip>
207
- }
208
- />
209
- </>
210
- )}
211
110
  {tabSelected === 'Data Table Settings' && (
212
111
  <>
213
112
  <div className='wrap'>
@@ -23,7 +23,8 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
23
23
  const { overlay } = useGlobalContext()
24
24
  const inputRef = createRef<HTMLInputElement>()
25
25
 
26
- const onBlur = () => {
26
+ const saveName = e => {
27
+ e.stopPropagation()
27
28
  const newVal = inputRef.current.value
28
29
  const sameName = newVal === name
29
30
  const blankName = !newVal
@@ -64,15 +65,26 @@ const Tab = ({ name, handleClick, tabs, index, active }) => {
64
65
  const canMoveRight = index <= tabs.length - 2
65
66
 
66
67
  return (
67
- <li className='nav-item'>
68
- <a className={`edit nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : null} href='#' onClick={onClick}>
69
- {canMoveLeft && <button onClick={() => handleReorder(index, -1)}>{'<'}</button>}
70
- {editing ? <input type='text' defaultValue={name} onBlur={onBlur} ref={inputRef} /> : <>{name}</>}
71
- {canMoveRight && <button onClick={() => handleReorder(index, 1)}>{'>'}</button>}
72
- <button className='remove' onClick={handleRemove}>
73
- X
74
- </button>
75
- </a>
68
+ <li className='nav-item d-flex mt-0'>
69
+ {canMoveLeft && editing && <button onClick={() => handleReorder(index, -1)}>{'<'}</button>}
70
+ <div className={`edit nav-link${active ? ' active' : ''}`} aria-current={active ? 'page' : null} onClick={onClick}>
71
+ {editing ? (
72
+ <div className='d-flex'>
73
+ <input type='text' defaultValue={name} onBlur={saveName} ref={inputRef} />
74
+ <button className='btn btn-link save' onClick={saveName}>
75
+ save
76
+ </button>
77
+ </div>
78
+ ) : (
79
+ <>
80
+ {name}
81
+ <button className='remove' onClick={handleRemove}>
82
+ X
83
+ </button>
84
+ </>
85
+ )}
86
+ </div>
87
+ {canMoveRight && editing && <button onClick={() => handleReorder(index, 1)}>{'>'}</button>}
76
88
  </li>
77
89
  )
78
90
  }
@@ -95,9 +107,9 @@ const MultiConfigTabs = () => {
95
107
  <Tab key={tab + index} name={tab} tabs={tabs} index={index} handleClick={() => saveAndLoad(index)} active={index === activeTab} />
96
108
  ))}
97
109
  <li className='nav-item'>
98
- <a className='nav-link add' href='#' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
110
+ <button className='nav-link add' onClick={() => dispatch({ type: 'ADD_NEW_DASHBOARD' })}>
99
111
  +
100
- </a>
112
+ </button>
101
113
  </li>
102
114
  </ul>
103
115
  )
@@ -17,6 +17,7 @@ import { DataDesignerModal } from './DataDesignerModal'
17
17
  import { useGlobalContext } from '@cdc/core/components/GlobalContext'
18
18
  import { iconHash } from '../helpers/iconHash'
19
19
  import _ from 'lodash'
20
+ import { Visualization } from '@cdc/core/types/Visualization'
20
21
 
21
22
  type RowMenuProps = {
22
23
  rowIdx: number
@@ -104,9 +105,20 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
104
105
  }
105
106
 
106
107
  const deleteRow = () => {
107
- rows.splice(rowIdx, 1) // Just delete the row. Don't delete the instantiated widgets for now.
108
+ let newVisualizations = { ...config.visualizations }
109
+
110
+ //delete the instantiated widgets
111
+ if (rows[rowIdx] && rows[rowIdx].columns && rows[rowIdx].columns.length && config.visualizations) {
112
+ rows[rowIdx].columns.forEach(column => {
113
+ if (column.widget) {
114
+ delete newVisualizations[column.widget]
115
+ }
116
+ })
117
+ }
108
118
 
109
- updateConfig({ ...config, rows })
119
+ rows.splice(rowIdx, 1) // delete the row
120
+
121
+ updateConfig({ ...config, rows, visualizations: newVisualizations })
110
122
  }
111
123
 
112
124
  const layoutList = [
@@ -149,31 +161,52 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
149
161
  )
150
162
  }
151
163
 
152
- const Row = ({ row, idx: rowIdx, uuid }) => {
164
+ type RowProps = { row: ConfigRow; idx: number; uuid: number | string }
165
+
166
+ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
153
167
  const { overlay } = useGlobalContext()
154
- return (
155
- <div className='builder-row' data-row-id={rowIdx}>
156
- <RowMenu rowIdx={rowIdx} />
157
- <div className='column-container'>
158
- <>
159
- <button
160
- title='Configure Data'
161
- className='btn btn-configure-row'
162
- onClick={() => {
163
- overlay?.actions.openOverlay(<DataDesignerModal rowIndex={rowIdx} />)
164
- }}
165
- >
166
- {iconHash['gear']}
167
- </button>
168
+ const dispatch = useContext(DashboardDispatchContext)
168
169
 
170
+ const configureFootnotes = () => {
171
+ if (!row.footnotesId) {
172
+ const type = 'footnotes'
173
+ const uid = type + Date.now()
174
+ const newVisualizationConfig = {
175
+ uid,
176
+ type,
177
+ visualizationType: type,
178
+ editing: true
179
+ }
180
+ dispatch({ type: 'ADD_FOOTNOTE', payload: { id: uid, rowIndex: rowIdx, config: newVisualizationConfig as Visualization } })
181
+ } else {
182
+ dispatch({ type: 'UPDATE_VISUALIZATION', payload: { vizKey: row.footnotesId, configureData: { editing: true } } })
183
+ }
184
+ }
185
+ return (
186
+ <>
187
+ <div className='builder-row' data-row-id={rowIdx}>
188
+ <RowMenu rowIdx={rowIdx} />
189
+ <button
190
+ title='Configure Data'
191
+ className='btn btn-configure-row'
192
+ onClick={() => {
193
+ overlay?.actions.openOverlay(<DataDesignerModal rowIndex={rowIdx} />)
194
+ }}
195
+ >
196
+ {iconHash['gearMulti']}
197
+ </button>
198
+ <div className='column-container'>
169
199
  {row.columns
170
200
  .filter(column => column.width)
171
201
  .map((column, colIdx) => (
172
202
  <Column data={column} key={`row-${uuid}-col-${colIdx}`} rowIdx={rowIdx} colIdx={colIdx} />
173
203
  ))}
174
- </>
204
+ </div>
205
+ <button className='btn btn-primary footnotes' onClick={configureFootnotes}>
206
+ {row.footnotesId ? 'Edit' : 'Add'} Footnotes
207
+ </button>
175
208
  </div>
176
- </div>
209
+ </>
177
210
  )
178
211
  }
179
212
 
@@ -1,7 +1,5 @@
1
- import { useContext } from 'react'
2
- import { DashboardDispatchContext } from '../../DashboardContext'
3
1
  import { ConfigRow } from '../../types/ConfigRow'
4
- import { Visualization } from '@cdc/core/types/Visualization'
2
+ import { AnyVisualization } from '@cdc/core/types/Visualization'
5
3
  import { getIcon } from '../../helpers/iconHash'
6
4
  import './toggle-style.css'
7
5
  import _ from 'lodash'
@@ -9,7 +7,7 @@ import _ from 'lodash'
9
7
  type ToggleProps = {
10
8
  active: number
11
9
  row: ConfigRow
12
- visualizations: Record<string, Visualization>
10
+ visualizations: Record<string, AnyVisualization>
13
11
  setToggled: (colIndex: number) => void
14
12
  }
15
13
  const Toggle: React.FC<ToggleProps> = ({ active, row, visualizations, setToggled }) => {
@@ -1,56 +1,132 @@
1
1
  import DataTableStandAlone from '@cdc/core/components/DataTable/DataTableStandAlone'
2
- import React, { MouseEventHandler, useContext, useMemo } from 'react'
2
+ import React, { useContext, useMemo } from 'react'
3
3
  import Toggle from './Toggle'
4
4
  import _ from 'lodash'
5
5
  import { ConfigRow } from '../types/ConfigRow'
6
- import CdcMap from '@cdc/map'
7
6
  import CdcChart from '@cdc/chart'
8
7
  import CdcDataBite from '@cdc/data-bite'
8
+ import CdcMap from '@cdc/map'
9
9
  import CdcWaffleChart from '@cdc/waffle-chart'
10
10
  import CdcMarkupInclude from '@cdc/markup-include'
11
11
  import CdcFilteredText from '@cdc/filtered-text'
12
- import Filters, { APIFilterDropdowns } from './Filters'
13
- import { FilterBehavior } from './Header/Header'
12
+ import DashboardSharedFilters, { APIFilterDropdowns } from './DashboardFilters'
14
13
  import { DashboardContext } from '../DashboardContext'
15
14
  import { ViewPort } from '@cdc/core/types/ViewPort'
16
- import { getVizConfig } from '../helpers/getVizConfig'
15
+ import { getFootnotesVizConfig, getVizConfig } from '../helpers/getVizConfig'
17
16
  import { TableConfig } from '@cdc/core/components/DataTable/types/TableConfig'
17
+ import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAlone'
18
+ import CollapsibleVisualizationRow from './CollapsibleVisualizationRow'
19
+ import { DashboardFilters } from '../types/DashboardFilters'
20
+ import { hasDashboardApplyBehavior } from '../helpers/hasDashboardApplyBehavior'
21
+
22
+ type VisualizationWrapperProps = {
23
+ allExpanded: boolean
24
+ children: React.ReactNode
25
+ currentViewport: ViewPort
26
+ groupName: string
27
+ row: ConfigRow
28
+ }
29
+
30
+ const VisualizationWrapper: React.FC<VisualizationWrapperProps> = ({
31
+ allExpanded,
32
+ currentViewport,
33
+ groupName,
34
+ row,
35
+ children
36
+ }) => {
37
+ return row.expandCollapseAllButtons ? (
38
+ <div className='collapsable-multiviz-container'>
39
+ <CollapsibleVisualizationRow
40
+ allExpanded={allExpanded}
41
+ fontSize={'26px'}
42
+ groupName={groupName}
43
+ currentViewport={currentViewport}
44
+ >
45
+ {children}
46
+ </CollapsibleVisualizationRow>
47
+ </div>
48
+ ) : (
49
+ <>
50
+ <h3>{groupName}</h3>
51
+ {children}
52
+ </>
53
+ )
54
+ }
18
55
 
19
56
  type VizRowProps = {
57
+ allExpanded: boolean
20
58
  filteredDataOverride?: Object[]
59
+ groupName: string
21
60
  row: ConfigRow
22
61
  rowIndex: number
23
62
  setSharedFilter: Function
24
63
  updateChildConfig: Function
25
- applyFilters: MouseEventHandler<HTMLButtonElement>
26
64
  apiFilterDropdowns: APIFilterDropdowns
27
- handleOnChange: Function
28
65
  currentViewport: ViewPort
29
66
  }
30
67
 
31
- const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, rowIndex: index, setSharedFilter, updateChildConfig, applyFilters, apiFilterDropdowns, handleOnChange, currentViewport }) => {
68
+ const VisualizationRow: React.FC<VizRowProps> = ({
69
+ allExpanded,
70
+ filteredDataOverride,
71
+ groupName,
72
+ row,
73
+ rowIndex: index,
74
+ setSharedFilter,
75
+ updateChildConfig,
76
+ apiFilterDropdowns,
77
+ currentViewport
78
+ }) => {
32
79
  const { config, filteredData: dashboardFilteredData, data: rawData } = useContext(DashboardContext)
33
80
  const [show, setShow] = React.useState(row.columns.map((col, i) => i === 0))
34
81
  const setToggled = (colIndex: number) => {
35
82
  setShow(show.map((_, i) => i === colIndex))
36
83
  }
37
84
  const inNoDataState = useMemo(() => {
38
- const vals = Object.values(rawData)
85
+ const vals = Object.values(rawData).flatMap(val => val)
39
86
  if (!vals.length) return true
40
87
  return vals.some(val => val === undefined)
41
88
  }, [rawData])
42
- const GoButton = ({ autoLoad }: { autoLoad?: boolean }) => {
43
- if (config.filterBehavior === FilterBehavior.Apply && !autoLoad) {
44
- return <button onClick={applyFilters}>GO!</button>
89
+
90
+ const footnotesConfig = useMemo(() => {
91
+ if (row.footnotesId) {
92
+ const footnoteConfig = getFootnotesVizConfig(row.footnotesId, index, config)
93
+ if (row.multiVizColumn && filteredDataOverride) {
94
+ const vizCategory = filteredDataOverride[0][row.multiVizColumn]
95
+ // the multiViz filtering filtering is applied after the dashboard filters
96
+ const categoryFootnote = footnoteConfig.formattedData.filter(d => d[row.multiVizColumn] === vizCategory)
97
+ footnoteConfig.formattedData = categoryFootnote
98
+ }
99
+ return footnoteConfig
45
100
  }
46
101
  return null
102
+ }, [config, row, rawData, dashboardFilteredData])
103
+
104
+ const applyButtonNotClicked = (vizConfig: DashboardFilters): boolean => {
105
+ const dashboardFilters = Object.values(config.visualizations).filter(
106
+ v => v.type === 'dashboardFilters'
107
+ ) as DashboardFilters[]
108
+ const applyFilters = dashboardFilters.filter(v => !v.autoLoad).flatMap(v => v.sharedFilterIndexes)
109
+ if (hasDashboardApplyBehavior(config.visualizations) && vizConfig.autoLoad) {
110
+ return applyFilters.some(index => {
111
+ const { queuedActive, active } = config.dashboard.sharedFilters[index]
112
+ if (!active && !queuedActive) return true
113
+ if (!queuedActive) return false
114
+ return queuedActive !== active
115
+ })
116
+ }
117
+ return false
47
118
  }
48
119
  return (
49
- <div className={`dashboard-row ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`} key={`row__${index}`}>
50
- {row.toggle && <Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />}
120
+ <div
121
+ className={`row mb-5 ${row.equalHeight ? 'equal-height' : ''} ${row.toggle ? 'toggle' : ''}`}
122
+ key={`row__${index}`}
123
+ >
124
+ {row.toggle && (
125
+ <Toggle row={row} visualizations={config.visualizations} active={show.indexOf(true)} setToggled={setToggled} />
126
+ )}
51
127
  {row.columns.map((col, colIndex) => {
52
128
  if (col.width) {
53
- if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`dashboard-col dashboard-col-${col.width}`}></div>
129
+ if (!col.widget) return <div key={`row__${index}__col__${colIndex}`} className={`col col-${col.width}`}></div>
54
130
 
55
131
  const visualizationConfig = getVizConfig(col.widget, index, config, rawData, dashboardFilteredData)
56
132
  if (filteredDataOverride) {
@@ -60,19 +136,37 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
60
136
  }
61
137
  }
62
138
 
63
- const setsSharedFilter = config.dashboard.sharedFilters && config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
64
- const setSharedFilterValue = setsSharedFilter ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active : undefined
139
+ const setsSharedFilter =
140
+ config.dashboard.sharedFilters &&
141
+ config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget).length > 0
142
+ const setSharedFilterValue = setsSharedFilter
143
+ ? config.dashboard.sharedFilters.filter(sharedFilter => sharedFilter.setBy === col.widget)[0].active
144
+ : undefined
65
145
  const tableLink = (
66
146
  <a href={`#data-table-${visualizationConfig.dataKey}`} className='margin-left-href'>
67
147
  {visualizationConfig.dataKey} (Go to Table)
68
148
  </a>
69
149
  )
70
- const hideFilter = visualizationConfig.autoLoad && inNoDataState
150
+ const hideFilter =
151
+ inNoDataState &&
152
+ visualizationConfig.type === 'dashboardFilters' &&
153
+ applyButtonNotClicked(visualizationConfig)
71
154
 
72
155
  const shouldShow = row.toggle === undefined || (row.toggle && show[colIndex])
156
+
157
+ const body = <></>
158
+
73
159
  return (
74
- <React.Fragment key={`vis__${index}__${colIndex}`}>
75
- <div className={`dashboard-col dashboard-col-${col.width} ${!shouldShow ? 'hidden-toggle' : ''}`}>
160
+ <div
161
+ key={`vis__${index}__${colIndex}`}
162
+ className={`p-1 col-12 col-md-${col.width} ${!shouldShow ? 'd-none' : ''}`}
163
+ >
164
+ <VisualizationWrapper
165
+ allExpanded={allExpanded}
166
+ currentViewport={currentViewport}
167
+ groupName={groupName}
168
+ row={row}
169
+ >
76
170
  {visualizationConfig.type === 'chart' && (
77
171
  <CdcChart
78
172
  key={col.widget}
@@ -84,7 +178,15 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
84
178
  }}
85
179
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
86
180
  isDashboard={true}
87
- link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
181
+ link={
182
+ config.table &&
183
+ config.table.show &&
184
+ config.datasets &&
185
+ visualizationConfig.table &&
186
+ visualizationConfig.table.showDataTableLink
187
+ ? tableLink
188
+ : undefined
189
+ }
88
190
  configUrl={undefined}
89
191
  setEditing={undefined}
90
192
  hostname={undefined}
@@ -103,7 +205,15 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
103
205
  setSharedFilter={setsSharedFilter ? setSharedFilter : undefined}
104
206
  setSharedFilterValue={setSharedFilterValue}
105
207
  isDashboard={true}
106
- link={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
208
+ link={
209
+ config.table &&
210
+ config.table.show &&
211
+ config.datasets &&
212
+ visualizationConfig.table &&
213
+ visualizationConfig.table.showDataTableLink
214
+ ? tableLink
215
+ : undefined
216
+ }
107
217
  />
108
218
  )}
109
219
  {visualizationConfig.type === 'data-bite' && (
@@ -126,7 +236,15 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
126
236
  updateChildConfig(col.widget, newConfig)
127
237
  }}
128
238
  isDashboard={true}
129
- configUrl={config.table && config.table.show && config.datasets && visualizationConfig.table && visualizationConfig.table.showDataTableLink ? tableLink : undefined}
239
+ configUrl={
240
+ config.table &&
241
+ config.table.show &&
242
+ config.datasets &&
243
+ visualizationConfig.table &&
244
+ visualizationConfig.table.showDataTableLink
245
+ ? tableLink
246
+ : undefined
247
+ }
130
248
  />
131
249
  )}
132
250
  {visualizationConfig.type === 'markup-include' && (
@@ -153,11 +271,16 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
153
271
  configUrl={undefined}
154
272
  />
155
273
  )}
156
- {visualizationConfig.type === 'filter-dropdowns' && !hideFilter && (
157
- <React.Fragment key={col.widget}>
158
- <Filters hide={visualizationConfig.hide} filters={config.dashboard.sharedFilters} apiFilterDropdowns={apiFilterDropdowns} handleOnChange={handleOnChange} />
159
- <GoButton autoLoad={visualizationConfig.autoLoad} />
160
- </React.Fragment>
274
+ {visualizationConfig.type === 'dashboardFilters' && !hideFilter && (
275
+ <DashboardSharedFilters
276
+ setConfig={newConfig => {
277
+ updateChildConfig(col.widget, newConfig)
278
+ }}
279
+ key={col.widget}
280
+ visualizationConfig={visualizationConfig as DashboardFilters}
281
+ apiFilterDropdowns={apiFilterDropdowns}
282
+ currentViewport={currentViewport}
283
+ />
161
284
  )}
162
285
  {visualizationConfig.type === 'table' && (
163
286
  <DataTableStandAlone
@@ -170,12 +293,28 @@ const VisualizationRow: React.FC<VizRowProps> = ({ filteredDataOverride, row, ro
170
293
  viewport={currentViewport}
171
294
  />
172
295
  )}
173
- </div>
174
- </React.Fragment>
296
+ {visualizationConfig.type === 'footnotes' && (
297
+ <FootnotesStandAlone
298
+ key={col.widget}
299
+ visualizationKey={col.widget}
300
+ config={visualizationConfig}
301
+ viewport={currentViewport}
302
+ />
303
+ )}
304
+ </VisualizationWrapper>
305
+ </div>
175
306
  )
176
307
  }
177
308
  return <React.Fragment key={`vis__${index}__${colIndex}`}></React.Fragment>
178
309
  })}
310
+ {row.footnotesId ? (
311
+ <FootnotesStandAlone
312
+ isEditor={false}
313
+ visualizationKey={row.footnotesId}
314
+ config={footnotesConfig}
315
+ viewport={currentViewport}
316
+ />
317
+ ) : null}
179
318
  </div>
180
319
  )
181
320
  }