quasar-ui-danx 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
  }