@webitel/ui-sdk 24.6.0 → 24.6.2

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 (33) hide show
  1. package/dist/ui-sdk.css +1 -1
  2. package/dist/ui-sdk.mjs +547 -532
  3. package/dist/ui-sdk.umd.js +1 -1
  4. package/package.json +4 -6
  5. package/src/components/wt-table/wt-table.vue +41 -6
  6. package/src/components/wt-table-column-select/wt-table-column-select.vue +1 -1
  7. package/src/modules/Filters/classes/BaseFilterSchema.js +140 -7
  8. package/src/modules/Filters/components/__tests__/filter-pagination.spec.js +94 -0
  9. package/src/modules/Filters/components/__tests__/filter-table-fields.spec.js +71 -0
  10. package/src/modules/Filters/components/filter-pagination.vue +8 -8
  11. package/src/modules/Filters/components/filter-table-fields.vue +4 -11
  12. package/src/modules/Filters/composables/useTableFilters.js +2 -3
  13. package/src/modules/Filters/enums/FilterEvent.enum.js +6 -0
  14. package/src/modules/Filters/scripts/getters/index.js +9 -0
  15. package/src/modules/Filters/scripts/getters/localStorageGetter.js +17 -0
  16. package/src/modules/Filters/scripts/getters/queryGetter.js +10 -0
  17. package/src/modules/Filters/scripts/getters/valueGetter.js +9 -0
  18. package/src/modules/Filters/scripts/restores/index.js +7 -0
  19. package/src/modules/Filters/scripts/restores/localStorageRestore.js +7 -0
  20. package/src/modules/Filters/scripts/restores/queryRestore.js +7 -0
  21. package/src/modules/Filters/scripts/setters/index.js +9 -0
  22. package/src/modules/Filters/scripts/setters/localStorageSetter.js +16 -0
  23. package/src/modules/Filters/scripts/setters/querySetter.js +41 -0
  24. package/src/modules/Filters/scripts/setters/valueSetter.js +6 -0
  25. package/src/modules/Filters/scripts/utils/changeRouteQuery.js +17 -0
  26. package/src/modules/Filters/store/FiltersStoreModule.js +88 -139
  27. package/src/modules/Filters/store/__tests__/FiltersStoreModule.spec.js +157 -0
  28. package/src/modules/Notifications/store/__tests__/NotificationsStoreModule.actions.spec.js +1 -1
  29. package/src/modules/TableStoreModule/composables/useTableStore.js +13 -9
  30. package/src/modules/TableStoreModule/store/TableStoreModule.js +124 -106
  31. package/src/modules/TableStoreModule/store/__tests__/TableStoreModule.spec.js +241 -0
  32. package/src/modules/Filters/restores/filterFieldsRestore.js +0 -9
  33. package/src/modules/Filters/restores/readme.md +0 -1
@@ -1,48 +1,39 @@
1
+ import set from 'lodash/set';
1
2
  import {
2
3
  queryToSortAdapter,
3
- SortSymbols,
4
4
  sortToQueryAdapter,
5
5
  } from '../../../scripts/sortQueryAdapters';
6
6
  import BaseStoreModule from '../../../store/BaseStoreModules/BaseStoreModule';
7
+ import FilterEvent from '../../Filters/enums/FilterEvent.enum';
7
8
 
