@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +2 -2
  3. package/dist/cjs/components/ServerTable/ServerTable.js +1 -4
  4. package/dist/cjs/components/Table/Table.js +47 -16
  5. package/dist/cjs/components/Table/hooks/useSaveTableSettings/index.d.ts +1 -0
  6. package/dist/cjs/components/Table/hooks/useSaveTableSettings/index.js +25 -0
  7. package/dist/cjs/components/Table/hooks/useSaveTableSettings/types.d.ts +7 -0
  8. package/dist/cjs/components/Table/hooks/useSaveTableSettings/types.js +5 -0
  9. package/dist/cjs/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.d.ts +12 -0
  10. package/dist/cjs/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.js +44 -0
  11. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/index.d.ts +2 -0
  12. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/index.js +26 -0
  13. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/parser.d.ts +3 -0
  14. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/parser.js +42 -0
  15. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/serializer.d.ts +3 -0
  16. package/dist/cjs/components/Table/hooks/useSaveTableSettings/utils/serializer.js +43 -0
  17. package/dist/cjs/components/Table/hooks/useSaveTableSettings/vallidators.d.ts +5 -0
  18. package/dist/cjs/components/Table/hooks/useSaveTableSettings/vallidators.js +12 -0
  19. package/dist/cjs/components/Table/hooks/useStateControl.d.ts +1 -4
  20. package/dist/cjs/components/Table/hooks/useStateControl.js +7 -9
  21. package/dist/cjs/components/types.d.ts +1 -0
  22. package/dist/cjs/constants.d.ts +3 -0
  23. package/dist/cjs/constants.js +5 -2
  24. package/dist/cjs/utils.d.ts +1 -0
  25. package/dist/cjs/utils.js +14 -2
  26. package/dist/esm/components/ServerTable/ServerTable.js +1 -1
  27. package/dist/esm/components/Table/Table.js +39 -8
  28. package/dist/esm/components/Table/hooks/useSaveTableSettings/index.d.ts +1 -0
  29. package/dist/esm/components/Table/hooks/useSaveTableSettings/index.js +1 -0
  30. package/dist/esm/components/Table/hooks/useSaveTableSettings/types.d.ts +7 -0
  31. package/dist/esm/components/Table/hooks/useSaveTableSettings/types.js +1 -0
  32. package/dist/esm/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.d.ts +12 -0
  33. package/dist/esm/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.js +26 -0
  34. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/index.d.ts +2 -0
  35. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/index.js +2 -0
  36. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/parser.d.ts +3 -0
  37. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/parser.js +30 -0
  38. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/serializer.d.ts +3 -0
  39. package/dist/esm/components/Table/hooks/useSaveTableSettings/utils/serializer.js +34 -0
  40. package/dist/esm/components/Table/hooks/useSaveTableSettings/vallidators.d.ts +5 -0
  41. package/dist/esm/components/Table/hooks/useSaveTableSettings/vallidators.js +5 -0
  42. package/dist/esm/components/Table/hooks/useStateControl.d.ts +1 -4
  43. package/dist/esm/components/Table/hooks/useStateControl.js +7 -9
  44. package/dist/esm/components/types.d.ts +1 -0
  45. package/dist/esm/constants.d.ts +3 -0
  46. package/dist/esm/constants.js +3 -0
  47. package/dist/esm/utils.d.ts +1 -0
  48. package/dist/esm/utils.js +10 -0
  49. package/package.json +17 -16
  50. package/src/components/ServerTable/ServerTable.tsx +1 -1
  51. package/src/components/Table/Table.tsx +72 -11
  52. package/src/components/Table/hooks/useSaveTableSettings/index.ts +1 -0
  53. package/src/components/Table/hooks/useSaveTableSettings/types.ts +8 -0
  54. package/src/components/Table/hooks/useSaveTableSettings/useSaveTableSettings.ts +54 -0
  55. package/src/components/Table/hooks/useSaveTableSettings/utils/index.ts +2 -0
  56. package/src/components/Table/hooks/useSaveTableSettings/utils/parser.ts +40 -0
  57. package/src/components/Table/hooks/useSaveTableSettings/utils/serializer.ts +53 -0
  58. package/src/components/Table/hooks/useSaveTableSettings/vallidators.ts +17 -0
  59. package/src/components/Table/hooks/useStateControl.ts +12 -10
  60. package/src/components/types.ts +1 -0
  61. package/src/constants.tsx +3 -0
  62. 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,2 @@
