quasar-ui-danx 0.3.13 → 0.3.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,387 +2,392 @@ import { computed, Ref, ref, ShallowRef, shallowRef, watch } from "vue";
2
2
  import { ActionTargetItem, getItem, setItem, waitForRef } from "../../helpers";
3
3
  import { getFilterFromUrl } from "./listHelpers";
4
4
 
5
- export interface ListControlsRoutes {
6
- list: (pager: object) => Promise<ActionTargetItem[]>;
7
- details?: (item: object) => Promise<ActionTargetItem> | null;
8
- summary?: (filter: object | null) => Promise<object> | null;
9
- filterFieldOptions?: (filter: object | null) => Promise<object> | null;
10
- more?: (pager: object) => Promise<ActionTargetItem[]> | null;
11
- }
12
-
13
- export interface ListControlsOptions {
14
- routes: ListControlsRoutes;
15
- urlPattern?: RegExp | null;
16
- filterDefaults?: Record<string, any>;
17
- refreshFilters?: boolean;
5
+ export interface ListActionsOptions {
6
+ listRoute: Function;
7
+ summaryRoute?: Function | null;
8
+ filterFieldOptionsRoute?: Function | null;
9
+ moreRoute?: Function | null;
10
+ itemDetailsRoute?: Function | null;
11
+ urlPattern?: RegExp | null;
12
+ filterDefaults?: Record<string, any>;
13
+ refreshFilters?: boolean;
18
14
  }
19
15
 
20
16
  export interface PagedItems {
21
- data: ActionTargetItem[] | undefined;
22
- meta: {
23
- total: number;
24
- last_page?: number;
25
- } | undefined;
17
+ data: any[] | undefined;
18
+ meta: {
19
+ total: number;
20
+ last_page?: number;
21
+ } | undefined;
26
22
  }
27
23
 
28
- export function useListControls(name: string, options: ListControlsOptions) {
29
- let isInitialized = false;
30
- const PAGE_SETTINGS_KEY = `${name}-pagination-settings`;
31
- const pagedItems: Ref<PagedItems | null> = shallowRef(null);
32
- const filter: Ref<object | any> = ref({});
33
- const globalFilter = ref({});
34
- const showFilters = ref(false);
35
- const selectedRows = shallowRef([]);
36
- const isLoadingList = ref(false);
37
- const isLoadingSummary = ref(false);
38
- const summary: ShallowRef<object | null> = shallowRef(null);
39
-
40
- // The active ad for viewing / editing
41
- const activeItem: ShallowRef<ActionTargetItem | null> = shallowRef(null);
42
- // Controls the active panel (ie: tab) if rendering a panels drawer or similar
43
- const activePanel = shallowRef(null);
44
-
45
- // Filter fields are the field values available for the currently applied filter on Creative Groups
46
- // (ie: all states available under the current filter)
47
- const filterFieldOptions: Ref<object> = ref({});
48
- const isLoadingFilters = ref(false);
49
-
50
- const filterActiveCount = computed(() => Object.keys(filter.value).filter(key => filter.value[key] !== undefined).length);
51
-
52
- const PAGING_DEFAULT = {
53
- __sort: null,
54
- sortBy: null,
55
- descending: false,
56
- page: 1,
57
- rowsNumber: 0,
58
- rowsPerPage: 50
59
- };
60
- const quasarPagination = ref(PAGING_DEFAULT);
61
-
62
- const pager = computed(() => ({
63
- perPage: quasarPagination.value.rowsPerPage,
64
- page: quasarPagination.value.page,
65
- filter: { ...filter.value, ...globalFilter.value },
66
- sort: quasarPagination.value.__sort || undefined
67
- }));
68
-
69
- // When any part of the filter changes, get the new list of creatives
70
- watch(pager, () => {
71
- saveSettings();
72
- loadList();
73
- });
74
- watch(filter, () => {
75
- saveSettings();
76
- loadSummary();
77
- });
78
- watch(selectedRows, loadSummary);
79
-
80
- if (options.refreshFilters) {
81
- watch(filter, loadFilterFieldOptions);
82
- }
83
-
84
- async function loadList() {
85
- if (!isInitialized) return;
86
- isLoadingList.value = true;
87
- setPagedItems(await options.routes.list(pager.value));
88
- isLoadingList.value = false;
89
- }
90
-
91
- async function loadSummary() {
92
- if (!options.routes.summary || !isInitialized) return;
93
-
94
- isLoadingSummary.value = true;
95
- const summaryFilter = { id: null, ...filter.value, ...globalFilter.value };
96
- if (selectedRows.value.length) {
97
- summaryFilter.id = selectedRows.value.map((row: { id: string }) => row.id);
98
- }
99
- summary.value = await options.routes.summary(summaryFilter);
100
- isLoadingSummary.value = false;
101
- }
102
-
103
- /**
104
- * Loads the filter field options for the current filter.
105
- *
106
- * @returns {Promise<void>}
107
- */
108
- async function loadFilterFieldOptions() {
109
- if (!options.routes.filterFieldOptions || !isInitialized) return;
110
- isLoadingFilters.value = true;
111
- filterFieldOptions.value = await options.routes.filterFieldOptions(filter.value) || {};
112
- isLoadingFilters.value = false;
113
- }
114
-
115
- /**
116
- * Watches for a filter URL parameter and applies the filter if it is set.
117
- */
118
- function applyFilterFromUrl(url: string, filterFields: Ref<object[]> | null = null) {
119
- if (options.urlPattern && url.match(options.urlPattern)) {
120
- // A flat list of valid filterable field names
121
- const validFilterKeys = filterFields?.value?.map(group => group.fields.map(field => field.name)).flat();
122
-
123
- const urlFilter = getFilterFromUrl(url, validFilterKeys);
124
-
125
- if (Object.keys(urlFilter).length > 0) {
126
- filter.value = urlFilter;
127
-
128
- // Override whatever is in local storage with this new filter
129
- updateSettings("filter", filter.value);
130
- }
131
- }
132
- }
133
-
134
- // Set the reactive pager to map from the Laravel pagination to Quasar pagination
135
- // and automatically update the list of ads
136
- function setPagedItems(items: object[] | PagedItems) {
137
- let data, meta;
138
-
139
- if (Array.isArray(items)) {
140
- data = items;
141
- meta = { total: items.length };
142
-
143
- } else {
144
- data = items.data;
145
- meta = items.meta;
146
- }
147
-
148
- // Update the Quasar pagination rows number if it is different from the total
149
- if (meta && meta.total !== quasarPagination.value.rowsNumber) {
150
- quasarPagination.value.rowsNumber = meta.total;
151
- }
152
-
153
- // Add a reactive isSaving property to each item (for performance reasons in checking saving state)
154
- data = data.map((item: object) => {
155
- // We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
156
- // components, and therefore reactivity is not responding to the new isSaving state
157
- const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
158
- return { ...item, isSaving: oldItem?.isSaving || ref(false) };
159
- });
160
-
161
- pagedItems.value = { data, meta };
162
- }
163
-
164
- /**
165
- * Resets the filter and pagination settings to their defaults.
166
- */
167
- function resetPaging() {
168
- quasarPagination.value = PAGING_DEFAULT;
169
- }
170
-
171
- /**
172
- * Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
173
- *
174
- * @param updatedItem
175
- */
176
- function setItemInList(updatedItem: ActionTargetItem) {
177
- const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
178
- setPagedItems({
179
- data,
180
- meta: { total: pagedItems.value?.meta?.total || 0 }
181
- });
182
-
183
- // Update the active item as well if it is set
184
- if (activeItem.value?.id === updatedItem.id) {
185
- activeItem.value = { ...activeItem.value, ...updatedItem };
186
- }
187
- }
188
-
189
- /**
190
- * Loads more items into the list.
191
- */
192
- async function loadMore(index: number, perPage = undefined) {
193
- if (!options.routes.more) return;
194
-
195
- const newItems = await options.routes.more({
196
- page: index + 1,
197
- perPage,
198
- filter: { ...filter.value, ...globalFilter.value }
199
- });
200
-
201
- if (newItems && newItems.length > 0) {
202
- setPagedItems({
203
- data: [...(pagedItems.value?.data || []), ...newItems],
204
- meta: { total: pagedItems.value?.meta?.total || 0 }
205
- });
206
- return true;
207
- }
208
-
209
- return false;
210
- }
211
-
212
- /**
213
- * Refreshes the list, summary, and filter field options.
214
- */
215
- async function refreshAll() {
216
- return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions(), getActiveItemDetails()]);
217
- }
218
-
219
- /**
220
- * Updates the settings in local storage
221
- */
222
- function updateSettings(key: string, value: any) {
223
- const settings = getItem(PAGE_SETTINGS_KEY) || {};
224
- settings[key] = value;
225
- setItem(PAGE_SETTINGS_KEY, settings);
226
- }
227
-
228
- /**
229
- * Loads the filter and pagination settings from local storage.
230
- */
231
- function loadSettings() {
232
- // Only load settings when the class is fully initialized
233
- if (!isInitialized) return;
234
-
235
- const settings = getItem(PAGE_SETTINGS_KEY);
236
-
237
- // Load the filter settings from local storage
238
- if (settings) {
239
- filter.value = { ...settings.filter, ...filter.value };
240
- quasarPagination.value = settings.quasarPagination;
241
- } else {
242
- // If no local storage settings, apply the default filters
243
- filter.value = { ...options.filterDefaults, ...filter.value };
244
- }
245
-
246
- setTimeout(() => {
247
- if (!isLoadingList.value) {
248
- loadList();
249
- }
250
-
251
- if (!isLoadingSummary.value) {
252
- loadSummary();
253
- }
254
-
255
- if (!isLoadingFilters.value) {
256
- loadFilterFieldOptions();
257
- }
258
- }, 1);
259
- }
260
-
261
- /**
262
- * Saves the current filter and pagination settings to local storage.
263
- */
264
- async function saveSettings() {
265
- const settings = {
266
- filter: filter.value,
267
- quasarPagination: { ...quasarPagination.value, page: 1 }
268
- };
269
- // save in local storage
270
- setItem(PAGE_SETTINGS_KEY, settings);
271
- }
272
-
273
- /**
274
- * Gets the additional details for the currently active item.
275
- * (ie: data that is not normally loaded in the list because it is not needed for the list view)
276
- * @returns {Promise<void>}
277
- */
278
- async function getActiveItemDetails() {
279
- if (!activeItem.value || !options.routes.details) return;
280
-
281
- const result = await options.routes.details(activeItem.value);
282
-
283
- // Only set the ad details if we are the response for the currently loaded item
284
- // NOTE: race conditions might allow the finished loading item to be different to the currently
285
- // requested item
286
- if (result?.id === activeItem.value?.id) {
287
- const loadedItem = pagedItems.value?.data?.find((i: ActionTargetItem) => i.id === result.id);
288
- activeItem.value = { ...result, isSaving: loadedItem?.isSaving || ref(false) };
289
- }
290
- }
291
-
292
- // Whenever the active item changes, fill the additional item details
293
- // (ie: tasks, verifications, creatives, etc.)
294
- if (options.routes.details) {
295
- watch(() => activeItem.value, async (newItem, oldItem) => {
296
- if (newItem && oldItem?.id !== newItem.id) {
297
- await getActiveItemDetails();
298
- }
299
- });
300
- }
301
-
302
- /**
303
- * Opens the item's form with the given item and tab
304
- *
305
- * @param item
306
- * @param panel
307
- */
308
- function activatePanel(item: ActionTargetItem, panel) {
309
- activeItem.value = item;
310
- activePanel.value = panel;
311
- }
312
-
313
- /**
314
- * Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
315
- * selected item. If the next item is on a previous or next page, it will load the page first then select the item
316
- */
317
- async function getNextItem(offset: number) {
318
- if (!pagedItems.value?.data) return;
319
-
320
- const index = pagedItems.value.data.findIndex((i: ActionTargetItem) => i.id === activeItem.value?.id);
321
- if (index === undefined || index === null) return;
322
-
323
- let nextIndex = index + offset;
324
-
325
- // Load the previous page if the offset is before index 0
326
- if (nextIndex < 0) {
327
- if (quasarPagination.value.page > 1) {
328
- quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page - 1 };
329
- await waitForRef(isLoadingList, false);
330
- nextIndex = pagedItems.value.data.length - 1;
331
- } else {
332
- // There are no more previous pages
333
- return;
334
- }
335
- }
336
-
337
- // Load the next page if the offset is past the last index
338
- if (nextIndex >= pagedItems.value.data.length) {
339
- if (quasarPagination.value.page < (pagedItems.value?.meta?.last_page || 1)) {
340
- quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page + 1 };
341
- await waitForRef(isLoadingList, false);
342
- nextIndex = 0;
343
- } else {
344
- // There are no more next pages
345
- return;
346
- }
347
- }
348
-
349
- activeItem.value = pagedItems.value?.data[nextIndex];
350
- }
351
-
352
- // Initialize the list actions and load settings, lists, summaries, filter fields, etc.
353
- function initialize() {
354
- isInitialized = true;
355
- loadSettings();
356
- }
357
-
358
- return {
359
- // State
360
- pagedItems,
361
- filter,
362
- globalFilter,
363
- filterActiveCount,
364
- showFilters,
365
- summary,
366
- filterFieldOptions,
367
- selectedRows,
368
- isLoadingList,
369
- isLoadingFilters,
370
- isLoadingSummary,
371
- pager,
372
- quasarPagination,
373
- activeItem,
374
- activePanel,
375
-
376
- // Actions
377
- initialize,
378
- loadSummary,
379
- resetPaging,
380
- loadList,
381
- loadMore,
382
- refreshAll,
383
- getNextItem,
384
- activatePanel,
385
- applyFilterFromUrl,
386
- setItemInList
387
- };
24
+ export function useListControls(name: string, {
25
+ listRoute,
26
+ summaryRoute = null,
27
+ filterFieldOptionsRoute = null,
28
+ moreRoute = null,
29
+ itemDetailsRoute = null,
30
+ refreshFilters = false,
31
+ urlPattern = null,
32
+ filterDefaults = {}
33
+ }: ListActionsOptions) {
34
+ let isInitialized = false;
35
+ const PAGE_SETTINGS_KEY = `${name}-pagination-settings`;
36
+ const pagedItems: Ref<PagedItems | null> = shallowRef(null);
37
+ const filter: Ref<object | any> = ref({});
38
+ const globalFilter = ref({});
39
+ const showFilters = ref(false);
40
+ const selectedRows = shallowRef([]);
41
+ const isLoadingList = ref(false);
42
+ const isLoadingSummary = ref(false);
43
+ const summary = shallowRef(null);
44
+
45
+ // The active ad for viewing / editing
46
+ const activeItem: ShallowRef<ActionTargetItem | null> = shallowRef(null);
47
+ // Controls the active panel (ie: tab) if rendering a panels drawer or similar
48
+ const activePanel = shallowRef(null);
49
+
50
+ // Filter fields are the field values available for the currently applied filter on Creative Groups
51
+ // (ie: all states available under the current filter)
52
+ const filterFieldOptions = ref({});
53
+ const isLoadingFilters = ref(false);
54
+
55
+ const filterActiveCount = computed(() => Object.keys(filter.value).filter(key => filter.value[key] !== undefined).length);
56
+
57
+ const PAGING_DEFAULT = {
58
+ __sort: null,
59
+ sortBy: null,
60
+ descending: false,
61
+ page: 1,
62
+ rowsNumber: 0,
63
+ rowsPerPage: 50
64
+ };
65
+ const quasarPagination = ref(PAGING_DEFAULT);
66
+
67
+ const pager = computed(() => ({
68
+ perPage: quasarPagination.value.rowsPerPage,
69
+ page: quasarPagination.value.page,
70
+ filter: { ...filter.value, ...globalFilter.value },
71
+ sort: quasarPagination.value.__sort || undefined
72
+ }));
73
+
74
+ // When any part of the filter changes, get the new list of creatives
75
+ watch(pager, () => {
76
+ saveSettings();
77
+ loadList();
78
+ });
79
+ watch(filter, () => {
80
+ saveSettings();
81
+ loadSummary();
82
+ });
83
+ watch(selectedRows, loadSummary);
84
+
85
+ if (refreshFilters) {
86
+ watch(filter, loadFilterFieldOptions);
87
+ }
88
+
89
+ async function loadList() {
90
+ if (!isInitialized) return;
91
+ isLoadingList.value = true;
92
+ setPagedItems(await listRoute(pager.value));
93
+ isLoadingList.value = false;
94
+ }
95
+
96
+ async function loadSummary() {
97
+ if (!summaryRoute || !isInitialized) return;
98
+
99
+ isLoadingSummary.value = true;
100
+ const summaryFilter = { id: null, ...filter.value, ...globalFilter.value };
101
+ if (selectedRows.value.length) {
102
+ summaryFilter.id = selectedRows.value.map((row: { id: string }) => row.id);
103
+ }
104
+ summary.value = await summaryRoute(summaryFilter);
105
+ isLoadingSummary.value = false;
106
+ }
107
+
108
+ /**
109
+ * Loads the filter field options for the current filter.
110
+ *
111
+ * @returns {Promise<void>}
112
+ */
113
+ async function loadFilterFieldOptions() {
114
+ if (!filterFieldOptionsRoute || !isInitialized) return;
115
+ isLoadingFilters.value = true;
116
+ filterFieldOptions.value = await filterFieldOptionsRoute(filter.value);
117
+ isLoadingFilters.value = false;
118
+ }
119
+
120
+ /**
121
+ * Watches for a filter URL parameter and applies the filter if it is set.
122
+ */
123
+ function applyFilterFromUrl(url: string, filterFields = null) {
124
+ if (urlPattern && url.match(urlPattern)) {
125
+ // A flat list of valid filterable field names
126
+ const validFilterKeys = filterFields?.value?.map(group => group.fields.map(field => field.name)).flat();
127
+
128
+ const urlFilter = getFilterFromUrl(url, validFilterKeys);
129
+
130
+ if (Object.keys(urlFilter).length > 0) {
131
+ filter.value = urlFilter;
132
+
133
+ // Override whatever is in local storage with this new filter
134
+ updateSettings("filter", filter.value);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Set the reactive pager to map from the Laravel pagination to Quasar pagination
140
+ // and automatically update the list of ads
141
+ function setPagedItems(items: any[] | PagedItems) {
142
+ let data, meta;
143
+
144
+ if (Array.isArray(items)) {
145
+ data = items;
146
+ meta = { total: items.length };
147
+
148
+ } else {
149
+ data = items.data;
150
+ meta = items.meta;
151
+ }
152
+
153
+ // Update the Quasar pagination rows number if it is different from the total
154
+ if (meta && meta.total !== quasarPagination.value.rowsNumber) {
155
+ quasarPagination.value.rowsNumber = meta.total;
156
+ }
157
+
158
+ // Add a reactive isSaving property to each item (for performance reasons in checking saving state)
159
+ data = data.map((item: any) => {
160
+ // We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
161
+ // components, and therefore reactivity is not responding to the new isSaving state
162
+ const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
163
+ return { ...item, isSaving: oldItem?.isSaving || ref(false) };
164
+ });
165
+
166
+ pagedItems.value = { data, meta };
167
+ }
168
+
169
+ /**
170
+ * Resets the filter and pagination settings to their defaults.
171
+ */
172
+ function resetPaging() {
173
+ quasarPagination.value = PAGING_DEFAULT;
174
+ }
175
+
176
+ /**
177
+ * Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
178
+ *
179
+ * @param updatedItem
180
+ */
181
+ function setItemInList(updatedItem: any) {
182
+ const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
183
+ setPagedItems({
184
+ data,
185
+ meta: { total: pagedItems.value.meta.total }
186
+ });
187
+
188
+ // Update the active item as well if it is set
189
+ if (activeItem.value?.id === updatedItem.id) {
190
+ activeItem.value = { ...activeItem.value, ...updatedItem };
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Loads more items into the list.
196
+ */
197
+ async function loadMore(index: number, perPage = undefined) {
198
+ if (!moreRoute) return;
199
+
200
+ const newItems = await moreRoute({
201
+ page: index + 1,
202
+ perPage,
203
+ filter: { ...filter.value, ...globalFilter.value }
204
+ });
205
+
206
+ if (newItems && newItems.length > 0) {
207
+ setPagedItems({
208
+ data: [...pagedItems.value.data, ...newItems],
209
+ meta: { total: pagedItems.value.meta.total }
210
+ });
211
+ return true;
212
+ }
213
+
214
+ return false;
215
+ }
216
+
217
+ /**
218
+ * Refreshes the list, summary, and filter field options.
219
+ */
220
+ async function refreshAll() {
221
+ return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions(), getActiveItemDetails()]);
222
+ }
223
+
224
+ /**
225
+ * Updates the settings in local storage
226
+ */
227
+ function updateSettings(key: string, value: any) {
228
+ const settings = getItem(PAGE_SETTINGS_KEY) || {};
229
+ settings[key] = value;
230
+ setItem(PAGE_SETTINGS_KEY, settings);
231
+ }
232
+
233
+ /**
234
+ * Loads the filter and pagination settings from local storage.
235
+ */
236
+ function loadSettings() {
237
+ // Only load settings when the class is fully initialized
238
+ if (!isInitialized) return;
239
+
240
+ const settings = getItem(PAGE_SETTINGS_KEY);
241
+
242
+ // Load the filter settings from local storage
243
+ if (settings) {
244
+ filter.value = { ...settings.filter, ...filter.value };
245
+ quasarPagination.value = settings.quasarPagination;
246
+ } else {
247
+ // If no local storage settings, apply the default filters
248
+ filter.value = { ...filterDefaults, ...filter.value };
249
+ }
250
+
251
+ setTimeout(() => {
252
+ if (!isLoadingList.value) {
253
+ loadList();
254
+ }
255
+
256
+ if (!isLoadingSummary.value) {
257
+ loadSummary();
258
+ }
259
+
260
+ if (!isLoadingFilters.value) {
261
+ loadFilterFieldOptions();
262
+ }
263
+ }, 1);
264
+ }
265
+
266
+ /**
267
+ * Saves the current filter and pagination settings to local storage.
268
+ */
269
+ async function saveSettings() {
270
+ const settings = {
271
+ filter: filter.value,
272
+ quasarPagination: { ...quasarPagination.value, page: 1 }
273
+ };
274
+ // save in local storage
275
+ setItem(PAGE_SETTINGS_KEY, settings);
276
+ }
277
+
278
+ /**
279
+ * Gets the additional details for the currently active item.
280
+ * (ie: data that is not normally loaded in the list because it is not needed for the list view)
281
+ * @returns {Promise<void>}
282
+ */
283
+ async function getActiveItemDetails() {
284
+ if (!activeItem.value || !itemDetailsRoute) return;
285
+
286
+ const result = await itemDetailsRoute(activeItem.value);
287
+
288
+ // Only set the ad details if we are the response for the currently loaded item
289
+ // NOTE: race conditions might allow the finished loading item to be different to the currently
290
+ // requested item
291
+ if (result?.id === activeItem.value?.id) {
292
+ const loadedItem = pagedItems.value?.data.find((i: { id: string }) => i.id === result.id);
293
+ activeItem.value = { ...result, isSaving: loadedItem.isSaving || ref(false) };
294
+ }
295
+ }
296
+
297
+ // Whenever the active item changes, fill the additional item details
298
+ // (ie: tasks, verifications, creatives, etc.)
299
+ if (itemDetailsRoute) {
300
+ watch(() => activeItem.value, async (newItem, oldItem) => {
301
+ if (newItem && oldItem?.id !== newItem.id) {
302
+ await getActiveItemDetails();
303
+ }
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Opens the item's form with the given item and tab
309
+ *
310
+ * @param item
311
+ * @param panel
312
+ */
313
+ function activatePanel(item, panel) {
314
+ activeItem.value = item;
315
+ activePanel.value = panel;
316
+ }
317
+
318
+ /**
319
+ * Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
320
+ * selected item. If the next item is on a previous or next page, it will load the page first then select the item
321
+ */
322
+ async function getNextItem(offset: number) {
323
+ if (!pagedItems.value) return;
324
+
325
+ const index = pagedItems.value.data.findIndex((i: { id: string }) => i.id === activeItem.value?.id);
326
+ if (index === undefined || index === null) return;
327
+
328
+ let nextIndex = index + offset;
329
+
330
+ // Load the previous page if the offset is before index 0
331
+ if (nextIndex < 0) {
332
+ if (quasarPagination.value.page > 1) {
333
+ quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page - 1 };
334
+ await waitForRef(isLoadingList, false);
335
+ nextIndex = pagedItems.value.data.length - 1;
336
+ } else {
337
+ // There are no more previous pages
338
+ return;
339
+ }
340
+ }
341
+
342
+ // Load the next page if the offset is past the last index
343
+ if (nextIndex >= pagedItems.value.data.length) {
344
+ if (quasarPagination.value.page < pagedItems.value.meta.last_page) {
345
+ quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page + 1 };
346
+ await waitForRef(isLoadingList, false);
347
+ nextIndex = 0;
348
+ } else {
349
+ // There are no more next pages
350
+ return;
351
+ }
352
+ }
353
+
354
+ activeItem.value = pagedItems.value?.data[nextIndex];
355
+ }
356
+
357
+ // Initialize the list actions and load settings, lists, summaries, filter fields, etc.
358
+ function initialize() {
359
+ isInitialized = true;
360
+ loadSettings();
361
+ }
362
+
363
+ return {
364
+ // State
365
+ pagedItems,
366
+ filter,
367
+ globalFilter,
368
+ filterActiveCount,
369
+ showFilters,
370
+ summary,
371
+ filterFieldOptions,
372
+ selectedRows,
373
+ isLoadingList,
374
+ isLoadingFilters,
375
+ isLoadingSummary,
376
+ pager,
377
+ quasarPagination,
378
+ activeItem,
379
+ activePanel,
380
+
381
+ // Actions
382
+ initialize,
383
+ loadSummary,
384
+ resetPaging,
385
+ loadList,
386
+ loadMore,
387
+ refreshAll,
388
+ getNextItem,
389
+ activatePanel,
390
+ applyFilterFromUrl,
391
+ setItemInList
392
+ };
388
393
  }