@verdnatura/vn-front-lib 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components-BrEjZWQj.js +3278 -0
- package/dist/components-Dty6kM2e.cjs +2 -0
- package/dist/components.cjs +1 -1
- package/dist/components.js +2 -2
- package/dist/composables-DUOUI4DI.js +3558 -0
- package/dist/composables-ZxHJjJGN.cjs +4 -0
- package/dist/composables.cjs +1 -1
- package/dist/composables.js +2 -2
- package/dist/filters.cjs +1 -0
- package/dist/filters.js +62 -0
- package/dist/rolldown-runtime-DF3sbh8G.cjs +1 -0
- package/dist/stores.cjs +1 -1
- package/dist/stores.js +144 -2
- package/dist/utils.cjs +1 -0
- package/dist/utils.js +131 -0
- package/dist/vn-front-lib.cjs +1 -1
- package/dist/vn-front-lib.css +1 -1
- package/dist/vn-front-lib.js +6 -7
- package/lib/components/dialogs/VnConfirm.vue +142 -0
- package/lib/components/feedback/VnSkeletonForm.vue +14 -0
- package/lib/components/feedback/VnSpinner.vue +63 -0
- package/lib/components/index.js +5 -0
- package/lib/components/models/VnFetch.vue +65 -0
- package/lib/components/models/VnForm.vue +492 -0
- package/lib/composables/index.js +7 -0
- package/lib/composables/tMobile.js +8 -0
- package/lib/composables/useArrayData.js +451 -0
- package/lib/composables/useForm.js +12 -0
- package/lib/composables/useNotify.js +24 -0
- package/lib/composables/useObjectComparison.js +67 -0
- package/lib/composables/useQueryResolver.js +306 -0
- package/lib/composables/useState.js +81 -0
- package/lib/filters/filterPanel.js +95 -0
- package/lib/filters/getDifferences.js +18 -0
- package/lib/filters/getUpdatedValues.js +6 -0
- package/lib/filters/index.js +5 -0
- package/lib/filters/isDialogOpened.js +3 -0
- package/lib/filters/onBeforeSave.js +9 -0
- package/lib/stores/index.js +2 -0
- package/lib/stores/useArrayDataStore.js +82 -0
- package/lib/stores/useStateStore.js +84 -0
- package/lib/utils/advancedFilters.js +125 -0
- package/lib/utils/index.js +2 -0
- package/lib/utils/trackAnalytics.js +18 -0
- package/package.json +9 -2
- package/dist/components-BLui5eyq.cjs +0 -1
- package/dist/components-Dl-Ieazh.js +0 -2466
- package/dist/useRequired-BqsSq8Mw.cjs +0 -1
- package/dist/useRequired-CTxBp3qy.js +0 -58
- package/dist/useValidationsStore-8JX1-oLO.cjs +0 -1
- package/dist/useValidationsStore-9Fxtj_pd.js +0 -12
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { onMounted, computed, ref, markRaw } from 'vue';
|
|
2
|
+
import { useRouter, useRoute } from 'vue-router';
|
|
3
|
+
import { useArrayDataStore } from 'lib/stores';
|
|
4
|
+
import { buildFilter, isDialogOpened } from 'lib/filters';
|
|
5
|
+
import { advancedFilters } from 'lib/utils';
|
|
6
|
+
import { requestData } from 'lib/composables';
|
|
7
|
+
|
|
8
|
+
export function useArrayData(key, userOptions) {
|
|
9
|
+
if (key == null) key = useRoute().meta.moduleName;
|
|
10
|
+
|
|
11
|
+
if (!key) throw new Error('ArrayData: A key is required to use this composable');
|
|
12
|
+
const arrayDataStore = useArrayDataStore();
|
|
13
|
+
if (!arrayDataStore.get(key)) arrayDataStore.set(key);
|
|
14
|
+
|
|
15
|
+
const store = arrayDataStore.get(key);
|
|
16
|
+
store.dataKey = key;
|
|
17
|
+
|
|
18
|
+
const route = useRoute();
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
let canceller = null;
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
setOptions(userOptions ?? {});
|
|
23
|
+
reset(['skip']);
|
|
24
|
+
|
|
25
|
+
const query = route.query;
|
|
26
|
+
const searchUrl = store.searchUrl;
|
|
27
|
+
if (query[searchUrl]) {
|
|
28
|
+
const params = JSON.parse(query[searchUrl]);
|
|
29
|
+
const filter =
|
|
30
|
+
params?.filter && typeof params?.filter == 'object'
|
|
31
|
+
? params?.filter
|
|
32
|
+
: JSON.parse(params?.filter ?? '{}');
|
|
33
|
+
delete params.filter;
|
|
34
|
+
store.userParams = {
|
|
35
|
+
...sanitizerParams(removeIgnoreUrlParams(params)),
|
|
36
|
+
...getIgnoreParams(),
|
|
37
|
+
};
|
|
38
|
+
store.filter = { ...filter, ...store.userFilter };
|
|
39
|
+
if (filter?.order) store.order = filter.order;
|
|
40
|
+
}
|
|
41
|
+
if (query.stackedParams) store.stackedParams = JSON.parse(query.stackedParams);
|
|
42
|
+
|
|
43
|
+
setCurrentFilter();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (userOptions) setOptions(userOptions);
|
|
47
|
+
|
|
48
|
+
function setOptions(params) {
|
|
49
|
+
if (!params) return;
|
|
50
|
+
const allowedOptions = [
|
|
51
|
+
'url',
|
|
52
|
+
'filter',
|
|
53
|
+
'where',
|
|
54
|
+
'order',
|
|
55
|
+
'limit',
|
|
56
|
+
'skip',
|
|
57
|
+
'userParams',
|
|
58
|
+
'userFilter',
|
|
59
|
+
'exprBuilder',
|
|
60
|
+
'dynamicExprBuilder',
|
|
61
|
+
'searchUrl',
|
|
62
|
+
'navigate',
|
|
63
|
+
'mapKey',
|
|
64
|
+
'oneRecord',
|
|
65
|
+
'hasChanges',
|
|
66
|
+
'isLocal',
|
|
67
|
+
'customFilterFn',
|
|
68
|
+
'remoteParamsArray',
|
|
69
|
+
'keyData',
|
|
70
|
+
'isStackableParams',
|
|
71
|
+
'nonStackableParams',
|
|
72
|
+
'stackedParams',
|
|
73
|
+
'dataAnalyticsFilter',
|
|
74
|
+
'ignoreUrlParams',
|
|
75
|
+
];
|
|
76
|
+
if (typeof params === 'object') {
|
|
77
|
+
for (const option in params) {
|
|
78
|
+
const isEmpty = params[option] == null || params[option] === '';
|
|
79
|
+
if (isEmpty || !allowedOptions.includes(option)) continue;
|
|
80
|
+
|
|
81
|
+
if (Object.hasOwn(store, option)) {
|
|
82
|
+
const defaultOpts = params[option];
|
|
83
|
+
store[option] = params.keepOpts?.includes(option)
|
|
84
|
+
? Object.assign(defaultOpts, store[option])
|
|
85
|
+
: defaultOpts;
|
|
86
|
+
if (option === 'userParams') store.defaultParams = store[option];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fetch({ append = false, updateRouter = true }) {
|
|
93
|
+
cancelRequest();
|
|
94
|
+
canceller = new AbortController();
|
|
95
|
+
const { params, limit } = setCurrentFilter();
|
|
96
|
+
|
|
97
|
+
let exprFilter;
|
|
98
|
+
if (store?.exprBuilder || Object.keys(store.dynamicWhereBuilder).length) {
|
|
99
|
+
exprFilter = buildFilter(params, (param, value) => {
|
|
100
|
+
if (param == 'filter') return;
|
|
101
|
+
let res = store.exprBuilder?.(param, value);
|
|
102
|
+
if (store.dynamicWhereBuilder[param]) res = { [param]: value };
|
|
103
|
+
if (res) delete params[param];
|
|
104
|
+
return res;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (params.filter.where || exprFilter)
|
|
109
|
+
params.filter.where = { ...params.filter.where, ...exprFilter };
|
|
110
|
+
|
|
111
|
+
if (!params?.filter?.order?.length) delete params?.filter?.order;
|
|
112
|
+
if (!Object.keys(params?.filter?.where ?? {})?.length)
|
|
113
|
+
delete params?.filter?.where;
|
|
114
|
+
|
|
115
|
+
store.isLoading = true;
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await requestData(params, canceller, store);
|
|
119
|
+
} finally {
|
|
120
|
+
store.isLoading = false;
|
|
121
|
+
canceller = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
store.hasMoreData = limit && response.data.length >= limit;
|
|
125
|
+
|
|
126
|
+
if (!append && !isDialogOpened() && updateRouter) {
|
|
127
|
+
if (updateStateParams()?.redirect) return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
processData(response.data, {
|
|
131
|
+
map: !!store.mapKey,
|
|
132
|
+
append,
|
|
133
|
+
oneRecord: store.oneRecord,
|
|
134
|
+
});
|
|
135
|
+
return response;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function destroy() {
|
|
139
|
+
if (arrayDataStore.get(key)) arrayDataStore.clear(key);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function deleteOption(option) {
|
|
143
|
+
delete store[option];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reset(opts = []) {
|
|
147
|
+
if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resetPagination() {
|
|
151
|
+
if (arrayDataStore.get(key)) arrayDataStore.resetPagination(key);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function cancelRequest() {
|
|
155
|
+
if (canceller) {
|
|
156
|
+
canceller.abort();
|
|
157
|
+
canceller = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function applyFilter({ filter, params }, fetchOptions = {}) {
|
|
162
|
+
if (filter) store.filter = filter;
|
|
163
|
+
if (params) store.userParams = { ...params };
|
|
164
|
+
|
|
165
|
+
const response = await fetch(fetchOptions);
|
|
166
|
+
return response;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function addFilter({ filter, params }) {
|
|
170
|
+
if (filter) store.filter = filter;
|
|
171
|
+
|
|
172
|
+
let userParams = { ...store.userParams, ...params };
|
|
173
|
+
userParams = sanitizerParams(userParams);
|
|
174
|
+
|
|
175
|
+
store.userParams = userParams;
|
|
176
|
+
resetPagination();
|
|
177
|
+
const response = await fetch({});
|
|
178
|
+
return { response, filter, params };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @deprecated Use addFilter instead, altering the "where" is a bad practice
|
|
183
|
+
*/
|
|
184
|
+
async function addFilterWhere(where) {
|
|
185
|
+
const storedFilter = { ...store.filter };
|
|
186
|
+
if (!storedFilter?.where) storedFilter.where = {};
|
|
187
|
+
where = { ...storedFilter.where, ...where };
|
|
188
|
+
await addFilter({ filter: { where } });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function addOrder(field, direction = 'ASC') {
|
|
192
|
+
const newOrder = `${field} ${direction}`;
|
|
193
|
+
const order = toArray(store.order);
|
|
194
|
+
|
|
195
|
+
let index = getOrderIndex(order, field);
|
|
196
|
+
if (index > -1) {
|
|
197
|
+
order[index] = newOrder;
|
|
198
|
+
} else {
|
|
199
|
+
index = order.length;
|
|
200
|
+
order.push(newOrder);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
store.order = order;
|
|
204
|
+
resetPagination();
|
|
205
|
+
await fetch({});
|
|
206
|
+
index++;
|
|
207
|
+
|
|
208
|
+
return { index, order };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function applyOrder(field, direction = 'ASC') {
|
|
212
|
+
const newOrder = `${field} ${direction}`;
|
|
213
|
+
store.order = [newOrder];
|
|
214
|
+
resetPagination();
|
|
215
|
+
await fetch({});
|
|
216
|
+
|
|
217
|
+
return { index: 1, order: [newOrder] };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function applyOrders(order, reload = true) {
|
|
221
|
+
store.order = order;
|
|
222
|
+
resetPagination();
|
|
223
|
+
if (reload) await fetch({});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function deleteOrder(field) {
|
|
227
|
+
const order = toArray(store.order);
|
|
228
|
+
const index = getOrderIndex(order, field);
|
|
229
|
+
if (index > -1) order.splice(index, 1);
|
|
230
|
+
|
|
231
|
+
store.order = order;
|
|
232
|
+
fetch({});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function sanitizerParams(params) {
|
|
236
|
+
for (const param in params) {
|
|
237
|
+
if (params[param] && typeof params[param] == 'object') {
|
|
238
|
+
let operator = Object.keys(params[param])[0];
|
|
239
|
+
if (Object.values(params[param])[0] === null)
|
|
240
|
+
operator = operator == 'eq' ? 'null' : 'notNull';
|
|
241
|
+
if (advancedFilters[operator]) {
|
|
242
|
+
store.dynamicExprBuilder[param] = operator;
|
|
243
|
+
const newValue = Object.values(params[param])[0];
|
|
244
|
+
store.userParams[param] = newValue;
|
|
245
|
+
params[param] = newValue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!isErasable(params[param], param)) continue;
|
|
249
|
+
delete store.userParams[param];
|
|
250
|
+
delete params[param];
|
|
251
|
+
|
|
252
|
+
if (store.filter?.where) {
|
|
253
|
+
const key = param;
|
|
254
|
+
if (store?.exprBuilder) {
|
|
255
|
+
const expr = store.exprBuilder(param);
|
|
256
|
+
if (!expr) continue;
|
|
257
|
+
const canErase = Object.values(expr).every((value) =>
|
|
258
|
+
isErasable(value, param),
|
|
259
|
+
);
|
|
260
|
+
if (!canErase) continue;
|
|
261
|
+
}
|
|
262
|
+
delete store.filter.where[key];
|
|
263
|
+
delete store.dynamicExprBuilder[key];
|
|
264
|
+
if (!Object.keys(store.filter.where).length) delete store.filter.where;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return params;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function loadMore() {
|
|
271
|
+
if (!store.hasMoreData) return;
|
|
272
|
+
|
|
273
|
+
store.skip = (store?.filter?.limit ?? store.limit) * store.page;
|
|
274
|
+
store.page += 1;
|
|
275
|
+
|
|
276
|
+
await fetch({ append: true });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function refresh() {
|
|
280
|
+
if (Object.values(store.userParams).length) await fetch({});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function updateStateParams() {
|
|
284
|
+
if (!route?.path) return;
|
|
285
|
+
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
|
|
286
|
+
if (store?.searchUrl) {
|
|
287
|
+
newUrl.query[store.searchUrl] = JSON.stringify(
|
|
288
|
+
removeIgnoreUrlParams(store.currentFilter),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (store.stackedParams?.length)
|
|
292
|
+
newUrl.query.stackedParams = JSON.stringify(store.stackedParams);
|
|
293
|
+
else delete newUrl.query.stackedParams;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
router.replace(newUrl);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getCurrentFilter() {
|
|
300
|
+
if (!Object.keys(store?.userParams ?? {}).length)
|
|
301
|
+
store.userParams = store.defaultParams ?? {};
|
|
302
|
+
|
|
303
|
+
const filter = {
|
|
304
|
+
limit: store.limit,
|
|
305
|
+
...store.userFilter,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
let where;
|
|
309
|
+
if (filter?.where || store.filter?.where)
|
|
310
|
+
where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
|
|
311
|
+
Object.assign(filter, store.filter);
|
|
312
|
+
filter.where = where;
|
|
313
|
+
const params = { filter };
|
|
314
|
+
|
|
315
|
+
Object.assign(params, store.userParams);
|
|
316
|
+
if (params.filter) params.filter.skip = store.skip;
|
|
317
|
+
if (store.order) params.filter.order = toArray(store.order);
|
|
318
|
+
else delete params.filter.order;
|
|
319
|
+
|
|
320
|
+
for (const [key, value] of Object.entries(params)) {
|
|
321
|
+
if (
|
|
322
|
+
isErasable(value, key) ||
|
|
323
|
+
(value && typeof value == 'object' && !Array.isArray(value))
|
|
324
|
+
)
|
|
325
|
+
continue;
|
|
326
|
+
const res = getAdvancedFilter(key)?.expr?.(value);
|
|
327
|
+
if (res) params[key] = res;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { filter, params, limit: filter.limit };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function setCurrentFilter() {
|
|
334
|
+
const { filter, params, limit } = getCurrentFilter();
|
|
335
|
+
const saveFilter = JSON.parse(JSON.stringify(params));
|
|
336
|
+
|
|
337
|
+
delete saveFilter.filter.include;
|
|
338
|
+
delete saveFilter.filter.fields;
|
|
339
|
+
delete saveFilter.filter.limit;
|
|
340
|
+
delete saveFilter.filter.skip;
|
|
341
|
+
saveFilter.filter = JSON.stringify(saveFilter.filter);
|
|
342
|
+
store.currentFilter = markRaw(saveFilter);
|
|
343
|
+
return { filter, params, limit };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function processData(data, { map = true, append = true, oneRecord = false }) {
|
|
347
|
+
store.append = append;
|
|
348
|
+
if (oneRecord) {
|
|
349
|
+
store.data = Array.isArray(data) ? data[0] : data;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!append) {
|
|
353
|
+
store.data = [];
|
|
354
|
+
store.map = new Map();
|
|
355
|
+
}
|
|
356
|
+
if (!Array.isArray(data)) {
|
|
357
|
+
store.data = data;
|
|
358
|
+
} else if (!map && append) {
|
|
359
|
+
for (const row of data) store.data.push(row);
|
|
360
|
+
} else {
|
|
361
|
+
for (const row of data) {
|
|
362
|
+
const key = row[store.mapKey];
|
|
363
|
+
row.key = key;
|
|
364
|
+
const existing = store.map.get(key);
|
|
365
|
+
if (key && existing) {
|
|
366
|
+
const { position } = existing;
|
|
367
|
+
row.position = position;
|
|
368
|
+
store.map.set(key, row);
|
|
369
|
+
store.data[position] = row;
|
|
370
|
+
} else {
|
|
371
|
+
row.position = store.map.size;
|
|
372
|
+
store.map.set(key, row);
|
|
373
|
+
store.data.push(row);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function getAdvancedFilter(param) {
|
|
380
|
+
const operator = store.dynamicExprBuilder[param];
|
|
381
|
+
if (!operator) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
return advancedFilters[operator];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function isErasable(value, param) {
|
|
388
|
+
return (
|
|
389
|
+
value === '' ||
|
|
390
|
+
value === undefined ||
|
|
391
|
+
(value === null && !getAdvancedFilter(param)?.isNullable)
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function removeIgnoreUrlParams(params) {
|
|
396
|
+
const currentParams = { ...params };
|
|
397
|
+
if (store.ignoreUrlParams.length && Object.keys(currentParams).length)
|
|
398
|
+
for (const param of store.ignoreUrlParams) delete currentParams[param];
|
|
399
|
+
|
|
400
|
+
return currentParams;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function getIgnoreParams() {
|
|
404
|
+
const currentParams = {};
|
|
405
|
+
if (store.ignoreUrlParams.length && Object.keys(store.userParams).length)
|
|
406
|
+
for (const param of store.ignoreUrlParams)
|
|
407
|
+
currentParams[param] = store.userParams[param];
|
|
408
|
+
|
|
409
|
+
return currentParams;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
|
413
|
+
const isLoading = ref(store.isLoading || false);
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
fetch,
|
|
417
|
+
applyFilter,
|
|
418
|
+
addFilter,
|
|
419
|
+
getCurrentFilter,
|
|
420
|
+
setCurrentFilter,
|
|
421
|
+
addFilterWhere,
|
|
422
|
+
addOrder,
|
|
423
|
+
applyOrder,
|
|
424
|
+
applyOrders,
|
|
425
|
+
deleteOrder,
|
|
426
|
+
refresh,
|
|
427
|
+
destroy,
|
|
428
|
+
loadMore,
|
|
429
|
+
store,
|
|
430
|
+
totalRows,
|
|
431
|
+
updateStateParams,
|
|
432
|
+
isLoading,
|
|
433
|
+
deleteOption,
|
|
434
|
+
reset,
|
|
435
|
+
resetPagination,
|
|
436
|
+
setOptions,
|
|
437
|
+
sanitizerParams,
|
|
438
|
+
processData,
|
|
439
|
+
dataKey: key,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function getOrderIndex(order, field) {
|
|
444
|
+
return order.findIndex((o) => o.split(' ')[0] === field);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function toArray(str = []) {
|
|
448
|
+
if (!str) return [];
|
|
449
|
+
if (Array.isArray(str)) return str;
|
|
450
|
+
if (typeof str === 'string') return str.split(',').map((item) => item.trim());
|
|
451
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function focusFirstInput(form) {
|
|
2
|
+
if (!form || form.hasAttribute('data-no-focus')) return;
|
|
3
|
+
const textareas = form.querySelectorAll(
|
|
4
|
+
'textarea:not([disabled]), [contenteditable]:not([disabled])',
|
|
5
|
+
);
|
|
6
|
+
const inputs = form.querySelectorAll('input:not([disabled]):not([type="checkbox"])');
|
|
7
|
+
const [firstInput] = inputs;
|
|
8
|
+
const textareaLen = textareas.length;
|
|
9
|
+
const el = textareaLen ? textareas[textareaLen - 1] : firstInput;
|
|
10
|
+
|
|
11
|
+
if (el !== document.activeElement) el?.focus();
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Notify } from 'quasar';
|
|
2
|
+
import { useI18n } from 'vue-i18n';
|
|
3
|
+
|
|
4
|
+
export function useNotify() {
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
const notify = (message, type = 'positive', icon, opts = {}) => {
|
|
7
|
+
const defaultIcons = {
|
|
8
|
+
warning: 'warning',
|
|
9
|
+
negative: 'error',
|
|
10
|
+
positive: 'check',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
Notify.create({
|
|
14
|
+
message: t(message, opts.translationParams || {}),
|
|
15
|
+
type: type,
|
|
16
|
+
icon: icon ? icon : defaultIcons[type],
|
|
17
|
+
...opts,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
notify,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function getObjectByKeys(source, keys) {
|
|
2
|
+
if (!source || typeof source !== 'object') return {};
|
|
3
|
+
if (!Array.isArray(keys) || !keys.length) return {};
|
|
4
|
+
|
|
5
|
+
const selected = {};
|
|
6
|
+
for (const key of keys) {
|
|
7
|
+
if (Object.hasOwn(source, key)) selected[key] = source[key];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return selected;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getObjectDifferences(source, target) {
|
|
14
|
+
const left = isComparableObject(source) ? source : {};
|
|
15
|
+
const right = isComparableObject(target) ? target : {};
|
|
16
|
+
const differences = {};
|
|
17
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
18
|
+
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
const expected = left[key];
|
|
21
|
+
const actual = right[key];
|
|
22
|
+
|
|
23
|
+
if (isComparableObject(expected) && isComparableObject(actual)) {
|
|
24
|
+
const nestedDiff = getObjectDifferences(expected, actual);
|
|
25
|
+
if (Object.keys(nestedDiff).length) differences[key] = nestedDiff;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!isSameValue(expected, actual)) {
|
|
30
|
+
differences[key] = { expected, actual };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return differences;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isComparableObject(value) {
|
|
38
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isSameValue(left, right) {
|
|
42
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
43
|
+
if (left.length !== right.length) return false;
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < left.length; i++) {
|
|
46
|
+
if (!isSameValue(left[i], right[i])) return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isComparableObject(left) && isComparableObject(right)) {
|
|
53
|
+
const nestedDiff = getObjectDifferences(left, right);
|
|
54
|
+
return !Object.keys(nestedDiff).length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Object.is(left, right);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useObjectComparison() {
|
|
61
|
+
return {
|
|
62
|
+
getObjectByKeys,
|
|
63
|
+
getObjectDifferences,
|
|
64
|
+
isComparableObject,
|
|
65
|
+
isSameValue,
|
|
66
|
+
};
|
|
67
|
+
}
|