quasar-ui-danx 0.3.22 → 0.4.1
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/.eslintrc.cjs +32 -30
- package/danx-local.sh +1 -1
- package/dist/danx.es.js +7490 -7519
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +5 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +1 -1
- package/src/components/ActionTable/ActionTable.vue +64 -45
- package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
- package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
- package/src/components/ActionTable/Columns/index.ts +2 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -34
- package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
- package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
- package/src/components/ActionTable/Form/form.d.ts +31 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
- package/src/components/ActionTable/TableSummaryRow.vue +4 -4
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
- package/src/components/ActionTable/Toolbars/index.ts +1 -0
- package/src/components/ActionTable/index.ts +1 -2
- package/src/components/ActionTable/listControls.ts +512 -385
- package/src/components/ActionTable/listHelpers.ts +46 -44
- package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
- package/src/components/Utility/Buttons/ExportButton.vue +1 -1
- package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
- package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
- package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
- package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
- package/src/helpers/actions.ts +197 -187
- package/src/styles/general.scss +12 -11
- package/src/styles/quasar-reset.scss +59 -11
- package/src/styles/themes/danx/action-table.scss +19 -0
- package/src/styles/themes/danx/buttons.scss +13 -0
- package/src/styles/themes/danx/forms.scss +5 -0
- package/src/styles/themes/danx/index.scss +3 -0
- package/src/styles/themes/danx/panels.scss +19 -0
- package/src/styles/themes/danx/sidebar.scss +3 -0
- package/src/styles/themes/danx/toolbar.scss +3 -0
- package/types/index.d.ts +1 -0
- package/src/styles/actions.scss +0 -10
@@ -1,393 +1,520 @@
|
|
1
|
-
import { computed, Ref, ref, ShallowRef, shallowRef, watch } from "vue";
|
2
|
-
import { ActionTargetItem, getItem, setItem, waitForRef } from "../../helpers";
|
1
|
+
import { computed, ComputedRef, Ref, ref, ShallowRef, shallowRef, VNode, watch } from "vue";
|
2
|
+
import { ActionTargetItem, AnyObject, getItem, setItem, waitForRef } from "../../helpers";
|
3
3
|
import { getFilterFromUrl } from "./listHelpers";
|
4
4
|
|
5
|
-
export interface
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
export interface ActionController {
|
6
|
+
name: string;
|
7
|
+
label: string;
|
8
|
+
pagedItems: Ref<PagedItems | null>;
|
9
|
+
activeFilter: Ref<ListControlsFilter>;
|
10
|
+
globalFilter: Ref<ListControlsFilter>;
|
11
|
+
filterActiveCount: ComputedRef<number>;
|
12
|
+
showFilters: Ref<boolean>;
|
13
|
+
summary: ShallowRef<object | null>;
|
14
|
+
filterFieldOptions: Ref<AnyObject>;
|
15
|
+
selectedRows: ShallowRef<ActionTargetItem[]>;
|
16
|
+
isLoadingList: Ref<boolean>;
|
17
|
+
isLoadingFilters: Ref<boolean>;
|
18
|
+
isLoadingSummary: Ref<boolean>;
|
19
|
+
pager: ComputedRef<{
|
20
|
+
perPage: number;
|
21
|
+
page: number;
|
22
|
+
filter: ListControlsFilter;
|
23
|
+
sort: object[] | undefined;
|
24
|
+
}>;
|
25
|
+
pagination: ShallowRef<ListControlsPagination>;
|
26
|
+
activeItem: ShallowRef<ActionTargetItem | null>;
|
27
|
+
activePanel: ShallowRef<string | null>;
|
28
|
+
|
29
|
+
// Actions
|
30
|
+
initialize: () => void;
|
31
|
+
loadSummary: () => Promise<void>;
|
32
|
+
resetPaging: () => void;
|
33
|
+
setPagination: (updated: ListControlsPagination) => void;
|
34
|
+
setSelectedRows: (selection: ActionTargetItem[]) => void;
|
35
|
+
loadList: () => Promise<void>;
|
36
|
+
loadMore: (index: number, perPage?: number) => Promise<boolean>;
|
37
|
+
refreshAll: () => Promise<void[]>;
|
38
|
+
exportList: () => Promise<void>;
|
39
|
+
setActiveItem: (item: ActionTargetItem | null) => void;
|
40
|
+
getNextItem: (offset: number) => Promise<void>;
|
41
|
+
activatePanel: (item: ActionTargetItem | null, panel: string | null) => void;
|
42
|
+
setActiveFilter: (filter: ListControlsFilter) => void;
|
43
|
+
applyFilterFromUrl: (url: string, filterFields: Ref<FilterGroup[]> | null) => void;
|
44
|
+
setItemInList: (updatedItem: ActionTargetItem) => void;
|
45
|
+
}
|
46
|
+
|
47
|
+
export interface LabelValueItem {
|
48
|
+
label: string;
|
49
|
+
value: string | number | boolean;
|
50
|
+
}
|
51
|
+
|
52
|
+
export interface FilterField {
|
53
|
+
name: string;
|
54
|
+
label: string;
|
55
|
+
type: string;
|
56
|
+
options?: string[] | number[] | LabelValueItem[];
|
57
|
+
inline?: boolean;
|
58
|
+
}
|
59
|
+
|
60
|
+
export interface FilterGroup {
|
61
|
+
name?: string;
|
62
|
+
flat?: boolean;
|
63
|
+
fields: FilterField[];
|
64
|
+
}
|
65
|
+
|
66
|
+
export interface ActionPanel {
|
67
|
+
name: string;
|
68
|
+
label: string;
|
69
|
+
category?: string;
|
70
|
+
enabled: boolean | (() => boolean);
|
71
|
+
tabVnode: () => VNode;
|
72
|
+
vnode: () => VNode;
|
73
|
+
}
|
74
|
+
|
75
|
+
export interface ListControlsFilter {
|
76
|
+
[key: string]: object | object[] | null | undefined | string | number | boolean;
|
77
|
+
}
|
78
|
+
|
79
|
+
export interface ListControlsRoutes {
|
80
|
+
list: (pager: object) => Promise<ActionTargetItem[]>;
|
81
|
+
details?: (item: object) => Promise<ActionTargetItem> | null;
|
82
|
+
summary?: (filter: object | null) => Promise<object> | null;
|
83
|
+
filterFieldOptions?: (filter: object | null) => Promise<object> | null;
|
84
|
+
more?: (pager: object) => Promise<ActionTargetItem[]> | null;
|
85
|
+
export: (filter: object) => Promise<void>;
|
86
|
+
}
|
87
|
+
|
88
|
+
export interface ListControlsOptions {
|
89
|
+
label?: string,
|
90
|
+
routes: ListControlsRoutes;
|
91
|
+
urlPattern?: RegExp | null;
|
92
|
+
filterDefaults?: Record<string, object>;
|
93
|
+
refreshFilters?: boolean;
|
94
|
+
}
|
95
|
+
|
96
|
+
export interface ListControlsPagination {
|
97
|
+
__sort: object[] | null;
|
98
|
+
sortBy: string | null;
|
99
|
+
descending: boolean;
|
100
|
+
page: number;
|
101
|
+
rowsNumber: number;
|
102
|
+
rowsPerPage: number;
|
14
103
|
}
|
15
104
|
|
16
105
|
export interface PagedItems {
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
106
|
+
data: ActionTargetItem[] | undefined;
|
107
|
+
meta: {
|
108
|
+
total: number;
|
109
|
+
last_page?: number;
|
110
|
+
} | undefined;
|
22
111
|
}
|
23
112
|
|
24
|
-
export function useListControls(name: string, {
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
113
|
+
export function useListControls(name: string, options: ListControlsOptions) {
|
114
|
+
let isInitialized = false;
|
115
|
+
const PAGE_SETTINGS_KEY = `dx-${name}-pager`;
|
116
|
+
const pagedItems: Ref<PagedItems | null> = shallowRef(null);
|
117
|
+
const activeFilter: Ref<ListControlsFilter> = ref({});
|
118
|
+
const globalFilter = ref({});
|
119
|
+
const showFilters = ref(false);
|
120
|
+
const selectedRows: ShallowRef<ActionTargetItem[]> = shallowRef([]);
|
121
|
+
const isLoadingList = ref(false);
|
122
|
+
const isLoadingSummary = ref(false);
|
123
|
+
const summary: ShallowRef<object | null> = shallowRef(null);
|
124
|
+
|
125
|
+
// The active ad for viewing / editing
|
126
|
+
const activeItem: ShallowRef<ActionTargetItem | null> = shallowRef(null);
|
127
|
+
// Controls the active panel (ie: tab) if rendering a panels drawer or similar
|
128
|
+
const activePanel: ShallowRef<string> = shallowRef("");
|
129
|
+
|
130
|
+
// Filter fields are the field values available for the currently applied filter on Creative Groups
|
131
|
+
// (ie: all states available under the current filter)
|
132
|
+
const filterFieldOptions: Ref<AnyObject> = ref({});
|
133
|
+
const isLoadingFilters = ref(false);
|
134
|
+
|
135
|
+
const filterActiveCount = computed(() => Object.keys(activeFilter.value).filter(key => activeFilter.value[key] !== undefined).length);
|
136
|
+
|
137
|
+
const PAGING_DEFAULT = {
|
138
|
+
__sort: null,
|
139
|
+
sortBy: null,
|
140
|
+
descending: false,
|
141
|
+
page: 0,
|
142
|
+
rowsNumber: 0,
|
143
|
+
rowsPerPage: 50
|
144
|
+
};
|
145
|
+
const pagination = shallowRef(PAGING_DEFAULT);
|
146
|
+
|
147
|
+
const pager = computed(() => ({
|
148
|
+
perPage: pagination.value.rowsPerPage,
|
149
|
+
page: pagination.value.page,
|
150
|
+
filter: { ...activeFilter.value, ...globalFilter.value },
|
151
|
+
sort: pagination.value.__sort || undefined
|
152
|
+
}));
|
153
|
+
|
154
|
+
// When any part of the filter changes, get the new list of creatives
|
155
|
+
watch(pager, () => {
|
156
|
+
saveSettings();
|
157
|
+
loadList();
|
158
|
+
});
|
159
|
+
watch(activeFilter, () => {
|
160
|
+
saveSettings();
|
161
|
+
loadSummary();
|
162
|
+
});
|
163
|
+
watch(selectedRows, loadSummary);
|
164
|
+
|
165
|
+
if (options.refreshFilters) {
|
166
|
+
watch(activeFilter, loadFilterFieldOptions);
|
167
|
+
}
|
168
|
+
|
169
|
+
async function loadList() {
|
170
|
+
if (!isInitialized) return;
|
171
|
+
isLoadingList.value = true;
|
172
|
+
setPagedItems(await options.routes.list(pager.value));
|
173
|
+
isLoadingList.value = false;
|
174
|
+
}
|
175
|
+
|
176
|
+
async function loadSummary() {
|
177
|
+
if (!options.routes.summary || !isInitialized) return;
|
178
|
+
|
179
|
+
isLoadingSummary.value = true;
|
180
|
+
const summaryFilter: ListControlsFilter = { id: null, ...activeFilter.value, ...globalFilter.value };
|
181
|
+
if (selectedRows.value.length) {
|
182
|
+
summaryFilter.id = selectedRows.value.map((row) => row.id);
|
183
|
+
}
|
184
|
+
summary.value = await options.routes.summary(summaryFilter);
|
185
|
+
isLoadingSummary.value = false;
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Gets the field options for the given field name.
|
190
|
+
*/
|
191
|
+
function getFieldOptions(field: string) {
|
192
|
+
return filterFieldOptions.value[field] || [];
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* Loads the filter field options for the current filter.
|
197
|
+
*
|
198
|
+
* @returns {Promise<void>}
|
199
|
+
*/
|
200
|
+
async function loadFilterFieldOptions() {
|
201
|
+
if (!options.routes.filterFieldOptions || !isInitialized) return;
|
202
|
+
isLoadingFilters.value = true;
|
203
|
+
filterFieldOptions.value = await options.routes.filterFieldOptions(activeFilter.value) || {};
|
204
|
+
isLoadingFilters.value = false;
|
205
|
+
}
|
206
|
+
|
207
|
+
/**
|
208
|
+
* Watches for a filter URL parameter and applies the filter if it is set.
|
209
|
+
*/
|
210
|
+
function applyFilterFromUrl(url: string, filterFields: Ref<FilterGroup[]> | null = null) {
|
211
|
+
if (options.urlPattern && url.match(options.urlPattern)) {
|
212
|
+
// A flat list of valid filterable field names
|
213
|
+
const validFilterKeys = filterFields?.value?.map(group => group.fields.map(field => field.name)).flat();
|
214
|
+
|
215
|
+
const urlFilter = getFilterFromUrl(url, validFilterKeys);
|
216
|
+
|
217
|
+
if (Object.keys(urlFilter).length > 0) {
|
218
|
+
activeFilter.value = urlFilter;
|
219
|
+
|
220
|
+
// Override whatever is in local storage with this new filter
|
221
|
+
updateSettings("filter", activeFilter.value);
|
222
|
+
}
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
// Set the reactive pager to map from the Laravel pagination to Quasar pagination
|
227
|
+
// and automatically update the list of ads
|
228
|
+
function setPagedItems(items: ActionTargetItem[] | PagedItems) {
|
229
|
+
let data: ActionTargetItem[] = [], meta;
|
230
|
+
|
231
|
+
if (Array.isArray(items)) {
|
232
|
+
data = items;
|
233
|
+
meta = { total: items.length };
|
234
|
+
|
235
|
+
} else if (items.data) {
|
236
|
+
data = items.data;
|
237
|
+
meta = items.meta;
|
238
|
+
}
|
239
|
+
|
240
|
+
// Update the Quasar pagination rows number if it is different from the total
|
241
|
+
if (meta && meta.total !== pagination.value.rowsNumber) {
|
242
|
+
pagination.value.rowsNumber = meta.total;
|
243
|
+
}
|
244
|
+
|
245
|
+
// Add a reactive isSaving property to each item (for performance reasons in checking saving state)
|
246
|
+
data = data.map((item) => {
|
247
|
+
// We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
|
248
|
+
// components, and therefore reactivity is not responding to the new isSaving state
|
249
|
+
const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
|
250
|
+
return { ...item, isSaving: oldItem?.isSaving || ref(false) };
|
251
|
+
});
|
252
|
+
|
253
|
+
pagedItems.value = { data, meta };
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Resets the filter and pagination settings to their defaults.
|
258
|
+
*/
|
259
|
+
function resetPaging() {
|
260
|
+
pagination.value = PAGING_DEFAULT;
|
261
|
+
}
|
262
|
+
|
263
|
+
/**
|
264
|
+
* Sets the pagination settings to the given values.
|
265
|
+
*/
|
266
|
+
function setPagination(updated: ListControlsPagination) {
|
267
|
+
// @ts-expect-error Seems like a bug in the typescript linting?
|
268
|
+
pagination.value = updated;
|
269
|
+
}
|
270
|
+
|
271
|
+
/**
|
272
|
+
* Sets the selected rows in the list for batch actions or other operations.
|
273
|
+
*/
|
274
|
+
function setSelectedRows(selection: ActionTargetItem[]) {
|
275
|
+
selectedRows.value = selection;
|
276
|
+
}
|
277
|
+
|
278
|
+
/**
|
279
|
+
* Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
|
280
|
+
*
|
281
|
+
* @param updatedItem
|
282
|
+
*/
|
283
|
+
function setItemInList(updatedItem: ActionTargetItem) {
|
284
|
+
if (updatedItem && updatedItem.id) {
|
285
|
+
const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
|
286
|
+
setPagedItems({
|
287
|
+
data,
|
288
|
+
meta: { total: pagedItems.value?.meta?.total || 0 }
|
289
|
+
});
|
290
|
+
|
291
|
+
// Update the active item as well if it is set
|
292
|
+
if (activeItem.value?.id === updatedItem.id) {
|
293
|
+
activeItem.value = { ...activeItem.value, ...updatedItem };
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
298
|
+
/**
|
299
|
+
* Loads more items into the list.
|
300
|
+
*/
|
301
|
+
async function loadMore(index: number, perPage = undefined) {
|
302
|
+
if (!options.routes.more) return;
|
303
|
+
|
304
|
+
const newItems = await options.routes.more({
|
305
|
+
page: index + 1,
|
306
|
+
perPage,
|
307
|
+
filter: { ...activeFilter.value, ...globalFilter.value }
|
308
|
+
});
|
309
|
+
|
310
|
+
if (newItems && newItems.length > 0) {
|
311
|
+
setPagedItems({
|
312
|
+
data: [...(pagedItems.value?.data || []), ...newItems],
|
313
|
+
meta: { total: pagedItems.value?.meta?.total || 0 }
|
314
|
+
});
|
315
|
+
return true;
|
316
|
+
}
|
317
|
+
|
318
|
+
return false;
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* Refreshes the list, summary, and filter field options.
|
323
|
+
*/
|
324
|
+
async function refreshAll() {
|
325
|
+
return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions(), getActiveItemDetails()]);
|
326
|
+
}
|
327
|
+
|
328
|
+
/**
|
329
|
+
* Updates the settings in local storage
|
330
|
+
*/
|
331
|
+
function updateSettings(key: string, value: any) {
|
332
|
+
const settings = getItem(PAGE_SETTINGS_KEY) || {};
|
333
|
+
settings[key] = value;
|
334
|
+
setItem(PAGE_SETTINGS_KEY, settings);
|
335
|
+
}
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Loads the filter and pagination settings from local storage.
|
339
|
+
*/
|
340
|
+
function loadSettings() {
|
341
|
+
// Only load settings when the class is fully initialized
|
342
|
+
if (!isInitialized) return;
|
343
|
+
|
344
|
+
const settings = getItem(PAGE_SETTINGS_KEY);
|
345
|
+
|
346
|
+
// Load the filter settings from local storage
|
347
|
+
if (settings) {
|
348
|
+
activeFilter.value = { ...settings.filter, ...activeFilter.value };
|
349
|
+
pagination.value = settings.pagination;
|
350
|
+
} else {
|
351
|
+
// If no local storage settings, apply the default filters
|
352
|
+
activeFilter.value = { ...options.filterDefaults, ...activeFilter.value };
|
353
|
+
}
|
354
|
+
|
355
|
+
setTimeout(() => {
|
356
|
+
if (!isLoadingList.value) {
|
357
|
+
loadList();
|
358
|
+
}
|
359
|
+
|
360
|
+
if (!isLoadingSummary.value) {
|
361
|
+
loadSummary();
|
362
|
+
}
|
363
|
+
|
364
|
+
if (!isLoadingFilters.value) {
|
365
|
+
loadFilterFieldOptions();
|
366
|
+
}
|
367
|
+
}, 1);
|
368
|
+
}
|
369
|
+
|
370
|
+
/**
|
371
|
+
* Saves the current filter and pagination settings to local storage.
|
372
|
+
*/
|
373
|
+
async function saveSettings() {
|
374
|
+
const settings = {
|
375
|
+
filter: activeFilter.value,
|
376
|
+
pagination: { ...pagination.value, page: 1 }
|
377
|
+
};
|
378
|
+
// save in local storage
|
379
|
+
setItem(PAGE_SETTINGS_KEY, settings);
|
380
|
+
}
|
381
|
+
|
382
|
+
/**
|
383
|
+
* Gets the additional details for the currently active item.
|
384
|
+
* (ie: data that is not normally loaded in the list because it is not needed for the list view)
|
385
|
+
* @returns {Promise<void>}
|
386
|
+
*/
|
387
|
+
async function getActiveItemDetails() {
|
388
|
+
if (!activeItem.value || !options.routes.details) return;
|
389
|
+
|
390
|
+
const result = await options.routes.details(activeItem.value);
|
391
|
+
|
392
|
+
// Only set the ad details if we are the response for the currently loaded item
|
393
|
+
// NOTE: race conditions might allow the finished loading item to be different to the currently
|
394
|
+
// requested item
|
395
|
+
if (result?.id === activeItem.value?.id) {
|
396
|
+
const loadedItem = pagedItems.value?.data?.find((i: ActionTargetItem) => i.id === result.id);
|
397
|
+
activeItem.value = { ...result, isSaving: loadedItem?.isSaving || ref(false) };
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
// Whenever the active item changes, fill the additional item details
|
402
|
+
// (ie: tasks, verifications, creatives, etc.)
|
403
|
+
if (options.routes.details) {
|
404
|
+
watch(() => activeItem.value, async (newItem, oldItem) => {
|
405
|
+
if (newItem && oldItem?.id !== newItem.id) {
|
406
|
+
await getActiveItemDetails();
|
407
|
+
}
|
408
|
+
});
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Opens the item's form with the given item and tab
|
413
|
+
*/
|
414
|
+
function activatePanel(item: ActionTargetItem | null, panel: string) {
|
415
|
+
activeItem.value = item;
|
416
|
+
activePanel.value = panel;
|
417
|
+
}
|
418
|
+
|
419
|
+
/**
|
420
|
+
* Sets the currently active item in the list.
|
421
|
+
*/
|
422
|
+
function setActiveItem(item: ActionTargetItem | null) {
|
423
|
+
activeItem.value = item;
|
424
|
+
}
|
425
|
+
|
426
|
+
/**
|
427
|
+
* Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
|
428
|
+
* selected item. If the next item is on a previous or next page, it will load the page first then select the item
|
429
|
+
*/
|
430
|
+
async function getNextItem(offset: number) {
|
431
|
+
if (!pagedItems.value?.data) return;
|
432
|
+
|
433
|
+
const index = pagedItems.value.data.findIndex((i: ActionTargetItem) => i.id === activeItem.value?.id);
|
434
|
+
if (index === undefined || index === null) return;
|
435
|
+
|
436
|
+
let nextIndex = index + offset;
|
437
|
+
|
438
|
+
// Load the previous page if the offset is before index 0
|
439
|
+
if (nextIndex < 0) {
|
440
|
+
if (pagination.value.page > 1) {
|
441
|
+
pagination.value = { ...pagination.value, page: pagination.value.page - 1 };
|
442
|
+
await waitForRef(isLoadingList, false);
|
443
|
+
nextIndex = pagedItems.value.data.length - 1;
|
444
|
+
} else {
|
445
|
+
// There are no more previous pages
|
446
|
+
return;
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
// Load the next page if the offset is past the last index
|
451
|
+
if (nextIndex >= pagedItems.value.data.length) {
|
452
|
+
if (pagination.value.page < (pagedItems.value?.meta?.last_page || 1)) {
|
453
|
+
pagination.value = { ...pagination.value, page: pagination.value.page + 1 };
|
454
|
+
await waitForRef(isLoadingList, false);
|
455
|
+
nextIndex = 0;
|
456
|
+
} else {
|
457
|
+
// There are no more next pages
|
458
|
+
return;
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
activeItem.value = pagedItems.value?.data[nextIndex];
|
463
|
+
}
|
464
|
+
|
465
|
+
/**
|
466
|
+
* Sets the active filter to the given filter.
|
467
|
+
*/
|
468
|
+
function setActiveFilter(filter: ListControlsFilter) {
|
469
|
+
activeFilter.value = filter;
|
470
|
+
}
|
471
|
+
|
472
|
+
async function exportList(filter: object) {
|
473
|
+
return options.routes.export(filter);
|
474
|
+
}
|
475
|
+
|
476
|
+
// Initialize the list actions and load settings, lists, summaries, filter fields, etc.
|
477
|
+
function initialize() {
|
478
|
+
isInitialized = true;
|
479
|
+
loadSettings();
|
480
|
+
}
|
481
|
+
|
482
|
+
return {
|
483
|
+
// State
|
484
|
+
name,
|
485
|
+
label: options.label || name,
|
486
|
+
pagedItems,
|
487
|
+
activeFilter,
|
488
|
+
globalFilter,
|
489
|
+
filterActiveCount,
|
490
|
+
showFilters,
|
491
|
+
summary,
|
492
|
+
filterFieldOptions,
|
493
|
+
selectedRows,
|
494
|
+
isLoadingList,
|
495
|
+
isLoadingFilters,
|
496
|
+
isLoadingSummary,
|
497
|
+
pager,
|
498
|
+
pagination,
|
499
|
+
activeItem,
|
500
|
+
activePanel,
|
501
|
+
|
502
|
+
// Actions
|
503
|
+
initialize,
|
504
|
+
loadSummary,
|
505
|
+
resetPaging,
|
506
|
+
setPagination,
|
507
|
+
setSelectedRows,
|
508
|
+
loadList,
|
509
|
+
loadMore,
|
510
|
+
refreshAll,
|
511
|
+
exportList,
|
512
|
+
setActiveItem,
|
513
|
+
getNextItem,
|
514
|
+
activatePanel,
|
515
|
+
setActiveFilter,
|
516
|
+
applyFilterFromUrl,
|
517
|
+
setItemInList,
|
518
|
+
getFieldOptions
|
519
|
+
};
|
393
520
|
}
|