@cdc/dashboard 4.25.5-1 → 4.25.6

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.
@@ -1,205 +1,205 @@
1
- import React from 'react'
2
- import MultiSelect from '@cdc/core/components/MultiSelect'
3
- import { SharedFilter } from '../../types/SharedFilter'
4
- import { APIFilterDropdowns, DropdownOptions } from './DashboardFiltersWrapper'
5
- import { FILTER_STYLE } from '../../types/FilterStyles'
6
- import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
7
- import NestedDropdown from '@cdc/core/components/NestedDropdown'
8
- import { MouseEventHandler } from 'react'
9
- import Loader from '@cdc/core/components/Loader'
10
- import _ from 'lodash'
11
- import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/components/Dropdown'
12
-
13
- type DashboardFilterProps = {
14
- show: number[]
15
- filters: SharedFilter[]
16
- apiFilterDropdowns: APIFilterDropdowns
17
- handleOnChange: (index: number, value: string | string[]) => void
18
- showSubmit: boolean
19
- applyFilters: MouseEventHandler<HTMLButtonElement>
20
- applyFiltersButtonText?: string
21
- }
22
-
23
- const DashboardFilters: React.FC<DashboardFilterProps> = ({
24
- show,
25
- filters: sharedFilters,
26
- apiFilterDropdowns,
27
- handleOnChange,
28
- showSubmit,
29
- applyFilters,
30
- applyFiltersButtonText
31
- }) => {
32
- const nullVal = (filter: SharedFilter) => {
33
- const val = filter.queuedActive || filter.active
34
- return val === null || val === undefined || val === ''
35
- }
36
-
37
- const updateField = (_section, _subsection, fieldName, value) => {
38
- handleOnChange(fieldName, value) // fieldName is the sharedFilterIndex
39
- }
40
-
41
- const stripDuplicateLabelIncrement = (label: string): string => {
42
- // converts 'Label (1)' to 'Label'
43
- return label.replace(/\s\(\d+\)$/, '')
44
- }
45
-
46
- const getNestedDropdownOptions = (options?: DropdownOptions): NestedOptions => {
47
- if (!options) return []
48
- const getValueTextTuple = (value: string, text?: string): ValueTextPair => (text ? [value, text] : [value])
49
- return options.map(({ value, text, subOptions }) => [
50
- getValueTextTuple(value, text),
51
- (subOptions || []).map(({ value, text }) => getValueTextTuple(value, text))
52
- ])
53
- }
54
-
55
- return (
56
- <form className='d-flex flex-wrap'>
57
- {sharedFilters.map((filter, filterIndex) => {
58
- const urlFilterType = filter.type === 'urlfilter'
59
- const label = stripDuplicateLabelIncrement(filter.key || '')
60
-
61
- if (
62
- (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
63
- (show && !show.includes(filterIndex))
64
- )
65
- return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
66
- const values: JSX.Element[] = []
67
-
68
- const _key = filter.apiFilter?.apiEndpoint
69
- const loading = apiFilterDropdowns[_key] === null
70
-
71
- const multiValues: { value; label }[] = []
72
- const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
73
- ([key, data]) => [
74
- [key, key], // Main option: [value, text]
75
- Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
76
- ]
77
- )
78
-
79
- const activeSubGroupValue = _.get(
80
- filter?.subGrouping?.valuesLookup,
81
- [filter?.active as string, 'values', 0],
82
- null // Default to null if the path is invalid
83
- )
84
- if (_key && apiFilterDropdowns[_key]) {
85
- // URL Filter
86
- if (filter.filterStyle !== FILTER_STYLE.nestedDropdown) {
87
- apiFilterDropdowns[_key].forEach(({ text, value }, index) => {
88
- values.push(
89
- <option key={`${value}-option-${index}`} value={value}>
90
- {text}
91
- </option>
92
- )
93
- multiValues.push({ value, label: text })
94
- })
95
- }
96
- } else {
97
- // Data Filter
98
- const orderedFilterValues = filter.orderedValues || filter.values
99
- orderedFilterValues?.forEach((filterOption, index) => {
100
- const labeledOpt = filter.labels && filter.labels[filterOption]
101
- const resetLabelHasMatch = (filterOption || labeledOpt) === filter.resetLabel
102
-
103
- if (!resetLabelHasMatch) {
104
- values.push(
105
- <option key={`${filter.key}-option-${index}`} value={filterOption}>
106
- {labeledOpt || filterOption}
107
- </option>
108
- )
109
- } else {
110
- // add label to the front of list if it matches with reset label
111
- values.unshift(
112
- <option key={`${filter.key}-option-${index}`} value={filterOption}>
113
- {labeledOpt || filterOption}
114
- </option>
115
- )
116
- }
117
-
118
- multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
119
- })
120
- }
121
-
122
- const isDisabled = !values.length
123
- // push reset label only if it does not includes in filter values options
124
- if (filter.resetLabel && !filter.values.includes(filter.resetLabel)) {
125
- values.unshift(
126
- <option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
127
- {filter.resetLabel}
128
- </option>
129
- )
130
- }
131
-
132
- const formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
133
- return (
134
- <div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
135
- {label && (
136
- <label className='font-weight-bold mb-2' htmlFor={`filter-${filterIndex}`}>
137
- {label}
138
- </label>
139
- )}
140
- {filter.filterStyle === FILTER_STYLE.multiSelect ? (
141
- <MultiSelect
142
- label={label}
143
- options={multiValues}
144
- fieldName={filterIndex}
145
- updateField={updateField}
146
- selected={filter.active as string[]}
147
- limit={filter.selectLimit || 5}
148
- loading={loading}
149
- />
150
- ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
151
- <NestedDropdown
152
- activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
153
- activeSubGroup={_key ? filter.queuedActive?.[1] || filter.subGrouping?.active : activeSubGroupValue}
154
- filterIndex={filterIndex}
155
- options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
156
- listLabel={label}
157
- handleSelectedItems={value => updateField(null, null, filterIndex, value)}
158
- loading={loading}
159
- />
160
- ) : (
161
- <>
162
- <select
163
- id={`filter-${filterIndex}`}
164
- className={`cove-form-select ${DROPDOWN_STYLES}`}
165
- data-index='0'
166
- value={loading ? 'Loading...' : filter.queuedActive || filter.active}
167
- onChange={val => {
168
- handleOnChange(filterIndex, val.target.value)
169
- }}
170
- disabled={loading || isDisabled}
171
- >
172
- {loading && <option value='Loading...'>Loading...</option>}
173
- {nullVal(filter) && (
174
- <option key={`select`} value=''>
175
- {filter.resetLabel || '- Select -'}
176
- </option>
177
- )}
178
- {values}
179
- </select>
180
- {loading && <Loader spinnerType={'text-secondary'} />}
181
- </>
182
- )}
183
- </div>
184
- )
185
- })}
186
- {showSubmit && (
187
- <button
188
- className='btn btn-primary mb-1'
189
- onClick={applyFilters}
190
- disabled={show.some(filterIndex => {
191
- const emptyFilterValues = [undefined, '', '- Select -']
192
- return (
193
- emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
194
- emptyFilterValues.includes(sharedFilters[filterIndex].active)
195
- )
196
- })}
197
- >
198
- {applyFiltersButtonText || 'GO!'}
199
- </button>
200
- )}
201
- </form>
202
- )
203
- }
204
-
205
- export default DashboardFilters
1
+ import React from 'react'
2
+ import MultiSelect from '@cdc/core/components/MultiSelect'
3
+ import { SharedFilter } from '../../types/SharedFilter'
4
+ import { APIFilterDropdowns, DropdownOptions } from './DashboardFiltersWrapper'
5
+ import { FILTER_STYLE } from '../../types/FilterStyles'
6
+ import { NestedOptions, ValueTextPair } from '@cdc/core/components/NestedDropdown/nestedDropdownHelpers'
7
+ import NestedDropdown from '@cdc/core/components/NestedDropdown'
8
+ import { MouseEventHandler } from 'react'
9
+ import Loader from '@cdc/core/components/Loader'
10
+ import _ from 'lodash'
11
+ import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/components/Dropdown'
12
+
13
+ type DashboardFilterProps = {
14
+ show: number[]
15
+ filters: SharedFilter[]
16
+ apiFilterDropdowns: APIFilterDropdowns
17
+ handleOnChange: (index: number, value: string | string[]) => void
18
+ showSubmit: boolean
19
+ applyFilters: MouseEventHandler<HTMLButtonElement>
20
+ applyFiltersButtonText?: string
21
+ }
22
+
23
+ const DashboardFilters: React.FC<DashboardFilterProps> = ({
24
+ show,
25
+ filters: sharedFilters,
26
+ apiFilterDropdowns,
27
+ handleOnChange,
28
+ showSubmit,
29
+ applyFilters,
30
+ applyFiltersButtonText
31
+ }) => {
32
+ const nullVal = (filter: SharedFilter) => {
33
+ const val = filter.queuedActive || filter.active
34
+ return val === null || val === undefined || val === ''
35
+ }
36
+
37
+ const updateField = (_section, _subsection, fieldName, value) => {
38
+ handleOnChange(fieldName, value) // fieldName is the sharedFilterIndex
39
+ }
40
+
41
+ const stripDuplicateLabelIncrement = (label: string): string => {
42
+ // converts 'Label (1)' to 'Label'
43
+ return label.replace(/\s\(\d+\)$/, '')
44
+ }
45
+
46
+ const getNestedDropdownOptions = (options?: DropdownOptions): NestedOptions => {
47
+ if (!options) return []
48
+ const getValueTextTuple = (value: string, text?: string): ValueTextPair => (text ? [value, text] : [value])
49
+ return options.map(({ value, text, subOptions }) => [
50
+ getValueTextTuple(value, text),
51
+ (subOptions || []).map(({ value, text }) => getValueTextTuple(value, text))
52
+ ])
53
+ }
54
+
55
+ return (
56
+ <form className='d-flex flex-wrap'>
57
+ {sharedFilters.map((filter, filterIndex) => {
58
+ const urlFilterType = filter.type === 'urlfilter'
59
+ const label = stripDuplicateLabelIncrement(filter.key || '')
60
+
61
+ if (
62
+ (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown) ||
63
+ (show && !show.includes(filterIndex))
64
+ )
65
+ return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
66
+ const values: JSX.Element[] = []
67
+
68
+ const _key = filter.apiFilter?.apiEndpoint
69
+ const loading = apiFilterDropdowns[_key] === null
70
+
71
+ const multiValues: { value; label }[] = []
72
+ const nestedOptions: NestedOptions = Object.entries(filter?.subGrouping?.valuesLookup || {}).map(
73
+ ([key, data]) => [
74
+ [key, key], // Main option: [value, text]
75
+ Array.isArray(data?.values) ? data.values.map(value => [value, value]) : [] // Ensure `values` is an array
76
+ ]
77
+ )
78
+
79
+ const activeSubGroupValue = _.get(
80
+ filter?.subGrouping?.valuesLookup,
81
+ [filter?.active as string, 'values', 0],
82
+ null // Default to null if the path is invalid
83
+ )
84
+ if (_key && apiFilterDropdowns[_key]) {
85
+ // URL Filter
86
+ if (filter.filterStyle !== FILTER_STYLE.nestedDropdown) {
87
+ apiFilterDropdowns[_key].forEach(({ text, value }, index) => {
88
+ values.push(
89
+ <option key={`${value}-option-${index}`} value={value}>
90
+ {text}
91
+ </option>
92
+ )
93
+ multiValues.push({ value, label: text })
94
+ })
95
+ }
96
+ } else {
97
+ // Data Filter
98
+ const orderedFilterValues = filter.orderedValues || filter.values
99
+ orderedFilterValues?.forEach((filterOption, index) => {
100
+ const labeledOpt = filter.labels && filter.labels[filterOption]
101
+ const resetLabelHasMatch = (filterOption || labeledOpt) === filter.resetLabel
102
+
103
+ if (!resetLabelHasMatch) {
104
+ values.push(
105
+ <option key={`${filter.key}-option-${index}`} value={filterOption}>
106
+ {labeledOpt || filterOption}
107
+ </option>
108
+ )
109
+ } else {
110
+ // add label to the front of list if it matches with reset label
111
+ values.unshift(
112
+ <option key={`${filter.key}-option-${index}`} value={filterOption}>
113
+ {labeledOpt || filterOption}
114
+ </option>
115
+ )
116
+ }
117
+
118
+ multiValues.push({ value: filterOption, label: labeledOpt || filterOption })
119
+ })
120
+ }
121
+
122
+ const isDisabled = !values.length
123
+ // push reset label only if it does not includes in filter values options
124
+ if (filter.resetLabel && !filter.values.includes(filter.resetLabel)) {
125
+ values.unshift(
126
+ <option key={`${filter.resetLabel}-option`} value={filter.resetLabel}>
127
+ {filter.resetLabel}
128
+ </option>
129
+ )
130
+ }
131
+
132
+ const formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
133
+ return (
134
+ <div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
135
+ {label && (
136
+ <label className='font-weight-bold mb-2' htmlFor={`filter-${filterIndex}`}>
137
+ {label}
138
+ </label>
139
+ )}
140
+ {filter.filterStyle === FILTER_STYLE.multiSelect ? (
141
+ <MultiSelect
142
+ label={label}
143
+ options={multiValues}
144
+ fieldName={filterIndex}
145
+ updateField={updateField}
146
+ selected={filter.active as string[]}
147
+ limit={filter.selectLimit || 5}
148
+ loading={loading}
149
+ />
150
+ ) : filter.filterStyle === FILTER_STYLE.nestedDropdown ? (
151
+ <NestedDropdown
152
+ activeGroup={(filter.queuedActive?.[0] || filter.active) as string}
153
+ activeSubGroup={_key ? filter.queuedActive?.[1] || filter.subGrouping?.active : activeSubGroupValue}
154
+ filterIndex={filterIndex}
155
+ options={_key ? getNestedDropdownOptions(apiFilterDropdowns[_key]) : nestedOptions}
156
+ listLabel={label}
157
+ handleSelectedItems={value => updateField(null, null, filterIndex, value)}
158
+ loading={loading}
159
+ />
160
+ ) : (
161
+ <>
162
+ <select
163
+ id={`filter-${filterIndex}`}
164
+ className={`cove-form-select ${DROPDOWN_STYLES}`}
165
+ data-index='0'
166
+ value={loading ? 'Loading...' : filter.queuedActive || filter.active}
167
+ onChange={val => {
168
+ handleOnChange(filterIndex, val.target.value)
169
+ }}
170
+ disabled={loading || isDisabled}
171
+ >
172
+ {loading && <option value='Loading...'>Loading...</option>}
173
+ {nullVal(filter) && (
174
+ <option key={`select`} value=''>
175
+ {filter.resetLabel || '- Select -'}
176
+ </option>
177
+ )}
178
+ {values}
179
+ </select>
180
+ {loading && <Loader spinnerType={'text-secondary'} />}
181
+ </>
182
+ )}
183
+ </div>
184
+ )
185
+ })}
186
+ {showSubmit && (
187
+ <button
188
+ className='btn btn-primary mb-1'
189
+ onClick={applyFilters}
190
+ disabled={show.some(filterIndex => {
191
+ const emptyFilterValues = [undefined, '', '- Select -']
192
+ return (
193
+ emptyFilterValues.includes(sharedFilters[filterIndex].queuedActive) &&
194
+ emptyFilterValues.includes(sharedFilters[filterIndex].active)
195
+ )
196
+ })}
197
+ >
198
+ {applyFiltersButtonText || 'GO!'}
199
+ </button>
200
+ )}
201
+ </form>
202
+ )
203
+ }
204
+
205
+ export default DashboardFilters