@onehat/ui 0.2.49 → 0.2.50
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/package.json +1 -1
- package/src/Components/Buttons/IconButton.js +3 -0
- package/src/Components/Buttons/PlusMinusButton.js +40 -0
- package/src/Components/Filter/NumberRange.js +2 -0
- package/src/Components/Form/Field/Combo/Combo.js +27 -7
- package/src/Components/Form/Field/Number.js +1 -1
- package/src/Components/Form/Form.js +14 -3
- package/src/Components/Hoc/withFilters.js +367 -372
- package/src/Components/index.js +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Row,
|
|
4
|
+
Text,
|
|
5
|
+
} from 'native-base';
|
|
6
|
+
import IconButton from './IconButton.js';
|
|
7
|
+
import Plus from '../Icons/Plus.js';
|
|
8
|
+
import Minus from '../Icons/Minus.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const PlusMinusButton = React.forwardRef((props, ref) => {
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
isPlusDisabled = false,
|
|
15
|
+
isMinusDisabled = false,
|
|
16
|
+
plusHandler = () => {},
|
|
17
|
+
minusHandler = () => {},
|
|
18
|
+
} = props;
|
|
19
|
+
|
|
20
|
+
return <Row {...props}>
|
|
21
|
+
<Row alignItems="center">
|
|
22
|
+
<IconButton
|
|
23
|
+
icon={<Minus color="#fff" />}
|
|
24
|
+
onPress={minusHandler}
|
|
25
|
+
bg="primary.200"
|
|
26
|
+
isDisabled={isMinusDisabled}
|
|
27
|
+
/>
|
|
28
|
+
<IconButton
|
|
29
|
+
icon={<Plus color="#fff" />}
|
|
30
|
+
onPress={plusHandler}
|
|
31
|
+
bg="primary.200"
|
|
32
|
+
isDisabled={isPlusDisabled}
|
|
33
|
+
ml={1}
|
|
34
|
+
/>
|
|
35
|
+
</Row>
|
|
36
|
+
</Row>;
|
|
37
|
+
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default PlusMinusButton;
|
|
@@ -74,6 +74,7 @@ import _ from 'lodash';
|
|
|
74
74
|
minValue={minValue}
|
|
75
75
|
maxValue={maxValue}
|
|
76
76
|
tooltip={(tooltip ? tooltip + ' ' : '') + 'Low'}
|
|
77
|
+
maxWidth={120}
|
|
77
78
|
/>
|
|
78
79
|
<Text px={2} userSelect="none">to</Text>
|
|
79
80
|
<Number
|
|
@@ -83,6 +84,7 @@ import _ from 'lodash';
|
|
|
83
84
|
minValue={minValue}
|
|
84
85
|
maxValue={maxValue}
|
|
85
86
|
tooltip={(tooltip ? tooltip + ' ' : '') + 'High'}
|
|
87
|
+
maxWidth={120}
|
|
86
88
|
/>
|
|
87
89
|
</Row>;
|
|
88
90
|
},
|
|
@@ -178,17 +178,32 @@ export function Combo(props) {
|
|
|
178
178
|
const {
|
|
179
179
|
relatedTarget
|
|
180
180
|
} = e;
|
|
181
|
+
|
|
182
|
+
// If user focused on the trigger and text is blank, clear the selection and close the menu
|
|
183
|
+
if ((triggerRef.current === relatedTarget || triggerRef.current.contains(relatedTarget)) && (_.isEmpty(textValue) || _.isNil(textValue))) {
|
|
184
|
+
setSelection([]); // delete current selection
|
|
185
|
+
hideMenu();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
181
188
|
|
|
182
|
-
// If user
|
|
183
|
-
if (
|
|
189
|
+
// If user focused on the menu or trigger, ignore this blur
|
|
190
|
+
if (triggerRef.current === relatedTarget ||
|
|
191
|
+
triggerRef.current.contains(relatedTarget) ||
|
|
192
|
+
menuRef.current=== relatedTarget ||
|
|
193
|
+
menuRef.current?.contains(relatedTarget)) {
|
|
184
194
|
return;
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
if (!relatedTarget ||
|
|
188
|
-
(
|
|
198
|
+
(
|
|
199
|
+
!inputRef.current.contains(relatedTarget) &&
|
|
200
|
+
triggerRef.current !== relatedTarget &&
|
|
201
|
+
(!menuRef.current || !menuRef.current.contains(relatedTarget))
|
|
202
|
+
)
|
|
203
|
+
) {
|
|
189
204
|
hideMenu();
|
|
190
205
|
}
|
|
191
|
-
if (textValue
|
|
206
|
+
if (_.isEmpty(textValue) || _.isNil(textValue)) {
|
|
192
207
|
setSelection([]); // delete current selection
|
|
193
208
|
|
|
194
209
|
} else if (isManuallyEnteringText) {
|
|
@@ -221,12 +236,17 @@ export function Combo(props) {
|
|
|
221
236
|
inputRef.current.focus();
|
|
222
237
|
},
|
|
223
238
|
onTriggerBlur = (e) => {
|
|
224
|
-
if (!isMenuShown) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
239
|
const {
|
|
228
240
|
relatedTarget
|
|
229
241
|
} = e;
|
|
242
|
+
|
|
243
|
+
if (_.isEmpty(textValue) || _.isNil(textValue)) {
|
|
244
|
+
setSelection([]); // delete current selection
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!isMenuShown) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
230
250
|
if (!relatedTarget ||
|
|
231
251
|
(!inputRef.current.contains(relatedTarget) && triggerRef.current !== relatedTarget && !menuRef.current.contains(relatedTarget))) {
|
|
232
252
|
hideMenu();
|
|
@@ -119,7 +119,7 @@ function NumberElement(props) {
|
|
|
119
119
|
isIncrementDisabled = typeof maxValue !== 'undefined' && value === maxValue,
|
|
120
120
|
isDecrementDisabled = typeof minValue !== 'undefined' && (value === minValue || (!value && minValue === 0));
|
|
121
121
|
|
|
122
|
-
return <Row flex={1} h="100%" p={0} borderWidth={1} borderColor="trueGray.400" borderRadius={6}>
|
|
122
|
+
return <Row flex={1} h="100%" p={0} borderWidth={1} borderColor="trueGray.400" borderRadius={6} {...props}>
|
|
123
123
|
<IconButton
|
|
124
124
|
icon={<Icon as={Minus} color={isDecrementDisabled ? 'disabled' : 'trueGray.500'} />}
|
|
125
125
|
onPress={onDecrement}
|
|
@@ -62,7 +62,8 @@ function Form(props) {
|
|
|
62
62
|
validator, // custom validator, mainly for EDITOR_TYPE__PLAIN
|
|
63
63
|
footerProps = {},
|
|
64
64
|
buttonGroupProps = {}, // buttons in footer
|
|
65
|
-
onBack,
|
|
65
|
+
onBack,
|
|
66
|
+
onReset,
|
|
66
67
|
onViewMode,
|
|
67
68
|
additionalButtons = [],
|
|
68
69
|
ancillaryComponents = [],
|
|
@@ -360,7 +361,12 @@ function Form(props) {
|
|
|
360
361
|
let element = <Element
|
|
361
362
|
name={name}
|
|
362
363
|
value={value}
|
|
363
|
-
onChangeValue={
|
|
364
|
+
onChangeValue={(value) => {
|
|
365
|
+
onChange(value); // form onChange handler
|
|
366
|
+
if (propsToPass.onChange) {
|
|
367
|
+
propsToPass.onChange(value); // item onChange handler
|
|
368
|
+
}
|
|
369
|
+
}}
|
|
364
370
|
onBlur={onBlur}
|
|
365
371
|
selectorId={selectorId}
|
|
366
372
|
selectorSelected={selectorSelected}
|
|
@@ -507,7 +513,12 @@ function Form(props) {
|
|
|
507
513
|
<Button.Group space={2} {...buttonGroupProps}>
|
|
508
514
|
{!isViewOnly && <IconButton
|
|
509
515
|
key="resetBtn"
|
|
510
|
-
onPress={() =>
|
|
516
|
+
onPress={() => {
|
|
517
|
+
if (onReset) {
|
|
518
|
+
onReset();
|
|
519
|
+
}
|
|
520
|
+
reset();
|
|
521
|
+
}}
|
|
511
522
|
icon={<Rotate color="#fff" />}
|
|
512
523
|
/>}
|
|
513
524
|
{!isViewOnly && onCancel && <Button
|
|
@@ -17,351 +17,385 @@ import _ from 'lodash';
|
|
|
17
17
|
|
|
18
18
|
// Filters only work with Repository; not data array
|
|
19
19
|
|
|
20
|
+
// Yet to do:
|
|
21
|
+
// - Save user choice in cookie for next time this component loads
|
|
22
|
+
//
|
|
23
|
+
// Model defaultFilters should adjust to this new arrangement
|
|
24
|
+
|
|
20
25
|
export default function withFilters(WrappedComponent) {
|
|
21
26
|
return (props) => {
|
|
22
27
|
const {
|
|
28
|
+
// config
|
|
23
29
|
useFilters = false,
|
|
24
30
|
searchAllText = true,
|
|
25
31
|
showLabels = true,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
filter1StartingValue = null,
|
|
32
|
-
filter2StartingValue = null,
|
|
33
|
-
filter3StartingValue = null,
|
|
34
|
-
filter4StartingValue = null,
|
|
35
|
-
filter5StartingValue = null,
|
|
32
|
+
showFilterSelector = true,
|
|
33
|
+
defaultFilters = [], // likely a list of field names, possibly could be of shape below
|
|
34
|
+
customFilters = [], // of shape: { title, type, field, value, getRepoFilters(value) }
|
|
35
|
+
minFilters = 3,
|
|
36
|
+
maxFilters = 6,
|
|
36
37
|
|
|
37
38
|
// withData
|
|
38
39
|
Repository,
|
|
39
|
-
} = props
|
|
40
|
-
styles = UiGlobals.styles;
|
|
40
|
+
} = props;
|
|
41
41
|
|
|
42
|
-
let modal,
|
|
42
|
+
let modal = null,
|
|
43
43
|
topToolbar = null;
|
|
44
44
|
|
|
45
45
|
if (useFilters && Repository) {
|
|
46
|
+
|
|
46
47
|
const
|
|
48
|
+
// aliases
|
|
49
|
+
{
|
|
50
|
+
defaultFilters: modelDefaultFilters,
|
|
51
|
+
filterTypes: modelFilterTypes,
|
|
52
|
+
titles: modelTitles,
|
|
53
|
+
virtualFields: modelVirtualFields,
|
|
54
|
+
excludeFields: modelExcludeFields,
|
|
55
|
+
filteringDisabled: modelFilteringDisabled,
|
|
56
|
+
} = Repository.getSchema().model,
|
|
57
|
+
|
|
58
|
+
// determine the starting filters
|
|
59
|
+
startingFilters = !_.isEmpty(customFilters) ? customFilters : // custom filters override component filters
|
|
60
|
+
!_.isEmpty(defaultFilters) ? defaultFilters : // component filters override model filters
|
|
61
|
+
!_.isEmpty(modelDefaultFilters) ? modelDefaultFilters : [],
|
|
62
|
+
isUsingCustomFilters = startingFilters === customFilters,
|
|
47
63
|
[isReady, setIsReady] = useState(false),
|
|
48
64
|
[isFilterSelectorShown, setIsFilterSelectorShown] = useState(false),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
[filter3Value, setFilter3Value] = useState(filter3StartingValue),
|
|
63
|
-
[filter4Value, setFilter4Value] = useState(filter4StartingValue),
|
|
64
|
-
[filter5Value, setFilter5Value] = useState(filter5StartingValue),
|
|
65
|
-
[filterFields, setFilterFields] = useState([]),
|
|
66
|
-
onFilterChange = (ix, value) => {
|
|
67
|
-
switch(ix) {
|
|
68
|
-
case 'q':
|
|
69
|
-
setFilterQValue(value);
|
|
70
|
-
break;
|
|
71
|
-
case 0:
|
|
72
|
-
setFilter1Value(value);
|
|
73
|
-
break;
|
|
74
|
-
case 1:
|
|
75
|
-
setFilter2Value(value);
|
|
76
|
-
break;
|
|
77
|
-
case 2:
|
|
78
|
-
setFilter3Value(value);
|
|
79
|
-
break;
|
|
80
|
-
case 3:
|
|
81
|
-
setFilter4Value(value);
|
|
82
|
-
break;
|
|
83
|
-
case 4:
|
|
84
|
-
setFilter5Value(value);
|
|
85
|
-
break;
|
|
86
|
-
default:
|
|
65
|
+
getFormattedFilter = (filter) => {
|
|
66
|
+
let formatted = null;
|
|
67
|
+
if (_.isString(filter)) {
|
|
68
|
+
const field = filter;
|
|
69
|
+
formatted = {
|
|
70
|
+
field,
|
|
71
|
+
title: modelTitles[field],
|
|
72
|
+
type: modelFilterTypes[field],
|
|
73
|
+
value: null, // value starts as null
|
|
74
|
+
};
|
|
75
|
+
} else if (_.isPlainObject(filter)) {
|
|
76
|
+
// already formatted
|
|
77
|
+
formatted = filter;
|
|
87
78
|
}
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
return formatted;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let formattedStartingFilters = [],
|
|
83
|
+
startingSlots = [];
|
|
84
|
+
if (!isReady) {
|
|
85
|
+
// Generate initial starting state
|
|
86
|
+
if (searchAllText) {
|
|
87
|
+
formattedStartingFilters.push({ field: 'q', title: 'Search all text fields', type: 'Input', value: null, });
|
|
88
|
+
}
|
|
89
|
+
_.each(startingFilters, (filter) => {
|
|
90
90
|
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
} else {
|
|
97
|
-
filterType = filterTypeDefinition.type;
|
|
91
|
+
formattedFilter = getFormattedFilter(filter),
|
|
92
|
+
field = formattedFilter.field;
|
|
93
|
+
formattedStartingFilters.push(formattedFilter);
|
|
94
|
+
if (!isUsingCustomFilters) {
|
|
95
|
+
startingSlots.push(field);
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
switch(ix) {
|
|
104
|
-
case 0:
|
|
105
|
-
field = filter1Field;
|
|
106
|
-
break;
|
|
107
|
-
case 1:
|
|
108
|
-
field = filter2Field;
|
|
109
|
-
break;
|
|
110
|
-
case 2:
|
|
111
|
-
field = filter3Field;
|
|
112
|
-
break;
|
|
113
|
-
case 3:
|
|
114
|
-
field = filter4Field;
|
|
115
|
-
break;
|
|
116
|
-
case 4:
|
|
117
|
-
field = filter5Field;
|
|
118
|
-
break;
|
|
119
|
-
default:
|
|
97
|
+
});
|
|
98
|
+
if (startingSlots.length < minFilters) {
|
|
99
|
+
for (let i = startingSlots.length; i < minFilters; i++) {
|
|
100
|
+
startingSlots.push(null);
|
|
120
101
|
}
|
|
121
|
-
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const
|
|
106
|
+
[filters, setFilters] = useState(formattedStartingFilters), // array of formatted filters
|
|
107
|
+
[slots, setSlots] = useState(startingSlots), // array of field names user is currently filtering on; blank slots have a null entry in array
|
|
108
|
+
[modalFilters, setModalFilters] = useState([]),
|
|
109
|
+
[modalSlots, setModalSlots] = useState([]),
|
|
110
|
+
[previousFilterNames, setPreviousFilterNames] = useState([]), // names of filters the repository used last query
|
|
111
|
+
canAddSlot = (() => {
|
|
112
|
+
let canAdd = true;
|
|
113
|
+
if (!!maxFilters && modalSlots.length >= maxFilters) {
|
|
114
|
+
canAdd = false; // maxFilters has been reached
|
|
115
|
+
}
|
|
116
|
+
if (canAdd) {
|
|
117
|
+
_.each(modalSlots, (field) => {
|
|
118
|
+
if (_.isNil(field)) {
|
|
119
|
+
canAdd = false; // at least one slot has no selected field to filter
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return canAdd;
|
|
125
|
+
})(),
|
|
126
|
+
canDeleteSlot = modalSlots.length > minFilters,
|
|
127
|
+
onAddSlot = () => {
|
|
128
|
+
if (!canAddSlot) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const newSlots = _.clone(modalSlots);
|
|
132
|
+
newSlots.push(null);
|
|
133
|
+
setModalSlots(newSlots);
|
|
122
134
|
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
case 'q':
|
|
127
|
-
value = filterQValue;
|
|
128
|
-
break;
|
|
129
|
-
case 0:
|
|
130
|
-
value = filter1Value;
|
|
131
|
-
break;
|
|
132
|
-
case 1:
|
|
133
|
-
value = filter2Value;
|
|
134
|
-
break;
|
|
135
|
-
case 2:
|
|
136
|
-
value = filter3Value;
|
|
137
|
-
break;
|
|
138
|
-
case 3:
|
|
139
|
-
value = filter4Value;
|
|
140
|
-
break;
|
|
141
|
-
case 4:
|
|
142
|
-
value = filter5Value;
|
|
143
|
-
break;
|
|
144
|
-
default:
|
|
135
|
+
onDeleteSlot = () => {
|
|
136
|
+
if (!canDeleteSlot) {
|
|
137
|
+
return;
|
|
145
138
|
}
|
|
146
|
-
|
|
139
|
+
const
|
|
140
|
+
newFilters = _.clone(modalFilters),
|
|
141
|
+
newSlots = _.clone(modalSlots);
|
|
142
|
+
newFilters.pop();
|
|
143
|
+
newSlots.pop();
|
|
144
|
+
setModalFilters(newFilters);
|
|
145
|
+
setModalSlots(newSlots);
|
|
146
|
+
},
|
|
147
|
+
onFilterChangeValue = (field, value) => {
|
|
148
|
+
// handler for when a filter value changes
|
|
149
|
+
const newFilters = [];
|
|
150
|
+
_.each(filters, (filter) => {
|
|
151
|
+
if (filter.field === field) {
|
|
152
|
+
filter.value = value;
|
|
153
|
+
}
|
|
154
|
+
newFilters.push(filter);
|
|
155
|
+
});
|
|
156
|
+
setFilters(newFilters);
|
|
157
|
+
},
|
|
158
|
+
onClearFilters = () => {
|
|
159
|
+
// Clears values for all active filters
|
|
160
|
+
const newFilters = [];
|
|
161
|
+
_.each(filters, (filter) => {
|
|
162
|
+
filter.value = null;
|
|
163
|
+
newFilters.push(filter);
|
|
164
|
+
});
|
|
165
|
+
setFilters(newFilters);
|
|
147
166
|
},
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
getFilterByField = (field) => {
|
|
168
|
+
return _.find(filters, (filter) => {
|
|
169
|
+
return filter.field === field;
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
getFilterValue = (field) => {
|
|
173
|
+
const filter = getFilterByField(field);
|
|
174
|
+
return filter?.value;
|
|
175
|
+
},
|
|
176
|
+
getFilterType = (field) => {
|
|
177
|
+
// Finds filter type for the field name, from active filters
|
|
178
|
+
const filter = getFilterByField(field);
|
|
179
|
+
return filter?.type;
|
|
180
|
+
},
|
|
181
|
+
getIsFilterRange = (field) => {
|
|
182
|
+
// determines if filter is a "range" filter
|
|
183
|
+
const filterType = getFilterType(field);
|
|
150
184
|
return inArray(filterType, ['NumberRange', 'DateRange']);
|
|
151
185
|
},
|
|
152
186
|
renderFilters = () => {
|
|
153
|
-
if (!Repository) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
187
|
const
|
|
158
|
-
{
|
|
159
|
-
titles = [],
|
|
160
|
-
filterTypes = [],
|
|
161
|
-
virtualFields = [],
|
|
162
|
-
excludeFields = [],
|
|
163
|
-
} = Repository.getSchema().model,
|
|
164
188
|
filterProps = {
|
|
165
189
|
mx: 1,
|
|
166
190
|
},
|
|
167
|
-
filterElements = []
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/>);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (inArray(fieldName, virtualFields) || inArray(fieldName, excludeFields)) {
|
|
184
|
-
return; // skip
|
|
185
|
-
}
|
|
186
|
-
const filterType = filterTypes[fieldName];
|
|
187
|
-
let Element,
|
|
188
|
-
modelProps = {};
|
|
189
|
-
if (_.isString(filterType)) {
|
|
190
|
-
Element = getComponentFromType(filterType);
|
|
191
|
-
} else if (_.isPlainObject(filterType)) {
|
|
192
|
-
const {
|
|
193
|
-
type,
|
|
194
|
-
...p
|
|
195
|
-
} = filterType;
|
|
196
|
-
modelProps = p;
|
|
197
|
-
Element = getComponentFromType(type);
|
|
198
|
-
}
|
|
199
|
-
if (!Element) {
|
|
200
|
-
debugger;
|
|
191
|
+
filterElements = [];
|
|
192
|
+
_.each(filters, (filter, ix) => {
|
|
193
|
+
let Element,
|
|
194
|
+
elementProps = {};
|
|
195
|
+
const {
|
|
196
|
+
field,
|
|
197
|
+
type: filterType,
|
|
198
|
+
} = filter;
|
|
199
|
+
|
|
200
|
+
if (_.isString(filterType)) {
|
|
201
|
+
Element = getComponentFromType(filterType);
|
|
202
|
+
if (filterType === 'Input') {
|
|
203
|
+
elementProps.autoSubmit = true;
|
|
201
204
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (showLabels) {
|
|
212
|
-
filterElement = <Row key={'label-' + ix} alignItems="center">
|
|
213
|
-
<Text ml={2} mr={1} fontSize={styles.FILTER_LABEL_FONTSIZE}>{titles[fieldName]}</Text>
|
|
214
|
-
{filterElement}
|
|
215
|
-
</Row>;
|
|
205
|
+
} else if (_.isPlainObject(filterType)) {
|
|
206
|
+
const {
|
|
207
|
+
type,
|
|
208
|
+
...p
|
|
209
|
+
} = filterType;
|
|
210
|
+
elementProps = p;
|
|
211
|
+
Element = getComponentFromType(type);
|
|
212
|
+
if (type === 'Input') {
|
|
213
|
+
elementProps.autoSubmit = true;
|
|
216
214
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
215
|
+
}
|
|
216
|
+
if (field === 'q') {
|
|
217
|
+
elementProps.flex = 1;
|
|
218
|
+
elementProps.minWidth = 100;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const tooltip = filter.tooltip || filter.title || modelTitles[filter.field];
|
|
222
|
+
let filterElement = <Element
|
|
223
|
+
key={'filter-' + field}
|
|
224
|
+
tooltip={tooltip}
|
|
225
|
+
placeholder={tooltip}
|
|
226
|
+
value={getFilterValue(field)}
|
|
227
|
+
onChangeValue={(value) => onFilterChangeValue(field, value)}
|
|
228
|
+
{...filterProps}
|
|
229
|
+
{...elementProps}
|
|
230
|
+
/>;
|
|
231
|
+
if (showLabels && field !== 'q') {
|
|
232
|
+
filterElement = <Row key={'label-' + ix} alignItems="center">
|
|
233
|
+
<Text ml={2} mr={1} fontSize={UiGlobals.styles.FILTER_LABEL_FONTSIZE}>{modelTitles[field]}</Text>
|
|
234
|
+
{filterElement}
|
|
235
|
+
</Row>;
|
|
236
|
+
}
|
|
237
|
+
filterElements.push(filterElement);
|
|
238
|
+
});
|
|
238
239
|
return filterElements;
|
|
239
|
-
},
|
|
240
|
-
setFiltersOn = (ix, filters, newFilterFields) => {
|
|
241
|
-
const
|
|
242
|
-
filterIxField = getFilterField(ix),
|
|
243
|
-
filterIxValue = getFilterValue(ix),
|
|
244
|
-
isFilterRange = getIsFilterRange(ix);
|
|
245
|
-
let highValue,
|
|
246
|
-
lowValue,
|
|
247
|
-
highField,
|
|
248
|
-
lowField;
|
|
249
|
-
if (isFilterRange && !!filterIxValue) {
|
|
250
|
-
highValue = filterIxValue.high;
|
|
251
|
-
lowValue = filterIxValue.low;
|
|
252
|
-
highField = filterIxField + ' <=';
|
|
253
|
-
lowField = filterIxField + ' >=';
|
|
254
|
-
|
|
255
|
-
newFilterFields.push(highField);
|
|
256
|
-
newFilterFields.push(lowField);
|
|
257
|
-
filters.push({ name: highField, value: highValue, });
|
|
258
|
-
filters.push({ name: lowField, value: lowValue, });
|
|
259
|
-
} else {
|
|
260
|
-
newFilterFields.push(filterIxField);
|
|
261
|
-
filters.push({ name: filterIxField, value: filterIxValue, });
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
onClearFilters = () => {
|
|
265
|
-
setFilterQValue(null);
|
|
266
|
-
setFilter1Value(null);
|
|
267
|
-
setFilter2Value(null);
|
|
268
|
-
setFilter3Value(null);
|
|
269
|
-
setFilter4Value(null);
|
|
270
|
-
setFilter5Value(null);
|
|
271
240
|
};
|
|
272
241
|
|
|
273
242
|
useEffect(() => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
newFilterFields = [];
|
|
243
|
+
// Whenever the filters change in some way, make repository conform to these new filters
|
|
244
|
+
const newRepoFilters = [];
|
|
277
245
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
246
|
+
if (isUsingCustomFilters) {
|
|
247
|
+
_.each(filters, (filter) => {
|
|
248
|
+
const repoFiltersFromFilter = filter.getRepoFilters(value);
|
|
249
|
+
_.each(repoFiltersFromFilter, (repoFilter) => { // one custom filter might generate multiple filters for the repository
|
|
250
|
+
newRepoFilters.push(repoFilter);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
} else {
|
|
254
|
+
const newFilterNames = [];
|
|
255
|
+
_.each(filters, (filter) => {
|
|
256
|
+
const {
|
|
257
|
+
field,
|
|
258
|
+
value,
|
|
259
|
+
} = filter,
|
|
260
|
+
isFilterRange = getIsFilterRange(field);
|
|
261
|
+
if (isFilterRange) {
|
|
262
|
+
if (!!value) {
|
|
263
|
+
const
|
|
264
|
+
highField = field + ' <=',
|
|
265
|
+
lowField = field + ' >=',
|
|
266
|
+
highValue = value.high,
|
|
267
|
+
lowValue = value.low;
|
|
268
|
+
newFilterNames.push(highField);
|
|
269
|
+
newFilterNames.push(lowField);
|
|
270
|
+
newRepoFilters.push({ name: highField, value: highValue, });
|
|
271
|
+
newRepoFilters.push({ name: lowField, value: lowValue, });
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
newFilterNames.push(field);
|
|
275
|
+
newRepoFilters.push({ name: field, value, });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Go through previousFilterNames and see if any are no longer used.
|
|
280
|
+
_.each(previousFilterNames, (name) => {
|
|
281
|
+
if (!inArray(name, newFilterNames)) {
|
|
282
|
+
newRepoFilters.push({ name, value: null, }); // no longer used, so set it to null so it'll be deleted
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
setPreviousFilterNames(newFilterNames);
|
|
301
286
|
}
|
|
302
|
-
setFilterFields(newFilterFields);
|
|
303
287
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
Repository.filter(filters, null, false); // false so other filters remain
|
|
288
|
+
Repository.filter(newRepoFilters, null, false); // false so other filters remain
|
|
289
|
+
|
|
290
|
+
if (searchAllText && Repository.searchAncillary && !Repository.hasBaseParam('searchAncillary')) {
|
|
291
|
+
Repository.setBaseParam('searchAncillary', true);
|
|
292
|
+
}
|
|
312
293
|
|
|
313
294
|
if (!isReady) {
|
|
314
295
|
setIsReady(true);
|
|
315
296
|
}
|
|
316
297
|
|
|
317
|
-
}, [
|
|
318
|
-
filter1Value, filter2Value, filter3Value, filter4Value, filter5Value,
|
|
319
|
-
filterQValue,]);
|
|
298
|
+
}, [filters]);
|
|
320
299
|
|
|
321
300
|
if (!isReady) {
|
|
322
301
|
return null;
|
|
323
302
|
}
|
|
324
303
|
|
|
304
|
+
const
|
|
305
|
+
renderedFilters = renderFilters(),
|
|
306
|
+
hasFilters = !!renderedFilters.length;
|
|
307
|
+
topToolbar = <Toolbar justifyContent="space-between" alignItems="center">
|
|
308
|
+
<Text pr={2} userSelect="none">Filters:{hasFilters ? '' : ' None'}</Text>
|
|
309
|
+
{renderedFilters}
|
|
310
|
+
<Row flex={hasFilters ? null : 1} justifyContent="flex-end">
|
|
311
|
+
<IconButton
|
|
312
|
+
key="clear"
|
|
313
|
+
_icon={{
|
|
314
|
+
as: Ban,
|
|
315
|
+
}}
|
|
316
|
+
ml={1}
|
|
317
|
+
onPress={onClearFilters}
|
|
318
|
+
tooltip="Clear all filters"
|
|
319
|
+
/>
|
|
320
|
+
{showFilterSelector && !isUsingCustomFilters && <IconButton
|
|
321
|
+
key="gear"
|
|
322
|
+
_icon={{
|
|
323
|
+
as: Gear,
|
|
324
|
+
}}
|
|
325
|
+
ml={1}
|
|
326
|
+
onPress={() => {
|
|
327
|
+
setModalFilters(filters);
|
|
328
|
+
setModalSlots(slots);
|
|
329
|
+
setIsFilterSelectorShown(true);
|
|
330
|
+
}}
|
|
331
|
+
tooltip="Swap filters"
|
|
332
|
+
/>}
|
|
333
|
+
</Row>
|
|
334
|
+
</Toolbar>;
|
|
325
335
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
filterComboProps.data = [];
|
|
329
|
-
const schemaModel = Repository.getSchema().model;
|
|
330
|
-
_.each(schemaModel.titles, (title, fieldName) => {
|
|
331
|
-
if (!inArray(fieldName, schemaModel.virtualFields) && !inArray(fieldName, schemaModel.excludeFields) && !inArray(fieldName, schemaModel.filteringDisabled)) {
|
|
332
|
-
filterComboProps.data.push([fieldName, title]);
|
|
333
|
-
}
|
|
334
|
-
});
|
|
336
|
+
if (isFilterSelectorShown) { // this is always false when isUsingCustomFilters
|
|
337
|
+
// Build the modal to select the filters
|
|
335
338
|
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
339
|
+
modalFilterElements = [],
|
|
340
|
+
usedFields = _.filter(_.map(modalFilters, (filter) => {
|
|
341
|
+
return filter?.field;
|
|
342
|
+
}), el => !_.isNil(el)),
|
|
343
|
+
formStartingValues = {};
|
|
344
|
+
|
|
345
|
+
_.each(modalSlots, (field, ix) => {
|
|
346
|
+
|
|
347
|
+
// Create the data for the combobox.
|
|
348
|
+
const data = [];
|
|
349
|
+
_.each(modelFilterTypes, (filterType, filterField) => {
|
|
350
|
+
if (inArray(filterField, usedFields) && field !== filterField) { // Show all filters not yet applied, but include the current filter
|
|
351
|
+
return; // skip, since it's already been used
|
|
352
|
+
}
|
|
353
|
+
data.push([ filterField, modelTitles[filterField] ]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const
|
|
357
|
+
ixPlusOne = (ix +1),
|
|
358
|
+
filterName = 'filter' + ixPlusOne;
|
|
359
|
+
|
|
360
|
+
modalFilterElements.push({
|
|
361
|
+
key: filterName,
|
|
362
|
+
name: filterName,
|
|
363
|
+
type: 'Combo',
|
|
364
|
+
label: 'Filter ' + ixPlusOne,
|
|
365
|
+
data,
|
|
366
|
+
onChange: (value) => {
|
|
367
|
+
const
|
|
368
|
+
newFilters = _.clone(modalFilters),
|
|
369
|
+
newSlots = _.clone(modalSlots),
|
|
370
|
+
i = searchAllText ? ixPlusOne : ix; // compensate for 'q' filter's possible presence
|
|
371
|
+
|
|
372
|
+
if (newFilters[i]?.value) {
|
|
373
|
+
newFilters[i].value = value;
|
|
374
|
+
} else {
|
|
375
|
+
newFilters[i] = getFormattedFilter(value);
|
|
376
|
+
}
|
|
377
|
+
newSlots[ix] = value;
|
|
378
|
+
|
|
379
|
+
setModalFilters(newFilters);
|
|
380
|
+
setModalSlots(newSlots);
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
formStartingValues[filterName] = field;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
if (canAddSlot || canDeleteSlot) {
|
|
388
|
+
modalFilterElements.push({
|
|
389
|
+
type: 'PlusMinusButton',
|
|
390
|
+
name: 'plusMinusButton',
|
|
391
|
+
plusHandler: onAddSlot,
|
|
392
|
+
minusHandler: onDeleteSlot,
|
|
393
|
+
isPlusDisabled: !canAddSlot,
|
|
394
|
+
isMinusDisabled: !canDeleteSlot,
|
|
395
|
+
justifyContent: 'flex-end',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
363
398
|
|
|
364
|
-
if (isFilterSelectorShown) {
|
|
365
399
|
modal = <Modal
|
|
366
400
|
isOpen={true}
|
|
367
401
|
onClose={() => setIsFilterSelectorShown(false)}
|
|
@@ -369,99 +403,60 @@ export default function withFilters(WrappedComponent) {
|
|
|
369
403
|
<Column bg="#fff" w={500}>
|
|
370
404
|
<FormPanel
|
|
371
405
|
title="Filter Selector"
|
|
372
|
-
instructions="Please select which fields to filter by.
|
|
406
|
+
instructions="Please select which fields to filter by."
|
|
373
407
|
flex={1}
|
|
374
|
-
startingValues={
|
|
375
|
-
filter1: filter1Field,
|
|
376
|
-
filter2: filter2Field,
|
|
377
|
-
filter3: filter3Field,
|
|
378
|
-
filter4: filter4Field,
|
|
379
|
-
filter5: filter5Field,
|
|
380
|
-
}}
|
|
408
|
+
startingValues={formStartingValues}
|
|
381
409
|
items={[
|
|
382
410
|
{
|
|
383
411
|
type: 'Column',
|
|
384
412
|
flex: 1,
|
|
385
|
-
items:
|
|
386
|
-
|
|
387
|
-
type: 'Combo',
|
|
388
|
-
label: 'Filter 1',
|
|
389
|
-
name: 'filter1',
|
|
390
|
-
onChangeValue: (value) => {
|
|
391
|
-
setFilter1FieldForModal(value);
|
|
392
|
-
},
|
|
393
|
-
...filterComboProps,
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
type: 'Combo',
|
|
397
|
-
label: 'Filter 2',
|
|
398
|
-
name: 'filter2',
|
|
399
|
-
onChangeValue: (value) => {
|
|
400
|
-
setFilter2FieldForModal(value);
|
|
401
|
-
},
|
|
402
|
-
...filterComboProps,
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
type: 'Combo',
|
|
406
|
-
label: 'Filter 3',
|
|
407
|
-
name: 'filter3',
|
|
408
|
-
onChangeValue: (value) => {
|
|
409
|
-
setFilter3FieldForModal(value);
|
|
410
|
-
},
|
|
411
|
-
...filterComboProps,
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
type: 'Combo',
|
|
415
|
-
label: 'Filter 4',
|
|
416
|
-
name: 'filter4',
|
|
417
|
-
onChangeValue: (value) => {
|
|
418
|
-
setFilter4FieldForModal(value);
|
|
419
|
-
},
|
|
420
|
-
...filterComboProps,
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
type: 'Combo',
|
|
424
|
-
label: 'Filter 5',
|
|
425
|
-
name: 'filter5',
|
|
426
|
-
onChangeValue: (value) => {
|
|
427
|
-
setFilter5FieldForModal(value);
|
|
428
|
-
},
|
|
429
|
-
...filterComboProps,
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
}, // END Column
|
|
413
|
+
items: modalFilterElements,
|
|
414
|
+
},
|
|
433
415
|
]}
|
|
434
416
|
onCancel={(e) => {
|
|
435
|
-
|
|
436
|
-
setFilter2FieldForModal(filter2Field);
|
|
437
|
-
setFilter3FieldForModal(filter3Field);
|
|
438
|
-
setFilter4FieldForModal(filter4Field);
|
|
439
|
-
setFilter5FieldForModal(filter5Field);
|
|
417
|
+
// Just close the modal
|
|
440
418
|
setIsFilterSelectorShown(false);
|
|
441
419
|
}}
|
|
442
420
|
onSave={(data, e) => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (filter3FieldForModal !== filter3Field) {
|
|
452
|
-
setFilter3Field(filter3FieldForModal);
|
|
453
|
-
setFilter3Value(null);
|
|
454
|
-
}
|
|
455
|
-
if (filter4FieldForModal !== filter4Field) {
|
|
456
|
-
setFilter4Field(filter4FieldForModal);
|
|
457
|
-
setFilter4Value(null);
|
|
421
|
+
// Conform filters to this new choice of filters
|
|
422
|
+
|
|
423
|
+
const
|
|
424
|
+
newFilters = [],
|
|
425
|
+
newSlots = [];
|
|
426
|
+
|
|
427
|
+
if (searchAllText) {
|
|
428
|
+
newFilters.push(filters[0]);
|
|
458
429
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
430
|
+
|
|
431
|
+
// Conform the filters to the modal selection
|
|
432
|
+
_.each(data, (field, ix) => {
|
|
433
|
+
if (_.isEmpty(field) || !ix.match(/^filter/)) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const newFilter = getFormattedFilter(field);
|
|
438
|
+
|
|
439
|
+
newFilters.push(newFilter);
|
|
440
|
+
newSlots.push(field);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (newSlots.length < minFilters) {
|
|
444
|
+
// Add more slots until we get to minFilters
|
|
445
|
+
for(let i = newSlots.length; i < minFilters; i++) {
|
|
446
|
+
newSlots.push(null);
|
|
447
|
+
}
|
|
462
448
|
}
|
|
449
|
+
|
|
450
|
+
setFilters(newFilters);
|
|
451
|
+
setSlots(newSlots);
|
|
452
|
+
|
|
453
|
+
// Close the modal
|
|
463
454
|
setIsFilterSelectorShown(false);
|
|
464
455
|
}}
|
|
456
|
+
onReset={() => {
|
|
457
|
+
setModalFilters(filters);
|
|
458
|
+
setModalSlots(slots);
|
|
459
|
+
}}
|
|
465
460
|
/>
|
|
466
461
|
</Column>
|
|
467
462
|
</Modal>;
|
package/src/Components/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import Number from './Form/Field/Number.js';
|
|
|
35
35
|
import NumberRange from './Filter/NumberRange.js';
|
|
36
36
|
import Panel from './Panel/Panel.js';
|
|
37
37
|
// import Picker from '../Components/Panel/Picker.js';
|
|
38
|
+
import PlusMinusButton from './Buttons/PlusMinusButton.js';
|
|
38
39
|
import RadioGroup from './Form/Field/RadioGroup/RadioGroup.js';
|
|
39
40
|
import TabPanel from './Panel/TabPanel.js';
|
|
40
41
|
import Tag from './Form/Field/Combo/Tag.js';
|
|
@@ -82,6 +83,7 @@ const components = {
|
|
|
82
83
|
NumberRange,
|
|
83
84
|
Panel,
|
|
84
85
|
// Picker,
|
|
86
|
+
PlusMinusButton,
|
|
85
87
|
RadioGroup,
|
|
86
88
|
TabPanel,
|
|
87
89
|
Tag,
|