@servicetitan/table 23.4.0 → 24.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/demo/filters/async-select-filter.d.ts +3 -0
- package/dist/demo/filters/async-select-filter.d.ts.map +1 -0
- package/dist/demo/filters/async-select-filter.js +23 -0
- package/dist/demo/filters/async-select-filter.js.map +1 -0
- package/dist/demo/filters/categories.d.ts +9 -0
- package/dist/demo/filters/categories.d.ts.map +1 -0
- package/dist/demo/filters/categories.js +33 -0
- package/dist/demo/filters/categories.js.map +1 -0
- package/dist/demo/filters/multiselect-filter.d.ts +3 -0
- package/dist/demo/filters/multiselect-filter.d.ts.map +1 -0
- package/dist/demo/filters/multiselect-filter.js +14 -0
- package/dist/demo/filters/multiselect-filter.js.map +1 -0
- package/dist/demo/filters/range-filter.d.ts +5 -0
- package/dist/demo/filters/range-filter.d.ts.map +1 -0
- package/dist/demo/filters/range-filter.js +24 -0
- package/dist/demo/filters/range-filter.js.map +1 -0
- package/dist/demo/filters/single-select-filter.d.ts +3 -0
- package/dist/demo/filters/single-select-filter.d.ts.map +1 -0
- package/dist/demo/filters/single-select-filter.js +20 -0
- package/dist/demo/filters/single-select-filter.js.map +1 -0
- package/dist/demo/filters/table.store.d.ts +9 -0
- package/dist/demo/filters/table.store.d.ts.map +1 -0
- package/dist/demo/filters/table.store.js +73 -0
- package/dist/demo/filters/table.store.js.map +1 -0
- package/dist/demo/overview/products.js +10 -10
- package/dist/filters/async-select/async-select-filter.d.ts +19 -0
- package/dist/filters/async-select/async-select-filter.d.ts.map +1 -0
- package/dist/filters/async-select/async-select-filter.js +168 -0
- package/dist/filters/async-select/async-select-filter.js.map +1 -0
- package/dist/filters/async-select/async-select-filter.stories.d.ts +7 -0
- package/dist/filters/async-select/async-select-filter.stories.d.ts.map +1 -0
- package/dist/filters/async-select/async-select-filter.stories.js +7 -0
- package/dist/filters/async-select/async-select-filter.stories.js.map +1 -0
- package/dist/filters/index.d.ts +3 -1
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +3 -1
- package/dist/filters/index.js.map +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.d.ts.map +1 -0
- package/dist/filters/{multiselect-filter.js → multiselect-filter/multiselect-filter.js} +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.js.map +1 -0
- package/dist/filters/multiselect-filter/multiselect-filter.stories.d.ts +7 -0
- package/dist/filters/multiselect-filter/multiselect-filter.stories.d.ts.map +1 -0
- package/dist/filters/multiselect-filter/multiselect-filter.stories.js +7 -0
- package/dist/filters/multiselect-filter/multiselect-filter.stories.js.map +1 -0
- package/dist/filters/range-filter/range-filter.stories.d.ts +9 -0
- package/dist/filters/range-filter/range-filter.stories.d.ts.map +1 -0
- package/dist/filters/range-filter/range-filter.stories.js +9 -0
- package/dist/filters/range-filter/range-filter.stories.js.map +1 -0
- package/dist/filters/single-select/single-select-filter.d.ts +9 -0
- package/dist/filters/single-select/single-select-filter.d.ts.map +1 -0
- package/dist/filters/single-select/single-select-filter.js +30 -0
- package/dist/filters/single-select/single-select-filter.js.map +1 -0
- package/dist/filters/single-select/single-select-filter.stories.d.ts +7 -0
- package/dist/filters/single-select/single-select-filter.stories.d.ts.map +1 -0
- package/dist/filters/single-select/single-select-filter.stories.js +7 -0
- package/dist/filters/single-select/single-select-filter.stories.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/filters.d.ts +4 -0
- package/dist/utils/filters.d.ts.map +1 -0
- package/dist/utils/filters.js +22 -0
- package/dist/utils/filters.js.map +1 -0
- package/package.json +6 -6
- package/src/demo/filters/async-select-filter.tsx +53 -0
- package/src/demo/filters/categories.tsx +40 -0
- package/src/demo/filters/multiselect-filter.tsx +32 -0
- package/src/demo/filters/range-filter.tsx +72 -0
- package/src/demo/filters/single-select-filter.tsx +51 -0
- package/src/demo/filters/table.store.ts +45 -0
- package/src/demo/overview/products.ts +10 -10
- package/src/filters/async-select/async-select-filter.stories.tsx +7 -0
- package/src/filters/async-select/async-select-filter.tsx +249 -0
- package/src/filters/index.ts +3 -1
- package/src/filters/multiselect-filter/multiselect-filter.stories.tsx +7 -0
- package/src/filters/{multiselect-filter.tsx → multiselect-filter/multiselect-filter.tsx} +1 -1
- package/src/filters/range-filter/range-filter.stories.tsx +13 -0
- package/src/filters/single-select/single-select-filter.stories.tsx +7 -0
- package/src/filters/single-select/single-select-filter.tsx +66 -0
- package/src/index.ts +1 -0
- package/src/utils/__tests__/filters.test.ts +31 -0
- package/src/utils/filters.ts +33 -0
- package/dist/filters/multiselect-filter.d.ts.map +0 -1
- package/dist/filters/multiselect-filter.js.map +0 -1
- /package/dist/filters/{multiselect-filter.d.ts → multiselect-filter/multiselect-filter.d.ts} +0 -0
@@ -10,7 +10,7 @@ export const products: Product[] = [
|
|
10
10
|
QuantityPerUnit: '10 boxes x 20 bags',
|
11
11
|
UnitPrice: 18.0,
|
12
12
|
UnitsInStock: 39,
|
13
|
-
UnitsOnOrder: new Date('1/11/
|
13
|
+
UnitsOnOrder: new Date('1/11/2023'),
|
14
14
|
Discontinued: false,
|
15
15
|
AvailableFor: UserRole.Admin,
|
16
16
|
},
|
@@ -23,7 +23,7 @@ export const products: Product[] = [
|
|
23
23
|
QuantityPerUnit: '24 - 12 oz bottles',
|
24
24
|
UnitPrice: 19.0,
|
25
25
|
UnitsInStock: 17,
|
26
|
-
UnitsOnOrder: new Date('2/11/
|
26
|
+
UnitsOnOrder: new Date('2/11/2023'),
|
27
27
|
Discontinued: false,
|
28
28
|
},
|
29
29
|
{
|
@@ -35,7 +35,7 @@ export const products: Product[] = [
|
|
35
35
|
QuantityPerUnit: '12 - 550 ml bottles',
|
36
36
|
UnitPrice: 10.0,
|
37
37
|
UnitsInStock: 13,
|
38
|
-
UnitsOnOrder: new Date('3/11/
|
38
|
+
UnitsOnOrder: new Date('3/11/2023'),
|
39
39
|
Discontinued: false,
|
40
40
|
AvailableFor: UserRole.Owner,
|
41
41
|
},
|
@@ -48,7 +48,7 @@ export const products: Product[] = [
|
|
48
48
|
QuantityPerUnit: '48 - 6 oz jars',
|
49
49
|
UnitPrice: 22.0,
|
50
50
|
UnitsInStock: 53,
|
51
|
-
UnitsOnOrder: new Date('4/11/
|
51
|
+
UnitsOnOrder: new Date('4/11/2023'),
|
52
52
|
Discontinued: false,
|
53
53
|
},
|
54
54
|
{
|
@@ -60,7 +60,7 @@ export const products: Product[] = [
|
|
60
60
|
QuantityPerUnit: '36 boxes',
|
61
61
|
UnitPrice: 21.35,
|
62
62
|
UnitsInStock: 0,
|
63
|
-
UnitsOnOrder: new Date('5/11/
|
63
|
+
UnitsOnOrder: new Date('5/11/2023'),
|
64
64
|
Discontinued: true,
|
65
65
|
AvailableFor: UserRole.GeneralOffice,
|
66
66
|
},
|
@@ -73,7 +73,7 @@ export const products: Product[] = [
|
|
73
73
|
QuantityPerUnit: '12 - 8 oz jars',
|
74
74
|
UnitPrice: 25.0,
|
75
75
|
UnitsInStock: 120,
|
76
|
-
UnitsOnOrder: new Date('6/11/
|
76
|
+
UnitsOnOrder: new Date('6/11/2023'),
|
77
77
|
Discontinued: false,
|
78
78
|
},
|
79
79
|
{
|
@@ -85,7 +85,7 @@ export const products: Product[] = [
|
|
85
85
|
QuantityPerUnit: '12 - 1 lb pkgs.',
|
86
86
|
UnitPrice: 30.0,
|
87
87
|
UnitsInStock: 15,
|
88
|
-
UnitsOnOrder: new Date('7/11/
|
88
|
+
UnitsOnOrder: new Date('7/11/2023'),
|
89
89
|
Discontinued: false,
|
90
90
|
},
|
91
91
|
{
|
@@ -97,7 +97,7 @@ export const products: Product[] = [
|
|
97
97
|
QuantityPerUnit: '12 - 12 oz jars',
|
98
98
|
UnitPrice: 40.0,
|
99
99
|
UnitsInStock: 6,
|
100
|
-
UnitsOnOrder: new Date('8/11/
|
100
|
+
UnitsOnOrder: new Date('8/11/2023'),
|
101
101
|
Discontinued: false,
|
102
102
|
},
|
103
103
|
{
|
@@ -109,7 +109,7 @@ export const products: Product[] = [
|
|
109
109
|
QuantityPerUnit: '18 - 500 g pkgs.',
|
110
110
|
UnitPrice: 97.0,
|
111
111
|
UnitsInStock: 29,
|
112
|
-
UnitsOnOrder: new Date('9/11/
|
112
|
+
UnitsOnOrder: new Date('9/11/2023'),
|
113
113
|
Discontinued: true,
|
114
114
|
},
|
115
115
|
{
|
@@ -121,7 +121,7 @@ export const products: Product[] = [
|
|
121
121
|
QuantityPerUnit: '12 - 200 ml jars',
|
122
122
|
UnitPrice: 31.0,
|
123
123
|
UnitsInStock: 31,
|
124
|
-
UnitsOnOrder: new Date('10/11/
|
124
|
+
UnitsOnOrder: new Date('10/11/2023'),
|
125
125
|
Discontinued: false,
|
126
126
|
},
|
127
127
|
];
|
@@ -0,0 +1,249 @@
|
|
1
|
+
import { Component, SyntheticEvent, ReactNode, Fragment, KeyboardEvent, FC } from 'react';
|
2
|
+
|
3
|
+
import {
|
4
|
+
Checkbox,
|
5
|
+
TableFilterCellProps,
|
6
|
+
Radio,
|
7
|
+
Input,
|
8
|
+
Spinner,
|
9
|
+
BodyText,
|
10
|
+
} from '@servicetitan/design-system';
|
11
|
+
import { IdType } from '@servicetitan/data-query';
|
12
|
+
|
13
|
+
import { renderCustomColumnMenuFilter } from '../column-menu-filters';
|
14
|
+
import { makeObservable, observable, runInAction } from 'mobx';
|
15
|
+
import { observer } from 'mobx-react';
|
16
|
+
|
17
|
+
export type AsyncSelectFilterDataFetcher<
|
18
|
+
TV extends IdType = IdType,
|
19
|
+
TO extends AsyncSelectItem<TV> = AsyncSelectItem<TV>
|
20
|
+
> = (opts: { search?: string }) => Promise<{ data: TO[] }>;
|
21
|
+
|
22
|
+
export interface AsyncSelectItem<TV extends IdType = IdType> {
|
23
|
+
value: TV;
|
24
|
+
text: string;
|
25
|
+
}
|
26
|
+
|
27
|
+
interface SelectorProps<TID extends IdType, TO extends AsyncSelectItem<TID>> {
|
28
|
+
placeholder: string;
|
29
|
+
selected: TO[];
|
30
|
+
dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
|
31
|
+
itemComponent: FC<SelectorItemProps>;
|
32
|
+
onChange(option: TO, checked: boolean, event: SyntheticEvent<HTMLInputElement>): void;
|
33
|
+
renderer?(item: TO): ReactNode;
|
34
|
+
}
|
35
|
+
|
36
|
+
interface SelectorItemProps {
|
37
|
+
option: AsyncSelectItem;
|
38
|
+
checked: boolean;
|
39
|
+
onChange(
|
40
|
+
option: AsyncSelectItem,
|
41
|
+
checked: boolean,
|
42
|
+
event: SyntheticEvent<HTMLInputElement>
|
43
|
+
): void;
|
44
|
+
renderer?(item: AsyncSelectItem): ReactNode;
|
45
|
+
}
|
46
|
+
|
47
|
+
@observer
|
48
|
+
class SelectorAsync<TID extends IdType, TO extends AsyncSelectItem<TID>> extends Component<
|
49
|
+
SelectorProps<TID, TO>
|
50
|
+
> {
|
51
|
+
@observable shownOptions: AsyncSelectItem<TID>[] = [];
|
52
|
+
@observable search = '';
|
53
|
+
@observable error = false;
|
54
|
+
@observable loading = false;
|
55
|
+
|
56
|
+
constructor(props: SelectorProps<TID, TO>) {
|
57
|
+
super(props);
|
58
|
+
makeObservable(this);
|
59
|
+
}
|
60
|
+
|
61
|
+
componentDidMount() {
|
62
|
+
this.searchOptions().catch();
|
63
|
+
}
|
64
|
+
|
65
|
+
handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
66
|
+
if (event.key === 'Enter') {
|
67
|
+
event.stopPropagation();
|
68
|
+
}
|
69
|
+
};
|
70
|
+
|
71
|
+
handleSearch = (_0: SyntheticEvent<HTMLInputElement>, data: { value: string }) => {
|
72
|
+
runInAction(() => (this.search = data.value));
|
73
|
+
this.searchOptions().catch();
|
74
|
+
};
|
75
|
+
|
76
|
+
searchOptions = async () => {
|
77
|
+
runInAction(() => {
|
78
|
+
this.loading = true;
|
79
|
+
});
|
80
|
+
|
81
|
+
try {
|
82
|
+
const { data } = await this.props.dataFetcher({
|
83
|
+
search: this.search,
|
84
|
+
});
|
85
|
+
|
86
|
+
runInAction(() => {
|
87
|
+
this.shownOptions = data;
|
88
|
+
this.error = false;
|
89
|
+
this.loading = false;
|
90
|
+
});
|
91
|
+
} catch {
|
92
|
+
runInAction(() => {
|
93
|
+
this.error = true;
|
94
|
+
this.loading = false;
|
95
|
+
});
|
96
|
+
}
|
97
|
+
};
|
98
|
+
|
99
|
+
render() {
|
100
|
+
const selectedOptions = this.props.selected;
|
101
|
+
const selected = new Set(selectedOptions.map(opt => opt.value));
|
102
|
+
const ItemComponent = this.props.itemComponent;
|
103
|
+
|
104
|
+
return (
|
105
|
+
<Fragment>
|
106
|
+
<Input
|
107
|
+
className="m-x-half m-t-half m-b-2"
|
108
|
+
placeholder={this.props.placeholder}
|
109
|
+
onChange={this.handleSearch}
|
110
|
+
onKeyDown={this.handleKeyDown}
|
111
|
+
size="xsmall"
|
112
|
+
value={this.search}
|
113
|
+
/>
|
114
|
+
<div className="p-x-half position-relative" onKeyDown={this.handleKeyDown}>
|
115
|
+
{!!selectedOptions.length && (
|
116
|
+
<div className="border-bottom m-y-half">
|
117
|
+
{selectedOptions.map(option => (
|
118
|
+
<ItemComponent
|
119
|
+
key={option.value}
|
120
|
+
option={option}
|
121
|
+
checked
|
122
|
+
renderer={this.props.renderer}
|
123
|
+
onChange={this.props.onChange}
|
124
|
+
/>
|
125
|
+
))}
|
126
|
+
</div>
|
127
|
+
)}
|
128
|
+
{this.error ? (
|
129
|
+
<BodyText className="c-red-500">Unable to load options</BodyText>
|
130
|
+
) : this.shownOptions.length ? (
|
131
|
+
this.shownOptions
|
132
|
+
.filter(opt => !selected.has(opt.value))
|
133
|
+
.map(option => (
|
134
|
+
<ItemComponent
|
135
|
+
key={option.value}
|
136
|
+
option={option}
|
137
|
+
checked={false}
|
138
|
+
renderer={this.props.renderer}
|
139
|
+
onChange={this.props.onChange}
|
140
|
+
/>
|
141
|
+
))
|
142
|
+
) : this.loading ? (
|
143
|
+
<BodyText> </BodyText>
|
144
|
+
) : (
|
145
|
+
<BodyText>No options found</BodyText>
|
146
|
+
)}
|
147
|
+
|
148
|
+
{this.loading && (
|
149
|
+
<div className="position-absolute top-0 bottom-0 left-0 right-0 opacity-disabled bg-white d-f justify-content-center">
|
150
|
+
<Spinner size="tiny" />
|
151
|
+
</div>
|
152
|
+
)}
|
153
|
+
</div>
|
154
|
+
</Fragment>
|
155
|
+
);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
const SelectorItemSingle: FC<SelectorItemProps> = ({ option, renderer, checked, onChange }) => (
|
160
|
+
<Radio
|
161
|
+
label={renderer?.(option) ?? option.text}
|
162
|
+
checked={checked}
|
163
|
+
onChange={(_, event) => onChange(option, true, event)}
|
164
|
+
className="m-b-1"
|
165
|
+
/>
|
166
|
+
);
|
167
|
+
|
168
|
+
const SelectorItemMultiple: FC<SelectorItemProps> = ({ option, renderer, checked, onChange }) => (
|
169
|
+
<Checkbox
|
170
|
+
label={renderer?.(option) ?? option.text}
|
171
|
+
checked={checked}
|
172
|
+
onChange={(_, checked, event) => onChange(option, checked, event)}
|
173
|
+
className="m-b-1"
|
174
|
+
/>
|
175
|
+
);
|
176
|
+
|
177
|
+
export interface AsyncSelectFilterOptions<TID extends IdType, TO extends AsyncSelectItem<TID>> {
|
178
|
+
dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
|
179
|
+
placeholder: string;
|
180
|
+
multiple?: boolean;
|
181
|
+
renderItem?: (item: TO) => ReactNode;
|
182
|
+
}
|
183
|
+
|
184
|
+
interface TableFilterCellPropsTyped<T = any> extends Omit<TableFilterCellProps, 'value'> {
|
185
|
+
value?: T;
|
186
|
+
}
|
187
|
+
|
188
|
+
export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends AsyncSelectItem<TID>>(
|
189
|
+
options: AsyncSelectFilterOptions<TID, TO>
|
190
|
+
) {
|
191
|
+
const contains = (value: TID, options?: TO[]) => options?.some(opt => opt.value === value);
|
192
|
+
const equals = (value: TID, option?: TO) => option?.value === value;
|
193
|
+
|
194
|
+
const FilterCell = options.multiple
|
195
|
+
? ({ value, onChange }: TableFilterCellPropsTyped<TO[]>) => {
|
196
|
+
const handleChange = (
|
197
|
+
option: TO,
|
198
|
+
checked: boolean,
|
199
|
+
event: SyntheticEvent<HTMLInputElement>
|
200
|
+
) => {
|
201
|
+
const val = checked
|
202
|
+
? (value ?? []).concat(option)
|
203
|
+
: (value ?? []).filter(opt => opt.value !== option.value);
|
204
|
+
|
205
|
+
onChange({
|
206
|
+
value: val.length ? val : undefined,
|
207
|
+
operator: val.length ? contains : '',
|
208
|
+
syntheticEvent: event,
|
209
|
+
});
|
210
|
+
};
|
211
|
+
|
212
|
+
return (
|
213
|
+
<SelectorAsync
|
214
|
+
selected={value ?? []}
|
215
|
+
itemComponent={SelectorItemMultiple}
|
216
|
+
onChange={handleChange}
|
217
|
+
placeholder={options.placeholder}
|
218
|
+
renderer={options.renderItem}
|
219
|
+
dataFetcher={options.dataFetcher}
|
220
|
+
/>
|
221
|
+
);
|
222
|
+
}
|
223
|
+
: ({ value, onChange }: TableFilterCellPropsTyped<TO>) => {
|
224
|
+
const handleChange = (
|
225
|
+
option: TO,
|
226
|
+
checked: boolean,
|
227
|
+
event: SyntheticEvent<HTMLInputElement>
|
228
|
+
) => {
|
229
|
+
onChange({
|
230
|
+
value: option,
|
231
|
+
operator: option ? equals : '',
|
232
|
+
syntheticEvent: event,
|
233
|
+
});
|
234
|
+
};
|
235
|
+
|
236
|
+
return (
|
237
|
+
<SelectorAsync
|
238
|
+
selected={value ? [value] : []}
|
239
|
+
onChange={handleChange}
|
240
|
+
itemComponent={SelectorItemSingle}
|
241
|
+
placeholder={options.placeholder}
|
242
|
+
renderer={options.renderItem}
|
243
|
+
dataFetcher={options.dataFetcher}
|
244
|
+
/>
|
245
|
+
);
|
246
|
+
};
|
247
|
+
|
248
|
+
return renderCustomColumnMenuFilter(FilterCell);
|
249
|
+
}
|
package/src/filters/index.ts
CHANGED
@@ -4,5 +4,7 @@ export * from './standard-filter-with-multiselect/standard-filter-with-multisele
|
|
4
4
|
export * from './time-filter/time-filter';
|
5
5
|
export * from './column-menu-filters';
|
6
6
|
export * from './field-values-filter';
|
7
|
-
export * from './multiselect-filter';
|
7
|
+
export * from './multiselect-filter/multiselect-filter';
|
8
8
|
export * from './numeric-filter-extended/numeric-filter-extended';
|
9
|
+
export * from './single-select/single-select-filter';
|
10
|
+
export * from './async-select/async-select-filter';
|
@@ -2,7 +2,7 @@ import { Component, SyntheticEvent, ReactNode, FC } from 'react';
|
|
2
2
|
|
3
3
|
import { TableFilterCellProps, Checkbox } from '@servicetitan/design-system';
|
4
4
|
|
5
|
-
import { renderCustomColumnMenuFilter } from '
|
5
|
+
import { renderCustomColumnMenuFilter } from '../column-menu-filters';
|
6
6
|
|
7
7
|
interface SelectorProps<T> {
|
8
8
|
options: T[];
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import {
|
2
|
+
TableExampleCurrency,
|
3
|
+
TableExampleDate,
|
4
|
+
TableExampleNumber,
|
5
|
+
} from '../../demo/filters/range-filter';
|
6
|
+
|
7
|
+
export default {
|
8
|
+
title: 'Table/Filters/Range',
|
9
|
+
};
|
10
|
+
|
11
|
+
export const DateRange = () => <TableExampleDate />;
|
12
|
+
export const NumericRange = () => <TableExampleNumber />;
|
13
|
+
export const CurrencyRange = () => <TableExampleCurrency />;
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { Component, SyntheticEvent, ReactNode, FC } from 'react';
|
2
|
+
|
3
|
+
import { TableFilterCellProps, Radio } from '@servicetitan/design-system';
|
4
|
+
|
5
|
+
import { renderCustomColumnMenuFilter } from '../column-menu-filters';
|
6
|
+
import { IdType } from '@servicetitan/data-query';
|
7
|
+
|
8
|
+
interface SelectorProps<TV, TO> {
|
9
|
+
options: TO[];
|
10
|
+
value?: TV;
|
11
|
+
valueSelector(item: TO): TV;
|
12
|
+
onChange(value: TV | undefined, event: SyntheticEvent<HTMLInputElement>): void;
|
13
|
+
renderer?(item: TO): ReactNode;
|
14
|
+
}
|
15
|
+
|
16
|
+
class SelectorRadio<TV, TO> extends Component<SelectorProps<TV, TO>> {
|
17
|
+
render() {
|
18
|
+
const { options, value, renderer } = this.props;
|
19
|
+
|
20
|
+
return options.map((option, index) => {
|
21
|
+
const optionValue = this.props.valueSelector(option);
|
22
|
+
return (
|
23
|
+
// eslint-disable-next-line react/no-array-index-key
|
24
|
+
<span key={index} className="k-widget m-b-1-i d-b">
|
25
|
+
<Radio
|
26
|
+
label={renderer ? renderer(option) : `${option}`}
|
27
|
+
checked={value === optionValue}
|
28
|
+
onChange={(_, event) => this.props.onChange(optionValue, event)}
|
29
|
+
/>
|
30
|
+
</span>
|
31
|
+
);
|
32
|
+
});
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
export interface SingleSelectColumnMenuOptions<TV, TO = TV> {
|
37
|
+
options: TO[];
|
38
|
+
valueSelector?: (item: TO) => TV;
|
39
|
+
renderItem?: (item: TO) => ReactNode | string;
|
40
|
+
}
|
41
|
+
|
42
|
+
export function singleSelectColumnMenuFilter<TV extends IdType, TO = TV>(
|
43
|
+
props: SingleSelectColumnMenuOptions<TV, TO>
|
44
|
+
) {
|
45
|
+
const FilterCell: FC<TableFilterCellProps> = ({ value, onChange }) => {
|
46
|
+
const handleChange = (value: TV | undefined, event: SyntheticEvent<HTMLInputElement>) => {
|
47
|
+
onChange({
|
48
|
+
value: value ?? '',
|
49
|
+
operator: value ? 'equals' : '',
|
50
|
+
syntheticEvent: event,
|
51
|
+
});
|
52
|
+
};
|
53
|
+
|
54
|
+
return (
|
55
|
+
<SelectorRadio
|
56
|
+
options={props.options}
|
57
|
+
value={value === '' ? undefined : value}
|
58
|
+
onChange={handleChange}
|
59
|
+
renderer={props.renderItem}
|
60
|
+
valueSelector={props.valueSelector ?? (option => option)}
|
61
|
+
/>
|
62
|
+
);
|
63
|
+
};
|
64
|
+
|
65
|
+
return renderCustomColumnMenuFilter(FilterCell);
|
66
|
+
}
|
package/src/index.ts
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
import { getFiltersFlat, getFiltersMap } from '../filters';
|
2
|
+
import { CompositeFilterDescriptor, FilterDescriptor } from '@servicetitan/data-query';
|
3
|
+
|
4
|
+
const filter = (field: string) => ({ field, operator: 'qq', value: 'ee' });
|
5
|
+
const composite = (
|
6
|
+
filters: (FilterDescriptor | CompositeFilterDescriptor)[]
|
7
|
+
): CompositeFilterDescriptor => ({ filters, logic: 'and' });
|
8
|
+
|
9
|
+
const filters1 = () =>
|
10
|
+
composite([composite([filter('f1'), filter('f2')]), filter('f3'), composite([filter('f1')])]);
|
11
|
+
|
12
|
+
describe('getFiltersFlat', () => {
|
13
|
+
test('getFiltersFlat', () => {
|
14
|
+
expect(getFiltersFlat(filters1())).toEqual([
|
15
|
+
filter('f1'),
|
16
|
+
filter('f2'),
|
17
|
+
filter('f3'),
|
18
|
+
filter('f1'),
|
19
|
+
]);
|
20
|
+
});
|
21
|
+
});
|
22
|
+
|
23
|
+
describe('getFiltersMap', () => {
|
24
|
+
test('getFiltersMap', () => {
|
25
|
+
expect(getFiltersMap(filters1())).toEqual({
|
26
|
+
f1: filter('f1'),
|
27
|
+
f2: filter('f2'),
|
28
|
+
f3: filter('f3'),
|
29
|
+
});
|
30
|
+
});
|
31
|
+
});
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import {
|
2
|
+
CompositeFilterDescriptor,
|
3
|
+
FilterDescriptor,
|
4
|
+
isCompositeFilterDescriptor,
|
5
|
+
State,
|
6
|
+
} from '@servicetitan/data-query';
|
7
|
+
|
8
|
+
const getFiltersFlatInner = (
|
9
|
+
filter?: FilterDescriptor | CompositeFilterDescriptor
|
10
|
+
): FilterDescriptor[] => {
|
11
|
+
if (!filter) {
|
12
|
+
return [];
|
13
|
+
} else if (isCompositeFilterDescriptor(filter)) {
|
14
|
+
return filter.filters.reduce(
|
15
|
+
(out, item) => [...out, ...getFiltersFlatInner(item)],
|
16
|
+
[] as FilterDescriptor[]
|
17
|
+
);
|
18
|
+
}
|
19
|
+
return [filter];
|
20
|
+
};
|
21
|
+
|
22
|
+
export const getFiltersFlat = (filter: State['filter']): FilterDescriptor[] => {
|
23
|
+
return getFiltersFlatInner(filter);
|
24
|
+
};
|
25
|
+
|
26
|
+
export const getFiltersMap = (filter: State['filter']): Record<string, FilterDescriptor> => {
|
27
|
+
return getFiltersFlatInner(filter).reduce((out, filter) => {
|
28
|
+
if (typeof filter.field === 'string') {
|
29
|
+
out[filter.field] = filter;
|
30
|
+
}
|
31
|
+
return out;
|
32
|
+
}, {} as Record<string, FilterDescriptor>);
|
33
|
+
};
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"multiselect-filter.d.ts","sourceRoot":"","sources":["../../src/filters/multiselect-filter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,SAAS,EAAM,MAAM,OAAO,CAAC;AAuCjE,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,sFAqB5F;AAED,wBAAgB,sCAAsC,CAAC,CAAC,EACpD,IAAI,EAAE,CAAC,EAAE,EACT,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,sFAgCtC;AAED,wBAAgB,oCAAoC,CAAC,CAAC,EAClD,IAAI,EAAE,CAAC,EAAE,EACT,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,sFAgCtC"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"multiselect-filter.js","sourceRoot":"","sources":["../../src/filters/multiselect-filter.tsx"],"names":[],"mappings":"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"}
|
/package/dist/filters/{multiselect-filter.d.ts → multiselect-filter/multiselect-filter.d.ts}
RENAMED
File without changes
|