@cdc/core 4.24.5 → 4.24.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon-gear-multi.svg +23 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +93 -0
- package/components/AdvancedEditor/advanced-editor-styles.css +3 -0
- package/components/AdvancedEditor/index.ts +1 -0
- package/components/Alert/components/Alert.styles.css +15 -0
- package/components/Alert/components/Alert.tsx +39 -0
- package/components/Alert/index.tsx +3 -0
- package/components/DataTable/DataTable.tsx +127 -32
- package/components/DataTable/DataTableStandAlone.tsx +4 -25
- package/components/DataTable/components/DataTableEditorPanel.tsx +4 -4
- package/components/DataTable/components/ExpandCollapse.tsx +1 -1
- package/components/DataTable/helpers/chartCellMatrix.tsx +6 -12
- package/components/DataTable/helpers/getChartCellValue.ts +9 -5
- package/components/DataTable/helpers/getDataSeriesColumns.ts +10 -7
- package/components/DataTable/helpers/getRowType.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +3 -3
- package/components/DataTable/types/TableConfig.ts +2 -1
- package/components/EditorPanel/ColumnsEditor.tsx +3 -30
- package/components/EditorPanel/DataTableEditor.tsx +66 -22
- package/components/EditorPanel/FieldSetWrapper.tsx +51 -0
- package/components/EditorPanel/FootnotesEditor.tsx +77 -0
- package/components/EditorPanel/Inputs.tsx +13 -4
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +268 -0
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +306 -0
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +40 -0
- package/components/EditorPanel/VizFilterEditor/index.ts +1 -0
- package/components/EditorWrapper/EditorWrapper.tsx +3 -4
- package/components/EditorWrapper/index.ts +1 -0
- package/components/Filters.tsx +520 -0
- package/components/Footnotes/Footnotes.tsx +25 -0
- package/components/Footnotes/FootnotesStandAlone.tsx +45 -0
- package/components/Footnotes/footnotes.css +5 -0
- package/components/Footnotes/index.ts +1 -0
- package/components/Layout/components/Responsive.tsx +14 -4
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +14 -5
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +23 -20
- package/components/Layout/components/Visualization/index.tsx +19 -6
- package/components/Layout/components/Visualization/visualizations.scss +32 -26
- package/components/Layout/styles/editor.scss +0 -8
- package/components/Legend/Legend.Gradient.tsx +133 -0
- package/components/LegendShape.tsx +28 -0
- package/components/MultiSelect/MultiSelect.tsx +41 -11
- package/components/MultiSelect/multiselect.styles.css +0 -3
- package/components/NestedDropdown/NestedDropdown.tsx +47 -52
- package/components/NestedDropdown/nesteddropdown.styles.css +19 -25
- package/components/Table/Table.tsx +8 -5
- package/components/Table/components/Cell.tsx +2 -2
- package/components/Table/components/Row.tsx +25 -7
- package/components/_stories/Footnotes.stories.tsx +17 -0
- package/components/_stories/Layout.Debug.stories.tsx +91 -0
- package/components/_stories/_mocks/bar-chart-suppressed.json +474 -0
- package/components/_stories/styles.scss +14 -1
- package/components/createBarElement.jsx +4 -4
- package/components/inputs/InputSelect.tsx +17 -6
- package/components/ui/Icon.tsx +22 -16
- package/components/ui/Title/Title.scss +0 -8
- package/helpers/DataTransform.ts +2 -2
- package/helpers/addValuesToFilters.ts +135 -0
- package/helpers/cove/accessibility.ts +17 -4
- package/helpers/cove/fontSettings.ts +2 -0
- package/helpers/coveUpdateWorker.ts +30 -9
- package/helpers/filterVizData.ts +49 -0
- package/helpers/formatConfigBeforeSave.ts +95 -0
- package/helpers/gatherQueryParams.ts +14 -7
- package/helpers/getGradientLegendWidth.ts +15 -0
- package/helpers/getTextWidth.ts +18 -0
- package/helpers/lineChartHelpers.js +2 -1
- package/helpers/pivotData.ts +18 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/scaling.ts +7 -0
- package/helpers/tests/addValuesToFilters.test.ts +55 -0
- package/helpers/tests/filterVizData.test.ts +31 -0
- package/helpers/tests/invertValue.test.ts +35 -0
- package/helpers/tests/updateFieldFactory.test.ts +1 -0
- package/helpers/updateFieldFactory.ts +1 -1
- package/helpers/updatePaletteNames.ts +19 -0
- package/helpers/{useDataVizClasses.js → useDataVizClasses.ts} +3 -2
- package/helpers/ver/4.24.5.ts +3 -3
- package/helpers/ver/4.24.7.ts +123 -0
- package/helpers/ver/4.24.9.ts +63 -0
- package/helpers/ver/tests/4.24.9.test.ts +22 -0
- package/helpers/ver/versionNeedsUpdate.ts +9 -0
- package/package.json +6 -4
- package/styles/_button-section.scss +7 -2
- package/styles/_data-table.scss +0 -1
- package/styles/_global.scss +6 -2
- package/styles/base.scss +4 -0
- package/styles/filters.scss +4 -0
- package/styles/v2/themes/_color-definitions.scss +1 -0
- package/types/Annotation.ts +46 -0
- package/types/Axis.ts +3 -2
- package/types/ConfigureData.ts +1 -1
- package/types/Dimensions.ts +1 -0
- package/types/Footnotes.ts +17 -0
- package/types/General.ts +5 -0
- package/types/Runtime.ts +2 -7
- package/types/Table.ts +6 -0
- package/types/Visualization.ts +31 -9
- package/types/VizFilter.ts +39 -7
- package/LICENSE +0 -201
- package/components/AdvancedEditor.jsx +0 -74
- package/components/EditorPanel/VizFilterEditor.tsx +0 -234
- package/components/Filters.jsx +0 -461
- package/components/LegendCircle.jsx +0 -17
- package/helpers/queryStringUtils.js +0 -26
- package/helpers/updatePaletteNames.js +0 -16
- package/types/BaseVisualizationType.ts +0 -1
- /package/components/{Waiting.jsx → Waiting.tsx} +0 -0
- /package/helpers/ver/{4.23.4.ts → 4.24.4.ts} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { SubGrouping, VizFilter, OrderBy } from '../../../types/VizFilter'
|
|
3
|
+
import { filterOrderOptions, handleSorting } from '../../Filters'
|
|
4
|
+
import FilterOrder from './components/FilterOrder'
|
|
5
|
+
import { Visualization } from '../../../types/Visualization'
|
|
6
|
+
|
|
7
|
+
type NestedDropdownEditorProps = {
|
|
8
|
+
config: Visualization
|
|
9
|
+
dataColumns: string[]
|
|
10
|
+
filterIndex: number
|
|
11
|
+
handleNameChange: Function
|
|
12
|
+
rawData: Object[]
|
|
13
|
+
updateField: Function
|
|
14
|
+
updateFilterStyle: Function
|
|
15
|
+
handleGroupingCustomOrder: (index1: number, index2: number) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
|
|
19
|
+
config,
|
|
20
|
+
dataColumns,
|
|
21
|
+
handleGroupingCustomOrder,
|
|
22
|
+
handleNameChange: handleGroupColumnNameChange,
|
|
23
|
+
filterIndex,
|
|
24
|
+
rawData,
|
|
25
|
+
updateField
|
|
26
|
+
}) => {
|
|
27
|
+
const filter = config.filters[filterIndex]
|
|
28
|
+
const subGrouping = filter?.subGrouping
|
|
29
|
+
const listOfUsedColumnNames: string[] = []
|
|
30
|
+
|
|
31
|
+
config.filters.forEach((filter: VizFilter, index) => {
|
|
32
|
+
if (filterIndex === index) return
|
|
33
|
+
listOfUsedColumnNames.push(filter.columnName)
|
|
34
|
+
if (subGrouping?.columnName) listOfUsedColumnNames.push(subGrouping.columnName)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const updateGroupingFilterProp = (prop, value) => {
|
|
38
|
+
updateField('filters', filterIndex, prop, value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleGroupingOrderBy = (order: OrderBy) => {
|
|
42
|
+
const groupSortObject = {
|
|
43
|
+
values: _.cloneDeep(filter.values),
|
|
44
|
+
order
|
|
45
|
+
}
|
|
46
|
+
const newOrderedValues = handleSorting(groupSortObject).values
|
|
47
|
+
|
|
48
|
+
const newAllFilters = _.cloneDeep(config.filters)
|
|
49
|
+
newAllFilters[filterIndex] = { ...filter, values: newOrderedValues, order }
|
|
50
|
+
if (order === 'cust') {
|
|
51
|
+
newAllFilters[filterIndex].orderedValues = newOrderedValues
|
|
52
|
+
} else {
|
|
53
|
+
delete newAllFilters[filterIndex].orderedValues
|
|
54
|
+
}
|
|
55
|
+
updateField(null, null, 'filters', newAllFilters)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const updateSubGroupingFilterProperty = (newSubGrouping: SubGrouping) => {
|
|
59
|
+
updateField('filters', filterIndex, 'subGrouping', newSubGrouping)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleSubGroupColumnNameChange = value => {
|
|
63
|
+
const filterGroups = filter.orderedValues?.length ? filter.orderedValues : filter.values
|
|
64
|
+
|
|
65
|
+
const valuesLookup = filterGroups.reduce((acc, groupName) => {
|
|
66
|
+
const values: string[] = _.uniq(
|
|
67
|
+
rawData
|
|
68
|
+
.map(d => {
|
|
69
|
+
return d[filter.columnName] === groupName ? d[value] : ''
|
|
70
|
+
})
|
|
71
|
+
.filter(value => value !== '')
|
|
72
|
+
).sort()
|
|
73
|
+
|
|
74
|
+
acc[groupName] = {
|
|
75
|
+
values // add temp values when column changes
|
|
76
|
+
}
|
|
77
|
+
return acc
|
|
78
|
+
}, {})
|
|
79
|
+
const newSubGrouping: SubGrouping = {
|
|
80
|
+
...subGrouping,
|
|
81
|
+
columnName: value,
|
|
82
|
+
valuesLookup
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
updateSubGroupingFilterProperty(newSubGrouping)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handleSubGroupingOrderBy = (order: OrderBy) => {
|
|
89
|
+
const newValuesLookup = Object.keys(subGrouping.valuesLookup).reduce((acc, groupName) => {
|
|
90
|
+
const subGroup = subGrouping.valuesLookup[groupName]
|
|
91
|
+
|
|
92
|
+
const { values } = handleSorting({ values: subGroup.values, order })
|
|
93
|
+
acc[groupName] = {
|
|
94
|
+
values
|
|
95
|
+
}
|
|
96
|
+
if (order === 'cust') {
|
|
97
|
+
acc[groupName].orderedValues = values
|
|
98
|
+
} else {
|
|
99
|
+
delete acc[groupName].orderedValues
|
|
100
|
+
}
|
|
101
|
+
return acc
|
|
102
|
+
}, {})
|
|
103
|
+
const newSortedSubGrouping = { ...subGrouping, order, valuesLookup: newValuesLookup }
|
|
104
|
+
updateSubGroupingFilterProperty(newSortedSubGrouping)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const handleSubGroupingCustomOrder = (
|
|
108
|
+
currentIndex,
|
|
109
|
+
newIndex,
|
|
110
|
+
subGroupingFitlerOrder: string[],
|
|
111
|
+
groupName: string
|
|
112
|
+
) => {
|
|
113
|
+
const updatedGroupOrderedValues = _.cloneDeep(subGroupingFitlerOrder)
|
|
114
|
+
const [movedItem] = updatedGroupOrderedValues.splice(currentIndex, 1)
|
|
115
|
+
updatedGroupOrderedValues.splice(newIndex, 0, movedItem)
|
|
116
|
+
const newSubGrouping = _.cloneDeep(subGrouping)
|
|
117
|
+
newSubGrouping.valuesLookup[groupName].values = updatedGroupOrderedValues
|
|
118
|
+
newSubGrouping.valuesLookup[groupName].orderedValues = updatedGroupOrderedValues
|
|
119
|
+
updateSubGroupingFilterProperty({ ...newSubGrouping, order: 'cust' })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const columnNameOptions = dataColumns.filter(columnName => !listOfUsedColumnNames.includes(columnName))
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className='nesteddropdown-editor'>
|
|
126
|
+
<label>
|
|
127
|
+
<span className='edit-label column-heading mt-2'>Label</span>
|
|
128
|
+
<input
|
|
129
|
+
type='text'
|
|
130
|
+
value={filter.label}
|
|
131
|
+
onChange={e => {
|
|
132
|
+
updateGroupingFilterProp('label', e.target.value)
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
</label>
|
|
136
|
+
|
|
137
|
+
<label>
|
|
138
|
+
<div className='edit-label column-heading mt-2'>
|
|
139
|
+
Filter Grouping
|
|
140
|
+
<span></span>
|
|
141
|
+
</div>
|
|
142
|
+
<select value={filter.columnName} onChange={e => handleGroupColumnNameChange(e.target.value)}>
|
|
143
|
+
<option value=''>- Select Option -</option>
|
|
144
|
+
{columnNameOptions.map((option, index) => (
|
|
145
|
+
<option value={option} key={index}>
|
|
146
|
+
{option}
|
|
147
|
+
</option>
|
|
148
|
+
))}
|
|
149
|
+
</select>
|
|
150
|
+
</label>
|
|
151
|
+
<label>
|
|
152
|
+
<div className='edit-label column-heading mt-2'>
|
|
153
|
+
Filter SubGrouping
|
|
154
|
+
<span></span>
|
|
155
|
+
</div>
|
|
156
|
+
<select
|
|
157
|
+
value={subGrouping?.columnName ?? ''}
|
|
158
|
+
onChange={e => {
|
|
159
|
+
handleSubGroupColumnNameChange(e.target.value)
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<option value=''>- Select Option -</option>
|
|
163
|
+
{columnNameOptions.map((option, index) => {
|
|
164
|
+
if (option !== filter.columnName) {
|
|
165
|
+
return (
|
|
166
|
+
<option value={option} key={index}>
|
|
167
|
+
{option}
|
|
168
|
+
</option>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
})}
|
|
172
|
+
</select>
|
|
173
|
+
</label>
|
|
174
|
+
|
|
175
|
+
<label>
|
|
176
|
+
<input
|
|
177
|
+
type='checkbox'
|
|
178
|
+
checked={!!filter.setByQueryParameter}
|
|
179
|
+
aria-label='Create query parameters'
|
|
180
|
+
onChange={e => {
|
|
181
|
+
updateGroupingFilterProp('setByQueryParameter', filter.columnName)
|
|
182
|
+
updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter: subGrouping.columnName })
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
<span> Create query parameters</span>
|
|
186
|
+
{!!filter.setByQueryParameter && (
|
|
187
|
+
<>
|
|
188
|
+
<span className='edit-label column-heading mt-2'>
|
|
189
|
+
Grouping: Default Value Set By Query String Parameter
|
|
190
|
+
</span>
|
|
191
|
+
<input
|
|
192
|
+
type='text'
|
|
193
|
+
value={filter.setByQueryParameter}
|
|
194
|
+
onChange={e => {
|
|
195
|
+
updateGroupingFilterProp('setByQueryParameter', e.target.value)
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
<span className='edit-label column-heading mt-2'>
|
|
199
|
+
SubGrouping: Default Value Set By Query String Parameter
|
|
200
|
+
</span>
|
|
201
|
+
<input
|
|
202
|
+
type='text'
|
|
203
|
+
value={subGrouping.setByQueryParameter}
|
|
204
|
+
onChange={e => {
|
|
205
|
+
const setByQueryParameter = e.target.value
|
|
206
|
+
updateSubGroupingFilterProperty({ ...subGrouping, setByQueryParameter })
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</>
|
|
210
|
+
)}
|
|
211
|
+
</label>
|
|
212
|
+
|
|
213
|
+
<label className='mt-2'>
|
|
214
|
+
<div className='edit-label column-heading float-right'>{filter.columnName} </div>
|
|
215
|
+
<span className={'edit-filterOrder column-heading '}>Group Order</span>
|
|
216
|
+
<select value={filter.order} onChange={e => handleGroupingOrderBy(e.target.value as OrderBy)}>
|
|
217
|
+
{filterOrderOptions.map((option, index) => {
|
|
218
|
+
return (
|
|
219
|
+
<option value={option.value} key={`filter-${option.label}-${index}`}>
|
|
220
|
+
{option.label}
|
|
221
|
+
</option>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
</select>
|
|
225
|
+
{filter.order === 'cust' && (
|
|
226
|
+
<FilterOrder orderedValues={filter.orderedValues} handleFilterOrder={handleGroupingCustomOrder} />
|
|
227
|
+
)}
|
|
228
|
+
</label>
|
|
229
|
+
|
|
230
|
+
{subGrouping?.columnName && (
|
|
231
|
+
<label className='mt-2'>
|
|
232
|
+
<span className={'edit-filterOrder column-heading'}>SubGrouping Order</span>
|
|
233
|
+
<div className='edit-label column-heading float-right'>{subGrouping.columnName} </div>
|
|
234
|
+
<select
|
|
235
|
+
value={subGrouping.order ? subGrouping.order : 'asc'}
|
|
236
|
+
onChange={e => handleSubGroupingOrderBy(e.target.value as OrderBy)}
|
|
237
|
+
>
|
|
238
|
+
{filterOrderOptions.map((option, index) => {
|
|
239
|
+
return (
|
|
240
|
+
<option value={option.value} key={`filter-${index}`}>
|
|
241
|
+
{option.label}
|
|
242
|
+
</option>
|
|
243
|
+
)
|
|
244
|
+
})}
|
|
245
|
+
</select>
|
|
246
|
+
{subGrouping?.order === 'cust' &&
|
|
247
|
+
filter.values.map((groupName, i) => {
|
|
248
|
+
const orderedSubGroupValues = subGrouping.valuesLookup[groupName].orderedValues
|
|
249
|
+
return (
|
|
250
|
+
<div key={`group-subgroup-values-${groupName}-${i}`}>
|
|
251
|
+
<span className='font-weight-bold'>{groupName}</span>
|
|
252
|
+
<FilterOrder
|
|
253
|
+
key={`subgroup-values-${groupName}-${i}`}
|
|
254
|
+
orderedValues={orderedSubGroupValues}
|
|
255
|
+
handleFilterOrder={(sourceIndex, destinationIndex) => {
|
|
256
|
+
handleSubGroupingCustomOrder(sourceIndex, destinationIndex, orderedSubGroupValues, groupName)
|
|
257
|
+
}}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
)
|
|
261
|
+
})}
|
|
262
|
+
</label>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default NestedDropdownEditor
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { Select, TextField } from '../Inputs'
|
|
2
|
+
import Tooltip from '../../ui/Tooltip'
|
|
3
|
+
import Icon from '../../ui/Icon'
|
|
4
|
+
import { Visualization } from '../../../types/Visualization'
|
|
5
|
+
import { UpdateFieldFunc } from '../../../types/UpdateFieldFunc'
|
|
6
|
+
import _ from 'lodash'
|
|
7
|
+
import { MultiSelectFilter, VizFilter, VizFilterStyle } from '../../../types/VizFilter'
|
|
8
|
+
import { filterStyleOptions, handleSorting, filterOrderOptions } from '../../Filters'
|
|
9
|
+
import FieldSetWrapper from '../FieldSetWrapper'
|
|
10
|
+
|
|
11
|
+
import FilterOrder from './components/FilterOrder'
|
|
12
|
+
import { useMemo, useState } from 'react'
|
|
13
|
+
import MultiSelect from '../../MultiSelect'
|
|
14
|
+
import NestedDropdownEditor from './NestedDropdownEditor'
|
|
15
|
+
|
|
16
|
+
type VizFilterProps = {
|
|
17
|
+
config: Visualization
|
|
18
|
+
updateField: UpdateFieldFunc<string | VizFilter[] | VizFilter>
|
|
19
|
+
rawData: Object[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData }) => {
|
|
23
|
+
const openControls = useState({})
|
|
24
|
+
const dataColumns = useMemo(() => {
|
|
25
|
+
return _.uniq(_.flatten(rawData?.map(row => Object.keys(row))))
|
|
26
|
+
}, [rawData])
|
|
27
|
+
|
|
28
|
+
const removeFilter = index => {
|
|
29
|
+
let filters = _.cloneDeep(config.filters)
|
|
30
|
+
|
|
31
|
+
filters.splice(index, 1)
|
|
32
|
+
|
|
33
|
+
updateField(null, null, 'filters', filters)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const updateFilterProp = (prop, filterIndex, value) => {
|
|
37
|
+
updateField('filters', filterIndex, prop, value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const updateFilterStyle = (index, style: VizFilterStyle) => {
|
|
41
|
+
const filters = _.cloneDeep(config.filters)
|
|
42
|
+
const currentFilter = { ...filters[index], orderedValues: filters[index].values }
|
|
43
|
+
currentFilter.filterStyle = style
|
|
44
|
+
if (style === 'multi-select') {
|
|
45
|
+
currentFilter.active = Array.isArray(currentFilter.active) ? currentFilter.active : [currentFilter.active]
|
|
46
|
+
} else if (Array.isArray(currentFilter.active)) {
|
|
47
|
+
currentFilter.active = currentFilter.active[0]
|
|
48
|
+
}
|
|
49
|
+
if (style === 'nested-dropdown') {
|
|
50
|
+
currentFilter.showDropdown = true
|
|
51
|
+
}
|
|
52
|
+
filters[index] = currentFilter
|
|
53
|
+
updateField(null, null, 'filters', filters)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleNameChange = (filterIndex, columnName) => {
|
|
57
|
+
const values = _.uniq(rawData.map(row => row[columnName]))
|
|
58
|
+
const copiedFilter = { ..._.cloneDeep(config.filters[filterIndex]), columnName, values }
|
|
59
|
+
handleSorting(copiedFilter) // sorts dropdown values in place
|
|
60
|
+
copiedFilter.active = copiedFilter.values[0]
|
|
61
|
+
const newFilters = config.filters.map((filter, index) => {
|
|
62
|
+
if (index === filterIndex) return copiedFilter
|
|
63
|
+
return filter
|
|
64
|
+
})
|
|
65
|
+
updateField(null, null, 'filters', newFilters)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const addNewFilter = () => {
|
|
69
|
+
const filters = config.filters ? [...config.filters] : []
|
|
70
|
+
const newVizFilter: VizFilter = { values: [], filterStyle: 'dropdown', id: Date.now() } as VizFilter
|
|
71
|
+
filters.push(newVizFilter)
|
|
72
|
+
updateField(null, null, 'filters', filters)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handleFilterOrder = (idx1, idx2, filterIndex) => {
|
|
76
|
+
const filter = config.filters[filterIndex]
|
|
77
|
+
// Create a shallow copy of the filter values array & update position of the values
|
|
78
|
+
const updatedValues = [...(filter.orderedValues || filter.values)]
|
|
79
|
+
|
|
80
|
+
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
81
|
+
updatedValues.splice(idx2, 0, movedItem)
|
|
82
|
+
|
|
83
|
+
const filtersCopy = _.cloneDeep(config.filters)
|
|
84
|
+
const filterItem = { ...filtersCopy[filterIndex] }
|
|
85
|
+
|
|
86
|
+
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
87
|
+
filterItem.values = updatedValues
|
|
88
|
+
filterItem.orderedValues = updatedValues
|
|
89
|
+
filterItem.active = updatedValues[0]
|
|
90
|
+
|
|
91
|
+
filterItem.order = 'cust'
|
|
92
|
+
|
|
93
|
+
// Update the filters
|
|
94
|
+
filtersCopy[filterIndex] = filterItem
|
|
95
|
+
updateField(null, null, 'filters', filtersCopy)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const getParentFilterOptions = (index: number): { label: string; value: number }[] => {
|
|
99
|
+
return config.filters
|
|
100
|
+
.filter((f, i) => i !== index)
|
|
101
|
+
.map(({ label, columnName, id }) => ({ label: label || columnName, value: id }))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
{config.filters && (
|
|
107
|
+
<>
|
|
108
|
+
<Select
|
|
109
|
+
value={config.filterBehavior}
|
|
110
|
+
fieldName='filterBehavior'
|
|
111
|
+
label='Filter Behavior'
|
|
112
|
+
updateField={updateField}
|
|
113
|
+
options={['Apply Button', 'Filter Change']}
|
|
114
|
+
tooltip={
|
|
115
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
116
|
+
<Tooltip.Target>
|
|
117
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
118
|
+
</Tooltip.Target>
|
|
119
|
+
<Tooltip.Content>
|
|
120
|
+
<p>
|
|
121
|
+
The Apply Button option changes the visualization when the user clicks "apply". The Filter Change
|
|
122
|
+
option immediately changes the visualization when the selection is changed.
|
|
123
|
+
</p>
|
|
124
|
+
</Tooltip.Content>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
}
|
|
127
|
+
/>
|
|
128
|
+
<br />
|
|
129
|
+
<ul className='filters-list'>
|
|
130
|
+
{/* Whether filters should apply onChange or Apply Button */}
|
|
131
|
+
|
|
132
|
+
{config.filters.map((filter, filterIndex) => {
|
|
133
|
+
if (filter.type === 'url') return <></>
|
|
134
|
+
return (
|
|
135
|
+
<FieldSetWrapper
|
|
136
|
+
key={filter.columnName}
|
|
137
|
+
fieldName={filter.columnName}
|
|
138
|
+
fieldKey={filterIndex}
|
|
139
|
+
fieldType='Filter'
|
|
140
|
+
controls={openControls}
|
|
141
|
+
deleteField={() => removeFilter(filterIndex)}
|
|
142
|
+
>
|
|
143
|
+
<label>
|
|
144
|
+
<span className='edit-label column-heading'>Filter Style</span>
|
|
145
|
+
|
|
146
|
+
<select
|
|
147
|
+
value={filter.filterStyle}
|
|
148
|
+
onChange={e => {
|
|
149
|
+
updateFilterStyle(filterIndex, e.target.value)
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{filterStyleOptions.map((item, index) => {
|
|
153
|
+
return (
|
|
154
|
+
<option key={`filter-style-${index}`} value={item}>
|
|
155
|
+
{item}
|
|
156
|
+
</option>
|
|
157
|
+
)
|
|
158
|
+
})}
|
|
159
|
+
</select>
|
|
160
|
+
</label>
|
|
161
|
+
|
|
162
|
+
{filter.filterStyle !== 'nested-dropdown' ? (
|
|
163
|
+
<>
|
|
164
|
+
<label>
|
|
165
|
+
<span className='edit-label column-heading'>Filter</span>
|
|
166
|
+
<select
|
|
167
|
+
value={filter.columnName}
|
|
168
|
+
onChange={e => {
|
|
169
|
+
handleNameChange(filterIndex, e.target.value)
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<option value=''>- Select Option -</option>
|
|
173
|
+
{dataColumns.map((dataKey, filterIndex) => (
|
|
174
|
+
<option value={dataKey} key={filterIndex}>
|
|
175
|
+
{dataKey}
|
|
176
|
+
</option>
|
|
177
|
+
))}
|
|
178
|
+
</select>
|
|
179
|
+
</label>
|
|
180
|
+
|
|
181
|
+
<label>
|
|
182
|
+
<span className='edit-showDropdown column-heading'>Show Filter Input</span>
|
|
183
|
+
<input
|
|
184
|
+
type='checkbox'
|
|
185
|
+
checked={filter.showDropdown === undefined ? true : filter.showDropdown}
|
|
186
|
+
onChange={e => {
|
|
187
|
+
updateFilterProp('showDropdown', filterIndex, e.target.checked)
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
</label>
|
|
191
|
+
|
|
192
|
+
<label>
|
|
193
|
+
<span className='edit-label column-heading'>Label</span>
|
|
194
|
+
<input
|
|
195
|
+
type='text'
|
|
196
|
+
value={filter.label}
|
|
197
|
+
onChange={e => {
|
|
198
|
+
updateFilterProp('label', filterIndex, e.target.value)
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
</label>
|
|
202
|
+
|
|
203
|
+
{filter.filterStyle === 'multi-select' && (
|
|
204
|
+
<TextField
|
|
205
|
+
label='Select Limit'
|
|
206
|
+
value={(filter as MultiSelectFilter).selectLimit}
|
|
207
|
+
updateField={updateField}
|
|
208
|
+
section='filters'
|
|
209
|
+
subsection={filterIndex}
|
|
210
|
+
fieldName='selectLimit'
|
|
211
|
+
type='number'
|
|
212
|
+
tooltip={
|
|
213
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
214
|
+
<Tooltip.Target>
|
|
215
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
216
|
+
</Tooltip.Target>
|
|
217
|
+
<Tooltip.Content>
|
|
218
|
+
<p>The maximum number of items that can be selected.</p>
|
|
219
|
+
</Tooltip.Content>
|
|
220
|
+
</Tooltip>
|
|
221
|
+
}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
<label>
|
|
226
|
+
<span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
|
|
227
|
+
<input
|
|
228
|
+
type='text'
|
|
229
|
+
value={filter.setByQueryParameter}
|
|
230
|
+
onChange={e => {
|
|
231
|
+
updateFilterProp('setByQueryParameter', filterIndex, e.target.value)
|
|
232
|
+
}}
|
|
233
|
+
/>
|
|
234
|
+
</label>
|
|
235
|
+
|
|
236
|
+
<label>
|
|
237
|
+
<span className='edit-filterOrder column-heading'>Filter Order</span>
|
|
238
|
+
<select
|
|
239
|
+
value={filter.order ? filter.order : 'asc'}
|
|
240
|
+
onChange={e => updateFilterProp('order', filterIndex, e.target.value)}
|
|
241
|
+
>
|
|
242
|
+
{filterOrderOptions.map((option, index) => {
|
|
243
|
+
return (
|
|
244
|
+
<option value={option.value} key={`filter-${index}`}>
|
|
245
|
+
{option.label}
|
|
246
|
+
</option>
|
|
247
|
+
)
|
|
248
|
+
})}
|
|
249
|
+
</select>
|
|
250
|
+
{filter.order === 'cust' && (
|
|
251
|
+
<FilterOrder
|
|
252
|
+
orderedValues={filter.orderedValues || filter.values}
|
|
253
|
+
handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
</label>
|
|
257
|
+
</>
|
|
258
|
+
) : (
|
|
259
|
+
<NestedDropdownEditor
|
|
260
|
+
config={config}
|
|
261
|
+
dataColumns={dataColumns}
|
|
262
|
+
filterIndex={filterIndex}
|
|
263
|
+
rawData={rawData}
|
|
264
|
+
handleGroupingCustomOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
|
|
265
|
+
handleNameChange={value => handleNameChange(filterIndex, value)}
|
|
266
|
+
updateField={updateField}
|
|
267
|
+
updateFilterStyle={updateFilterStyle}
|
|
268
|
+
/>
|
|
269
|
+
)}
|
|
270
|
+
<label>
|
|
271
|
+
<span className='edit-label column-heading'>
|
|
272
|
+
Filter Parents{' '}
|
|
273
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
274
|
+
<Tooltip.Target>
|
|
275
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
276
|
+
</Tooltip.Target>
|
|
277
|
+
<Tooltip.Content>
|
|
278
|
+
<p>
|
|
279
|
+
A selected parent's value will be used to filter the available options of this child filter.
|
|
280
|
+
</p>
|
|
281
|
+
</Tooltip.Content>
|
|
282
|
+
</Tooltip>
|
|
283
|
+
</span>
|
|
284
|
+
<MultiSelect
|
|
285
|
+
fieldName='parents'
|
|
286
|
+
updateField={(_section, _subsection, _fieldname, value) => {
|
|
287
|
+
updateFilterProp('parents', filterIndex, value)
|
|
288
|
+
}}
|
|
289
|
+
options={getParentFilterOptions(filterIndex)}
|
|
290
|
+
/>
|
|
291
|
+
</label>
|
|
292
|
+
</FieldSetWrapper>
|
|
293
|
+
)
|
|
294
|
+
})}
|
|
295
|
+
</ul>
|
|
296
|
+
</>
|
|
297
|
+
)}
|
|
298
|
+
{!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
|
|
299
|
+
<button type='button' onClick={addNewFilter} className='btn btn-primary full-width'>
|
|
300
|
+
Add Filter
|
|
301
|
+
</button>
|
|
302
|
+
</>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export default VizFilterEditor
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
2
|
+
|
|
3
|
+
type FilterOrderProps = {
|
|
4
|
+
orderedValues: string[]
|
|
5
|
+
handleFilterOrder?: (index1: number, index2: number) => void
|
|
6
|
+
}
|
|
7
|
+
const FilterOrder: React.FC<FilterOrderProps> = ({ orderedValues, handleFilterOrder }) => {
|
|
8
|
+
return (
|
|
9
|
+
<DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source?.index, destination?.index)}>
|
|
10
|
+
<Droppable droppableId='filter_order'>
|
|
11
|
+
{provided => (
|
|
12
|
+
<ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
|
|
13
|
+
{orderedValues?.map((value, index) => {
|
|
14
|
+
return (
|
|
15
|
+
<Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
|
|
16
|
+
{(provided, snapshot) => (
|
|
17
|
+
<li>
|
|
18
|
+
<div
|
|
19
|
+
className={snapshot.isDragging ? 'currently-dragging' : ''}
|
|
20
|
+
style={provided.draggableProps.style}
|
|
21
|
+
ref={provided.innerRef}
|
|
22
|
+
{...provided.draggableProps}
|
|
23
|
+
{...provided.dragHandleProps}
|
|
24
|
+
>
|
|
25
|
+
{value}
|
|
26
|
+
</div>
|
|
27
|
+
</li>
|
|
28
|
+
)}
|
|
29
|
+
</Draggable>
|
|
30
|
+
)
|
|
31
|
+
})}
|
|
32
|
+
{provided.placeholder}
|
|
33
|
+
</ul>
|
|
34
|
+
)}
|
|
35
|
+
</Droppable>
|
|
36
|
+
</DragDropContext>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default FilterOrder
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './VizFilterEditor'
|
|
@@ -2,7 +2,6 @@ import React from 'react'
|
|
|
2
2
|
import { Visualization } from '../../types/Visualization'
|
|
3
3
|
import { ViewPort } from '../../types/ViewPort'
|
|
4
4
|
import './editor-wrapper.style.css'
|
|
5
|
-
import { Accordion } from 'react-accessible-accordion'
|
|
6
5
|
|
|
7
6
|
type StandAloneComponentProps = {
|
|
8
7
|
visualizationKey: string
|
|
@@ -12,6 +11,8 @@ type StandAloneComponentProps = {
|
|
|
12
11
|
setEditing: Function
|
|
13
12
|
hostname: string
|
|
14
13
|
viewport?: ViewPort
|
|
14
|
+
|
|
15
|
+
[key: string]: any
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
type EditorProps = {
|
|
@@ -33,9 +34,7 @@ const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ childre
|
|
|
33
34
|
<div aria-level={2} role='heading' className='heading-2'>
|
|
34
35
|
Configure {type}
|
|
35
36
|
</div>
|
|
36
|
-
<section>
|
|
37
|
-
<Accordion allowZeroExpanded={true}>{children}</Accordion>
|
|
38
|
-
</section>
|
|
37
|
+
<section>{children}</section>
|
|
39
38
|
</section>
|
|
40
39
|
<div className='preview-wrapper'>
|
|
41
40
|
<Component visualizationKey={visualizationKey} config={visualizationConfig} updateConfig={updateConfig} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './EditorWrapper'
|