@cdc/dashboard 4.25.5-1 → 4.25.6-2

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 (55) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcdashboard.js +77621 -77591
  3. package/examples/private/DEV-10120.json +1294 -0
  4. package/examples/private/DEV-10527.json +845 -0
  5. package/examples/private/DEV-10586.json +54319 -0
  6. package/examples/private/DEV-10856.json +54319 -0
  7. package/examples/private/DEV-9199.json +606 -0
  8. package/examples/private/DEV-9644.json +20092 -0
  9. package/examples/private/DEV-9684.json +2135 -0
  10. package/examples/private/DEV-9932.json +95 -0
  11. package/examples/private/DEV-9989.json +229 -0
  12. package/examples/private/art-dashboard.json +18174 -0
  13. package/examples/private/art-scratch.json +2406 -0
  14. package/examples/private/bird-flu-2.json +440 -0
  15. package/examples/private/bird-flu.json +413 -0
  16. package/examples/private/crashing-sidebar.json +975 -0
  17. package/examples/private/d.json +1561 -0
  18. package/examples/private/dashboard-config-ehdi.json +29915 -0
  19. package/examples/private/dashboard-map-filter.json +815 -0
  20. package/examples/private/dashboard-margins.js +15 -0
  21. package/examples/private/dataset.json +1452 -0
  22. package/examples/private/dev-10856-2.json +1348 -0
  23. package/examples/private/ehdi-data.json +29502 -0
  24. package/examples/private/exposure-source-h5-data.csv +26 -0
  25. package/examples/private/fatal-data.csv +3159 -0
  26. package/examples/private/feelings.json +1 -0
  27. package/examples/private/gaza-issue.json +1214 -0
  28. package/examples/private/josh.json +6175 -0
  29. package/examples/private/map-issue.json +628 -0
  30. package/examples/private/markup.json +115 -0
  31. package/examples/private/mpox.json +429 -0
  32. package/examples/private/nhis.json +1792 -0
  33. package/examples/private/testing-pie.json +436 -0
  34. package/examples/private/workforce.json +2041 -0
  35. package/examples/special-classes.json +54340 -0
  36. package/package.json +9 -9
  37. package/src/CdcDashboardComponent.tsx +36 -214
  38. package/src/_stories/_mock/api-filter-map.json +1 -0
  39. package/src/components/CollapsibleVisualizationRow.tsx +2 -4
  40. package/src/components/DashboardEditors.tsx +143 -0
  41. package/src/components/DashboardFilters/DashboardFilters.tsx +205 -205
  42. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +286 -287
  43. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +198 -198
  44. package/src/components/Header/Header.tsx +7 -9
  45. package/src/components/Row.tsx +1 -24
  46. package/src/components/VisualizationRow.tsx +190 -213
  47. package/src/helpers/getVizConfig.ts +93 -80
  48. package/src/helpers/getVizRowColumnLocator.ts +0 -1
  49. package/src/helpers/reloadURLHelpers.ts +10 -13
  50. package/src/store/dashboard.actions.ts +61 -64
  51. package/src/store/dashboard.reducer.ts +0 -11
  52. package/src/types/ConfigRow.ts +0 -1
  53. package/src/types/Dashboard.ts +1 -1
  54. package/src/types/DashboardConfig.ts +1 -1
  55. package/src/types/DataSet.ts +0 -12
@@ -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
@@ -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 && (
@@ -215,31 +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} />
242
- <p className='ml-2 mt-n3'>Row - {rowIdx + 1}</p>
222
+ <span className='ms-2 mt-n3'>Row - {rowIdx + 1}</span>
243
223
  <button
244
224
  title='Configure Data'
245
225
  className='btn btn-configure-row'
@@ -262,9 +242,6 @@ const Row: React.FC<RowProps> = ({ row, idx: rowIdx, uuid }) => {
262
242
  />
263
243
  ))}
264
244
  </div>
265
- <button className='btn btn-primary footnotes' onClick={configureFootnotes}>
266
- {row.footnotesId ? 'Edit' : 'Add'} Footnotes
267
- </button>
268
245
  </div>
269
246
  </>
270
247
  )