8
9
  export default class TableStoreModule extends BaseStoreModule {
9
10
  state = {
10
11
  headers: [],
11
12
  dataList: [],
13
+ selected: [],
12
14
  error: {},
13
15
  isLoading: false,
14
16
  isNextPage: false,
15
17
  };
16
18
 
17
19
  getters = {
18
- PARENT_ID: () => null,
20
+ PARENT_ID: () => null, // override me
19
21
 
20
- GET_FILTERS: (state, getters) => getters['filters/GET_FILTERS'],
22
+ // FIXME: maybe move to filters module?
23
+ FILTERS: (state, getters) => getters['filters/GET_FILTERS'],
21
24
 
22
- // required fields to send as "fields" param with GET_LIST
23
- REQUIRED_FIELDS: () => ['id'],
25
+ FIELDS: (state) => {
26
+ const fields = state.headers.reduce((fields, { show, field }) => {
27
+ if (show) return [...fields, field];
28
+ return fields;
29
+ }, []);
24
30
 
25
- FIELDS: (state, getters) => {
26
- const filtersFields = getters.GET_FILTERS.filters;
27
-
28
- const defaultFields = state.headers.filter((header) => header.show)
29
- .map((header) => header.field);
30
-
31
- const getFieldsByFieldValues = (values) => {
32
- values.map((passedValue) => state.headers.find(({ value }) => passedValue
33
- === value).field);
34
- };
35
-
36
- return [
37
- ...new Set(filtersFields
38
- ? getFieldsByFieldValues(filtersFields)
39
- : defaultFields),
40
- ].concat(getters.REQUIRED_FIELDS);
31
+ return [...new Set(['id', ...fields])];
41
32
  },
42
33
 
43
34
  // main GET_LIST params collector
44
- GET_LIST_PARAMS: (state, getters) => (overrideFilters) => {
45
- const filters = getters.GET_FILTERS;
35
+ GET_LIST_PARAMS: (state, getters) => (overrides) => {
36
+ const filters = getters.FILTERS;
46
37
  const fields = getters.FIELDS;
47
38
  const parentId = getters.PARENT_ID;
48
39
 
@@ -50,96 +41,126 @@ export default class TableStoreModule extends BaseStoreModule {
50
41
  parentId,
51
42
  ...filters,
52
43
  fields,
53
- ...overrideFilters,
44
+ ...overrides,
54
45
  };
55
46
  },
56
47
  };
57
48
 
58
49
  actions = {
50
+ // FIXME: maybe move to filters module?
51
+ SET_FILTER: (
52
+ context,
53
+ payload,
54
+ ) => context.dispatch('filters/SET_FILTER', payload),
55
+
56
+ // FIXME: maybe move to filters module?
57
+ ON_FILTER_EVENT: async (context, { event, payload }) => {
58
+ switch (event) {
59
+ case FilterEvent.RESTORED:
60
+ return context.dispatch('HANDLE_FILTERS_RESTORE', payload);
61
+ case FilterEvent.FILTER_SET:
62
+ return context.dispatch('HANDLE_FILTER_SET', payload);
63
+ default:
64
+ throw new Error(`Unknown filter event: ${event}`);
65
+ }
66
+ },
67
+
68
+ // FIXME: maybe move to filters module?
69
+ HANDLE_FILTERS_RESTORE: (context) => {
70
+ return context.dispatch('LOAD_DATA_LIST');
71
+ },
72
+
73
+ // FIXME: maybe move to filters module?
74
+ HANDLE_FILTER_SET: async (context, payload) => {
75
+ if (payload.name === 'fields') {
76
+ await context.dispatch('HANDLE_FIELDS_CHANGE', payload);
77
+ } else if (payload.name === 'sort') {
78
+ await context.dispatch('HANDLE_SORT_CHANGE', payload);
79
+ }
80
+
81
+ if (context.getters.FILTERS.page && payload.value !== 'page') {
82
+ await context.dispatch('SET_FILTER', {
83
+ name: 'page',
84
+ value: 1,
85
+ silent: true,
86
+ });
87
+ }
88
+
89
+ return context.dispatch('LOAD_DATA_LIST');
90
+ },
91
+
92
+ // FIXME: maybe move to filters module?
93
+ HANDLE_FIELDS_CHANGE: (context, payload) => {
94
+ const headers = context.state.headers.map((header) => ({
95
+ ...header,
96
+ show: payload.value.includes(header.value),
97
+ }));
98
+
99
+ context.commit('SET', { path: 'headers', value: headers });
100
+ },
101
+
102
+ // FIXME: maybe move to filters module?
103
+ HANDLE_SORT_CHANGE: (context, payload) => {
104
+ const nextSort = queryToSortAdapter(payload.value.slice(0, 1));
105
+ const field = nextSort ? payload.value.slice(1) : payload.value;
106
+
107
+ const headers = context.state.headers.map(({
108
+ sort: currentSort,
109
+ ...header
110
+ }) => {
111
+ const sort = field === header.field ? nextSort : currentSort;
112
+ return { ...header, sort };
113
+ });
114
+
115
+ context.commit('SET', { path: 'headers', value: headers });
116
+ },
117
+
59
118
  LOAD_DATA_LIST: async (context, query) => {
60
- /*
61
- https://my.webitel.com/browse/WTEL-3560
62
- preventively disable isNext to handle case when user is clicking
63
- "next" faster than actual request is made
64
- */
65
- context.commit('SET_IS_NEXT', false);
119
+ context.commit('SET', { path: 'isLoading', value: true });
120
+ context.commit('SET', { path: 'isNextPage', value: false }); // [WTEL-3560]
66
121
 
67
122
  const params = context.getters.GET_LIST_PARAMS(query);
68
123
  try {
69
- context.commit('SET_LOADING', true);
70
124
  let {
71
125
  items = [],
72
126
  next = false,
73
127
  } = await context.dispatch('api/GET_LIST', { context, params });
74
128
 
75
- /* [https://my.webitel.com/browse/WTEL-3793]
76
- * When deleting the last item from list,
77
- * if there are other items on the previous page, you need to go back */
78
- if (!items.length && params.page > 1) {
79
- return context.dispatch('filters/SET_FILTER', {
80
- filter: 'page',
81
- value: params.page - 1,
82
- });
83
- }
84
-
85
- /* we should set _isSelected property to all items in tables cause their checkbox selection
86
- * is based on this property. Previously, this prop was set it api consumers, but now
87
- * admin-specific were replaced by webitel-sdk consumers and i supposed it will be
88
- * weird to set this property in each api file through defaultListObject */
89
- items = items.map((item) => ({ ...item, _isSelected: false }));
90
- context.commit('SET_DATA_LIST', items);
91
- context.commit('SET_IS_NEXT', next);
129
+ context.commit('SET', { path: 'dataList', value: items });
130
+ context.commit('SET', { path: 'isNextPage', value: next });
92
131
  } catch (err) {
93
- context.commit('SET_ERROR', err);
132
+ context.commit('SET', { path: 'error', value: err });
133
+ throw err;
94
134
  } finally {
95
- context.commit('SET_LOADING', false);
135
+ context.commit('SET', { path: 'isLoading', value: false });
96
136
  }
97
137
  },
98
- SET_HEADERS: (context, headers) => context.commit('SET_HEADERS', headers),
99
- RESTORE_FIELDS_FROM_FILTER: (context, { value }) => {
100
- const headers = context.state.headers.map((header) => ({
101
- ...header,
102
- show: value.includes(header.value),
103
- }));
104
- return context.dispatch('SET_HEADERS', headers);
105
- },
106
- SORT: async (context, { header, nextSortOrder }) => {
138
+
139
+ // FIXME: maybe move to filters module?
140
+ SORT: (context, { header, nextSortOrder }) => {
107
141
  const sort = nextSortOrder
108
142
  ? `${sortToQueryAdapter(nextSortOrder)}${header.field}`
109
143
  : nextSortOrder;
110
- await context.dispatch('filters/SET_FILTER', {
111
- filter: 'sort',
144
+ return context.dispatch('SET_FILTER', {
145
+ name: 'sort',
112
146
  value: sort,
113
147
  });
114
- await context.dispatch('UPDATE_HEADER_SORT', { header, nextSortOrder });
115
- },
116
- RESTORE_SORT_FROM_FILTER: (context, { value }) => {
117
- const sort = queryToSortAdapter(value.slice(0, 1));
118
- const field = sort ? value.slice(1) : value;
119
- return context.dispatch('UPDATE_HEADER_SORT', {
120
- header: { field },
121
- nextSortOrder: sort,
122
- });
123
- },
124
- UPDATE_HEADER_SORT: (context, { header, nextSortOrder }) => {
125
- const headers = context.state.headers.map((oldHeader) => {
126
- const sortFieldValue = oldHeader?.sort;
127
- return {
128
- ...oldHeader,
129
- sort: oldHeader.field === header.field
130
- ? nextSortOrder
131
- : sortFieldValue,
132
- };
133
- });
134
- context.commit('SET_HEADERS', headers);
135
148
  },
149
+
136
150
  PATCH_ITEM_PROPERTY: async (context, {
137
- item, index, prop, value,
151
+ item: _item, index, prop, value,
138
152
  }) => {
139
- await context.commit('PATCH_ITEM_PROPERTY', { index, prop, value });
140
- const id = item?.id || context.state.dataList[index].id;
141
- const { etag } = item;
153
+ const item = _item || context.state.dataList[index];
154
+
155
+ const { id, etag } = item;
156
+
157
+ context.commit('SET', {
158
+ path: `dataList[${index}].${prop}`,
159
+ value,
160
+ });
161
+
142
162
  const changes = { [prop]: value };
163
+
143
164
  try {
144
165
  await context.dispatch('api/PATCH_ITEM', {
145
166
  context,
@@ -147,14 +168,12 @@ export default class TableStoreModule extends BaseStoreModule {
147
168
  etag,
148
169
  changes,
149
170
  });
150
- context.commit('PATCH_ITEM_PROPERTY', {
151
- item, index, prop, value,
152
- });
153
171
  } catch (err) {
154
172
  await context.dispatch('LOAD_DATA_LIST');
155
173
  throw err;
156
174
  }
157
175
  },
176
+
158
177
  DELETE: async (context, deleted) => {
159
178
  let action = 'DELETE_SINGLE';
160
179
  if (Array.isArray(deleted)) {
@@ -167,8 +186,17 @@ export default class TableStoreModule extends BaseStoreModule {
167
186
  throw err;
168
187
  } finally {
169
188
  await context.dispatch('LOAD_DATA_LIST');
189
+
190
+ /* if no items on current page after DELETE, move to prev page [WTEL-3793] */
191
+ if (!context.items.length && context.getters.FILTERS.page > 1) {
192
+ await context.dispatch('SET_FILTER', {
193
+ name: 'page',
194
+ value: context.getters.FILTERS.page - 1,
195
+ });
196
+ }
170
197
  }
171
198
  },
199
+
172
200
  DELETE_SINGLE: async (context, { id, etag }) => {
173
201
  try {
174
202
  await context.dispatch('api/DELETE_ITEM', { context, id, etag });
@@ -176,30 +204,20 @@ export default class TableStoreModule extends BaseStoreModule {
176
204
  throw err;
177
205
  }
178
206
  },
207
+
179
208
  DELETE_BULK: async (
180
209
  context,
181
210
  deleted,
182
211
  ) => Promise.allSettled(deleted.map((item) => context.dispatch('DELETE_SINGLE', item))),
212
+
213
+ SET_SELECTED: (context, selected) => {
214
+ context.commit('SET', { path: 'selected', value: selected });
215
+ },
183
216
  };
184
217
 
185
218
  mutations = {
186
- SET_DATA_LIST: (state, items) => {
187
- state.dataList = items;
188
- },
189
- SET_IS_NEXT: (state, next) => {
190
- state.isNextPage = next;
191
- },
192
- SET_HEADERS: (state, headers) => {
193
- state.headers = headers;
194
- },
195
- PATCH_ITEM_PROPERTY: (state, { index, prop, value }) => {
196
- state.dataList[index][prop] = value;
197
- },
198
- SET_LOADING: (state, value) => {
199
- state.isLoading = value;
200
- },
201
- SET_ERROR: (state, value) => {
202
- state.error = value;
219
+ SET: (state, { path, value }) => {
220
+ return set(state, path, value);
203
221
  },
204
222
  };
205
223
 
@@ -0,0 +1,241 @@
1
+ import { createStore } from 'vuex';
2
+ import { SortSymbols } from '../../../../scripts/sortQueryAdapters';
3
+ import FilterEvent from '../../../Filters/enums/FilterEvent.enum';
4
+ import FiltersStoreModule from '../../../Filters/store/FiltersStoreModule';
5
+ import TableStoreModule from '../TableStoreModule';
6
+
7
+ describe('TableStoreModule', () => {
8
+ it('correctly computes FIELDS getter', () => {
9
+ const headers = [
10
+ { show: true, field: 'id' },
11
+ { show: false, field: 'name' },
12
+ { show: true, field: 'age' },
13
+ ];
14
+
15
+ const state = { headers };
16
+
17
+ expect(new TableStoreModule({}).getters.FIELDS(state))
18
+ .toEqual(['id', 'age']);
19
+ });
20
+ });
21
+
22
+ describe('TableStoreModule integration with FiltersStoreModule', () => {
23
+ it('filters restore event triggers LOAD_DATA_LIST', async () => {
24
+ const filters = new FiltersStoreModule()
25
+ .addFilter({
26
+ name: 'vi',
27
+ value: 23,
28
+ defaultValue: 23,
29
+ get: ['value'],
30
+ set: ['value'],
31
+ restore: () => 'vivi',
32
+ })
33
+ .getModule();
34
+
35
+ const table = new TableStoreModule({})
36
+ .getModule({
37
+ modules: { filters },
38
+ });
39
+
40
+ const mock = vi.fn();
41
+ vi.spyOn(table.actions, 'LOAD_DATA_LIST')
42
+ .mockImplementationOnce(mock);
43
+
44
+ const store = createStore({
45
+ modules: { table },
46
+ });
47
+
48
+ await store.dispatch('table/filters/SUBSCRIBE', {
49
+ event: FilterEvent.RESTORED,
50
+ callback: (payload) => store.dispatch('table/ON_FILTER_EVENT', payload),
51
+ });
52
+
53
+ await store.dispatch('table/filters/RESTORE_FILTERS');
54
+
55
+ expect(mock).toHaveBeenCalledTimes(1);
56
+
57
+ await store.dispatch('table/filters/FLUSH_SUBSCRIBERS');
58
+ });
59
+
60
+ it('FILTER_SET event triggers LOAD_DATA_LIST', async () => {
61
+ const filters = new FiltersStoreModule()
62
+ .addFilter({
63
+ name: 'vi',
64
+ value: 23,
65
+ defaultValue: 23,
66
+ get: ['value'],
67
+ set: ['value'],
68
+ restore: () => {},
69
+ })
70
+ .getModule();
71
+
72
+ const table = new TableStoreModule({})
73
+ .getModule({
74
+ modules: { filters },
75
+ });
76
+
77
+ const mock = vi.fn();
78
+ vi.spyOn(table.actions, 'LOAD_DATA_LIST')
79
+ .mockImplementationOnce(mock);
80
+
81
+ const store = createStore({
82
+ modules: { table },
83
+ });
84
+
85
+ await store.dispatch('table/filters/SUBSCRIBE', {
86
+ event: FilterEvent.FILTER_SET,
87
+ callback: (payload) => store.dispatch('table/ON_FILTER_EVENT', payload),
88
+ });
89
+
90
+ await store.dispatch('table/filters/SET_FILTER', {
91
+ name: 'vi',
92
+ value: 24,
93
+ });
94
+
95
+ expect(store.getters['table/filters/GET_FILTER']('vi')).toBe(24);
96
+
97
+ expect(mock).toHaveBeenCalledTimes(1);
98
+
99
+ await store.dispatch('table/filters/FLUSH_SUBSCRIBERS');
100
+ });
101
+
102
+ it('FILTER_SET with not-page filter name resets page filter', async () => {
103
+ const filters = new FiltersStoreModule()
104
+ .addFilter([
105
+ {
106
+ name: 'vi',
107
+ value: 23,
108
+ defaultValue: 23,
109
+ get: ['value'],
110
+ set: ['value'],
111
+ restore: () => {},
112
+ },
113
+ {
114
+ name: 'page',
115
+ value: 12,
116
+ defaultValue: 12,
117
+ get: ['value'],
118
+ set: ['value'],
119
+ restore: () => {},
120
+ },
121
+ ])
122
+ .getModule();
123
+
124
+ const table = new TableStoreModule({})
125
+ .getModule({
126
+ modules: { filters },
127
+ });
128
+
129
+ vi.spyOn(table.actions, 'LOAD_DATA_LIST')
130
+ .mockImplementationOnce(vi.fn());
131
+
132
+ const store = createStore({
133
+ modules: { table },
134
+ });
135
+
136
+ await store.dispatch('table/filters/SUBSCRIBE', {
137
+ event: FilterEvent.FILTER_SET,
138
+ callback: (payload) => store.dispatch('table/ON_FILTER_EVENT', payload),
139
+ });
140
+
141
+ expect(store.getters['table/filters/GET_FILTER']('page')).toBe(12);
142
+
143
+ await store.dispatch('table/filters/SET_FILTER', {
144
+ name: 'vi',
145
+ value: 24,
146
+ });
147
+
148
+ expect(store.getters['table/filters/GET_FILTER']('page')).toBe(1);
149
+
150
+ await store.dispatch('table/filters/FLUSH_SUBSCRIBERS');
151
+ });
152
+
153
+ it('SORT changes both headers and sort filter', async () => {
154
+ const filters = new FiltersStoreModule()
155
+ .addFilter({
156
+ name: 'sort',
157
+ value: '',
158
+ get: ['value'],
159
+ set: ['value'],
160
+ restore: () => {},
161
+ })
162
+ .getModule();
163
+
164
+ const headers = [
165
+ { value: 'id', field: 'sort_me', sort: SortSymbols.NONE },
166
+ ];
167
+
168
+ const table = new TableStoreModule({ headers })
169
+ .getModule({
170
+ modules: { filters },
171
+ });
172
+
173
+ vi.spyOn(table.actions, 'LOAD_DATA_LIST')
174
+ .mockImplementationOnce(vi.fn());
175
+
176
+ const store = createStore({
177
+ modules: { table },
178
+ });
179
+
180
+ await store.dispatch('table/filters/SUBSCRIBE', {
181
+ event: FilterEvent.FILTER_SET,
182
+ callback: (payload) => store.dispatch('table/ON_FILTER_EVENT', payload),
183
+ });
184
+
185
+ await store.dispatch('table/SORT', {
186
+ header: headers[0],
187
+ nextSortOrder: SortSymbols.ASC,
188
+ });
189
+
190
+ expect(store.getters['table/filters/GET_FILTER']('sort')).toBe('+sort_me');
191
+
192
+ expect(store.state.table.headers[0].sort).toBe(SortSymbols.ASC);
193
+
194
+ await store.dispatch('table/filters/FLUSH_SUBSCRIBERS');
195
+ });
196
+
197
+ it('fields filter change changes headers', async () => {
198
+ const filters = new FiltersStoreModule()
199
+ .addFilter({
200
+ name: 'fields',
201
+ value: [],
202
+ get: ['value'],
203
+ set: ['value'],
204
+ restore: () => {},
205
+ })
206
+ .getModule();
207
+
208
+ const headers = [
209
+ { value: 'surname', field: 'included', show: false },
210
+ { value: 'name', field: 'excluded', show: true },
211
+ ];
212
+
213
+ const table = new TableStoreModule({ headers })
214
+ .getModule({
215
+ modules: { filters },
216
+ });
217
+
218
+ vi.spyOn(table.actions, 'LOAD_DATA_LIST')
219
+ .mockImplementationOnce(vi.fn());
220
+
221
+ const store = createStore({
222
+ modules: { table },
223
+ });
224
+
225
+ await store.dispatch('table/filters/SUBSCRIBE', {
226
+ event: FilterEvent.FILTER_SET,
227
+ callback: (payload) => store.dispatch('table/ON_FILTER_EVENT', payload),
228
+ });
229
+
230
+ await store.dispatch('table/filters/SET_FILTER', {
231
+ name: 'fields',
232
+ value: ['surname'],
233
+ });
234
+
235
+ expect(store.getters['table/filters/GET_FILTER']('fields')).toEqual(['surname']);
236
+
237
+ expect(store.getters['table/FIELDS']).toEqual(['id', 'included']);
238
+
239
+ await store.dispatch('table/filters/FLUSH_SUBSCRIBERS');
240
+ });
241
+ });
@@ -1,9 +0,0 @@
1
- const restore = (localStorageKey) => ({ query }) => {
2
- if (!query) {
3
- const value = localStorage.getItem(localStorageKey);
4
- return value ? value.split(',') : null;
5
- }
6
- return query;
7
- };
8
-
9
- export default restore;
@@ -1 +0,0 @@
1
- "restore" functions for filters