quasar-ui-danx 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. package/package.json +6 -1
  2. package/src/components/ActionTable/ActionTable.vue +49 -41
  3. package/src/components/ActionTable/BatchActionMenu.vue +20 -20
  4. package/src/components/ActionTable/EmptyTableState.vue +5 -5
  5. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +11 -11
  6. package/src/components/ActionTable/Filters/FilterGroupItem.vue +7 -7
  7. package/src/components/ActionTable/Filters/FilterGroupList.vue +29 -29
  8. package/src/components/ActionTable/Filters/FilterListToggle.vue +15 -15
  9. package/src/components/ActionTable/Filters/FilterableField.vue +82 -80
  10. package/src/components/ActionTable/Filters/index.ts +5 -0
  11. package/src/components/ActionTable/Form/Fields/BooleanField.vue +13 -13
  12. package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +11 -11
  13. package/src/components/ActionTable/Form/Fields/DateField.vue +13 -13
  14. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +25 -25
  15. package/src/components/ActionTable/Form/Fields/DateTimeField.vue +21 -21
  16. package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +23 -23
  17. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +31 -31
  18. package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +19 -19
  19. package/src/components/ActionTable/Form/Fields/IntegerField.vue +7 -7
  20. package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +22 -0
  21. package/src/components/ActionTable/Form/Fields/LabeledInput.vue +19 -19
  22. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +40 -40
  23. package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +23 -23
  24. package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +10 -10
  25. package/src/components/ActionTable/Form/Fields/NumberField.vue +29 -29
  26. package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +33 -33
  27. package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +36 -36
  28. package/src/components/ActionTable/Form/Fields/SelectField.vue +66 -66
  29. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +23 -23
  30. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +32 -32
  31. package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
  32. package/src/components/ActionTable/Form/Fields/WysiwygField.vue +16 -16
  33. package/src/components/ActionTable/Form/Fields/index.ts +23 -23
  34. package/src/components/ActionTable/Form/RenderedForm.vue +27 -25
  35. package/src/components/ActionTable/Form/index.ts +2 -0
  36. package/src/components/ActionTable/TableSummaryRow.vue +33 -33
  37. package/src/components/ActionTable/index.ts +8 -13
  38. package/src/components/ActionTable/listActions.ts +340 -339
  39. package/src/components/ActionTable/listHelpers.ts +74 -0
  40. package/src/components/ActionTable/tableColumns.ts +56 -56
  41. package/src/components/DragAndDrop/HandleDraggable.vue +29 -29
  42. package/src/components/DragAndDrop/ListItemDraggable.vue +10 -10
  43. package/src/components/DragAndDrop/index.ts +0 -1
  44. package/src/components/DragAndDrop/listDragAndDrop.ts +1 -1
  45. package/src/components/Utility/CollapsableSidebar.vue +35 -35
  46. package/src/components/Utility/ContentDrawer.vue +20 -20
  47. package/src/components/Utility/Dialogs/ConfirmDialog.vue +55 -55
  48. package/src/components/Utility/Dialogs/FullScreenDialog.vue +18 -18
  49. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +105 -0
  50. package/src/components/Utility/Dialogs/InfoDialog.vue +10 -10
  51. package/src/components/Utility/Dialogs/InputDialog.vue +13 -13
  52. package/src/components/Utility/ImagePreview.vue +192 -0
  53. package/src/components/Utility/Popover/PopoverMenu.vue +64 -0
  54. package/src/components/Utility/Transitions/StaggeredListTransition.vue +15 -15
  55. package/src/components/Utility/index.ts +11 -9
  56. package/src/components/index.ts +1 -1
  57. package/src/helpers/FileUpload.ts +274 -273
  58. package/src/helpers/compatibility.ts +45 -45
  59. package/src/helpers/date.ts +2 -2
  60. package/src/helpers/download.ts +166 -158
  61. package/src/helpers/downloadPdf.ts +48 -48
  62. package/src/helpers/files.ts +42 -42
  63. package/src/helpers/index.ts +2 -0
  64. package/src/helpers/multiFileUpload.ts +56 -56
  65. package/src/helpers/singleFileUpload.ts +49 -49
  66. package/src/index.esm.js +3 -4
  67. package/src/svg/FilterIcon.svg +7 -0
  68. package/src/svg/ImageIcon.svg +30 -0
  69. package/src/svg/PdfIcon.svg +21 -0
  70. package/src/svg/PercentIcon.svg +13 -0
  71. package/src/svg/TrashIcon.svg +15 -0
  72. package/src/svg/XIcon.svg +18 -0
  73. package/src/svg/index.ts +8 -0
  74. package/src/vendor/tinymce-config.ts +1 -0
  75. package/src/vue-plugin.js +7 -4
  76. package/tsconfig.json +14 -13
  77. package/src/components/ActionTable/tableHelpers.ts +0 -83
  78. package/src/components/DragAndDrop/Icons/index.ts +0 -2
  79. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleDotsIcon.svg +0 -0
  80. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleIcon.svg +0 -0
