grantthomas-nuxt 1.0.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.
Files changed (74) hide show
  1. package/README.md +484 -0
  2. package/dist/module.d.mts +23 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +54 -0
  5. package/dist/runtime/components/AlertDisplay.d.vue.ts +2 -0
  6. package/dist/runtime/components/AlertDisplay.vue +55 -0
  7. package/dist/runtime/components/AlertDisplay.vue.d.ts +2 -0
  8. package/dist/runtime/components/CrudAddressSearchField.d.vue.ts +8 -0
  9. package/dist/runtime/components/CrudAddressSearchField.vue +117 -0
  10. package/dist/runtime/components/CrudAddressSearchField.vue.d.ts +8 -0
  11. package/dist/runtime/components/CrudApiSelectorField.d.vue.ts +30 -0
  12. package/dist/runtime/components/CrudApiSelectorField.vue +99 -0
  13. package/dist/runtime/components/CrudApiSelectorField.vue.d.ts +30 -0
  14. package/dist/runtime/components/CrudConfirmDialog.d.vue.ts +18 -0
  15. package/dist/runtime/components/CrudConfirmDialog.vue +83 -0
  16. package/dist/runtime/components/CrudConfirmDialog.vue.d.ts +18 -0
  17. package/dist/runtime/components/CrudErrorDisplay.d.vue.ts +6 -0
  18. package/dist/runtime/components/CrudErrorDisplay.vue +28 -0
  19. package/dist/runtime/components/CrudErrorDisplay.vue.d.ts +6 -0
  20. package/dist/runtime/components/CrudFilter.d.vue.ts +8 -0
  21. package/dist/runtime/components/CrudFilter.vue +348 -0
  22. package/dist/runtime/components/CrudFilter.vue.d.ts +8 -0
  23. package/dist/runtime/components/CrudFilterList.d.vue.ts +5 -0
  24. package/dist/runtime/components/CrudFilterList.vue +20 -0
  25. package/dist/runtime/components/CrudFilterList.vue.d.ts +5 -0
  26. package/dist/runtime/components/CrudFormDialog.d.vue.ts +31 -0
  27. package/dist/runtime/components/CrudFormDialog.vue +180 -0
  28. package/dist/runtime/components/CrudFormDialog.vue.d.ts +31 -0
  29. package/dist/runtime/components/CrudFormLoader.d.vue.ts +41 -0
  30. package/dist/runtime/components/CrudFormLoader.vue +238 -0
  31. package/dist/runtime/components/CrudFormLoader.vue.d.ts +41 -0
  32. package/dist/runtime/components/CrudListLoader.d.vue.ts +37 -0
  33. package/dist/runtime/components/CrudListLoader.vue +130 -0
  34. package/dist/runtime/components/CrudListLoader.vue.d.ts +37 -0
  35. package/dist/runtime/components/CrudPaginatedLoader.d.vue.ts +70 -0
  36. package/dist/runtime/components/CrudPaginatedLoader.vue +415 -0
  37. package/dist/runtime/components/CrudPaginatedLoader.vue.d.ts +70 -0
  38. package/dist/runtime/components/CrudUploadField.d.vue.ts +23 -0
  39. package/dist/runtime/components/CrudUploadField.vue +311 -0
  40. package/dist/runtime/components/CrudUploadField.vue.d.ts +23 -0
  41. package/dist/runtime/components/CrudUploadFieldSelection.d.vue.ts +18 -0
  42. package/dist/runtime/components/CrudUploadFieldSelection.vue +178 -0
  43. package/dist/runtime/components/CrudUploadFieldSelection.vue.d.ts +18 -0
  44. package/dist/runtime/composables/PublicClientApplicationSingleton.d.ts +5 -0
  45. package/dist/runtime/composables/PublicClientApplicationSingleton.js +14 -0
  46. package/dist/runtime/composables/useAlertService.d.ts +14 -0
  47. package/dist/runtime/composables/useAlertService.js +38 -0
  48. package/dist/runtime/composables/useCrudApi.d.ts +35 -0
  49. package/dist/runtime/composables/useCrudApi.js +314 -0
  50. package/dist/runtime/composables/useCrudConverters.d.ts +33 -0
  51. package/dist/runtime/composables/useCrudConverters.js +202 -0
  52. package/dist/runtime/composables/useListenerService.d.ts +7 -0
  53. package/dist/runtime/composables/useListenerService.js +43 -0
  54. package/dist/runtime/composables/useMSAuth.d.ts +13 -0
  55. package/dist/runtime/composables/useMSAuth.js +125 -0
  56. package/dist/runtime/composables/usePreviousRoute.d.ts +1 -0
  57. package/dist/runtime/composables/usePreviousRoute.js +4 -0
  58. package/dist/runtime/constants/crudAuthInterceptor.d.ts +6 -0
  59. package/dist/runtime/constants/crudAuthInterceptor.js +8 -0
  60. package/dist/runtime/plugin.client.d.ts +2 -0
  61. package/dist/runtime/plugin.client.js +11 -0
  62. package/dist/runtime/plugin.luxon.d.ts +2 -0
  63. package/dist/runtime/plugin.luxon.js +5 -0
  64. package/dist/runtime/plugins/track-previous-route.client.d.ts +2 -0
  65. package/dist/runtime/plugins/track-previous-route.client.js +10 -0
  66. package/dist/runtime/server/api/maps/autocomplete.d.ts +5 -0
  67. package/dist/runtime/server/api/maps/autocomplete.js +12 -0
  68. package/dist/runtime/server/api/maps/geocode.d.ts +5 -0
  69. package/dist/runtime/server/api/maps/geocode.js +12 -0
  70. package/dist/runtime/server/api/maps/places.d.ts +5 -0
  71. package/dist/runtime/server/api/maps/places.js +12 -0
  72. package/dist/runtime/server/tsconfig.json +3 -0
  73. package/dist/types.d.mts +9 -0
  74. package/package.json +58 -0
