@snack-uikit/table 0.32.0 → 0.33.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/CHANGELOG.md +11 -0
- package/README.md +2 -2
- package/dist/cjs/components/ServerTable/ServerTable.js +1 -4
- package/dist/cjs/components/Table/Table.js +47 -16
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/index.d.ts +1 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/index.js +25 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/types.d.ts +7 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/types.js +5 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.d.ts +12 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.js +44 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/index.d.ts +2 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/index.js +26 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/parser.d.ts +3 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/parser.js +42 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/serializer.d.ts +3 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/serializer.js +43 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/vallidators.d.ts +5 -0
- package/dist/cjs/components/Table/hooks/useSaveTableSettings/vallidators.js +12 -0
- package/dist/cjs/components/Table/hooks/useStateControl.d.ts +1 -4
- package/dist/cjs/components/Table/hooks/useStateControl.js +7 -9
- package/dist/cjs/components/types.d.ts +1 -0
- package/dist/cjs/constants.d.ts +3 -0
- package/dist/cjs/constants.js +5 -2
- package/dist/cjs/utils.d.ts +1 -0
- package/dist/cjs/utils.js +14 -2
- package/dist/esm/components/ServerTable/ServerTable.js +1 -1
- package/dist/esm/components/Table/Table.js +39 -8
- package/dist/esm/components/Table/hooks/useSaveTableSettings/index.d.ts +1 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/index.js +1 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/types.d.ts +7 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/types.js +1 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.d.ts +12 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.js +26 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/index.d.ts +2 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/index.js +2 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/parser.d.ts +3 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/parser.js +30 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/serializer.d.ts +3 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/serializer.js +34 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/vallidators.d.ts +5 -0
- package/dist/esm/components/Table/hooks/useSaveTableSettings/vallidators.js +5 -0
- package/dist/esm/components/Table/hooks/useStateControl.d.ts +1 -4
- package/dist/esm/components/Table/hooks/useStateControl.js +7 -9
- package/dist/esm/components/types.d.ts +1 -0
- package/dist/esm/constants.d.ts +3 -0
- package/dist/esm/constants.js +3 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +10 -0
- package/package.json +17 -16
- package/src/components/ServerTable/ServerTable.tsx +1 -1
- package/src/components/Table/Table.tsx +72 -11
- package/src/components/Table/hooks/useSaveTableSettings/index.ts +1 -0
- package/src/components/Table/hooks/useSaveTableSettings/types.ts +8 -0
- package/src/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.ts +54 -0
- package/src/components/Table/hooks/useSaveTableSettings/utils/index.ts +2 -0
- package/src/components/Table/hooks/useSaveTableSettings/utils/parser.ts +40 -0
- package/src/components/Table/hooks/useSaveTableSettings/utils/serializer.ts +53 -0
- package/src/components/Table/hooks/useSaveTableSettings/vallidators.ts +17 -0
- package/src/components/Table/hooks/useStateControl.ts +12 -10
- package/src/components/types.ts +1 -0
- package/src/constants.tsx +3 -0
- package/src/utils.ts +14 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChipChoiceRowProps, FiltersState } from '@snack-uikit/chips';
|
|
2
|
+
import { TableProps } from '../../../types';
|
|
3
|
+
import { Settings } from './types';
|
|
4
|
+
type TableSettings<TFilter extends FiltersState = Record<string, unknown>> = {
|
|
5
|
+
options?: TableProps<TFilter>['savedState'];
|
|
6
|
+
filterSettings?: ChipChoiceRowProps<TFilter>['filters'];
|
|
7
|
+
};
|
|
8
|
+
export declare const useSaveTableSettings: <TFilter extends FiltersState = Record<string, unknown>>({ options, filterSettings, }: TableSettings<TFilter>) => {
|
|
9
|
+
defaultFilter: Settings<TFilter> | null | undefined;
|
|
10
|
+
setDataToStorages: (data: Settings<TFilter>) => void;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useDataPersist } from '@snack-uikit/utils';
|
|
3
|
+
import { parser, serializer } from './utils';
|
|
4
|
+
import { validateFilter, validatePaging, validateSorting } from './vallidators';
|
|
5
|
+
export const useSaveTableSettings = ({ options, filterSettings, }) => {
|
|
6
|
+
const validate = useCallback((data) => {
|
|
7
|
+
const isPaginationValid = validatePaging(data === null || data === void 0 ? void 0 : data.pagination);
|
|
8
|
+
const isSortingValid = validateSorting(data === null || data === void 0 ? void 0 : data.sorting);
|
|
9
|
+
const isSearchValid = typeof (data === null || data === void 0 ? void 0 : data.search) === 'string';
|
|
10
|
+
const isFilterValid = Boolean(filterSettings && validateFilter(data.filter, filterSettings));
|
|
11
|
+
return isPaginationValid && isSortingValid && isSearchValid && isFilterValid;
|
|
12
|
+
}, [filterSettings]);
|
|
13
|
+
const filterQueryKey = options === null || options === void 0 ? void 0 : options.filterQueryKey;
|
|
14
|
+
const filterLocalStorageKey = (options === null || options === void 0 ? void 0 : options.id) ? `${options === null || options === void 0 ? void 0 : options.id}_filter` : '';
|
|
15
|
+
const filterStateOptions = useMemo(() => (filterQueryKey ? { filterQueryKey, filterLocalStorageKey, validateData: validate } : undefined), [filterQueryKey, validate, filterLocalStorageKey]);
|
|
16
|
+
const { getDefaultFilter, setDataToStorages } = useDataPersist({
|
|
17
|
+
options: filterStateOptions,
|
|
18
|
+
serializer,
|
|
19
|
+
parser,
|
|
20
|
+
});
|
|
21
|
+
const defaultFilter = useMemo(getDefaultFilter, [getDefaultFilter]);
|
|
22
|
+
return {
|
|
23
|
+
defaultFilter,
|
|
24
|
+
setDataToStorages,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseQueryParamsString } from '@cloud-ru/ft-request-payload-transform';
|
|
2
|
+
import { DEFAULT_PAGE_SIZE } from '../../../../../constants';
|
|
3
|
+
const mapPagination = (value) => {
|
|
4
|
+
if (!value || !value.offset || !value.limit)
|
|
5
|
+
return { pageSize: DEFAULT_PAGE_SIZE, pageIndex: 0 };
|
|
6
|
+
return {
|
|
7
|
+
pageSize: value.limit || DEFAULT_PAGE_SIZE,
|
|
8
|
+
pageIndex: Math.floor(value.offset / value.limit),
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
const mapSort = (value = []) => value.map(column => ({
|
|
12
|
+
id: column.field,
|
|
13
|
+
desc: column.direction === 'd',
|
|
14
|
+
}));
|
|
15
|
+
const mapFilter = (value) => {
|
|
16
|
+
if (!value) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return Object.fromEntries(value.map(filter => [filter.field, filter.value]));
|
|
20
|
+
};
|
|
21
|
+
export const parser = (value) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const parsedValue = parseQueryParamsString(value);
|
|
24
|
+
return {
|
|
25
|
+
pagination: mapPagination(parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.pagination),
|
|
26
|
+
search: ((_a = parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.search) === null || _a === void 0 ? void 0 : _a.toString()) || '',
|
|
27
|
+
sorting: mapSort(parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.sort),
|
|
28
|
+
filter: mapFilter(parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.filter),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRequestPayload } from '@cloud-ru/ft-request-payload-transform';
|
|
2
|
+
const mapPagination = (value) => ({
|
|
3
|
+
limit: value.pageSize,
|
|
4
|
+
offset: value.pageSize * value.pageIndex,
|
|
5
|
+
});
|
|
6
|
+
const mapSort = (value) => value.map(column => ({
|
|
7
|
+
field: column.id,
|
|
8
|
+
direction: (column.desc ? 'd' : 'a'),
|
|
9
|
+
}));
|
|
10
|
+
const mapDateToString = (filter) => filter instanceof Date ? filter.toISOString() : filter;
|
|
11
|
+
const mapFilter = (value) => {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return Object.entries(value)
|
|
16
|
+
.filter(([_, value]) => value !== undefined)
|
|
17
|
+
.map(([key, value]) => Array.isArray(value)
|
|
18
|
+
? {
|
|
19
|
+
value: value.map(mapDateToString),
|
|
20
|
+
condition: 'in',
|
|
21
|
+
field: key,
|
|
22
|
+
}
|
|
23
|
+
: {
|
|
24
|
+
value: mapDateToString(value),
|
|
25
|
+
condition: 'eq',
|
|
26
|
+
field: key,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export const serializer = (value) => createRequestPayload({
|
|
30
|
+
pagination: mapPagination(value.pagination),
|
|
31
|
+
search: value.search,
|
|
32
|
+
sort: mapSort(value.sorting),
|
|
33
|
+
filter: mapFilter(value.filter),
|
|
34
|
+
}).toString();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PaginationState, SortingState } from '@tanstack/react-table';
|
|
2
|
+
import { ChipChoiceRowProps } from '@snack-uikit/chips';
|
|
3
|
+
export declare const validatePaging: (value: unknown) => value is PaginationState;
|
|
4
|
+
export declare const validateSorting: (value: unknown) => value is SortingState;
|
|
5
|
+
export declare const validateFilter: <TFilter extends Record<string, unknown>>(value: unknown, filterSettings: ChipChoiceRowProps<TFilter>["filters"]) => value is TFilter;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const validatePaging = (value) => typeof (value === null || value === void 0 ? void 0 : value.pageSize) === 'number' && typeof (value === null || value === void 0 ? void 0 : value.pageIndex) === 'number';
|
|
2
|
+
export const validateSorting = (value) => value === null || value === void 0 ? void 0 : value.every(column => typeof (column === null || column === void 0 ? void 0 : column.id) === 'string' && typeof (column === null || column === void 0 ? void 0 : column.desc) === 'boolean');
|
|
3
|
+
export const validateFilter = (value, filterSettings) => typeof value === 'object' &&
|
|
4
|
+
value !== null &&
|
|
5
|
+
Object.keys(value).every(field => Boolean(filterSettings.find(setting => setting.id === field)));
|
|
@@ -2,7 +2,4 @@ export declare function useStateControl<TState>(control: {
|
|
|
2
2
|
initialState?: TState;
|
|
3
3
|
state?: TState;
|
|
4
4
|
onChange?(state: TState): void;
|
|
5
|
-
} | undefined, defaultState: TState):
|
|
6
|
-
state: TState;
|
|
7
|
-
onStateChange: import("uncontrollable").Handler;
|
|
8
|
-
};
|
|
5
|
+
} | undefined, defaultState: TState): readonly [TState, import("uncontrollable").Handler];
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import { useUncontrolledProp } from 'uncontrollable';
|
|
2
3
|
export function useStateControl(control, defaultState) {
|
|
3
4
|
var _a, _b;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
state,
|
|
11
|
-
onStateChange,
|
|
12
|
-
};
|
|
5
|
+
const state = control === null || control === void 0 ? void 0 : control.state;
|
|
6
|
+
const onChange = control === null || control === void 0 ? void 0 : control.onChange;
|
|
7
|
+
return useUncontrolledProp(control === null || control === void 0 ? void 0 : control.state, (_b = (_a = control === null || control === void 0 ? void 0 : control.state) !== null && _a !== void 0 ? _a : control === null || control === void 0 ? void 0 : control.initialState) !== null && _b !== void 0 ? _b : defaultState, useCallback((controlState) => {
|
|
8
|
+
const newState = typeof controlState === 'function' ? controlState(state || defaultState) : controlState;
|
|
9
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newState);
|
|
10
|
+
}, [state, defaultState, onChange]));
|
|
13
11
|
}
|
package/dist/esm/constants.d.ts
CHANGED
|
@@ -35,3 +35,6 @@ export declare const SORT_FN: {
|
|
|
35
35
|
readonly AlphaNumeric: "alphanumeric";
|
|
36
36
|
};
|
|
37
37
|
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
38
|
+
export declare const DEFAULT_SORTING: never[];
|
|
39
|
+
export declare const DEFAULT_FILTER_VISIBILITY: never[];
|
|
40
|
+
export declare const DEFAULT_ROW_SELECTION: {};
|
package/dist/esm/constants.js
CHANGED
package/dist/esm/utils.d.ts
CHANGED
package/dist/esm/utils.js
CHANGED
|
@@ -15,3 +15,13 @@ export const preciseFilter = (row, columnId, value, addMeta) => {
|
|
|
15
15
|
});
|
|
16
16
|
return itemRank.passed;
|
|
17
17
|
};
|
|
18
|
+
const isDateString = (value) => typeof value === 'string' && !isNaN(Number(new Date(value)));
|
|
19
|
+
export const customDateParser = (value) => Object.fromEntries(Object.entries(value).map(([key, value]) => {
|
|
20
|
+
if (isDateString(value)) {
|
|
21
|
+
return [key, new Date(value)];
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value) && value.some(isDateString)) {
|
|
24
|
+
return [key, value.map(element => (isDateString(element) ? new Date(element) : element))];
|
|
25
|
+
}
|
|
26
|
+
return [key, value];
|
|
27
|
+
}));
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Table",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.33.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -36,20 +36,21 @@
|
|
|
36
36
|
"license": "Apache-2.0",
|
|
37
37
|
"scripts": {},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@snack-uikit/
|
|
41
|
-
"@snack-uikit/
|
|
42
|
-
"@snack-uikit/
|
|
43
|
-
"@snack-uikit/
|
|
44
|
-
"@snack-uikit/
|
|
45
|
-
"@snack-uikit/
|
|
46
|
-
"@snack-uikit/
|
|
47
|
-
"@snack-uikit/
|
|
48
|
-
"@snack-uikit/
|
|
49
|
-
"@snack-uikit/
|
|
50
|
-
"@snack-uikit/
|
|
51
|
-
"@snack-uikit/
|
|
52
|
-
"@snack-uikit/
|
|
39
|
+
"@cloud-ru/ft-request-payload-transform": "0.1.0",
|
|
40
|
+
"@snack-uikit/button": "0.19.8",
|
|
41
|
+
"@snack-uikit/chips": "0.25.5",
|
|
42
|
+
"@snack-uikit/icon-predefined": "0.7.4",
|
|
43
|
+
"@snack-uikit/icons": "0.24.3",
|
|
44
|
+
"@snack-uikit/info-block": "0.6.14",
|
|
45
|
+
"@snack-uikit/list": "0.26.1",
|
|
46
|
+
"@snack-uikit/pagination": "0.10.3",
|
|
47
|
+
"@snack-uikit/scroll": "0.9.4",
|
|
48
|
+
"@snack-uikit/skeleton": "0.6.3",
|
|
49
|
+
"@snack-uikit/toggles": "0.13.6",
|
|
50
|
+
"@snack-uikit/toolbar": "0.12.0",
|
|
51
|
+
"@snack-uikit/truncate-string": "0.6.10",
|
|
52
|
+
"@snack-uikit/typography": "0.8.5",
|
|
53
|
+
"@snack-uikit/utils": "3.8.0",
|
|
53
54
|
"@tanstack/match-sorter-utils": "8.11.8",
|
|
54
55
|
"@tanstack/react-table": "8.12.0",
|
|
55
56
|
"classnames": "2.5.1",
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"@snack-uikit/locale": "*"
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "ec4e54b1ea5c4455ca404632e1f205e22b881f98"
|
|
65
66
|
}
|
|
@@ -24,7 +24,7 @@ export function ServerTable<TData extends object, TFilters extends FiltersState
|
|
|
24
24
|
manualFiltering = true,
|
|
25
25
|
...rest
|
|
26
26
|
}: ServerTableProps<TData, TFilters>) {
|
|
27
|
-
const
|
|
27
|
+
const [search, setSearch] = useStateControl<string>(searchProp, '');
|
|
28
28
|
|
|
29
29
|
const [tempSearch, setTempSearch] = useState(search || '');
|
|
30
30
|
|
|
@@ -21,9 +21,15 @@ import { Scroll } from '@snack-uikit/scroll';
|
|
|
21
21
|
import { SkeletonContextProvider } from '@snack-uikit/skeleton';
|
|
22
22
|
import { Toolbar, ToolbarProps } from '@snack-uikit/toolbar';
|
|
23
23
|
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
24
|
-
import { extractSupportProps } from '@snack-uikit/utils';
|
|
24
|
+
import { extractSupportProps, useLayoutEffect } from '@snack-uikit/utils';
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_FILTER_VISIBILITY,
|
|
28
|
+
DEFAULT_PAGE_SIZE,
|
|
29
|
+
DEFAULT_ROW_SELECTION,
|
|
30
|
+
DEFAULT_SORTING,
|
|
31
|
+
TEST_IDS,
|
|
32
|
+
} from '../../constants';
|
|
27
33
|
import { CellAutoResizeContext, useCellAutoResizeController } from '../../contexts';
|
|
28
34
|
import {
|
|
29
35
|
BodyRow,
|
|
@@ -41,10 +47,11 @@ import {
|
|
|
41
47
|
} from '../../helperComponents';
|
|
42
48
|
import { getTreeColumnDef } from '../../helperComponents/Cells/TreeCell';
|
|
43
49
|
import { ColumnDefinition } from '../../types';
|
|
44
|
-
import { fuzzyFilter } from '../../utils';
|
|
50
|
+
import { customDateParser, fuzzyFilter } from '../../utils';
|
|
45
51
|
import { TableProps } from '../types';
|
|
46
52
|
import { useLoadingTable, useStateControl } from './hooks';
|
|
47
53
|
import { usePageReset } from './hooks/usePageReset';
|
|
54
|
+
import { useSaveTableSettings } from './hooks/useSaveTableSettings';
|
|
48
55
|
import styles from './styles.module.scss';
|
|
49
56
|
import {
|
|
50
57
|
getColumnStyleVars,
|
|
@@ -101,10 +108,16 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
101
108
|
rowAutoHeight,
|
|
102
109
|
...rest
|
|
103
110
|
}: TableProps<TData, TFilters>) {
|
|
104
|
-
const {
|
|
105
|
-
|
|
111
|
+
const { setDataToStorages, defaultFilter } = useSaveTableSettings({
|
|
112
|
+
options: savedState,
|
|
113
|
+
filterSettings: columnFilters?.filters,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const [globalFilter, onGlobalFilterChange] = useStateControl<string>(search, '');
|
|
117
|
+
|
|
118
|
+
const [rowSelection, onRowSelectionChange] = useStateControl<RowSelectionState>(
|
|
106
119
|
rowSelectionProp,
|
|
107
|
-
|
|
120
|
+
DEFAULT_ROW_SELECTION,
|
|
108
121
|
);
|
|
109
122
|
|
|
110
123
|
const defaultPaginationState = useMemo(
|
|
@@ -114,11 +127,59 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
114
127
|
}),
|
|
115
128
|
[pageSize],
|
|
116
129
|
);
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
|
|
131
|
+
const [sorting, onSortingChange] = useStateControl<SortingState>(sortingProp, DEFAULT_SORTING);
|
|
132
|
+
|
|
133
|
+
const [pagination, onPaginationChange] = useStateControl<PaginationState>(paginationProp, defaultPaginationState);
|
|
134
|
+
|
|
135
|
+
const [filter, setFilter] = useStateControl<TFilters | undefined>(
|
|
136
|
+
{
|
|
137
|
+
state: columnFilters?.value,
|
|
138
|
+
initialState: columnFilters?.defaultValue as TFilters,
|
|
139
|
+
onChange: columnFilters?.onChange,
|
|
140
|
+
},
|
|
141
|
+
undefined,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const [filterVisibility, setFilterVisibility] = useStateControl<string[]>(
|
|
145
|
+
{
|
|
146
|
+
state: columnFilters?.visibleFilters,
|
|
147
|
+
initialState: [],
|
|
148
|
+
onChange: columnFilters?.onVisibleFiltersChange,
|
|
149
|
+
},
|
|
150
|
+
DEFAULT_FILTER_VISIBILITY,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
setDataToStorages({ pagination, sorting, filter, search: globalFilter || '' });
|
|
155
|
+
}, [pagination, sorting, filter, setDataToStorages, globalFilter]);
|
|
156
|
+
|
|
157
|
+
useLayoutEffect(() => {
|
|
158
|
+
if (defaultFilter) {
|
|
159
|
+
defaultFilter.pagination && onPaginationChange(defaultFilter.pagination);
|
|
160
|
+
defaultFilter.search && onGlobalFilterChange(defaultFilter.search);
|
|
161
|
+
defaultFilter.sorting && onSortingChange(defaultFilter.sorting);
|
|
162
|
+
defaultFilter.filter && setFilter(customDateParser(defaultFilter.filter));
|
|
163
|
+
defaultFilter.filter && setFilterVisibility(Object.keys(defaultFilter.filter));
|
|
164
|
+
}
|
|
165
|
+
// Только для первого рендера, чтобы проинициализировать фильтр
|
|
166
|
+
// eslint-disable-next-line
|
|
167
|
+
}, [defaultFilter]);
|
|
168
|
+
|
|
169
|
+
const patchedFilter = useMemo(
|
|
170
|
+
() =>
|
|
171
|
+
columnFilters
|
|
172
|
+
? {
|
|
173
|
+
...columnFilters,
|
|
174
|
+
value: filter,
|
|
175
|
+
onChange: setFilter,
|
|
176
|
+
visibleFilters: filterVisibility,
|
|
177
|
+
onVisibleFiltersChange: setFilterVisibility,
|
|
178
|
+
}
|
|
179
|
+
: undefined,
|
|
180
|
+
[columnFilters, filter, setFilter, filterVisibility, setFilterVisibility],
|
|
121
181
|
);
|
|
182
|
+
|
|
122
183
|
const enableSelection = Boolean(rowSelectionProp?.enable);
|
|
123
184
|
|
|
124
185
|
const manualPagination = infiniteLoading || manualPaginationProp;
|
|
@@ -404,7 +465,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
404
465
|
) : undefined
|
|
405
466
|
}
|
|
406
467
|
moreActions={moreActions}
|
|
407
|
-
filterRow={
|
|
468
|
+
filterRow={patchedFilter}
|
|
408
469
|
data-test-id={TEST_IDS.toolbar}
|
|
409
470
|
/>
|
|
410
471
|
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useSaveTableSettings';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ChipChoiceRowProps, FiltersState } from '@snack-uikit/chips';
|
|
4
|
+
import { FilterStateOptions, useDataPersist } from '@snack-uikit/utils';
|
|
5
|
+
|
|
6
|
+
import { TableProps } from '../../../types';
|
|
7
|
+
import { Settings } from './types';
|
|
8
|
+
import { parser, serializer } from './utils';
|
|
9
|
+
import { validateFilter, validatePaging, validateSorting } from './vallidators';
|
|
10
|
+
|
|
11
|
+
type ValidationFn<TFilter> = (value: unknown) => value is Settings<TFilter>;
|
|
12
|
+
|
|
13
|
+
type TableSettings<TFilter extends FiltersState = Record<string, unknown>> = {
|
|
14
|
+
options?: TableProps<TFilter>['savedState'];
|
|
15
|
+
filterSettings?: ChipChoiceRowProps<TFilter>['filters'];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const useSaveTableSettings = <TFilter extends FiltersState = Record<string, unknown>>({
|
|
19
|
+
options,
|
|
20
|
+
filterSettings,
|
|
21
|
+
}: TableSettings<TFilter>) => {
|
|
22
|
+
const validate = useCallback<ValidationFn<TFilter>>(
|
|
23
|
+
(data: unknown): data is Settings<TFilter> => {
|
|
24
|
+
const isPaginationValid = validatePaging((data as Settings<TFilter>)?.pagination);
|
|
25
|
+
const isSortingValid = validateSorting((data as Settings<TFilter>)?.sorting);
|
|
26
|
+
const isSearchValid = typeof (data as Settings<TFilter>)?.search === 'string';
|
|
27
|
+
const isFilterValid = Boolean(
|
|
28
|
+
filterSettings && validateFilter((data as Settings<TFilter>).filter, filterSettings),
|
|
29
|
+
);
|
|
30
|
+
return isPaginationValid && isSortingValid && isSearchValid && isFilterValid;
|
|
31
|
+
},
|
|
32
|
+
[filterSettings],
|
|
33
|
+
);
|
|
34
|
+
const filterQueryKey = options?.filterQueryKey;
|
|
35
|
+
const filterLocalStorageKey = options?.id ? `${options?.id}_filter` : '';
|
|
36
|
+
|
|
37
|
+
const filterStateOptions = useMemo<FilterStateOptions<Settings<TFilter>> | undefined>(
|
|
38
|
+
() => (filterQueryKey ? { filterQueryKey, filterLocalStorageKey, validateData: validate } : undefined),
|
|
39
|
+
[filterQueryKey, validate, filterLocalStorageKey],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const { getDefaultFilter, setDataToStorages } = useDataPersist<Settings<TFilter>>({
|
|
43
|
+
options: filterStateOptions,
|
|
44
|
+
serializer,
|
|
45
|
+
parser,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const defaultFilter = useMemo(getDefaultFilter, [getDefaultFilter]);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
defaultFilter,
|
|
52
|
+
setDataToStorages,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PaginationState, SortingState } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
import { parseQueryParamsString, RequestPayloadParams } from '@cloud-ru/ft-request-payload-transform';
|
|
4
|
+
import { FiltersState } from '@snack-uikit/chips';
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_PAGE_SIZE } from '../../../../../constants';
|
|
7
|
+
import { Settings } from '../types';
|
|
8
|
+
|
|
9
|
+
const mapPagination = (value: RequestPayloadParams['pagination']): PaginationState => {
|
|
10
|
+
if (!value || !value.offset || !value.limit) return { pageSize: DEFAULT_PAGE_SIZE, pageIndex: 0 };
|
|
11
|
+
return {
|
|
12
|
+
pageSize: value.limit || DEFAULT_PAGE_SIZE,
|
|
13
|
+
pageIndex: Math.floor(value.offset / value.limit),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mapSort = (value: RequestPayloadParams['sort'] = []): SortingState =>
|
|
18
|
+
value.map(column => ({
|
|
19
|
+
id: column.field,
|
|
20
|
+
desc: column.direction === 'd',
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const mapFilter = <TFilter extends FiltersState = Record<string, unknown>>(
|
|
24
|
+
value?: RequestPayloadParams['filter'],
|
|
25
|
+
): TFilter | undefined => {
|
|
26
|
+
if (!value) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return Object.fromEntries(value.map(filter => [filter.field, filter.value])) as TFilter;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const parser = <TFilter extends FiltersState = Record<string, unknown>>(value: string): Settings<TFilter> => {
|
|
33
|
+
const parsedValue = parseQueryParamsString(value);
|
|
34
|
+
return {
|
|
35
|
+
pagination: mapPagination(parsedValue?.pagination),
|
|
36
|
+
search: parsedValue?.search?.toString() || '',
|
|
37
|
+
sorting: mapSort(parsedValue?.sort),
|
|
38
|
+
filter: mapFilter(parsedValue?.filter),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PaginationState, SortingState } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
import { createRequestPayload, RequestPayloadParams, SortDirection } from '@cloud-ru/ft-request-payload-transform';
|
|
4
|
+
import { FiltersState } from '@snack-uikit/chips';
|
|
5
|
+
|
|
6
|
+
import { Settings } from '../types';
|
|
7
|
+
|
|
8
|
+
const mapPagination = (value: PaginationState): RequestPayloadParams['pagination'] => ({
|
|
9
|
+
limit: value.pageSize,
|
|
10
|
+
offset: value.pageSize * value.pageIndex,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const mapSort = (value: SortingState): RequestPayloadParams['sort'] =>
|
|
14
|
+
value.map(column => ({
|
|
15
|
+
field: column.id,
|
|
16
|
+
direction: (column.desc ? 'd' : 'a') as SortDirection,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
type FilterValue = string | null | number | boolean | Date;
|
|
20
|
+
|
|
21
|
+
const mapDateToString = (filter: FilterValue): string | null | number | boolean =>
|
|
22
|
+
filter instanceof Date ? filter.toISOString() : filter;
|
|
23
|
+
|
|
24
|
+
const mapFilter = <TFilter extends FiltersState = Record<string, unknown>>(
|
|
25
|
+
value?: TFilter,
|
|
26
|
+
): RequestPayloadParams['filter'] => {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return Object.entries(value)
|
|
31
|
+
.filter(([_, value]) => value !== undefined)
|
|
32
|
+
.map(([key, value]) =>
|
|
33
|
+
Array.isArray(value)
|
|
34
|
+
? {
|
|
35
|
+
value: (value as FilterValue[]).map(mapDateToString),
|
|
36
|
+
condition: 'in',
|
|
37
|
+
field: key,
|
|
38
|
+
}
|
|
39
|
+
: {
|
|
40
|
+
value: mapDateToString(value as FilterValue),
|
|
41
|
+
condition: 'eq',
|
|
42
|
+
field: key,
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const serializer = <TFilter extends FiltersState = Record<string, unknown>>(value: Settings<TFilter>) =>
|
|
48
|
+
createRequestPayload({
|
|
49
|
+
pagination: mapPagination(value.pagination),
|
|
50
|
+
search: value.search,
|
|
51
|
+
sort: mapSort(value.sorting),
|
|
52
|
+
filter: mapFilter(value.filter),
|
|
53
|
+
}).toString();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PaginationState, SortingState } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
import { ChipChoiceRowProps } from '@snack-uikit/chips';
|
|
4
|
+
|
|
5
|
+
export const validatePaging = (value: unknown): value is PaginationState =>
|
|
6
|
+
typeof (value as PaginationState)?.pageSize === 'number' && typeof (value as PaginationState)?.pageIndex === 'number';
|
|
7
|
+
|
|
8
|
+
export const validateSorting = (value: unknown): value is SortingState =>
|
|
9
|
+
(value as SortingState)?.every(column => typeof column?.id === 'string' && typeof column?.desc === 'boolean');
|
|
10
|
+
|
|
11
|
+
export const validateFilter = <TFilter extends Record<string, unknown>>(
|
|
12
|
+
value: unknown,
|
|
13
|
+
filterSettings: ChipChoiceRowProps<TFilter>['filters'],
|
|
14
|
+
): value is TFilter =>
|
|
15
|
+
typeof value === 'object' &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
Object.keys(value).every(field => Boolean(filterSettings.find(setting => setting.id === field)));
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import { useUncontrolledProp } from 'uncontrollable';
|
|
2
3
|
|
|
3
4
|
export function useStateControl<TState>(
|
|
4
5
|
control: { initialState?: TState; state?: TState; onChange?(state: TState): void } | undefined,
|
|
5
6
|
defaultState: TState,
|
|
6
7
|
) {
|
|
7
|
-
const
|
|
8
|
+
const state = control?.state;
|
|
9
|
+
const onChange = control?.onChange;
|
|
10
|
+
|
|
11
|
+
return useUncontrolledProp<TState>(
|
|
8
12
|
control?.state,
|
|
9
13
|
control?.state ?? control?.initialState ?? defaultState,
|
|
10
|
-
(
|
|
11
|
-
|
|
14
|
+
useCallback(
|
|
15
|
+
(controlState: TState) => {
|
|
16
|
+
const newState = typeof controlState === 'function' ? controlState(state || defaultState) : controlState;
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
onChange?.(newState);
|
|
19
|
+
},
|
|
20
|
+
[state, defaultState, onChange],
|
|
21
|
+
),
|
|
15
22
|
);
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
state,
|
|
19
|
-
onStateChange,
|
|
20
|
-
};
|
|
21
23
|
}
|
package/src/components/types.ts
CHANGED