@@ -1,361 +1,362 @@
1
- import { getFilterFromUrl, mapSortBy, waitForRef } from "danx/src/components/ActionTable/tableHelpers";
2
- import { getItem, setItem } from "danx/src/helpers";
1
+ import { getItem, setItem } from "@ui/helpers";
3
2
  import { computed, ref, watch } from "vue";
3
+ import { getFilterFromUrl, mapSortBy, waitForRef } from "./listHelpers";
4
4
 
5
5
  export function useListActions(name, {
6
- listRoute,
7
- filterFieldOptionsRoute,
8
- summaryRoute = null,
9
- moreRoute = null,
10
- applyActionRoute = null,
11
- applyBatchActionRoute = null,
12
- itemDetailsRoute = null,
13
- columns = null,
14
- filterGroups = null,
15
- refreshFilters = false,
16
- urlPattern = null,
17
- filterDefaults = {}
6
+ listRoute,
7
+ filterFieldOptionsRoute,
8
+ summaryRoute = null,
9
+ moreRoute = null,
10
+ applyActionRoute = null,
11
+ applyBatchActionRoute = null,
12
+ itemDetailsRoute = null,
13
+ columns = null,
14
+ filterGroups = null,
15
+ refreshFilters = false,
16
+ urlPattern = null,
17
+ filterDefaults = {}
18
18
  }) {
19
- const PAGE_SETTINGS_KEY = `${name}-pagination-settings`;
20
- const pagedItems = ref(null);
21
- const filter = ref({});
22
- const showFilters = ref(getItem(`${name}-show-filters`, true));
23
- const selectedRows = ref([]);
24
- const isLoadingList = ref(false);
25
- const isLoadingSummary = ref(false);
26
- const summary = ref(null);
27
-
28
- const filterActiveCount = computed(() => Object.keys(filter.value).filter(key => filter.value[key] !== undefined).length);
29
-
30
- const PAGING_DEFAULT = {
31
- sortBy: null,
32
- descending: false,
33
- page: 1,
34
- rowsNumber: 0,
35
- rowsPerPage: 50
36
- };
37
- const quasarPagination = ref(PAGING_DEFAULT);
38
-
39
- const pager = computed(() => ({
40
- perPage: quasarPagination.value.rowsPerPage,
41
- page: quasarPagination.value.page,
42
- filter: filter.value,
43
- sort: columns ? mapSortBy(quasarPagination.value, columns) : undefined
44
- }));
45
-
46
- // When any part of the filter changes, get the new list of creatives
47
- watch(pager, () => {
48
- saveSettings();
49
- loadList();
50
- });
51
- watch(filter, () => {
52
- saveSettings();
53
- loadSummary();
54
- });
55
- watch(selectedRows, loadSummary);
56
-
57
- if (refreshFilters) {
58
- watch(filter, loadFilterFieldOptions);
59
- }
60
-
61
- async function loadList() {
62
- isLoadingList.value = true;
63
- setPagedItems(await listRoute(pager.value));
64
- isLoadingList.value = false;
65
- }
66
-
67
- async function loadSummary() {
68
- if (summaryRoute) {
69
- isLoadingSummary.value = true;
70
- const summaryFilter = { id: null, ...filter.value };
71
- if (selectedRows.value.length) {
72
- summaryFilter.id = selectedRows.value.map((row) => row.id);
73
- }
74
- summary.value = await summaryRoute(summaryFilter);
75
- isLoadingSummary.value = false;
19
+ const PAGE_SETTINGS_KEY = `${name}-pagination-settings`;
20
+ const pagedItems = ref(null);
21
+ const filter = ref({});
22
+ const showFilters = ref(getItem(`${name}-show-filters`, true));
23
+ const selectedRows = ref([]);
24
+ const isLoadingList = ref(false);
25
+ const isLoadingSummary = ref(false);
26
+ const summary = ref(null);
27
+
28
+ const filterActiveCount = computed(() => Object.keys(filter.value).filter(key => filter.value[key] !== undefined).length);
29
+
30
+ const PAGING_DEFAULT = {
31
+ sortBy: null,
32
+ descending: false,
33
+ page: 1,
34
+ rowsNumber: 0,
35
+ rowsPerPage: 50
36
+ };
37
+ const quasarPagination = ref(PAGING_DEFAULT);
38
+
39
+ const pager = computed(() => ({
40
+ perPage: quasarPagination.value.rowsPerPage,
41
+ page: quasarPagination.value.page,
42
+ filter: filter.value,
43
+ sort: columns ? mapSortBy(quasarPagination.value, columns) : undefined
44
+ }));
45
+
46
+ // When any part of the filter changes, get the new list of creatives
47
+ watch(pager, () => {
48
+ saveSettings();
49
+ loadList();
50
+ });
51
+ watch(filter, () => {
52
+ saveSettings();
53
+ loadSummary();
54
+ });
55
+ watch(selectedRows, loadSummary);
56
+
57
+ if (refreshFilters) {
58
+ watch(filter, loadFilterFieldOptions);
76
59
  }
77
- }
78
-
79
- // Filter fields are the field values available for the currently applied filter on Creative Groups
80
- // (ie: all states available under the current filter)
81
- const filterFieldOptions = ref({});
82
- const isLoadingFilters = ref(false);
83
-
84
- watch(() => showFilters.value, (show) => {
85
- setItem(`${name}-show-filters`, show);
86
- });
87
-
88
- async function loadFilterFieldOptions() {
89
- isLoadingFilters.value = true;
90
- filterFieldOptions.value = await filterFieldOptionsRoute(filter.value);
91
- isLoadingFilters.value = false;
92
- }
93
-
94
- // A flat list of valid filterable field names
95
- const validFilterKeys = computed(() => filterGroups?.value?.map(group => group.fields.map(field => field.name)).flat());
96
-
97
- /**
98
- * Watches for a filter URL parameter and applies the filter if it is set.
99
- */
100
- function applyFilterFromUrl(url) {
101
- if (url.match(urlPattern)) {
102
- const urlFilter = getFilterFromUrl(url, validFilterKeys.value);
103
-
104
- if (Object.keys(urlFilter).length > 0) {
105
- filter.value = urlFilter;
106
- }
60
+
61
+ async function loadList() {
62
+ isLoadingList.value = true;
63
+ setPagedItems(await listRoute(pager.value));
64
+ isLoadingList.value = false;
107
65
  }
108
- }
109
-
110
- // Set the reactive pager to map from the Laravel pagination to Quasar pagination
111
- // and automatically update the list of ads
112
- function setPagedItems(items) {
113
- if (Array.isArray(items)) {
114
- pagedItems.value = { data: items, meta: { total: items.length } };
115
- } else {
116
- pagedItems.value = items;
117
- if (items?.meta && items.meta.total !== quasarPagination.value.rowsNumber) {
118
- quasarPagination.value.rowsNumber = items.meta.total;
119
- }
66
+
67
+ async function loadSummary() {
68
+ if (summaryRoute) {
69
+ isLoadingSummary.value = true;
70
+ const summaryFilter = { id: null, ...filter.value };
71
+ if (selectedRows.value.length) {
72
+ summaryFilter.id = selectedRows.value.map((row) => row.id);
73
+ }
74
+ summary.value = await summaryRoute(summaryFilter);
75
+ isLoadingSummary.value = false;
76
+ }
120
77
  }
121
- }
122
-
123
- /**
124
- * Resets the filter and pagination settings to their defaults.
125
- */
126
- function resetPaging() {
127
- quasarPagination.value = PAGING_DEFAULT;
128
- }
129
-
130
- /**
131
- * Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
132
- *
133
- * @param updatedItem
134
- */
135
- function setItemInPagedList(updatedItem) {
136
- const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
137
- pagedItems.value = { ...pagedItems.value, data };
138
- }
139
-
140
- /**
141
- * Loads more items into the list.
142
- * @param index
143
- * @param perPage
144
- */
145
- async function loadMore(index, perPage = undefined) {
146
- const newItems = await moreRoute({
147
- page: index + 1,
148
- perPage,
149
- filter: filter.value
78
+
79
+ // Filter fields are the field values available for the currently applied filter on Creative Groups
80
+ // (ie: all states available under the current filter)
81
+ const filterFieldOptions = ref({});
82
+ const isLoadingFilters = ref(false);
83
+
84
+ watch(() => showFilters.value, (show) => {
85
+ setItem(`${name}-show-filters`, show);
150
86
  });
151
87
 
152
- if (newItems && newItems.length > 0) {
153
- pagedItems.value.data = [...pagedItems.value.data, ...newItems];
154
- return true;
88
+ async function loadFilterFieldOptions() {
89
+ isLoadingFilters.value = true;
90
+ filterFieldOptions.value = await filterFieldOptionsRoute(filter.value);
91
+ isLoadingFilters.value = false;
155
92
  }
156
93
 
157
- return false;
158
- }
159
-
160
- /**
161
- * Refreshes the list, summary, and filter field options.
162
- * @returns {Promise<Awaited<void>[]>}
163
- */
164
- async function refreshAll() {
165
- return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions()]);
166
- }
167
-
168
- /**
169
- * Loads the filter and pagination settings from local storage.
170
- */
171
- function loadSettings() {
172
- const settings = getItem(PAGE_SETTINGS_KEY);
173
-
174
- // Load the filter settings from local storage
175
- if (settings) {
176
- filter.value = { ...settings.filter, ...filter.value };
177
- quasarPagination.value = settings.quasarPagination;
178
- } else {
179
- // If no local storage settings, apply the default filters
180
- filter.value = { ...filterDefaults, ...filter.value };
94
+ // A flat list of valid filterable field names
95
+ const validFilterKeys = computed(() => filterGroups?.value?.map(group => group.fields.map(field => field.name)).flat());
96
+
97
+ /**
98
+ * Watches for a filter URL parameter and applies the filter if it is set.
99
+ */
100
+ function applyFilterFromUrl(url) {
101
+ if (url.match(urlPattern)) {
102
+ const urlFilter = getFilterFromUrl(url, validFilterKeys.value);
103
+
104
+ if (Object.keys(urlFilter).length > 0) {
105
+ filter.value = urlFilter;
106
+ }
107
+ }
181
108
  }
182
109
 
183
- // Load the URL filters if they are set
184
- applyFilterFromUrl(window.location.href);
110
+ // Set the reactive pager to map from the Laravel pagination to Quasar pagination
111
+ // and automatically update the list of ads
112
+ function setPagedItems(items) {
113
+ if (Array.isArray(items)) {
114
+ pagedItems.value = { data: items, meta: { total: items.length } };
115
+ } else {
116
+ pagedItems.value = items;
117
+ if (items?.meta && items.meta.total !== quasarPagination.value.rowsNumber) {
118
+ quasarPagination.value.rowsNumber = items.meta.total;
119
+ }
120
+ }
121
+ }
185
122
 
186
- setTimeout(() => {
187
- if (!isLoadingList.value) {
188
- loadList();
189
- }
123
+ /**
124
+ * Resets the filter and pagination settings to their defaults.
125
+ */
126
+ function resetPaging() {
127
+ quasarPagination.value = PAGING_DEFAULT;
128
+ }
190
129
 
191
- if (!isLoadingSummary.value) {
192
- loadSummary();
193
- }
194
-
195
- if (!isLoadingFilters.value) {
196
- loadFilterFieldOptions();
197
- }
198
- }, 1);
199
- }
200
-
201
- /**
202
- * Saves the current filter and pagination settings to local storage.
203
- */
204
- async function saveSettings() {
205
- const settings = {
206
- filter: filter.value,
207
- quasarPagination: { ...quasarPagination.value, page: 1 }
208
- };
209
- // save in local storage
210
- setItem(PAGE_SETTINGS_KEY, settings);
211
- }
212
-
213
- /**
214
- * Applies an action to an item.
215
- */
216
- const isApplyingActionToItem = ref(null);
217
- let actionResultCount = 0;
218
-
219
- async function applyAction(item, input, itemData = {}) {
220
- isApplyingActionToItem.value = item;
221
- const resultNumber = ++actionResultCount;
222
- setItemInPagedList({ ...item, ...input, ...itemData });
223
- const result = await applyActionRoute(item, input);
224
- if (result.success) {
225
- // Only render the most recent campaign changes
226
- if (resultNumber !== actionResultCount) return;
227
-
228
- // Update the updated item in the previously loaded list if it exists
229
- setItemInPagedList(result.item);
230
-
231
- // Update the active item if it is the same as the updated item
232
- if (activeItem.value?.id === result.item.id) {
233
- activeItem.value = { ...activeItem.value, ...result.item };
234
- }
130
+ /**
131
+ * Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
132
+ *
133
+ * @param updatedItem
134
+ */
135
+ function setItemInPagedList(updatedItem) {
136
+ const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
137
+ pagedItems.value = { ...pagedItems.value, data };
235
138
  }
236
- isApplyingActionToItem.value = null;
237
- return result;
238
- }
239
-
240
- /**
241
- * Applies an action to all selected items.
242
- */
243
- const isApplyingBatchAction = ref(false);
244
-
245
- async function applyBatchAction(input) {
246
- isApplyingBatchAction.value = true;
247
- const batchFilter = { id: selectedRows.value.map(r => r.id) };
248
- const result = await applyBatchActionRoute(batchFilter, input);
249
- isApplyingBatchAction.value = false;
250
- await refreshAll();
251
-
252
- return result;
253
- }
254
-
255
- // The active ad for viewing / editing in the Ad Form
256
- const activeItem = ref(null);
257
- // Controls the tab on the Ad Form
258
- const formTab = ref("general");
259
-
260
- // Whenever the active item changes, fill the additional item details
261
- // (ie: tasks, verifications, creatives, etc.)
262
- if (itemDetailsRoute) {
263
- watch(() => activeItem.value, async (newItem, oldItem) => {
264
- if (newItem && oldItem?.id !== newItem.id) {
265
- const result = await itemDetailsRoute(newItem);
266
-
267
- // Only set the ad details if we are the response for the currently loaded item
268
- // NOTE: race conditions might allow the finished loading item to be different to the currently requested item
269
- if (result?.id === activeItem.value?.id) {
270
- activeItem.value = result;
139
+
140
+ /**
141
+ * Loads more items into the list.
142
+ * @param index
143
+ * @param perPage
144
+ */
145
+ async function loadMore(index, perPage = undefined) {
146
+ const newItems = await moreRoute({
147
+ page: index + 1,
148
+ perPage,
149
+ filter: filter.value
150
+ });
151
+
152
+ if (newItems && newItems.length > 0) {
153
+ pagedItems.value.data = [...pagedItems.value.data, ...newItems];
154
+ return true;
271
155
  }
272
- }
273
- });
274
- }
275
-
276
- /**
277
- * Opens the item's form with the given item and tab
278
- *
279
- * @param item
280
- * @param tab
281
- */
282
- function openItemForm(item, tab) {
283
- activeItem.value = item;
284
- formTab.value = tab;
285
- }
286
-
287
- /**
288
- * Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
289
- * selected item. If the next item is on a previous or next page, it will load the page first then select the item
290
- * @param offset
291
- * @returns {Promise<void>}
292
- */
293
- async function getNextItem(offset) {
294
- const index = pagedItems.value?.data.findIndex(i => i.id === activeItem.value.id);
295
- if (index === undefined) return;
296
- let nextIndex = index + offset;
297
-
298
- // Load the previous page if the offset is before index 0
299
- if (nextIndex < 0) {
300
- if (quasarPagination.value.page > 1) {
301
- quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page - 1 };
302
- await waitForRef(isLoadingList, false);
303
- nextIndex = pagedItems.value.data.length - 1;
304
- } else {
305
- // There are no more previous pages
306
- return;
307
- }
156
+
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Refreshes the list, summary, and filter field options.
162
+ * @returns {Promise<Awaited<void>[]>}
163
+ */
164
+ async function refreshAll() {
165
+ return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions()]);
308
166
  }
309
167
 
310
- // Load the next page if the offset is past the last index
311
- if (nextIndex >= pagedItems.value.data.length) {
312
- if (quasarPagination.value.page < pagedItems.value.meta.last_page) {
313
- quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page + 1 };
314
- await waitForRef(isLoadingList, false);
315
- nextIndex = 0;
316
- } else {
317
- // There are no more next pages
318
- return;
319
- }
168
+ /**
169
+ * Loads the filter and pagination settings from local storage.
170
+ */
171
+ function loadSettings() {
172
+ const settings = getItem(PAGE_SETTINGS_KEY);
173
+
174
+ // Load the filter settings from local storage
175
+ if (settings) {
176
+ filter.value = { ...settings.filter, ...filter.value };
177
+ quasarPagination.value = settings.quasarPagination;
178
+ } else {
179
+ // If no local storage settings, apply the default filters
180
+ filter.value = { ...filterDefaults, ...filter.value };
181
+ }
182
+
183
+ // Load the URL filters if they are set
184
+ applyFilterFromUrl(window.location.href);
185
+
186
+ setTimeout(() => {
187
+ if (!isLoadingList.value) {
188
+ loadList();
189
+ }
190
+
191
+ if (!isLoadingSummary.value) {
192
+ loadSummary();
193
+ }
194
+
195
+ if (!isLoadingFilters.value) {
196
+ loadFilterFieldOptions();
197
+ }
198
+ }, 1);
199
+ }
200
+
201
+ /**
202
+ * Saves the current filter and pagination settings to local storage.
203
+ */
204
+ async function saveSettings() {
205
+ const settings = {
206
+ filter: filter.value,
207
+ quasarPagination: { ...quasarPagination.value, page: 1 }
208
+ };
209
+ // save in local storage
210
+ setItem(PAGE_SETTINGS_KEY, settings);
320
211
  }
321
212
 
322
- activeItem.value = pagedItems.value.data[nextIndex];
323
- }
324
-
325
- // Async load the settings for this Action List
326
- setTimeout(loadSettings, 1);
327
-
328
- return {
329
- // State
330
- pagedItems,
331
- filter,
332
- filterActiveCount,
333
- showFilters,
334
- summary,
335
- filterFieldOptions,
336
- selectedRows,
337
- isLoadingList,
338
- isLoadingFilters,
339
- isLoadingSummary,
340
- pager,
341
- quasarPagination,
342
- isApplyingActionToItem,
343
- isApplyingBatchAction,
344
- activeItem,
345
- formTab,
346
- columns,
347
- filterGroups,
348
-
349
- // Actions
350
- loadSummary,
351
- resetPaging,
352
- loadList,
353
- loadMore,
354
- refreshAll,
355
- applyAction,
356
- applyBatchAction,
357
- getNextItem,
358
- openItemForm,
359
- applyFilterFromUrl
360
- };
213
+ /**
214
+ * Applies an action to an item.
215
+ */
216
+ const isApplyingActionToItem = ref(null);
217
+ let actionResultCount = 0;
218
+
219
+ async function applyAction(item, input, itemData = {}) {
220
+ isApplyingActionToItem.value = item;
221
+ const resultNumber = ++actionResultCount;
222
+ setItemInPagedList({ ...item, ...input, ...itemData });
223
+ const result = await applyActionRoute(item, input);
224
+ if (result.success) {
225
+ // Only render the most recent campaign changes
226
+ if (resultNumber !== actionResultCount) return;
227
+
228
+ // Update the updated item in the previously loaded list if it exists
229
+ setItemInPagedList(result.item);
230
+
231
+ // Update the active item if it is the same as the updated item
232
+ if (activeItem.value?.id === result.item.id) {
233
+ activeItem.value = { ...activeItem.value, ...result.item };
234
+ }
235
+ }
236
+ isApplyingActionToItem.value = null;
237
+ return result;
238
+ }
239
+
240
+ /**
241
+ * Applies an action to all selected items.
242
+ */
243
+ const isApplyingBatchAction = ref(false);
244
+
245
+ async function applyBatchAction(input) {
246
+ isApplyingBatchAction.value = true;
247
+ const batchFilter = { id: selectedRows.value.map(r => r.id) };
248
+ const result = await applyBatchActionRoute(batchFilter, input);
249
+ isApplyingBatchAction.value = false;
250
+ await refreshAll();
251
+
252
+ return result;
253
+ }
254
+
255
+ // The active ad for viewing / editing in the Ad Form
256
+ const activeItem = ref(null);
257
+ // Controls the tab on the Ad Form
258
+ const formTab = ref("general");
259
+
260
+ // Whenever the active item changes, fill the additional item details
261
+ // (ie: tasks, verifications, creatives, etc.)
262
+ if (itemDetailsRoute) {
263
+ watch(() => activeItem.value, async (newItem, oldItem) => {
264
+ if (newItem && oldItem?.id !== newItem.id) {
265
+ const result = await itemDetailsRoute(newItem);
266
+
267
+ // Only set the ad details if we are the response for the currently loaded item
268
+ // NOTE: race conditions might allow the finished loading item to be different to the currently
269
+ // requested item
270
+ if (result?.id === activeItem.value?.id) {
271
+ activeItem.value = result;
272
+ }
273
+ }
274
+ });
275
+ }
276
+
277
+ /**
278
+ * Opens the item's form with the given item and tab
279
+ *
280
+ * @param item
281
+ * @param tab
282
+ */
283
+ function openItemForm(item, tab) {
284
+ activeItem.value = item;
285
+ formTab.value = tab;
286
+ }
287
+
288
+ /**
289
+ * Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
290
+ * selected item. If the next item is on a previous or next page, it will load the page first then select the item
291
+ * @param offset
292
+ * @returns {Promise<void>}
293
+ */
294
+ async function getNextItem(offset) {
295
+ const index = pagedItems.value?.data.findIndex(i => i.id === activeItem.value.id);
296
+ if (index === undefined) return;
297
+ let nextIndex = index + offset;
298
+
299
+ // Load the previous page if the offset is before index 0
300
+ if (nextIndex < 0) {
301
+ if (quasarPagination.value.page > 1) {
302
+ quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page - 1 };
303
+ await waitForRef(isLoadingList, false);
304
+ nextIndex = pagedItems.value.data.length - 1;
305
+ } else {
306
+ // There are no more previous pages
307
+ return;
308
+ }
309
+ }
310
+
311
+ // Load the next page if the offset is past the last index
312
+ if (nextIndex >= pagedItems.value.data.length) {
313
+ if (quasarPagination.value.page < pagedItems.value.meta.last_page) {
314
+ quasarPagination.value = { ...quasarPagination.value, page: quasarPagination.value.page + 1 };
315
+ await waitForRef(isLoadingList, false);
316
+ nextIndex = 0;
317
+ } else {
318
+ // There are no more next pages
319
+ return;
320
+ }
321
+ }
322
+
323
+ activeItem.value = pagedItems.value.data[nextIndex];
324
+ }
325
+
326
+ // Async load the settings for this Action List
327
+ setTimeout(loadSettings, 1);
328
+
329
+ return {
330
+ // State
331
+ pagedItems,
332
+ filter,
333
+ filterActiveCount,
334
+ showFilters,
335
+ summary,
336
+ filterFieldOptions,
337
+ selectedRows,
338
+ isLoadingList,
339
+ isLoadingFilters,
340
+ isLoadingSummary,
341
+ pager,
342
+ quasarPagination,
343
+ isApplyingActionToItem,
344
+ isApplyingBatchAction,
345
+ activeItem,
346
+ formTab,
347
+ columns,
348
+ filterGroups,
349
+
350
+ // Actions
351
+ loadSummary,
352
+ resetPaging,
353
+ loadList,
354
+ loadMore,
355
+ refreshAll,
356
+ applyAction,
357
+ applyBatchAction,
358
+ getNextItem,
359
+ openItemForm,
360
+ applyFilterFromUrl
361
+ };
361
362
  }