1
+ export * from './serializer';
2
+ export * from './parser';
@@ -0,0 +1,2 @@
1
+ export * from './serializer';
2
+ export * from './parser';
@@ -0,0 +1,3 @@
1
+ import { FiltersState } from '@snack-uikit/chips';
2
+ import { Settings } from '../types';
3
+ export declare const parser: <TFilter extends FiltersState = Record<string, unknown>>(value: string) => Settings<TFilter>;
@@ -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,3 @@
1
+ import { FiltersState } from '@snack-uikit/chips';
2
+ import { Settings } from '../types';
3
+ export declare const serializer: <TFilter extends FiltersState = Record<string, unknown>>(value: Settings<TFilter>) => string;
@@ -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 [state, onStateChange] = 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, (controlState) => {
5
- var _a;
6
- const newState = typeof controlState === 'function' ? controlState(state) : controlState;
7
- (_a = control === null || control === void 0 ? void 0 : control.onChange) === null || _a === void 0 ? void 0 : _a.call(control, newState);
8
- });
9
- return {
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
  }
@@ -119,6 +119,7 @@ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record
119
119
  * */
120
120
  savedState?: {
121
121
  id: string;
122
+ filterQueryKey?: string;
122
123
  resize?: boolean;
123
124
  };
124
125
  }>;
@@ -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: {};
@@ -35,3 +35,6 @@ export const SORT_FN = {
35
35
  AlphaNumeric: 'alphanumeric',
36
36
  };
37
37
  export const DEFAULT_PAGE_SIZE = 10;
38
+ export const DEFAULT_SORTING = [];
39
+ export const DEFAULT_FILTER_VISIBILITY = [];
40
+ export const DEFAULT_ROW_SELECTION = {};
@@ -1,3 +1,4 @@
1
1
  import { FilterFn } from '@tanstack/react-table';
2
2
  export declare const fuzzyFilter: FilterFn<any>;
3
3
  export declare const preciseFilter: FilterFn<any>;
4
+ export declare const customDateParser: <T>(value: Record<string, unknown>) => T;
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.32.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
- "@snack-uikit/button": "0.19.7",
40
- "@snack-uikit/chips": "0.25.4",
41
- "@snack-uikit/icon-predefined": "0.7.3",
42
- "@snack-uikit/icons": "0.24.2",
43
- "@snack-uikit/info-block": "0.6.13",
44
- "@snack-uikit/list": "0.26.0",
45
- "@snack-uikit/pagination": "0.10.2",
46
- "@snack-uikit/scroll": "0.9.3",
47
- "@snack-uikit/skeleton": "0.6.2",
48
- "@snack-uikit/toggles": "0.13.5",
49
- "@snack-uikit/toolbar": "0.11.8",
50
- "@snack-uikit/truncate-string": "0.6.9",
51
- "@snack-uikit/typography": "0.8.4",
52
- "@snack-uikit/utils": "3.7.0",
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": "3c671e7c832a7d1c30dc4e1eed74c8b8160bd90c"
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 { state: search, onStateChange: setSearch } = useStateControl<string>(searchProp, '');
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 { DEFAULT_PAGE_SIZE, TEST_IDS } from '../../constants';
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 { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl<string>(search, '');
105
- const { state: rowSelection, onStateChange: onRowSelectionChange } = useStateControl<RowSelectionState>(
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
- const { state: sorting, onStateChange: onSortingChange } = useStateControl<SortingState>(sortingProp, []);
118
- const { state: pagination, onStateChange: onPaginationChange } = useStateControl<PaginationState>(
119
- paginationProp,
120
- defaultPaginationState,
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={columnFilters}
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,8 @@
1
+ import { PaginationState, SortingState } from '@tanstack/react-table';
2
+
3
+ export type Settings<TFilter> = {
4
+ filter?: TFilter;
5
+ pagination: PaginationState;
6
+ search: string;
7
+ sorting: SortingState;
8
+ };
@@ -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,2 @@
1
+ export * from './serializer';
2
+ export * from './parser';
@@ -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 [state, onStateChange] = useUncontrolledProp<TState>(
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
- (controlState: TState) => {
11
- const newState = typeof controlState === 'function' ? controlState(state) : controlState;
14
+ useCallback(
15
+ (controlState: TState) => {
16
+ const newState = typeof controlState === 'function' ? controlState(state || defaultState) : controlState;
12
17
 
13
- control?.onChange?.(newState);
14
- },
18
+ onChange?.(newState);
19
+ },
20
+ [state, defaultState, onChange],
21
+ ),
15
22
  );
16
-
17
- return {
18
- state,
19
- onStateChange,
20
- };
21
23
  }
@@ -148,6 +148,7 @@ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record
148
148
  * */
149
149
  savedState?: {
150
150
  id: string;
151
+ filterQueryKey?: string;
151
152
  resize?: boolean;
152
153
  };
153
154
  }>;
package/src/constants.tsx CHANGED
@@ -39,3 +39,6 @@ export const SORT_FN = {
39
39
  } as const;
40
40
 
41
41
  export const DEFAULT_PAGE_SIZE = 10;
42
+ export const DEFAULT_SORTING = [];
43
+ export const DEFAULT_FILTER_VISIBILITY = [];
44
+ export const DEFAULT_ROW_SELECTION = {};