@webitel/ui-datalist 26.8.5 → 26.8.7
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/Readme.md +1 -0
- package/package.json +3 -2
- package/src/modules/filters/__tests__/createTableFiltersStore.spec.ts +38 -2
- package/src/modules/filters/createTableFiltersStore.ts +13 -5
- package/src/modules/filters/modules/filterConfig/components/_custom/filterConfig.ts +1 -1
- package/src/modules/headers/__tests__/createTableHeadersStore.spec.ts +34 -2
- package/src/modules/headers/createTableHeadersStore.ts +19 -5
- package/src/modules/pagination/__tests__/createTablePaginationStore.spec.ts +27 -3
- package/src/modules/pagination/createTablePaginationStore.ts +23 -4
- package/src/modules/table/__tests__/createTableStore.spec.ts +26 -0
- package/src/modules/table/composables/useNestedTableList.ts +8 -4
- package/src/modules/table/createTableStore.store.ts +10 -12
- package/types/.tsbuildinfo +1 -1
- package/types/modules/filters/createTableFiltersStore.d.ts +6 -2
- package/types/modules/headers/createTableHeadersStore.d.ts +6 -2
- package/types/modules/pagination/createTablePaginationStore.d.ts +7 -3
- package/types/modules/table/composables/useNestedTableList.d.ts +11 -5
package/Readme.md
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webitel/ui-datalist",
|
|
3
|
-
"version": "26.8.
|
|
3
|
+
"version": "26.8.7",
|
|
4
4
|
"description": "Toolkit for building data lists in webitel ui system",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build:types": "vue-tsc -b tsconfig.build.json",
|
|
7
7
|
"typecheck": "vue-tsc --noEmit -p ./tsconfig.typecheck.json",
|
|
8
8
|
"lint:fix": "npx biome check --write ./src",
|
|
9
9
|
"biome:ci:gh": "biome ci ./src --reporter=github",
|
|
10
|
-
"test:unit": "
|
|
10
|
+
"test:unit": "vitest run --root ../.. packages/ui-datalist/src",
|
|
11
11
|
"lint": "biome ci ./src",
|
|
12
12
|
"test": "npm run test:unit",
|
|
13
13
|
"ci:check": "npm run biome:ci:gh && npm run test:unit",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@webitel/ui-sdk": "~26.8",
|
|
45
45
|
"axios": "^1.15.2",
|
|
46
46
|
"date-fns": "^4",
|
|
47
|
+
"lodash-es": "^4.17.21",
|
|
47
48
|
"pinia": "^3.x",
|
|
48
49
|
"vue": "^3.5",
|
|
49
50
|
"zod": "^4.x"
|
|
@@ -40,9 +40,9 @@ describe('tableFiltersStoreBody', () => {
|
|
|
40
40
|
runWithRouter = (fn) => app.runWithContext(fn);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
const setUpStore = async () => {
|
|
43
|
+
const setUpStore = async (options?: { isNested?: boolean }) => {
|
|
44
44
|
const store = tableFiltersStoreBody(namespace);
|
|
45
|
-
await runWithRouter(() => store.setupPersistence());
|
|
45
|
+
await runWithRouter(() => store.setupPersistence(options));
|
|
46
46
|
return store;
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -133,6 +133,42 @@ describe('tableFiltersStoreBody', () => {
|
|
|
133
133
|
expect(router.currentRoute.value.query.searchMode).toBeUndefined();
|
|
134
134
|
expect(localStorage.getItem(`${namespace}/searchMode`)).toBe('subject');
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
describe('a nested list', () => {
|
|
138
|
+
it('keeps an added filter out of the route query', async () => {
|
|
139
|
+
const store = await setUpStore({
|
|
140
|
+
isNested: true,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
store.addFilter({
|
|
144
|
+
name: 'status',
|
|
145
|
+
value: 'open',
|
|
146
|
+
});
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
router.currentRoute.value.query[filtersQueryKey],
|
|
151
|
+
).toBeUndefined();
|
|
152
|
+
expect(sessionStorage.getItem(`${namespace}/filters`)).toContain(
|
|
153
|
+
'open',
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('restores filters from sessionStorage, not the route query', async () => {
|
|
158
|
+
sessionStorage.setItem(
|
|
159
|
+
`${namespace}/filters`,
|
|
160
|
+
JSON.stringify({
|
|
161
|
+
status_val: 'open',
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const store = await setUpStore({
|
|
166
|
+
isNested: true,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(store.filtersManager.getFilter('status')?.value).toBe('open');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
136
172
|
});
|
|
137
173
|
|
|
138
174
|
describe('syncPersistence', () => {
|
|
@@ -45,7 +45,11 @@ export const tableFiltersStoreBody = (
|
|
|
45
45
|
|
|
46
46
|
let persistedStorageControllers: PersistedStorageController[] = [];
|
|
47
47
|
|
|
48
|
-
const setupPersistence = (
|
|
48
|
+
const setupPersistence = ({
|
|
49
|
+
isNested = false,
|
|
50
|
+
}: {
|
|
51
|
+
isNested?: boolean;
|
|
52
|
+
} = {}) => {
|
|
49
53
|
const filtersStorage = usePersistedStorage({
|
|
50
54
|
name: 'filters',
|
|
51
55
|
|
|
@@ -53,10 +57,14 @@ export const tableFiltersStoreBody = (
|
|
|
53
57
|
() => filtersManager,
|
|
54
58
|
) /* computed is used to provide value as ref(), not reactive() – as per usePersistedStorage interface */,
|
|
55
59
|
|
|
56
|
-
storages:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
storages: isNested
|
|
61
|
+
? [
|
|
62
|
+
PersistedStorageType.SessionStorage,
|
|
63
|
+
]
|
|
64
|
+
: [
|
|
65
|
+
PersistedStorageType.Route,
|
|
66
|
+
PersistedStorageType.SessionStorage,
|
|
67
|
+
],
|
|
60
68
|
storagePath: namespace,
|
|
61
69
|
|
|
62
70
|
/* use custom .toString() logic, provided by FiltersManager */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SysTypesAPI as sysTypes } from '@webitel/api-services/api';
|
|
2
2
|
import type { DataField } from '@webitel/api-services/gen/models';
|
|
3
3
|
import { WtTypeExtensionFieldKind } from '@webitel/ui-sdk/enums';
|
|
4
|
-
import { get } from 'lodash';
|
|
4
|
+
import { get } from 'lodash-es';
|
|
5
5
|
import type {
|
|
6
6
|
BaseFilterConfig,
|
|
7
7
|
FilterConfigBaseParams,
|
|
@@ -58,6 +58,7 @@ describe('tableHeadersStoreBody', () => {
|
|
|
58
58
|
|
|
59
59
|
beforeEach(async () => {
|
|
60
60
|
localStorage.clear();
|
|
61
|
+
sessionStorage.clear();
|
|
61
62
|
|
|
62
63
|
router = createRouter({
|
|
63
64
|
history: createMemoryHistory(),
|
|
@@ -73,9 +74,9 @@ describe('tableHeadersStoreBody', () => {
|
|
|
73
74
|
runWithRouter = (fn) => app.runWithContext(fn);
|
|
74
75
|
});
|
|
75
76
|
|
|
76
|
-
const setUpStore = async () => {
|
|
77
|
+
const setUpStore = async (options?: { isNested?: boolean }) => {
|
|
77
78
|
const store = createStore();
|
|
78
|
-
await runWithRouter(() => store.setupPersistence());
|
|
79
|
+
await runWithRouter(() => store.setupPersistence(options));
|
|
79
80
|
return store;
|
|
80
81
|
};
|
|
81
82
|
|
|
@@ -226,6 +227,37 @@ describe('tableHeadersStoreBody', () => {
|
|
|
226
227
|
]);
|
|
227
228
|
expect(store.headers.value.some(({ field }) => !field)).toBe(false);
|
|
228
229
|
});
|
|
230
|
+
|
|
231
|
+
describe('a nested list', () => {
|
|
232
|
+
it('keeps a changed sort out of the route query, in a namespaced sessionStorage key instead', async () => {
|
|
233
|
+
const store = await setUpStore({
|
|
234
|
+
isNested: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
store.updateSort(store.headers.value[0], 'asc');
|
|
238
|
+
await flushWrites();
|
|
239
|
+
|
|
240
|
+
expect(router.currentRoute.value.query.sort).toBeUndefined();
|
|
241
|
+
expect(sessionStorage.getItem(`${id}/sort`)).toBe('+name');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('keeps a changed field set out of the route query, in localStorage instead', async () => {
|
|
245
|
+
const store = await setUpStore({
|
|
246
|
+
isNested: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
store.updateShownHeaders(
|
|
250
|
+
store.headers.value.map((header) => ({
|
|
251
|
+
...header,
|
|
252
|
+
show: header.field === 'name',
|
|
253
|
+
})),
|
|
254
|
+
);
|
|
255
|
+
await flushWrites();
|
|
256
|
+
|
|
257
|
+
expect(router.currentRoute.value.query[fieldsQueryKey]).toBeUndefined();
|
|
258
|
+
expect(localStorage.getItem(fieldsQueryKey)).toBe('name');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
229
261
|
});
|
|
230
262
|
|
|
231
263
|
describe('access', () => {
|
|
@@ -249,15 +249,23 @@ export const tableHeadersStoreBody = ({
|
|
|
249
249
|
|
|
250
250
|
let persistedStorageControllers: PersistedStorageController[] = [];
|
|
251
251
|
|
|
252
|
-
const setupPersistence = async (
|
|
252
|
+
const setupPersistence = async ({
|
|
253
|
+
isNested = false,
|
|
254
|
+
}: {
|
|
255
|
+
isNested?: boolean;
|
|
256
|
+
} = {}) => {
|
|
253
257
|
const fieldsStorage = usePersistedStorage({
|
|
254
258
|
name: 'fields',
|
|
255
259
|
value: fields,
|
|
256
260
|
/* order is the restore priority: a shared link wins over local columns */
|
|
257
|
-
storages:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
storages: isNested
|
|
262
|
+
? [
|
|
263
|
+
PersistedStorageType.LocalStorage,
|
|
264
|
+
]
|
|
265
|
+
: [
|
|
266
|
+
PersistedStorageType.Route,
|
|
267
|
+
PersistedStorageType.LocalStorage,
|
|
268
|
+
],
|
|
261
269
|
storagePath: id,
|
|
262
270
|
onStore: (save, { name }) => {
|
|
263
271
|
const value = fields.value.join(',');
|
|
@@ -277,6 +285,12 @@ export const tableHeadersStoreBody = ({
|
|
|
277
285
|
const sortStorage = usePersistedStorage({
|
|
278
286
|
name: 'sort',
|
|
279
287
|
value: sort,
|
|
288
|
+
...(isNested && {
|
|
289
|
+
storages: [
|
|
290
|
+
PersistedStorageType.SessionStorage,
|
|
291
|
+
],
|
|
292
|
+
storagePath: id,
|
|
293
|
+
}),
|
|
280
294
|
});
|
|
281
295
|
|
|
282
296
|
const columnWidthsStorage = usePersistedStorage({
|
|
@@ -19,6 +19,8 @@ describe('tablePaginationStoreBody', () => {
|
|
|
19
19
|
let runWithRouter: <T>(fn: () => T) => T;
|
|
20
20
|
|
|
21
21
|
beforeEach(async () => {
|
|
22
|
+
sessionStorage.clear();
|
|
23
|
+
|
|
22
24
|
router = createRouter({
|
|
23
25
|
history: createMemoryHistory(),
|
|
24
26
|
routes,
|
|
@@ -33,9 +35,14 @@ describe('tablePaginationStoreBody', () => {
|
|
|
33
35
|
runWithRouter = (fn) => app.runWithContext(fn);
|
|
34
36
|
});
|
|
35
37
|
|
|
36
|
-
const setUpStore = async (
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const setUpStore = async (
|
|
39
|
+
options?: {
|
|
40
|
+
isNested?: boolean;
|
|
41
|
+
},
|
|
42
|
+
namespace?: string,
|
|
43
|
+
) => {
|
|
44
|
+
const store = tablePaginationStoreBody(namespace);
|
|
45
|
+
await runWithRouter(() => store.setupPersistence(options));
|
|
39
46
|
return store;
|
|
40
47
|
};
|
|
41
48
|
|
|
@@ -131,4 +138,21 @@ describe('tablePaginationStoreBody', () => {
|
|
|
131
138
|
expect(router.currentRoute.value.query.page).toBe('5');
|
|
132
139
|
});
|
|
133
140
|
});
|
|
141
|
+
|
|
142
|
+
describe('a nested list', () => {
|
|
143
|
+
it('keeps a changed page out of the route query, in a namespaced sessionStorage key instead', async () => {
|
|
144
|
+
const store = await setUpStore(
|
|
145
|
+
{
|
|
146
|
+
isNested: true,
|
|
147
|
+
},
|
|
148
|
+
'cases',
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
store.updatePage(3);
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
153
|
+
|
|
154
|
+
expect(router.currentRoute.value.query.page).toBeUndefined();
|
|
155
|
+
expect(sessionStorage.getItem('cases/page')).toBe('3');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
134
158
|
});
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { ref } from 'vue';
|
|
2
2
|
|
|
3
3
|
import { createDatalistStore } from '../_shared/createDatalistStore';
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
type PersistedStorageController,
|
|
6
|
+
PersistedStorageType,
|
|
7
|
+
} from '../persist/PersistedStorage.types';
|
|
5
8
|
import { usePersistedStorage } from '../persist/usePersistedStorage';
|
|
6
9
|
import type { Identifiable } from '../types/createDatalistStore.types';
|
|
7
10
|
import type { useTableStoreConfig } from '../types/tableStore.types';
|
|
8
11
|
|
|
9
|
-
export const tablePaginationStoreBody = () => {
|
|
12
|
+
export const tablePaginationStoreBody = (namespace?: string) => {
|
|
10
13
|
const page = ref(1);
|
|
11
14
|
const size = ref(10);
|
|
12
15
|
const next = ref(false);
|
|
@@ -27,10 +30,20 @@ export const tablePaginationStoreBody = () => {
|
|
|
27
30
|
|
|
28
31
|
let persistedStorageControllers: PersistedStorageController[] = [];
|
|
29
32
|
|
|
30
|
-
const setupPersistence = (
|
|
33
|
+
const setupPersistence = ({
|
|
34
|
+
isNested = false,
|
|
35
|
+
}: {
|
|
36
|
+
isNested?: boolean;
|
|
37
|
+
} = {}) => {
|
|
31
38
|
const pageStorage = usePersistedStorage({
|
|
32
39
|
name: 'page',
|
|
33
40
|
value: page,
|
|
41
|
+
...(isNested && {
|
|
42
|
+
storages: [
|
|
43
|
+
PersistedStorageType.SessionStorage,
|
|
44
|
+
],
|
|
45
|
+
storagePath: namespace,
|
|
46
|
+
}),
|
|
34
47
|
onStore: (save, { name }) => {
|
|
35
48
|
return save({
|
|
36
49
|
name,
|
|
@@ -47,6 +60,12 @@ export const tablePaginationStoreBody = () => {
|
|
|
47
60
|
const sizeStorage = usePersistedStorage({
|
|
48
61
|
name: 'size',
|
|
49
62
|
value: size,
|
|
63
|
+
...(isNested && {
|
|
64
|
+
storages: [
|
|
65
|
+
PersistedStorageType.SessionStorage,
|
|
66
|
+
],
|
|
67
|
+
storagePath: namespace,
|
|
68
|
+
}),
|
|
50
69
|
onStore: (save, { name }) => {
|
|
51
70
|
return save({
|
|
52
71
|
name,
|
|
@@ -98,7 +117,7 @@ export const createTablePaginationStore = <Entity extends Identifiable>(
|
|
|
98
117
|
) => {
|
|
99
118
|
const id = `${namespace}/pagination`;
|
|
100
119
|
return createDatalistStore({
|
|
101
|
-
storeBody: tablePaginationStoreBody,
|
|
120
|
+
storeBody: () => tablePaginationStoreBody(namespace),
|
|
102
121
|
namespace: id,
|
|
103
122
|
config,
|
|
104
123
|
});
|
|
@@ -259,4 +259,30 @@ describe('tableStoreBody', () => {
|
|
|
259
259
|
);
|
|
260
260
|
});
|
|
261
261
|
});
|
|
262
|
+
|
|
263
|
+
describe('filters persistence of a nested list', () => {
|
|
264
|
+
it('keeps an added filter out of the route query, in sessionStorage instead', async () => {
|
|
265
|
+
const useStore = createTableStore('cases-nested-filters/datalist', {
|
|
266
|
+
apiModule: {
|
|
267
|
+
getList,
|
|
268
|
+
},
|
|
269
|
+
headers,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
mountTab(useStore);
|
|
273
|
+
await flush();
|
|
274
|
+
|
|
275
|
+
const store = useStore();
|
|
276
|
+
store.addFilter({
|
|
277
|
+
name: 'search',
|
|
278
|
+
value: 'lorem',
|
|
279
|
+
});
|
|
280
|
+
await flush();
|
|
281
|
+
|
|
282
|
+
expect(router.currentRoute.value.query).toEqual({});
|
|
283
|
+
expect(
|
|
284
|
+
sessionStorage.getItem('cases-nested-filters/datalist/filters'),
|
|
285
|
+
).toContain('lorem');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
262
288
|
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { StoreDefinition } from 'pinia';
|
|
2
1
|
import { computed, type MaybeRefOrGetter, toValue, watch } from 'vue';
|
|
3
2
|
|
|
4
3
|
import { useInjectedCardStore } from '../../card/composables/useCardStoreProvider';
|
|
5
|
-
import type { CardItemId } from '../../card/types/CardStore.types';
|
|
4
|
+
import type { CardItemId, NestedList } from '../../card/types/CardStore.types';
|
|
5
|
+
|
|
6
|
+
/** what this needs of a table store, which `createTableStore` satisfies */
|
|
7
|
+
interface NestedTableListStore extends NestedList {
|
|
8
|
+
initialize: (options: { parentId: string | number }) => unknown;
|
|
9
|
+
}
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* The list of a card page's nested tab.
|
|
@@ -23,11 +27,11 @@ import type { CardItemId } from '../../card/types/CardStore.types';
|
|
|
23
27
|
*
|
|
24
28
|
* [WTEL-10350](https://webitel.atlassian.net/browse/WTEL-10350)
|
|
25
29
|
*/
|
|
26
|
-
export const useNestedTableList = ({
|
|
30
|
+
export const useNestedTableList = <Store extends NestedTableListStore>({
|
|
27
31
|
useTableStore,
|
|
28
32
|
parentId,
|
|
29
33
|
}: {
|
|
30
|
-
useTableStore:
|
|
34
|
+
useTableStore: () => Store;
|
|
31
35
|
/** only where the parent is not the card's own record */
|
|
32
36
|
parentId?: MaybeRefOrGetter<CardItemId>;
|
|
33
37
|
}) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import deepEqual from 'deep-equal';
|
|
2
|
-
import set from 'lodash
|
|
2
|
+
import { set } from 'lodash-es';
|
|
3
3
|
import { nextTick, type Ref, ref, toRaw, watch } from 'vue';
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -317,9 +317,15 @@ export const tableStoreBody = <Entity extends Identifiable>(
|
|
|
317
317
|
|
|
318
318
|
if (!disablePersistence) {
|
|
319
319
|
await Promise.allSettled([
|
|
320
|
-
setupPaginationPersistence(
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
setupPaginationPersistence({
|
|
321
|
+
isNested: isNested.value,
|
|
322
|
+
}),
|
|
323
|
+
setupFiltersPersistence({
|
|
324
|
+
isNested: isNested.value,
|
|
325
|
+
}),
|
|
326
|
+
setupHeadersPersistence({
|
|
327
|
+
isNested: isNested.value,
|
|
328
|
+
}),
|
|
323
329
|
]);
|
|
324
330
|
}
|
|
325
331
|
|
|
@@ -367,14 +373,6 @@ export const tableStoreBody = <Entity extends Identifiable>(
|
|
|
367
373
|
|
|
368
374
|
await setupStore();
|
|
369
375
|
|
|
370
|
-
/*
|
|
371
|
-
on the first setup the restore path is authoritative.
|
|
372
|
-
|
|
373
|
-
a store initialized with a parentId is a list nested in a card page, not a
|
|
374
|
-
registry: it shares the query param names with the registry stores, and
|
|
375
|
-
the url it would publish into is the card one – so it keeps writing on
|
|
376
|
-
change only, and syncs merely on demand
|
|
377
|
-
*/
|
|
378
376
|
if (isStoreAlreadySetUp && !disablePersistence && !storeParentId) {
|
|
379
377
|
await syncPersistence();
|
|
380
378
|
}
|