@iservice365/layer-common 0.0.6 → 0.2.0

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 (91) hide show
  1. package/.playground/app.vue +7 -2
  2. package/.playground/pages/feedback.vue +30 -0
  3. package/CHANGELOG.md +12 -0
  4. package/components/Chat/Bubbles.vue +53 -0
  5. package/components/Chat/Information.vue +187 -0
  6. package/components/Chat/ListCard.vue +62 -0
  7. package/components/Chat/Message.vue +149 -0
  8. package/components/Chat/Navigation.vue +150 -0
  9. package/components/ConfirmDialog.vue +66 -0
  10. package/components/Container/Standard.vue +33 -0
  11. package/components/Feedback/Form.vue +136 -0
  12. package/components/FeedbackDetail.vue +465 -0
  13. package/components/FeedbackMain.vue +454 -0
  14. package/components/FormDialog.vue +65 -0
  15. package/components/Input/File.vue +203 -0
  16. package/components/Input/ListGroupSelection.vue +96 -0
  17. package/components/Input/NewDate.vue +123 -0
  18. package/components/Input/Number.vue +124 -0
  19. package/components/InvitationMain.vue +284 -0
  20. package/components/Layout/Header.vue +28 -4
  21. package/components/ListView.vue +87 -0
  22. package/components/MemberMain.vue +459 -0
  23. package/components/RolePermissionFormCreate.vue +161 -0
  24. package/components/RolePermissionFormPreviewUpdate.vue +183 -0
  25. package/components/RolePermissionMain.vue +361 -0
  26. package/components/ServiceProviderFormCreate.vue +154 -0
  27. package/components/ServiceProviderMain.vue +195 -0
  28. package/components/SignaturePad.vue +73 -0
  29. package/components/SpecificAttr.vue +53 -0
  30. package/components/SwitchContext.vue +26 -5
  31. package/components/TableList.vue +150 -0
  32. package/components/TableListSecondary.vue +164 -0
  33. package/components/WorkOrder/Create.vue +197 -0
  34. package/components/WorkOrder/ListView.vue +96 -0
  35. package/components/WorkOrder/Main.vue +308 -0
  36. package/components/Workorder.vue +1 -0
  37. package/composables/useAddress.ts +107 -0
  38. package/composables/useCommonPermission.ts +130 -0
  39. package/composables/useCustomer.ts +113 -0
  40. package/composables/useFeedback.ts +117 -0
  41. package/composables/useFile.ts +40 -0
  42. package/composables/useInvoice.ts +18 -0
  43. package/composables/useLocal.ts +24 -4
  44. package/composables/useLocalAuth.ts +58 -14
  45. package/composables/useLocalSetup.ts +52 -0
  46. package/composables/useMember.ts +104 -0
  47. package/composables/useOrg.ts +76 -92
  48. package/composables/usePaymentMethod.ts +101 -0
  49. package/composables/usePrice.ts +15 -0
  50. package/composables/usePromoCode.ts +36 -0
  51. package/composables/useRole.ts +38 -7
  52. package/composables/useServiceProvider.ts +218 -0
  53. package/composables/useSite.ts +108 -0
  54. package/composables/useSubscription.ts +149 -0
  55. package/composables/useUser.ts +38 -14
  56. package/composables/useUtils.ts +218 -6
  57. package/composables/useVerification.ts +33 -0
  58. package/composables/useWorkOrder.ts +68 -0
  59. package/middleware/01.auth.ts +11 -0
  60. package/middleware/02.org.ts +18 -0
  61. package/middleware/03.customer.ts +13 -0
  62. package/nuxt.config.ts +2 -1
  63. package/package.json +7 -3
  64. package/pages/index.vue +3 -0
  65. package/pages/payment-method-linked.vue +31 -0
  66. package/pages/require-customer.vue +56 -0
  67. package/pages/require-organization-membership.vue +47 -0
  68. package/pages/unauthorized.vue +29 -0
  69. package/plugins/API.ts +1 -3
  70. package/plugins/iconify.client.ts +5 -0
  71. package/plugins/vuetify.ts +2 -0
  72. package/public/bg-camera.jpg +0 -0
  73. package/public/bg-city.jpg +0 -0
  74. package/public/bg-condo.jpg +0 -0
  75. package/public/images/icons/delete-icon.png +0 -0
  76. package/public/sprite.svg +1 -0
  77. package/types/address.d.ts +13 -0
  78. package/types/customer.d.ts +15 -0
  79. package/types/feedback.d.ts +63 -0
  80. package/types/local.d.ts +46 -38
  81. package/types/member.d.ts +21 -0
  82. package/types/org.d.ts +13 -0
  83. package/types/permission.d.ts +1 -0
  84. package/types/price.d.ts +17 -0
  85. package/types/promo-code.d.ts +19 -0
  86. package/types/service-provider.d.ts +15 -0
  87. package/types/site.d.ts +13 -0
  88. package/types/subscription.d.ts +23 -0
  89. package/types/user.d.ts +19 -0
  90. package/types/verification.d.ts +20 -0
  91. package/types/work-order.d.ts +40 -0
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <v-dialog v-model="dialog" max-width="400" persistent>
3
+ <v-card class="rounded-xl pa-6" elevation="2">
4
+ <!-- Close Button -->
5
+ <v-btn
6
+ icon
7
+ variant="text"
8
+ size="small"
9
+ class="position-absolute"
10
+ style="top: 8px; right: 8px"
11
+ @click="closeDialog"
12
+ >
13
+ <v-icon>mdi-close</v-icon>
14
+ </v-btn>
15
+
16
+ <v-card-text class="text-center">
17
+ <v-row v-if="imageSrc" justify="center" class="py-10">
18
+ <v-img height="120" :src="imageSrc" alt="Dialog Image" contain />
19
+ </v-row>
20
+
21
+ <!-- Title Slot -->
22
+ <div class="text-h6 font-weight-medium mb-2">
23
+ <slot name="title"></slot>
24
+ </div>
25
+
26
+ <!-- Description Slot -->
27
+ <div class="text-body-1 mb-4">
28
+ <slot name="description"></slot>
29
+ </div>
30
+ </v-card-text>
31
+
32
+ <!-- Footer Slot -->
33
+ <v-card-actions>
34
+ <slot name="footer"></slot>
35
+ </v-card-actions>
36
+ </v-card>
37
+ </v-dialog>
38
+ </template>
39
+
40
+ <script lang="ts" setup>
41
+ const props = defineProps({
42
+ modelValue: {
43
+ type: Boolean,
44
+ default: false,
45
+ },
46
+ imageSrc: {
47
+ type: String,
48
+ default: "",
49
+ },
50
+ loading: {
51
+ type: Boolean,
52
+ default: false,
53
+ },
54
+ });
55
+
56
+ const emit = defineEmits(["update:modelValue", "submit"]);
57
+
58
+ const dialog = computed({
59
+ get: () => props.modelValue,
60
+ set: (value) => emit("update:modelValue", value),
61
+ });
62
+
63
+ function closeDialog() {
64
+ dialog.value = false;
65
+ }
66
+ </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12">
4
+ <span
5
+ class="font-weight-medium text-subtitle-1 text-decoration-underline cursor-pointer"
6
+ @click="emit('back')"
7
+ >
8
+ Back
9
+ </span>
10
+ </v-col>
11
+ <v-col cols="12">
12
+ <span class="text-h5 font-weight-medium">{{ props.title }}</span>
13
+ </v-col>
14
+
15
+ <v-col cols="12" class="mt-4">
16
+ <slot name="default"> </slot>
17
+ </v-col>
18
+
19
+ <v-col cols="12" class="mt-4">
20
+ <slot name="footer"> </slot>
21
+ </v-col>
22
+ </v-row>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ const emit = defineEmits(["back"]);
27
+ const props = defineProps({
28
+ title: {
29
+ type: String,
30
+ default: "",
31
+ },
32
+ });
33
+ </script>
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <FormDialog v-model="dialog" @close="closeDialog">
3
+ <template #title>
4
+ <span class="text-h6 font-weight-medium pt-1 text-capitalize">
5
+ {{ isEditMode ? "Edit Feedback" : "Create Feedback" }}
6
+ </span>
7
+ </template>
8
+
9
+ <InputFile
10
+ :attachments="displayedAttachments"
11
+ :errored-images="localErroredImages"
12
+ :max-files="maxFiles"
13
+ @add="handleFileAdded"
14
+ @delete="deleteFile"
15
+ @errored="onImageError"
16
+ />
17
+
18
+ <v-text-field
19
+ label="Subject"
20
+ variant="outlined"
21
+ density="compact"
22
+ v-model="localFeedback.subject"
23
+ class="mb-2"
24
+ dense
25
+ />
26
+
27
+ <v-autocomplete
28
+ v-model="localFeedback.category"
29
+ :items="categories"
30
+ item-title="title"
31
+ item-value="value"
32
+ item-props
33
+ label="Category"
34
+ variant="outlined"
35
+ density="compact"
36
+ class="mb-2"
37
+ clearable
38
+ />
39
+
40
+ <v-text-field
41
+ label="Location"
42
+ variant="outlined"
43
+ density="compact"
44
+ v-model="localFeedback.location"
45
+ />
46
+
47
+ <v-textarea
48
+ label="Description"
49
+ variant="outlined"
50
+ density="compact"
51
+ auto-grow
52
+ rows="4"
53
+ v-model="localFeedback.description"
54
+ />
55
+
56
+ <template #footer>
57
+ <v-btn
58
+ color="primary-button"
59
+ class="text-capitalize mb-4"
60
+ block
61
+ height="40"
62
+ :loading="loading"
63
+ @click="submitFeedback"
64
+ >
65
+ Submit
66
+ </v-btn>
67
+ </template>
68
+ </FormDialog>
69
+ </template>
70
+
71
+ <script setup lang="ts">
72
+ const props = defineProps<{
73
+ modelValue: boolean;
74
+ feedback: TFeedbackCreate;
75
+ isEditMode: boolean;
76
+ loading: boolean;
77
+ categories: { title: string; value: string }[];
78
+ erroredImages?: string[];
79
+ maxFiles?: number;
80
+ }>();
81
+
82
+ const emit = defineEmits<{
83
+ (e: "update:modelValue", value: boolean): void;
84
+ (e: "update:feedback", value: TFeedbackCreate): void;
85
+ (e: "close"): void;
86
+ (e: "submit", payload: TFeedbackCreate): void;
87
+ (e: "fileAdded", file: File): void;
88
+ (e: "fileDeleted", url: string): void;
89
+ }>();
90
+
91
+ const dialog = computed({
92
+ get: () => props.modelValue,
93
+ set: (val) => emit("update:modelValue", val),
94
+ });
95
+
96
+ const localFeedback = computed({
97
+ get: () => props.feedback,
98
+ set: (val) => emit("update:feedback", val),
99
+ });
100
+
101
+ const localErroredImages = ref<string[]>(props.erroredImages || []);
102
+
103
+ watch(
104
+ () => props.erroredImages,
105
+ (val) => {
106
+ if (val) localErroredImages.value = val;
107
+ }
108
+ );
109
+
110
+ const displayedAttachments = computed(() => {
111
+ return localFeedback.value.attachments || [];
112
+ });
113
+
114
+ function handleFileAdded(file: File) {
115
+ emit("fileAdded", file);
116
+ }
117
+
118
+ function deleteFile(url: string) {
119
+ emit("fileDeleted", url);
120
+ }
121
+
122
+ function onImageError(file: string) {
123
+ if (!localErroredImages.value.includes(file)) {
124
+ localErroredImages.value.push(file);
125
+ }
126
+ }
127
+
128
+ function closeDialog() {
129
+ dialog.value = false;
130
+ emit("close");
131
+ }
132
+
133
+ function submitFeedback() {
134
+ emit("submit", { ...localFeedback.value });
135
+ }
136
+ </script>
@@ -0,0 +1,465 @@
1
+ <template>
2
+ <v-row no-gutters class="fill-height" align="stretch">
3
+ <v-col cols="12" md="3" class="fill-height">
4
+ <ChatNavigation
5
+ :title="'Feedbacks'"
6
+ :items="items"
7
+ @select="handleSelectFeedback"
8
+ @search="_getFeedbacks"
9
+ />
10
+ </v-col>
11
+
12
+ <v-col cols="12" md="6" class="fill-height">
13
+ <ChatMessage />
14
+ </v-col>
15
+
16
+ <v-col cols="12" md="3" class="fill-height">
17
+ <ChatInformation
18
+ :item="feedback"
19
+ :service-providers="_serviceProviders"
20
+ @edit="openEditDialog"
21
+ @mark-complete-request="showCompleteDialog = true"
22
+ @delete="showDeleteDialog = true"
23
+ />
24
+ </v-col>
25
+ </v-row>
26
+
27
+ <FeedbackForm
28
+ v-model="showCreateDialog"
29
+ :feedback="_feedback"
30
+ @update:feedback="(val: TFeedbackUpdate) => (_feedback = val)"
31
+ :is-edit-mode="isEditMode"
32
+ :loading="submitting"
33
+ :categories="categories"
34
+ :theme="'light'"
35
+ :errored-images="[]"
36
+ :max-files="5"
37
+ :message-fn="(msg:any) => console.log(msg)"
38
+ @close="handleCloseDialog"
39
+ @submit="submitFeedback"
40
+ @file-added="handleFileAdded"
41
+ @file-deleted="deleteFile"
42
+ />
43
+
44
+ <v-dialog v-model="showCompleteDialog" max-width="450px">
45
+ <v-card class="rounded-xl pa-0 d-flex flex-column" style="max-height: 90vh">
46
+ <!-- Title Bar -->
47
+ <v-card-title class="d-flex justify-space-between align-center pa-4">
48
+ <span class="text-h6">Completion Confirmation</span>
49
+ <v-icon @click="showCompleteDialog = false">mdi-close</v-icon>
50
+ </v-card-title>
51
+
52
+ <!-- Scrollable form content -->
53
+ <v-card-text class="px-4 pt-0 overflow-y-auto" style="flex: 1 1 auto">
54
+ <InputFile
55
+ ref="completionFileInput"
56
+ :attachments="completionAttachments"
57
+ :max-files="3"
58
+ :errored-images="[]"
59
+ @add="handleCompletionFileAdded"
60
+ @delete="handleCompletionFileDeleted"
61
+ />
62
+
63
+ <div class="mb-4">
64
+ <label class="text-subtitle-2 mb-1 d-block">
65
+ Full Name <span class="text-error">*</span>
66
+ </label>
67
+ <v-text-field
68
+ placeholder="Enter your full name"
69
+ variant="outlined"
70
+ density="comfortable"
71
+ v-model="feedback.name"
72
+ hide-details
73
+ class="mt-1"
74
+ ></v-text-field>
75
+ </div>
76
+
77
+ <div class="mb-4">
78
+ <label class="text-subtitle-2 mb-1 d-block">
79
+ Signature <span class="text-error">*</span>
80
+ </label>
81
+ <SignaturePad v-model="feedback.signature" />
82
+ </div>
83
+ </v-card-text>
84
+
85
+ <v-card-actions class="px-4 pb-4">
86
+ <v-btn
87
+ block
88
+ color="primary-button"
89
+ size="large"
90
+ variant="flat"
91
+ :loading="completeLoading"
92
+ @click="markFeedbackAsComplete"
93
+ class="text-capitalize"
94
+ >
95
+ Mark as complete
96
+ </v-btn>
97
+ </v-card-actions>
98
+ </v-card>
99
+ </v-dialog>
100
+
101
+ <ConfirmDialog
102
+ v-model="showDeleteDialog"
103
+ :loading="deleteLoading"
104
+ @submit="deleteFeedback"
105
+ :image-src="'/images/icons/delete-icon.png'"
106
+ >
107
+ <template #image>
108
+ <v-img
109
+ height="120"
110
+ src="/images/icons/delete-icon.png"
111
+ alt="Delete Icon"
112
+ contain
113
+ />
114
+ </template>
115
+
116
+ <template #title> Are you sure you want to delete this feedback? </template>
117
+
118
+ <template #footer>
119
+ <v-btn
120
+ color="primary-button"
121
+ variant="flat"
122
+ class="font-weight-bold py-5 d-flex text-capitalize align-center justify-center"
123
+ @click="deleteFeedback"
124
+ :loading="deleteLoading"
125
+ block
126
+ >
127
+ Confirm
128
+ </v-btn>
129
+ </template>
130
+ </ConfirmDialog>
131
+
132
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
133
+ </template>
134
+
135
+ <script lang="ts" setup>
136
+ const route = useRoute();
137
+ const id = route.params.id;
138
+
139
+ const {
140
+ feedback,
141
+ getFeedbackById,
142
+ feedbacks,
143
+ getFeedbacks: _getFeedbacks,
144
+ updateFeedback,
145
+ updateFeedbackServiceProvider,
146
+ updateStatusComplete,
147
+ deleteFeedback: _deleteFeedback,
148
+ } = useFeedback();
149
+
150
+ const { getServiceProviderNames } = useServiceProvider();
151
+
152
+ const _getFeedbackById = async () => {
153
+ try {
154
+ const data = await getFeedbackById(id as string);
155
+ feedback.value = data;
156
+ } catch (error) {
157
+ console.error("Error fetching feedback:", error);
158
+ }
159
+ };
160
+ const page = ref(1);
161
+ const pages = ref(0);
162
+ const pageRange = ref("-- - -- of --");
163
+ const items = ref<Array<Record<string, any>>>([]);
164
+
165
+ const {
166
+ data: getAllFeedbackReq,
167
+ status: getAllReqStatus,
168
+ refresh: getAllReqRefresh,
169
+ } = useLazyAsyncData("get-all-feedbacks", () =>
170
+ _getFeedbacks({
171
+ page: page.value,
172
+ organization: route.params.org as string,
173
+ site: route.params.site as string,
174
+ })
175
+ );
176
+
177
+ const loading = computed(() => getAllReqStatus.value === "pending");
178
+
179
+ watchEffect(() => {
180
+ if (getAllFeedbackReq.value) {
181
+ items.value = getAllFeedbackReq.value.items;
182
+ pages.value = getAllFeedbackReq.value.pages;
183
+ pageRange.value = getAllFeedbackReq.value.pageRange;
184
+ }
185
+ });
186
+
187
+ const handleSelectFeedback = async (id: string) => {
188
+ try {
189
+ await useRouter().push({ params: { id } });
190
+
191
+ const data = await getFeedbackById(id);
192
+ feedback.value = data;
193
+ } catch (error) {
194
+ console.error("Error selecting feedback:", error);
195
+ }
196
+ };
197
+
198
+ _getFeedbackById();
199
+
200
+ const _serviceProviders = ref<TServiceProviderName[]>([]);
201
+
202
+ const _getServiceProviderNames = async () => {
203
+ try {
204
+ const response = await getServiceProviderNames();
205
+ if (!response) return;
206
+
207
+ _serviceProviders.value = response.items.map((provider) => ({
208
+ _id: provider._id as string,
209
+ name: provider.name,
210
+ }));
211
+ } catch (error) {
212
+ console.error("Error fetching service providers:", error);
213
+ }
214
+ };
215
+
216
+ _getServiceProviderNames();
217
+
218
+ const showCreateDialog = ref(false);
219
+ const showCompleteDialog = ref(false);
220
+ const showDeleteDialog = ref(false);
221
+ const completeNotes = ref("");
222
+
223
+ const completionAttachments = ref<string[]>([]);
224
+
225
+ const completeLoading = ref(false);
226
+ const deleteLoading = ref(false);
227
+
228
+ const isEditMode = ref(false);
229
+
230
+ const _feedback = ref<TFeedbackCreate>({
231
+ attachments: [],
232
+ category: "",
233
+ subject: "",
234
+ location: "",
235
+ description: "",
236
+ });
237
+
238
+ const submitting = ref(false);
239
+ const message = ref("");
240
+ const messageColor = ref("");
241
+ const messageSnackbar = ref(false);
242
+
243
+ function showMessage(msg: string, color: string) {
244
+ message.value = msg;
245
+ messageColor.value = color;
246
+ messageSnackbar.value = true;
247
+ }
248
+
249
+ function openEditDialog() {
250
+ isEditMode.value = true;
251
+ _feedback.value = { ...feedback.value };
252
+ showCreateDialog.value = true;
253
+ }
254
+
255
+ function handleCloseDialog() {
256
+ resetFeedbackForm();
257
+ isEditMode.value = false;
258
+ showCreateDialog.value = false;
259
+ }
260
+
261
+ const { addFile, deleteFile: _deleteFile } = useFile();
262
+
263
+ async function handleFileAdded(file: File) {
264
+ try {
265
+ const res = await addFile(file);
266
+
267
+ if (res?.id && res?.name) {
268
+ const url = `https://seven365-storage.sgp1.cdn.digitaloceanspaces.com/dev/${res.id}`;
269
+
270
+ _feedback.value.attachments = _feedback.value.attachments ?? [];
271
+ _feedback.value.attachments.push(url);
272
+ }
273
+ } catch (error) {
274
+ console.error("Error uploading file:", error);
275
+ showMessage("Failed to upload file", "error");
276
+ }
277
+ }
278
+
279
+ async function deleteFile(value: string) {
280
+ try {
281
+ await _deleteFile(value);
282
+ _feedback.value.attachments = (_feedback.value.attachments ?? []).filter(
283
+ (file) => file !== value
284
+ );
285
+ } catch (error) {
286
+ showMessage("Failed to delete file", "error");
287
+ }
288
+ }
289
+
290
+ const resetFeedbackForm = () => {
291
+ _feedback.value = {
292
+ attachments: [],
293
+ category: "",
294
+ subject: "",
295
+ location: "",
296
+ description: "",
297
+ };
298
+ };
299
+
300
+ const completionFileInput = ref<any>(null);
301
+ const completionFileNames = ref<Record<string, string>>({});
302
+
303
+ async function handleCompletionFileAdded(file: File) {
304
+ try {
305
+ file.name;
306
+
307
+ const res = await addFile(file);
308
+
309
+ if (res?.id && res?.name) {
310
+ const url = `https://seven365-storage.sgp1.cdn.digitaloceanspaces.com/dev/${res.id}`;
311
+
312
+ if (!completionAttachments.value) {
313
+ completionAttachments.value = [];
314
+ }
315
+
316
+ completionAttachments.value.push(url);
317
+
318
+ completionFileNames.value[url] = res.name;
319
+
320
+ if (completionFileInput.value) {
321
+ completionFileInput.value.updateFileName(url, res.name);
322
+ }
323
+ }
324
+ } catch (error) {
325
+ console.error("Error uploading completion file:", error);
326
+ showMessage("Failed to upload file", "error");
327
+ }
328
+ }
329
+
330
+ async function handleCompletionFileDeleted(value: string) {
331
+ try {
332
+ await _deleteFile(value);
333
+
334
+ completionAttachments.value = completionAttachments.value || [];
335
+
336
+ completionAttachments.value = completionAttachments.value.filter(
337
+ (file) => file !== value
338
+ );
339
+ } catch (error) {
340
+ console.log(error);
341
+ showMessage("Failed to delete file", "error");
342
+ }
343
+ }
344
+
345
+ const resetCompletionForm = () => {
346
+ completionAttachments.value = [];
347
+
348
+ if (completionFileInput.value) {
349
+ completionFileInput.value.clearFiles?.();
350
+ }
351
+
352
+ feedback.value.name = "";
353
+ feedback.value.signature = "";
354
+ };
355
+
356
+ const { serviceProviderCategories, getServiceProviderCategories } =
357
+ useServiceProvider();
358
+
359
+ const categories = computed(() =>
360
+ serviceProviderCategories.value.map((name) => ({
361
+ title: name.charAt(0).toUpperCase() + name.slice(1),
362
+ value: name,
363
+ }))
364
+ );
365
+
366
+ getServiceProviderCategories();
367
+ async function markFeedbackAsComplete() {
368
+ if (!feedback.value?._id) {
369
+ console.warn("Update called without valid feedback ID.");
370
+ return;
371
+ }
372
+
373
+ if (!feedback.value.name || !feedback.value.signature) {
374
+ console.warn("Signature or name is missing:", feedback.value.signature);
375
+ showMessage("Please provide both name and signature.", "error");
376
+ return;
377
+ }
378
+
379
+ try {
380
+ completeLoading.value = true;
381
+
382
+ const payload: TFeedbackStatusComplete = {
383
+ name: feedback.value.name,
384
+ signature: feedback.value.signature,
385
+ attachments: completionAttachments.value || [],
386
+ };
387
+
388
+ const response = await updateStatusComplete(feedback.value._id, payload);
389
+ const message = response?.message || "Feedback marked as complete";
390
+ showMessage(message, "success");
391
+
392
+ showCompleteDialog.value = false;
393
+ resetCompletionForm();
394
+ await _getFeedbackById();
395
+ } catch (error) {
396
+ showMessage("Failed to mark feedback as complete", "error");
397
+ console.error("Error updating feedback status:", error);
398
+ } finally {
399
+ completeLoading.value = false;
400
+ }
401
+ }
402
+
403
+ async function deleteFeedback() {
404
+ if (!feedback.value?._id) {
405
+ console.warn("Delete called without valid feedback ID.");
406
+ return;
407
+ }
408
+
409
+ try {
410
+ deleteLoading.value = true;
411
+
412
+ const response = await _deleteFeedback(feedback.value._id);
413
+ const message = response?.message || "Feedback deleted successfully";
414
+ showMessage(message, "success");
415
+
416
+ showDeleteDialog.value = false;
417
+ await _getFeedbacks();
418
+
419
+ const org = useRoute().params.org || "customer1";
420
+ const site = useRoute().params.site || "site1";
421
+
422
+ await useRouter().push({
423
+ name: "org-site-feedbacks",
424
+ params: { org, site },
425
+ });
426
+ } catch (error) {
427
+ showMessage("Failed to delete feedback", "error");
428
+ console.error("Error deleting feedback:", error);
429
+ } finally {
430
+ deleteLoading.value = false;
431
+ }
432
+ }
433
+
434
+ async function submitFeedback(payload: TFeedbackUpdate) {
435
+ if (!isEditMode.value || !feedback.value?._id) {
436
+ console.warn("Update called without valid edit mode or feedback ID.");
437
+ return;
438
+ }
439
+
440
+ try {
441
+ submitting.value = true;
442
+
443
+ const payload = {
444
+ category: _feedback.value.category,
445
+ subject: _feedback.value.subject,
446
+ location: _feedback.value.location,
447
+ description: _feedback.value.description,
448
+ };
449
+
450
+ const response = await updateFeedback(feedback.value._id, payload);
451
+ const message = response?.message || "Feedback updated successfully";
452
+ showMessage(message, "success");
453
+
454
+ showCreateDialog.value = false;
455
+ await _getFeedbackById();
456
+
457
+ resetFeedbackForm();
458
+ } catch (error) {
459
+ showMessage("Something went wrong", "error");
460
+ console.error("Error updating feedback:", error);
461
+ } finally {
462
+ submitting.value = false;
463
+ }
464
+ }
465
+ </script>