@servicetitan/table 25.1.0 → 25.2.0
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/dist/demo/filters/async-select-filter.js +1 -1
- package/dist/demo/filters/async-select-filter.js.map +1 -1
- package/dist/demo/filters/categories.d.ts.map +1 -1
- package/dist/demo/filters/categories.js +24 -0
- package/dist/demo/filters/categories.js.map +1 -1
- package/dist/demo/filters/multiselect-filter.d.ts.map +1 -1
- package/dist/demo/filters/multiselect-filter.js +2 -1
- package/dist/demo/filters/multiselect-filter.js.map +1 -1
- package/dist/demo/filters/select-filter.d.ts +6 -0
- package/dist/demo/filters/select-filter.d.ts.map +1 -0
- package/dist/demo/filters/select-filter.js +58 -0
- package/dist/demo/filters/select-filter.js.map +1 -0
- package/dist/demo/filters/single-select-filter.d.ts.map +1 -1
- package/dist/demo/filters/single-select-filter.js +2 -1
- package/dist/demo/filters/single-select-filter.js.map +1 -1
- package/dist/demo/filters/table.store.d.ts +8 -1
- package/dist/demo/filters/table.store.d.ts.map +1 -1
- package/dist/demo/filters/table.store.js +35 -1
- package/dist/demo/filters/table.store.js.map +1 -1
- package/dist/demo/overview/product.d.ts +5 -0
- package/dist/demo/overview/product.d.ts.map +1 -1
- package/dist/demo/overview/products.d.ts.map +1 -1
- package/dist/demo/overview/products.js +12 -0
- package/dist/demo/overview/products.js.map +1 -1
- package/dist/demo/overview/table.store.d.ts.map +1 -1
- package/dist/demo/overview/table.store.js +1 -0
- package/dist/demo/overview/table.store.js.map +1 -1
- package/dist/filters/async-select/async-select-filter.d.ts +7 -3
- package/dist/filters/async-select/async-select-filter.d.ts.map +1 -1
- package/dist/filters/async-select/async-select-filter.js +21 -6
- package/dist/filters/async-select/async-select-filter.js.map +1 -1
- package/dist/filters/column-menu-filters.d.ts +10 -1
- package/dist/filters/column-menu-filters.d.ts.map +1 -1
- package/dist/filters/column-menu-filters.js +11 -3
- package/dist/filters/column-menu-filters.js.map +1 -1
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +1 -0
- package/dist/filters/index.js.map +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.d.ts +4 -3
- package/dist/filters/multiselect-filter/multiselect-filter.d.ts.map +1 -1
- package/dist/filters/multiselect-filter/multiselect-filter.js +7 -7
- package/dist/filters/multiselect-filter/multiselect-filter.js.map +1 -1
- package/dist/filters/select-filter/object-search.d.ts +2 -0
- package/dist/filters/select-filter/object-search.d.ts.map +1 -0
- package/dist/filters/select-filter/object-search.js +19 -0
- package/dist/filters/select-filter/object-search.js.map +1 -0
- package/dist/filters/select-filter/select-filter.d.ts +32 -0
- package/dist/filters/select-filter/select-filter.d.ts.map +1 -0
- package/dist/filters/select-filter/select-filter.js +214 -0
- package/dist/filters/select-filter/select-filter.js.map +1 -0
- package/dist/filters/select-filter/select-filter.stories.d.ts +8 -0
- package/dist/filters/select-filter/select-filter.stories.d.ts.map +1 -0
- package/dist/filters/select-filter/select-filter.stories.js +8 -0
- package/dist/filters/select-filter/select-filter.stories.js.map +1 -0
- package/dist/filters/select-filter/value-getter.d.ts +3 -0
- package/dist/filters/select-filter/value-getter.d.ts.map +1 -0
- package/dist/filters/select-filter/value-getter.js +10 -0
- package/dist/filters/select-filter/value-getter.js.map +1 -0
- package/dist/filters/single-select/single-select-filter.d.ts +3 -2
- package/dist/filters/single-select/single-select-filter.d.ts.map +1 -1
- package/dist/filters/single-select/single-select-filter.js +17 -7
- package/dist/filters/single-select/single-select-filter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/demo/filters/async-select-filter.tsx +1 -1
- package/src/demo/filters/categories.tsx +24 -0
- package/src/demo/filters/multiselect-filter.tsx +15 -0
- package/src/demo/filters/select-filter.tsx +147 -0
- package/src/demo/filters/single-select-filter.tsx +2 -1
- package/src/demo/filters/table.store.ts +45 -2
- package/src/demo/overview/product.ts +6 -0
- package/src/demo/overview/products.ts +12 -0
- package/src/demo/overview/table.store.ts +1 -0
- package/src/filters/async-select/async-select-filter.tsx +26 -15
- package/src/filters/column-menu-filters.tsx +44 -21
- package/src/filters/index.ts +1 -0
- package/src/filters/multiselect-filter/multiselect-filter.tsx +16 -7
- package/src/filters/select-filter/__tests__/object-search.test.ts +32 -0
- package/src/filters/select-filter/object-search.ts +25 -0
- package/src/filters/select-filter/select-filter.stories.tsx +8 -0
- package/src/filters/select-filter/select-filter.tsx +320 -0
- package/src/filters/select-filter/value-getter.ts +13 -0
- package/src/filters/single-select/single-select-filter.tsx +18 -10
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/table",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://docs.st.dev/docs/frontend/table",
|
|
6
6
|
"repository": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"memoize-one": "~6.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@servicetitan/data-query": "^25.
|
|
40
|
+
"@servicetitan/data-query": "^25.2.0",
|
|
41
41
|
"@servicetitan/design-system": ">=12.4.1",
|
|
42
|
-
"@servicetitan/form": "^25.
|
|
42
|
+
"@servicetitan/form": "^25.2.0",
|
|
43
43
|
"@servicetitan/react-ioc": "^21.6.0",
|
|
44
44
|
"@servicetitan/suppress-warnings": "^21.6.0",
|
|
45
45
|
"@types/accounting": "~0.4.2",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"react": "~17.0.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@servicetitan/data-query": "^25.
|
|
56
|
+
"@servicetitan/data-query": "^25.2.0",
|
|
57
57
|
"@servicetitan/design-system": ">=12.4.1",
|
|
58
|
-
"@servicetitan/form": "^25.
|
|
58
|
+
"@servicetitan/form": "^25.2.0",
|
|
59
59
|
"@servicetitan/react-ioc": ">21.0.0",
|
|
60
60
|
"@servicetitan/suppress-warnings": ">21.0.0",
|
|
61
61
|
"accounting": "~0.4.1",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"cli": {
|
|
73
73
|
"webpack": false
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "5ce3b7761ea46df59cee9e036aec6eefadd3e0ad"
|
|
76
76
|
}
|
|
@@ -31,6 +31,30 @@ export const categories: Category[] = [
|
|
|
31
31
|
CategoryID: 10,
|
|
32
32
|
CategoryName: 'Sashimi',
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
CategoryID: 21,
|
|
36
|
+
CategoryName: 'Borsh',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
CategoryID: 22,
|
|
40
|
+
CategoryName: 'Pasta Carbonara',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
CategoryID: 23,
|
|
44
|
+
CategoryName: 'Caesar salad',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
CategoryID: 24,
|
|
48
|
+
CategoryName: 'Ravioli',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
CategoryID: 25,
|
|
52
|
+
CategoryName: 'Pizza',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
CategoryID: 26,
|
|
56
|
+
CategoryName: 'Steak',
|
|
57
|
+
},
|
|
34
58
|
];
|
|
35
59
|
|
|
36
60
|
export const CategoryCell: FC<TableCellProps> = props => {
|
|
@@ -19,6 +19,16 @@ export const MultiSelectDefaultExample: FC = provide({
|
|
|
19
19
|
[madeInOptions]
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
+
const categoryColumnMenu = useMemo(
|
|
23
|
+
() =>
|
|
24
|
+
multiSelectColumnMenuFilter(
|
|
25
|
+
new Array(20).fill(0).map((_, index) => index),
|
|
26
|
+
undefined,
|
|
27
|
+
{ contentMaxHeight: '250px' }
|
|
28
|
+
),
|
|
29
|
+
[]
|
|
30
|
+
);
|
|
31
|
+
|
|
22
32
|
return (
|
|
23
33
|
<Table tableState={tableState}>
|
|
24
34
|
<TableColumn field="ProductID" title="ID" editable={false} width="100px" />
|
|
@@ -26,6 +36,11 @@ export const MultiSelectDefaultExample: FC = provide({
|
|
|
26
36
|
<TableColumn field="ProductName" title="Product Name" width="240px" />
|
|
27
37
|
|
|
28
38
|
<TableColumn field="MadeIn" title="Made In" columnMenu={madeInColumnMenu} />
|
|
39
|
+
<TableColumn
|
|
40
|
+
field="CategoryID"
|
|
41
|
+
title="CategoryID"
|
|
42
|
+
columnMenu={categoryColumnMenu}
|
|
43
|
+
/>
|
|
29
44
|
</Table>
|
|
30
45
|
);
|
|
31
46
|
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useMemo, FC, Fragment } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
import { Banner } from '@servicetitan/design-system';
|
|
4
|
+
import { provide, useDependencies } from '@servicetitan/react-ioc';
|
|
5
|
+
|
|
6
|
+
import { Table, TableColumn, selectColumnMenuFilter, TableCellProps } from '../..';
|
|
7
|
+
|
|
8
|
+
import { TableStore } from './table.store';
|
|
9
|
+
import { categories, CategoryCell } from './categories';
|
|
10
|
+
import { Product } from '../overview/product';
|
|
11
|
+
|
|
12
|
+
export const PackageCell: FC<TableCellProps> = props => {
|
|
13
|
+
const { dataItem } = props;
|
|
14
|
+
|
|
15
|
+
return <td>{dataItem?.Package?.PackageName}</td>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SelectAsyncExample: FC = provide({
|
|
19
|
+
singletons: [TableStore],
|
|
20
|
+
})(
|
|
21
|
+
observer(() => {
|
|
22
|
+
const [{ categoryFetcherClean, madeInFetcherClean, tableState }] =
|
|
23
|
+
useDependencies(TableStore);
|
|
24
|
+
|
|
25
|
+
const madeInColumnMenu = useMemo(
|
|
26
|
+
() =>
|
|
27
|
+
selectColumnMenuFilter({
|
|
28
|
+
dataFetcher: madeInFetcherClean,
|
|
29
|
+
search: true,
|
|
30
|
+
}),
|
|
31
|
+
[madeInFetcherClean]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const categoryColumnMenu = useMemo(
|
|
35
|
+
() =>
|
|
36
|
+
selectColumnMenuFilter({
|
|
37
|
+
dataFetcher: categoryFetcherClean,
|
|
38
|
+
multiple: true,
|
|
39
|
+
contentMaxHeight: '250px',
|
|
40
|
+
valueSelector: item => item.CategoryID,
|
|
41
|
+
}),
|
|
42
|
+
[categoryFetcherClean]
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Fragment>
|
|
47
|
+
<Banner title="Default filter with async select" className="m-b-2">
|
|
48
|
+
Async select filter allows to choose one or several option from the list that
|
|
49
|
+
will be fetched from BE. The list size is not limited, so you can also use
|
|
50
|
+
search to find desired option.
|
|
51
|
+
<br />
|
|
52
|
+
Use it when there can be greater than 20 possible options
|
|
53
|
+
</Banner>
|
|
54
|
+
<Table tableState={tableState} striped={false}>
|
|
55
|
+
<TableColumn field="ProductID" title="ID" editable={false} width="100px" />
|
|
56
|
+
<TableColumn field="ProductName" title="Product Name" width="240px" />
|
|
57
|
+
<TableColumn
|
|
58
|
+
field="MadeIn"
|
|
59
|
+
title="Made In (async single-select filter)"
|
|
60
|
+
columnMenu={madeInColumnMenu}
|
|
61
|
+
/>
|
|
62
|
+
<TableColumn
|
|
63
|
+
field="CategoryID"
|
|
64
|
+
title="Category (async multi-select filter)"
|
|
65
|
+
columnMenu={categoryColumnMenu}
|
|
66
|
+
cell={CategoryCell}
|
|
67
|
+
/>
|
|
68
|
+
</Table>
|
|
69
|
+
</Fragment>
|
|
70
|
+
);
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const SelectSyncExample: FC = provide({
|
|
75
|
+
singletons: [TableStore],
|
|
76
|
+
})(
|
|
77
|
+
observer(() => {
|
|
78
|
+
const [{ madeInOptions, packageOptions, tableState }] = useDependencies(TableStore);
|
|
79
|
+
|
|
80
|
+
const madeInColumnMenu = useMemo(
|
|
81
|
+
() =>
|
|
82
|
+
selectColumnMenuFilter({
|
|
83
|
+
data: madeInOptions,
|
|
84
|
+
search: {
|
|
85
|
+
placeholder: 'Search for made-ins',
|
|
86
|
+
filter: search => item => item.toLowerCase().includes(search),
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
[madeInOptions]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const categoryColumnMenu = useMemo(
|
|
93
|
+
() =>
|
|
94
|
+
selectColumnMenuFilter({
|
|
95
|
+
data: categories,
|
|
96
|
+
multiple: true,
|
|
97
|
+
contentMaxHeight: '250px',
|
|
98
|
+
valueSelector: item => item.CategoryID,
|
|
99
|
+
renderItem: item => item.CategoryName,
|
|
100
|
+
search: true,
|
|
101
|
+
}),
|
|
102
|
+
[]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const packageColumnMenu = useMemo(
|
|
106
|
+
() =>
|
|
107
|
+
selectColumnMenuFilter({
|
|
108
|
+
data: packageOptions,
|
|
109
|
+
multiple: false,
|
|
110
|
+
valueSelector: item => item.Id,
|
|
111
|
+
renderItem: item => item.Name,
|
|
112
|
+
rowValueSelector: (item: Product['Package']) => item?.PackageId,
|
|
113
|
+
}),
|
|
114
|
+
[packageOptions]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Fragment>
|
|
119
|
+
<Banner title="Default filter with sync select" className="m-b-2">
|
|
120
|
+
Select filter allows to choose one or multiple option(s) from the provided list.
|
|
121
|
+
You can pass simple options (string or number) or complex objects
|
|
122
|
+
</Banner>
|
|
123
|
+
<Table tableState={tableState} striped={false}>
|
|
124
|
+
<TableColumn field="ProductID" title="ID" editable={false} width="100px" />
|
|
125
|
+
<TableColumn field="ProductName" title="Product Name" width="240px" />
|
|
126
|
+
<TableColumn
|
|
127
|
+
field="MadeIn"
|
|
128
|
+
title="Made In (sync single-select filter)"
|
|
129
|
+
columnMenu={madeInColumnMenu}
|
|
130
|
+
/>
|
|
131
|
+
<TableColumn
|
|
132
|
+
field="CategoryID"
|
|
133
|
+
title="Category (sync multi-select filter)"
|
|
134
|
+
columnMenu={categoryColumnMenu}
|
|
135
|
+
cell={CategoryCell}
|
|
136
|
+
/>
|
|
137
|
+
<TableColumn
|
|
138
|
+
field="Package"
|
|
139
|
+
title="Package (sync single-select filter)"
|
|
140
|
+
columnMenu={packageColumnMenu}
|
|
141
|
+
cell={PackageCell}
|
|
142
|
+
/>
|
|
143
|
+
</Table>
|
|
144
|
+
</Fragment>
|
|
145
|
+
);
|
|
146
|
+
})
|
|
147
|
+
);
|
|
@@ -27,10 +27,11 @@ export const SingleSelectDefaultExample: FC = provide({
|
|
|
27
27
|
options: categories,
|
|
28
28
|
renderItem: cat => (
|
|
29
29
|
<BodyText>
|
|
30
|
-
<span className="fw-bold">cat.CategoryName</span> (#{cat.CategoryID})
|
|
30
|
+
<span className="fw-bold">{cat.CategoryName}</span> (#{cat.CategoryID})
|
|
31
31
|
</BodyText>
|
|
32
32
|
),
|
|
33
33
|
valueSelector: cat => cat.CategoryID,
|
|
34
|
+
contentMaxHeight: '200px',
|
|
34
35
|
}),
|
|
35
36
|
[]
|
|
36
37
|
);
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { injectable } from '@servicetitan/react-ioc';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
InMemoryDataSource,
|
|
5
|
+
TableState,
|
|
6
|
+
AsyncSelectFilterDataFetcher,
|
|
7
|
+
SelectFilterDataFetcher,
|
|
8
|
+
} from '../..';
|
|
4
9
|
import { products } from '../overview/products';
|
|
5
|
-
import { categories } from './categories';
|
|
10
|
+
import { categories, Category } from './categories';
|
|
6
11
|
|
|
7
12
|
@injectable()
|
|
8
13
|
export class TableStore {
|
|
@@ -15,6 +20,22 @@ export class TableStore {
|
|
|
15
20
|
return Array.from(new Set(products.map(p => p.MadeIn)));
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
get packageOptions() {
|
|
24
|
+
const map = products.reduce(
|
|
25
|
+
(out, product) => ({
|
|
26
|
+
...out,
|
|
27
|
+
...(product.Package
|
|
28
|
+
? { [product.Package.PackageId]: product.Package.PackageName }
|
|
29
|
+
: {}),
|
|
30
|
+
}),
|
|
31
|
+
{} as Record<number, string>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return Object.keys(map)
|
|
35
|
+
.map(Number)
|
|
36
|
+
.map(id => ({ Id: id, Name: map[id] }));
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
categoryFetcher: AsyncSelectFilterDataFetcher<number> = async opts => {
|
|
19
40
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
20
41
|
|
|
@@ -39,6 +60,28 @@ export class TableStore {
|
|
|
39
60
|
};
|
|
40
61
|
};
|
|
41
62
|
|
|
63
|
+
categoryFetcherClean: SelectFilterDataFetcher<Category> = async opts => {
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
65
|
+
|
|
66
|
+
const sv = opts.search?.trim().toLowerCase();
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
data: categories.filter(cat =>
|
|
70
|
+
sv ? cat.CategoryName.toLowerCase().includes(sv) : true
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
madeInFetcherClean: SelectFilterDataFetcher<string> = async opts => {
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
77
|
+
|
|
78
|
+
const sv = opts.search?.trim().toLowerCase();
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
data: this.madeInOptions.filter(opt => (sv ? opt.toLowerCase().includes(sv) : true)),
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
42
85
|
private getDataSource() {
|
|
43
86
|
return new InMemoryDataSource(products, row => row.ProductID);
|
|
44
87
|
}
|
|
@@ -13,6 +13,11 @@ export enum Supplier {
|
|
|
13
13
|
Benjamin,
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface ProductPackage {
|
|
17
|
+
PackageId: number;
|
|
18
|
+
PackageName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export interface Product {
|
|
17
22
|
ProductID: number;
|
|
18
23
|
ProductName: string;
|
|
@@ -25,4 +30,5 @@ export interface Product {
|
|
|
25
30
|
UnitsOnOrder: Date;
|
|
26
31
|
Discontinued: boolean;
|
|
27
32
|
AvailableFor?: UserRole;
|
|
33
|
+
Package?: ProductPackage;
|
|
28
34
|
}
|
|
@@ -13,6 +13,10 @@ export const products: Product[] = [
|
|
|
13
13
|
UnitsOnOrder: new Date('1/11/2023'),
|
|
14
14
|
Discontinued: false,
|
|
15
15
|
AvailableFor: UserRole.Admin,
|
|
16
|
+
Package: {
|
|
17
|
+
PackageId: 1,
|
|
18
|
+
PackageName: 'Standard package',
|
|
19
|
+
},
|
|
16
20
|
},
|
|
17
21
|
{
|
|
18
22
|
ProductID: 2,
|
|
@@ -25,6 +29,10 @@ export const products: Product[] = [
|
|
|
25
29
|
UnitsInStock: 17,
|
|
26
30
|
UnitsOnOrder: new Date('2/11/2023'),
|
|
27
31
|
Discontinued: false,
|
|
32
|
+
Package: {
|
|
33
|
+
PackageId: 1,
|
|
34
|
+
PackageName: 'Standard package',
|
|
35
|
+
},
|
|
28
36
|
},
|
|
29
37
|
{
|
|
30
38
|
ProductID: 3,
|
|
@@ -63,6 +71,10 @@ export const products: Product[] = [
|
|
|
63
71
|
UnitsOnOrder: new Date('5/11/2023'),
|
|
64
72
|
Discontinued: true,
|
|
65
73
|
AvailableFor: UserRole.GeneralOffice,
|
|
74
|
+
Package: {
|
|
75
|
+
PackageId: 2,
|
|
76
|
+
PackageName: 'Extended package',
|
|
77
|
+
},
|
|
66
78
|
},
|
|
67
79
|
{
|
|
68
80
|
ProductID: 6,
|
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
} from '@servicetitan/design-system';
|
|
11
11
|
import { IdType } from '@servicetitan/data-query';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CustomColumnMenuFilterSingleOpts,
|
|
15
|
+
renderCustomColumnMenuFilter,
|
|
16
|
+
} from '../column-menu-filters';
|
|
14
17
|
import { makeObservable, observable, runInAction } from 'mobx';
|
|
15
18
|
import { observer } from 'mobx-react';
|
|
16
19
|
|
|
@@ -25,7 +28,7 @@ export interface AsyncSelectItem<TV extends IdType = IdType> {
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
interface SelectorProps<TID extends IdType, TO extends AsyncSelectItem<TID>> {
|
|
28
|
-
placeholder
|
|
31
|
+
placeholder?: string;
|
|
29
32
|
selected: TO[];
|
|
30
33
|
dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
|
|
31
34
|
itemComponent: FC<SelectorItemProps>;
|
|
@@ -174,9 +177,10 @@ const SelectorItemMultiple: FC<SelectorItemProps> = ({ option, renderer, checked
|
|
|
174
177
|
/>
|
|
175
178
|
);
|
|
176
179
|
|
|
177
|
-
export interface AsyncSelectFilterOptions<TID extends IdType, TO extends AsyncSelectItem<TID>>
|
|
180
|
+
export interface AsyncSelectFilterOptions<TID extends IdType, TO extends AsyncSelectItem<TID>>
|
|
181
|
+
extends CustomColumnMenuFilterSingleOpts {
|
|
178
182
|
dataFetcher: AsyncSelectFilterDataFetcher<TID, TO>;
|
|
179
|
-
placeholder
|
|
183
|
+
placeholder?: string;
|
|
180
184
|
multiple?: boolean;
|
|
181
185
|
renderItem?: (item: TO) => ReactNode;
|
|
182
186
|
}
|
|
@@ -185,13 +189,20 @@ interface TableFilterCellPropsTyped<T = any> extends Omit<TableFilterCellProps,
|
|
|
185
189
|
value?: T;
|
|
186
190
|
}
|
|
187
191
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
/**
|
|
193
|
+
* @deprecated use selectColumnMenuFilter instead
|
|
194
|
+
*/
|
|
195
|
+
export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends AsyncSelectItem<TID>>({
|
|
196
|
+
dataFetcher,
|
|
197
|
+
placeholder,
|
|
198
|
+
multiple,
|
|
199
|
+
renderItem,
|
|
200
|
+
...opts
|
|
201
|
+
}: AsyncSelectFilterOptions<TID, TO>) {
|
|
191
202
|
const contains = (value: TID, options?: TO[]) => options?.some(opt => opt.value === value);
|
|
192
203
|
const equals = (value: TID, option?: TO) => option?.value === value;
|
|
193
204
|
|
|
194
|
-
const FilterCell =
|
|
205
|
+
const FilterCell = multiple
|
|
195
206
|
? ({ value, onChange }: TableFilterCellPropsTyped<TO[]>) => {
|
|
196
207
|
const handleChange = (
|
|
197
208
|
option: TO,
|
|
@@ -214,9 +225,9 @@ export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends Async
|
|
|
214
225
|
selected={value ?? []}
|
|
215
226
|
itemComponent={SelectorItemMultiple}
|
|
216
227
|
onChange={handleChange}
|
|
217
|
-
placeholder={
|
|
218
|
-
renderer={
|
|
219
|
-
dataFetcher={
|
|
228
|
+
placeholder={placeholder}
|
|
229
|
+
renderer={renderItem}
|
|
230
|
+
dataFetcher={dataFetcher}
|
|
220
231
|
/>
|
|
221
232
|
);
|
|
222
233
|
}
|
|
@@ -238,12 +249,12 @@ export function asyncSelectColumnMenuFilter<TID extends IdType, TO extends Async
|
|
|
238
249
|
selected={value ? [value] : []}
|
|
239
250
|
onChange={handleChange}
|
|
240
251
|
itemComponent={SelectorItemSingle}
|
|
241
|
-
placeholder={
|
|
242
|
-
renderer={
|
|
243
|
-
dataFetcher={
|
|
252
|
+
placeholder={placeholder}
|
|
253
|
+
renderer={renderItem}
|
|
254
|
+
dataFetcher={dataFetcher}
|
|
244
255
|
/>
|
|
245
256
|
);
|
|
246
257
|
};
|
|
247
258
|
|
|
248
|
-
return renderCustomColumnMenuFilter(FilterCell);
|
|
259
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
249
260
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentType, FC } from 'react';
|
|
1
|
+
import { ComponentType, CSSProperties, FC } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
TableColumnMenuFilter,
|
|
4
4
|
TableColumnMenuProps,
|
|
@@ -11,35 +11,58 @@ export const StandardColumnMenuFilter = (props: TableColumnMenuProps) => (
|
|
|
11
11
|
<TableColumnMenuFilter {...props} expanded />
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
+
export interface CustomColumnMenuFilterSingleOpts {
|
|
15
|
+
contentMaxHeight?: string;
|
|
16
|
+
contentClassName?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CustomColumnMenuFilterDoubleOpts {
|
|
19
|
+
double?: boolean;
|
|
20
|
+
doubleContentClassName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CustomColumnMenuFilterOpts = CustomColumnMenuFilterSingleOpts &
|
|
24
|
+
CustomColumnMenuFilterDoubleOpts;
|
|
25
|
+
|
|
14
26
|
export function renderCustomColumnMenuFilter(
|
|
15
27
|
FilterCell: ComponentType<TableFilterCellProps>,
|
|
16
|
-
|
|
28
|
+
doubleOrOptions?: boolean | CustomColumnMenuFilterOpts
|
|
17
29
|
) {
|
|
18
|
-
const
|
|
30
|
+
const opts: CustomColumnMenuFilterOpts =
|
|
31
|
+
typeof doubleOrOptions === 'boolean' ? { double: doubleOrOptions } : doubleOrOptions ?? {};
|
|
32
|
+
const FilterUI: FC<TableColumnMenuFilterUIProps> = ({
|
|
19
33
|
firstFilterProps,
|
|
20
34
|
secondFilterProps,
|
|
21
35
|
logicData,
|
|
22
36
|
logicValue,
|
|
23
37
|
onLogicChange,
|
|
24
|
-
}) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
}) => {
|
|
39
|
+
const contentStyles: CSSProperties = {};
|
|
40
|
+
|
|
41
|
+
if (opts.contentMaxHeight) {
|
|
42
|
+
contentStyles.maxHeight = opts.contentMaxHeight;
|
|
43
|
+
contentStyles.overflowY = 'auto';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={opts.contentClassName} style={contentStyles}>
|
|
48
|
+
<FilterCell {...firstFilterProps} />
|
|
49
|
+
{!!opts.double && (
|
|
50
|
+
<div className={opts.doubleContentClassName}>
|
|
51
|
+
<DropDownList
|
|
52
|
+
data={logicData}
|
|
53
|
+
value={logicValue}
|
|
54
|
+
onChange={onLogicChange}
|
|
55
|
+
className="k-filter-and m-b-1"
|
|
56
|
+
textField="text"
|
|
57
|
+
/>
|
|
58
|
+
<FilterCell {...secondFilterProps} />
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
41
64
|
|
|
42
65
|
return (props: TableColumnMenuProps) => (
|
|
43
|
-
<TableColumnMenuFilter {...props} filterUI={
|
|
66
|
+
<TableColumnMenuFilter {...props} filterUI={FilterUI} expanded />
|
|
44
67
|
);
|
|
45
68
|
}
|
package/src/filters/index.ts
CHANGED
|
@@ -8,3 +8,4 @@ export * from './multiselect-filter/multiselect-filter';
|
|
|
8
8
|
export * from './numeric-filter-extended/numeric-filter-extended';
|
|
9
9
|
export * from './single-select/single-select-filter';
|
|
10
10
|
export * from './async-select/async-select-filter';
|
|
11
|
+
export * from './select-filter/select-filter';
|
|
@@ -2,7 +2,10 @@ import { Component, SyntheticEvent, ReactNode, FC } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { TableFilterCellProps, Checkbox } from '@servicetitan/design-system';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CustomColumnMenuFilterSingleOpts,
|
|
7
|
+
renderCustomColumnMenuFilter,
|
|
8
|
+
} from '../column-menu-filters';
|
|
6
9
|
|
|
7
10
|
interface SelectorProps<T> {
|
|
8
11
|
options: T[];
|
|
@@ -37,7 +40,11 @@ class Selector<T> extends Component<SelectorProps<T>> {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export function multiSelectColumnMenuFilter<T>(
|
|
43
|
+
export function multiSelectColumnMenuFilter<T>(
|
|
44
|
+
data: T[],
|
|
45
|
+
renderItem?: (item: T) => ReactNode,
|
|
46
|
+
opts?: CustomColumnMenuFilterSingleOpts
|
|
47
|
+
) {
|
|
41
48
|
const FilterCell: FC<TableFilterCellProps> = ({ value, onChange }) => {
|
|
42
49
|
const handleChange = (value: T[] | undefined, event: SyntheticEvent<HTMLInputElement>) => {
|
|
43
50
|
onChange({
|
|
@@ -57,12 +64,13 @@ export function multiSelectColumnMenuFilter<T>(data: T[], renderItem?: (item: T)
|
|
|
57
64
|
);
|
|
58
65
|
};
|
|
59
66
|
|
|
60
|
-
return renderCustomColumnMenuFilter(FilterCell);
|
|
67
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
export function complexItemMultiSelectColumnMenuFilter<T>(
|
|
64
71
|
data: T[],
|
|
65
|
-
renderItem?: (item: T) => ReactNode
|
|
72
|
+
renderItem?: (item: T) => ReactNode,
|
|
73
|
+
opts?: CustomColumnMenuFilterSingleOpts
|
|
66
74
|
) {
|
|
67
75
|
class FilterCell extends Component<TableFilterCellProps> {
|
|
68
76
|
contains = (item: any) => {
|
|
@@ -93,12 +101,13 @@ export function complexItemMultiSelectColumnMenuFilter<T>(
|
|
|
93
101
|
}
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
return renderCustomColumnMenuFilter(FilterCell);
|
|
104
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
export function multiItemMultiSelectColumnMenuFilter<T>(
|
|
100
108
|
data: T[],
|
|
101
|
-
renderItem?: (item: T) => ReactNode
|
|
109
|
+
renderItem?: (item: T) => ReactNode,
|
|
110
|
+
opts?: CustomColumnMenuFilterSingleOpts
|
|
102
111
|
) {
|
|
103
112
|
class FilterCell extends Component<TableFilterCellProps> {
|
|
104
113
|
contains = (item: any) => {
|
|
@@ -129,5 +138,5 @@ export function multiItemMultiSelectColumnMenuFilter<T>(
|
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
|
|
132
|
-
return renderCustomColumnMenuFilter(FilterCell);
|
|
141
|
+
return renderCustomColumnMenuFilter(FilterCell, opts);
|
|
133
142
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { objectSearch } from '../object-search';
|
|
2
|
+
|
|
3
|
+
describe('objectSearch', () => {
|
|
4
|
+
const obj = () => ({
|
|
5
|
+
id: 12345,
|
|
6
|
+
name: 'Some Name',
|
|
7
|
+
active: true,
|
|
8
|
+
category: null,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('search and find in simple string', () => {
|
|
12
|
+
expect(objectSearch('iMPl')('sImplE String')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
test('search and not find in simple string', () => {
|
|
15
|
+
expect(objectSearch('iMoPl')('sImplE String')).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('search simple number', () => {
|
|
19
|
+
expect(objectSearch('23')(12345)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('search null', () => {
|
|
23
|
+
expect(objectSearch('23')(null)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('search and find in object', () => {
|
|
27
|
+
expect(objectSearch('Me ')(obj())).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
test('search and not find in object', () => {
|
|
30
|
+
expect(objectSearch('Mee')(obj)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|