@@ -0,0 +1,415 @@
1
+ <script setup async>
2
+ import { useCrudApi } from "../composables/useCrudApi";
3
+ import { useCrudConverters } from "../composables/useCrudConverters";
4
+ import { usePreviousRoute } from "../composables/usePreviousRoute";
5
+ import { useRoute, useRouter } from "#app";
6
+ import CrudFilterList from "./CrudFilterList.vue";
7
+ import { computed, ref, watch, onMounted, onBeforeUnmount } from "vue";
8
+ import { useDebounceFn } from "@vueuse/core";
9
+ import { useListenerService } from "../composables/useListenerService";
10
+ import CrudFormDialog from "./CrudFormDialog.vue";
11
+ import CrudErrorDisplay from "./CrudErrorDisplay.vue";
12
+ const { parseQuery, stringifyQuery, cloneDeep } = useCrudConverters();
13
+ const props = defineProps({
14
+ fullScreenDialog: {
15
+ type: Boolean,
16
+ required: false,
17
+ default: false
18
+ },
19
+ loaderKey: {
20
+ type: String,
21
+ required: false
22
+ },
23
+ initialItem: {
24
+ type: Object,
25
+ required: false
26
+ },
27
+ path: {
28
+ type: String,
29
+ required: true
30
+ },
31
+ allowCreate: {
32
+ type: Boolean,
33
+ default: false
34
+ },
35
+ hideFilters: {
36
+ type: Boolean,
37
+ default: false
38
+ },
39
+ title: {
40
+ type: String,
41
+ required: false
42
+ },
43
+ transformItem: {
44
+ type: Function,
45
+ required: false
46
+ },
47
+ transformItems: {
48
+ type: Function,
49
+ required: false
50
+ },
51
+ beforeSave: {
52
+ type: Function,
53
+ required: false
54
+ },
55
+ beforeCreate: {
56
+ type: Function,
57
+ required: false
58
+ },
59
+ beforeUpdate: {
60
+ type: Function,
61
+ required: false
62
+ },
63
+ beforeDelete: {
64
+ type: Function,
65
+ required: false
66
+ },
67
+ perPage: {
68
+ type: Number,
69
+ required: false,
70
+ default: 10
71
+ },
72
+ updateTitle: {
73
+ type: String,
74
+ required: false,
75
+ default() {
76
+ return "Update item";
77
+ }
78
+ },
79
+ deleteTitle: {
80
+ type: String,
81
+ required: false,
82
+ default() {
83
+ return "Delete item";
84
+ }
85
+ },
86
+ createTitle: {
87
+ type: String,
88
+ required: false,
89
+ default() {
90
+ return "Create item";
91
+ }
92
+ },
93
+ customFilters: {
94
+ type: Object,
95
+ required: false,
96
+ default: () => {
97
+ }
98
+ },
99
+ await: {
100
+ type: Boolean,
101
+ required: false,
102
+ default: false
103
+ },
104
+ listeners: {
105
+ type: Array,
106
+ required: false,
107
+ default: () => []
108
+ },
109
+ noResultsText: {
110
+ type: String,
111
+ required: false,
112
+ default: "No results"
113
+ },
114
+ contentClasses: {
115
+ type: String,
116
+ required: false,
117
+ default: ""
118
+ }
119
+ });
120
+ const {
121
+ getItems,
122
+ getItem,
123
+ updateItem,
124
+ deleteItem,
125
+ createItem,
126
+ filters,
127
+ item,
128
+ originalItem,
129
+ items,
130
+ listErrors,
131
+ listPending,
132
+ formErrors,
133
+ formPending,
134
+ itemErrors,
135
+ itemPending,
136
+ currentPage,
137
+ perPage,
138
+ totalPages,
139
+ totalItems,
140
+ exportErrors,
141
+ exportPending,
142
+ exportItems,
143
+ exportUrl,
144
+ hasExportErrors,
145
+ hasListErrors
146
+ } = useCrudApi(props.path, false, props.transformItem, props.transformItems);
147
+ perPage.value = props.perPage;
148
+ const route = useRoute();
149
+ const router = useRouter();
150
+ const previousRoute = usePreviousRoute();
151
+ const action = computed(() => route.query[(props.loaderKey ?? "") + "action"]);
152
+ const actionId = computed(() => route.query[(props.loaderKey ?? "") + "actionId"]);
153
+ const dialogOpen = computed({
154
+ get: () => !!(action.value === "create" || action.value === "update" && actionId.value || action.value === "delete" && actionId.value),
155
+ set: (value) => {
156
+ if (!value) {
157
+ const hasBackState = router.options.history.state.back;
158
+ const previousRouteName = previousRoute.value?.name;
159
+ const currentRouteName = route.name;
160
+ console.log("previousRouteName", previousRouteName);
161
+ console.log("currentRouteName", currentRouteName);
162
+ if (hasBackState && previousRouteName === currentRouteName) {
163
+ router.back();
164
+ } else {
165
+ const currentQuery = { ...route.query };
166
+ if (props.loaderKey) {
167
+ delete currentQuery[`${props.loaderKey}action`];
168
+ delete currentQuery[`${props.loaderKey}actionId`];
169
+ } else {
170
+ delete currentQuery.action;
171
+ delete currentQuery.actionId;
172
+ }
173
+ const finalRoute = {
174
+ path: route.path,
175
+ query: currentQuery
176
+ };
177
+ router.replace(finalRoute);
178
+ }
179
+ }
180
+ }
181
+ });
182
+ const page = computed(() => props.loaderKey ? parseQuery(route.query[props.loaderKey])?.page ?? 1 : route.query.page ?? 1);
183
+ const filterValues = props.loaderKey ? ref({ ...props.customFilters, ...parseQuery(route.query[props.loaderKey]) }) : ref({ ...props.customFilters, ...route.query });
184
+ const onQueryChange = (page2) => {
185
+ console.error("QUERY CHANGED");
186
+ const newQuery = {};
187
+ const existingQuery = parseQuery(route.query);
188
+ for (const key in filterValues.value) {
189
+ if (filterValues.value[key] !== null && filterValues.value[key] !== "") {
190
+ newQuery[key] = filterValues.value[key];
191
+ }
192
+ }
193
+ newQuery.page = page2;
194
+ let finalQuery = {
195
+ ...existingQuery
196
+ };
197
+ if (props.loaderKey) {
198
+ finalQuery[props.loaderKey] = stringifyQuery(newQuery);
199
+ } else {
200
+ finalQuery = newQuery;
201
+ }
202
+ if (props.loaderKey) {
203
+ delete finalQuery[`${props.loaderKey}action`];
204
+ delete finalQuery[`${props.loaderKey}actionId`];
205
+ } else {
206
+ delete finalQuery.action;
207
+ delete finalQuery.actionId;
208
+ }
209
+ console.error("Setting query", finalQuery);
210
+ router.replace({ path: route.path, query: finalQuery, force: true });
211
+ };
212
+ watch(currentPage, (newVal, oldValue) => {
213
+ onQueryChange(currentPage.value);
214
+ });
215
+ watch(filterValues, (newVal, oldValue) => {
216
+ console.error("FILTERS CHANGED");
217
+ onQueryChange(1);
218
+ }, { deep: true });
219
+ watch(() => page, () => {
220
+ console.error("ROUTE CHANGED");
221
+ debouncedGet();
222
+ }, { deep: true });
223
+ watch(() => filterValues, () => {
224
+ console.error("ROUTE CHANGED");
225
+ debouncedGet();
226
+ }, { deep: true });
227
+ const debouncedGet = useDebounceFn(() => {
228
+ console.error("DEBOUNCED GET");
229
+ getItems(Number.parseInt(page.value) ?? null, null, filterValues.value);
230
+ }, 200);
231
+ const updateForm = async (id) => {
232
+ const newQuery = { query: { ...route.query } };
233
+ if (props.loaderKey) {
234
+ newQuery.query[(props.loaderKey ?? "") + "action"] = "update";
235
+ newQuery.query[(props.loaderKey ?? "") + "actionId"] = id;
236
+ } else {
237
+ newQuery.query.action = "update";
238
+ newQuery.query.actionId = id;
239
+ }
240
+ await router.push(newQuery);
241
+ };
242
+ const deleteForm = async (id) => {
243
+ const newQuery = { query: { ...route.query } };
244
+ if (props.loaderKey) {
245
+ newQuery.query[(props.loaderKey ?? "") + "action"] = "delete";
246
+ newQuery.query[(props.loaderKey ?? "") + "actionId"] = id;
247
+ } else {
248
+ newQuery.query.action = "delete";
249
+ newQuery.query.actionId = id;
250
+ }
251
+ await router.push(newQuery);
252
+ };
253
+ const createForm = () => {
254
+ item.value = props.initialItem ? cloneDeep(props.initialItem) : {};
255
+ const newQuery = { query: { ...route.query } };
256
+ if (props.loaderKey) {
257
+ newQuery.query[(props.loaderKey ?? "") + "action"] = "create";
258
+ } else {
259
+ newQuery.query.action = "create";
260
+ }
261
+ router.push(newQuery);
262
+ };
263
+ const saveAction = async () => {
264
+ formPending.value = true;
265
+ let itemToSave = JSON.parse(JSON.stringify(item.value));
266
+ if (props.beforeSave != null) {
267
+ itemToSave = await props.beforeSave(itemToSave);
268
+ }
269
+ if (action.value === "create") {
270
+ if (props.beforeCreate != null) {
271
+ itemToSave = await props.beforeCreate(itemToSave);
272
+ }
273
+ await createItem(itemToSave);
274
+ } else if (action.value === "update") {
275
+ if (props.beforeUpdate != null) {
276
+ itemToSave = await props.beforeUpdate(itemToSave);
277
+ }
278
+ await updateItem(itemToSave.id, itemToSave);
279
+ } else if (action.value === "delete") {
280
+ if (props.beforeDelete != null) {
281
+ itemToSave = await props.beforeDelete(itemToSave);
282
+ }
283
+ await deleteItem(itemToSave.id);
284
+ }
285
+ if (formErrors.value == null || Object.keys(formErrors.value).length === 0) {
286
+ const savedPath = props.path;
287
+ const savedAction = action.value;
288
+ const savedItem = item.value;
289
+ dialogOpen.value = false;
290
+ await getItems(Number.parseInt(page.value) ?? null, null, filterValues.value);
291
+ notify(savedPath + ":" + savedAction, savedItem);
292
+ }
293
+ };
294
+ if (props.await) {
295
+ await getItems(page.value ?? null, null, filterValues.value);
296
+ } else {
297
+ getItems(page.value ?? null, null, filterValues.value);
298
+ }
299
+ watch(action, (newVal, oldValue) => {
300
+ try {
301
+ if (action.value === "update" && actionId.value) {
302
+ getItem(actionId.value);
303
+ } else if (action.value === "create") {
304
+ item.value = props.initialItem ? cloneDeep(props.initialItem) : {};
305
+ } else if (action.value === "delete" && actionId.value) {
306
+ getItem(actionId.value);
307
+ }
308
+ } catch (error) {
309
+ console.error("Error in actionId watcher:", error);
310
+ }
311
+ }, { immediate: true });
312
+ const { addListener, removeLocalListenersWithEvent, notify } = useListenerService();
313
+ onMounted(() => {
314
+ props.listeners.forEach((listener) => {
315
+ addListener(listener, (data) => {
316
+ getItems(Number.parseInt(page.value) ?? null, null, filterValues.value);
317
+ });
318
+ });
319
+ });
320
+ onBeforeUnmount(() => {
321
+ props.listeners.forEach((listener) => {
322
+ removeLocalListenersWithEvent(listener);
323
+ });
324
+ });
325
+ const exportAction = async () => {
326
+ await exportItems(filterValues.value);
327
+ if (!hasExportErrors.value) {
328
+ window.open(exportUrl.value, "_blank");
329
+ }
330
+ };
331
+ </script>
332
+
333
+ <template>
334
+ <div>
335
+ <slot
336
+ name="before"
337
+ :items="items"
338
+ :pending="listPending"
339
+ :create-action="createForm"
340
+ :update-action="updateForm"
341
+ :delete-action="deleteForm"
342
+ :form-pending="formPending"
343
+ :export-action="exportAction"
344
+ :export-pending="exportPending"
345
+ :export-errors="exportErrors"
346
+ >
347
+ </slot>
348
+ <div :class="contentClasses">
349
+ <v-progress-linear :active="listPending" indeterminate color="primary"></v-progress-linear>
350
+ <v-row>
351
+ <v-col v-if="!hideFilters && filters.length > 0" cols="12" md="3">
352
+ <div>
353
+ <slot name="filters" :filters="filters">
354
+ <crud-filter-list v-model="filterValues" :filters="filters"></crud-filter-list>
355
+ </slot>
356
+ </div>
357
+ </v-col>
358
+ <v-col cols="12" :md="hideFilters || filters.length === 0 ? '12' : '9'">
359
+ <div class="">
360
+ <slot v-if="!listPending && totalItems === 0 && !hasListErrors" name="empty" :create-action="createForm">
361
+ <v-alert icon="mdi-playlist-remove" type="info" class="pa-5">
362
+ {{ noResultsText }}
363
+ </v-alert>
364
+ </slot>
365
+ <slot v-if="hasListErrors" name="error" :errors="listErrors">
366
+ <crud-error-display :errors="listErrors"></crud-error-display>
367
+ </slot>
368
+ <slot
369
+ v-if="totalItems > 0"
370
+ :items="items"
371
+ :pending="listPending"
372
+ :update-action="updateForm"
373
+ :delete-action="deleteForm"
374
+ :form-pending="formPending"
375
+ >
376
+ </slot>
377
+ </div>
378
+ <div v-if="totalItems > 0 && totalPages > 1" class="pa-3">
379
+ <span class="d-none d-md-block pa-3">Page {{ currentPage }} of {{ totalPages }} pages with {{ totalItems }} items</span>
380
+ <v-pagination
381
+ v-model="currentPage"
382
+ :disabled="listPending"
383
+ :length="totalPages"
384
+ @input="getItems()"></v-pagination>
385
+ </div>
386
+ </v-col>
387
+ </v-row>
388
+ </div>
389
+
390
+ <crud-form-dialog
391
+ v-model:dialog="dialogOpen"
392
+ v-model="item"
393
+ :original-item="originalItem"
394
+ :action="action"
395
+ :form-errors="formErrors"
396
+ :item-errors="itemErrors"
397
+ :form-pending="formPending"
398
+ :item-pending="itemPending"
399
+ :create-title="createTitle"
400
+ :update-title="updateTitle"
401
+ :delete-title="deleteTitle"
402
+ :save-action="saveAction"
403
+ :full-screen="fullScreenDialog"
404
+ >
405
+ <slot
406
+ name="form" :action="action"
407
+ :item="item"
408
+ :original-item="originalItem"
409
+ :errors="formErrors">
410
+
411
+ </slot>
412
+ </crud-form-dialog>
413
+
414
+ </div>
415
+ </template>
@@ -0,0 +1,70 @@
1
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
2
+ export default _default;
3
+ type __VLS_WithSlots<T, S> = T & (new () => {
4
+ $slots: S;
5
+ });
6
+ declare const __VLS_component: import("vue").DefineComponent<{}, {
7
+ path: string;
8
+ customFilters: Record<string, any>;
9
+ perPage: number;
10
+ await: boolean;
11
+ listeners: unknown[];
12
+ noResultsText: string;
13
+ createTitle: string;
14
+ updateTitle: string;
15
+ deleteTitle: string;
16
+ allowCreate: boolean;
17
+ hideFilters: boolean;
18
+ fullScreenDialog: boolean;
19
+ contentClasses: string;
20
+ title?: string | undefined;
21
+ loaderKey?: string | undefined;
22
+ initialItem?: Record<string, any> | undefined;
23
+ transformItem?: Function | undefined;
24
+ transformItems?: Function | undefined;
25
+ beforeSave?: Function | undefined;
26
+ beforeCreate?: Function | undefined;
27
+ beforeUpdate?: Function | undefined;
28
+ beforeDelete?: Function | undefined;
29
+ $props: any;
30
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
31
+ type __VLS_Slots = {
32
+ before?: ((props: {
33
+ items: any;
34
+ pending: any;
35
+ createAction: any;
36
+ updateAction: any;
37
+ deleteAction: any;
38
+ formPending: any;
39
+ exportAction: any;
40
+ exportPending: any;
41
+ exportErrors: any;
42
+ }) => any) | undefined;
43
+ } & {
44
+ filters?: ((props: {
45
+ filters: any;
46
+ }) => any) | undefined;
47
+ } & {
48
+ empty?: ((props: {
49
+ createAction: any;
50
+ }) => any) | undefined;
51
+ } & {
52
+ error?: ((props: {
53
+ errors: any;
54
+ }) => any) | undefined;
55
+ } & {
56
+ default?: ((props: {
57
+ items: any;
58
+ pending: any;
59
+ updateAction: any;
60
+ deleteAction: any;
61
+ formPending: any;
62
+ }) => any) | undefined;
63
+ } & {
64
+ form?: ((props: {
65
+ action: any;
66
+ item: any;
67
+ originalItem: any;
68
+ errors: any;
69
+ }) => any) | undefined;
70
+ };
@@ -0,0 +1,23 @@
1
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
2
+ export default _default;
3
+ type __VLS_WithSlots<T, S> = T & (new () => {
4
+ $slots: S;
5
+ });
6
+ declare const __VLS_component: import("vue").DefineComponent<{}, {
7
+ path: string;
8
+ customFilters: Record<string, any>;
9
+ multiple: boolean;
10
+ disabled: boolean;
11
+ mimeKey: string;
12
+ fileNameKey: string;
13
+ replaceable: boolean;
14
+ isPublic: boolean;
15
+ additionalPostData: Record<string, any>;
16
+ title?: any;
17
+ $props: any;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
19
+ type __VLS_Slots = {
20
+ additionalActions?: ((props: {
21
+ item: any;
22
+ }) => any) | undefined;
23
+ };