@7365admin1/layer-common 1.8.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 (198) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.editorconfig +12 -0
  4. package/.github/workflows/main.yml +17 -0
  5. package/.github/workflows/publish.yml +39 -0
  6. package/.nuxtrc +1 -0
  7. package/.playground/app.vue +41 -0
  8. package/.playground/eslint.config.mjs +6 -0
  9. package/.playground/nuxt.config.ts +22 -0
  10. package/.playground/pages/feedback.vue +30 -0
  11. package/CHANGELOG.md +263 -0
  12. package/README.md +73 -0
  13. package/app.vue +3 -0
  14. package/components/AccessCardAddForm.vue +363 -0
  15. package/components/AccessManagement.vue +420 -0
  16. package/components/Avatar/Main.vue +68 -0
  17. package/components/BillingMain.vue +66 -0
  18. package/components/BtnUploadFile.vue +139 -0
  19. package/components/BuildingForm.vue +303 -0
  20. package/components/BuildingManagement/buildings.vue +335 -0
  21. package/components/BuildingManagement/units.vue +350 -0
  22. package/components/BuildingUnitFormAdd.vue +441 -0
  23. package/components/BuildingUnitFormEdit.vue +429 -0
  24. package/components/CameraForm.vue +264 -0
  25. package/components/CameraMain.vue +352 -0
  26. package/components/Card/DeleteConfirmation.vue +51 -0
  27. package/components/Card/MemberInfoSummary.vue +44 -0
  28. package/components/Card/Toggle.vue +25 -0
  29. package/components/Chat/Bubbles.vue +53 -0
  30. package/components/Chat/Information.vue +416 -0
  31. package/components/Chat/ListCard.vue +62 -0
  32. package/components/Chat/Message.vue +158 -0
  33. package/components/Chat/Navigation.vue +150 -0
  34. package/components/ConfirmDialog.vue +66 -0
  35. package/components/Container/Standard.vue +33 -0
  36. package/components/DashboardPlaceholder.vue +1524 -0
  37. package/components/Dialog/DeleteConfirmation.vue +51 -0
  38. package/components/Dialog/ReplaceAutofillPrompt.vue +49 -0
  39. package/components/Dialog/UpdateMoreAction.vue +103 -0
  40. package/components/DocumentForm.vue +187 -0
  41. package/components/DocumentManagement.vue +376 -0
  42. package/components/Editor.vue +95 -0
  43. package/components/EntryPassMain.vue +518 -0
  44. package/components/Feedback/Form.vue +173 -0
  45. package/components/FeedbackDetail.vue +599 -0
  46. package/components/FeedbackMain.vue +588 -0
  47. package/components/FormDialog.vue +65 -0
  48. package/components/ImageCarousel.vue +138 -0
  49. package/components/Input/Date.vue +177 -0
  50. package/components/Input/DateTimePicker.vue +131 -0
  51. package/components/Input/File.vue +236 -0
  52. package/components/Input/FileV2.vue +234 -0
  53. package/components/Input/InputPhoneNumberV2.vue +164 -0
  54. package/components/Input/ListGroupSelection.vue +96 -0
  55. package/components/Input/NRICNumber.vue +53 -0
  56. package/components/Input/NewDate.vue +123 -0
  57. package/components/Input/Number.vue +124 -0
  58. package/components/Input/Password.vue +22 -0
  59. package/components/Input/PhoneNumber.vue +188 -0
  60. package/components/Input/VehicleNumber.vue +49 -0
  61. package/components/InputLabel.vue +22 -0
  62. package/components/InvitationForm.vue +359 -0
  63. package/components/InvitationMain.vue +310 -0
  64. package/components/Layout/Header.vue +129 -0
  65. package/components/Layout/NavigationDrawer.vue +44 -0
  66. package/components/ListItem.vue +35 -0
  67. package/components/ListView.vue +87 -0
  68. package/components/LocalPagination.vue +31 -0
  69. package/components/MemberMain.vue +459 -0
  70. package/components/NFC/NFCPatrolReportMain.vue +591 -0
  71. package/components/NFC/NFCPatrolRouteForm.vue +596 -0
  72. package/components/NFC/NFCPatrolRouteMain.vue +539 -0
  73. package/components/NFC/NFCTagForm.vue +236 -0
  74. package/components/NFC/NFCTagMain.vue +337 -0
  75. package/components/NFC/PatrolSettings.vue +130 -0
  76. package/components/NavigationItem.vue +83 -0
  77. package/components/NumberSettingField.vue +107 -0
  78. package/components/OnlineFormConfigurationForm.vue +290 -0
  79. package/components/OnlineFormsConfiguration.vue +429 -0
  80. package/components/PeopleForm.vue +452 -0
  81. package/components/PlaceholderComponent.vue +34 -0
  82. package/components/RolePermissionFormCreate.vue +161 -0
  83. package/components/RolePermissionFormPreviewUpdate.vue +183 -0
  84. package/components/RolePermissionMain.vue +361 -0
  85. package/components/SearchVehicleNumberUser.vue +91 -0
  86. package/components/ServiceProviderFormCreate.vue +154 -0
  87. package/components/ServiceProviderMain.vue +547 -0
  88. package/components/SignaturePad.vue +73 -0
  89. package/components/Snackbar.vue +23 -0
  90. package/components/SpecificAttr.vue +53 -0
  91. package/components/SupplyManagement.vue +292 -0
  92. package/components/SwitchContext.vue +108 -0
  93. package/components/TableList.vue +150 -0
  94. package/components/TableListSecondary.vue +164 -0
  95. package/components/TableMain.vue +142 -0
  96. package/components/TableWithButton.vue +94 -0
  97. package/components/VehicleUpdateMoreAction.vue +84 -0
  98. package/components/VideoPlayer.vue +125 -0
  99. package/components/VisitorForm.vue +659 -0
  100. package/components/VisitorFormSelection.vue +53 -0
  101. package/components/VisitorManagement.vue +490 -0
  102. package/components/WorkOrder/Create.vue +284 -0
  103. package/components/WorkOrder/Detail.vue +71 -0
  104. package/components/WorkOrder/ListView.vue +96 -0
  105. package/components/WorkOrder/Main.vue +489 -0
  106. package/components/Workorder.vue +1 -0
  107. package/composables/useAddress.ts +107 -0
  108. package/composables/useBuilding.ts +250 -0
  109. package/composables/useBuildingUnit.ts +116 -0
  110. package/composables/useCard.ts +46 -0
  111. package/composables/useCommonPermission.ts +207 -0
  112. package/composables/useCustomer.ts +113 -0
  113. package/composables/useCustomerSite.ts +56 -0
  114. package/composables/useDashboard.ts +31 -0
  115. package/composables/useDashboardData.ts +425 -0
  116. package/composables/useDocument.ts +57 -0
  117. package/composables/useFacility.ts +246 -0
  118. package/composables/useFeedback.ts +119 -0
  119. package/composables/useFile.ts +55 -0
  120. package/composables/useInvoice.ts +18 -0
  121. package/composables/useLocal.ts +131 -0
  122. package/composables/useLocalAuth.ts +137 -0
  123. package/composables/useLocalSetup.ts +13 -0
  124. package/composables/useMember.ts +111 -0
  125. package/composables/useNFCPatrolRoute.ts +77 -0
  126. package/composables/useNFCPatrolSettings.ts +19 -0
  127. package/composables/useNFCPatrolTag.ts +53 -0
  128. package/composables/useOnlineForm.ts +67 -0
  129. package/composables/useOrg.ts +129 -0
  130. package/composables/usePDFDownload.ts +25 -0
  131. package/composables/usePaymentMethod.ts +101 -0
  132. package/composables/usePeople.ts +81 -0
  133. package/composables/usePermission.ts +54 -0
  134. package/composables/usePhoneCountries.ts +561 -0
  135. package/composables/usePrice.ts +15 -0
  136. package/composables/usePromoCode.ts +36 -0
  137. package/composables/useRecapPermission.ts +26 -0
  138. package/composables/useRole.ts +104 -0
  139. package/composables/useSecurityUtils.ts +18 -0
  140. package/composables/useServiceProvider.ts +224 -0
  141. package/composables/useSite.ts +109 -0
  142. package/composables/useSiteEntryPassSettings.ts +46 -0
  143. package/composables/useSiteSettings.ts +123 -0
  144. package/composables/useSubscription.ts +150 -0
  145. package/composables/useUser.ts +132 -0
  146. package/composables/useUtils.ts +445 -0
  147. package/composables/useVerification.ts +34 -0
  148. package/composables/useVisitor.ts +120 -0
  149. package/composables/useWorkOrder.ts +85 -0
  150. package/error.vue +41 -0
  151. package/layouts/plain.vue +7 -0
  152. package/middleware/01.auth.ts +20 -0
  153. package/middleware/02.org.ts +21 -0
  154. package/middleware/03.customer.ts +13 -0
  155. package/middleware/member.ts +4 -0
  156. package/nuxt.config.ts +54 -0
  157. package/package.json +39 -0
  158. package/pages/index.vue +3 -0
  159. package/pages/payment-method-linked.vue +31 -0
  160. package/pages/require-customer.vue +56 -0
  161. package/pages/require-organization-membership.vue +47 -0
  162. package/pages/unauthorized.vue +29 -0
  163. package/plugins/API.ts +21 -0
  164. package/plugins/iconify.client.ts +5 -0
  165. package/plugins/secure-member.client.ts +86 -0
  166. package/plugins/vuetify.ts +62 -0
  167. package/public/bg-camera.jpg +0 -0
  168. package/public/bg-city.jpg +0 -0
  169. package/public/bg-condo.jpg +0 -0
  170. package/public/images/icons/delete-icon.png +0 -0
  171. package/public/sprite.svg +1 -0
  172. package/tsconfig.json +3 -0
  173. package/types/address.d.ts +13 -0
  174. package/types/building.d.ts +27 -0
  175. package/types/camera.d.ts +31 -0
  176. package/types/card.d.ts +22 -0
  177. package/types/customer.d.ts +27 -0
  178. package/types/document.d.ts +6 -0
  179. package/types/feedback.d.ts +68 -0
  180. package/types/local.d.ts +74 -0
  181. package/types/member.d.ts +21 -0
  182. package/types/online-form.d.ts +15 -0
  183. package/types/org.d.ts +13 -0
  184. package/types/people.d.ts +24 -0
  185. package/types/permission.d.ts +25 -0
  186. package/types/phone-number.d.ts +10 -0
  187. package/types/price.d.ts +17 -0
  188. package/types/promo-code.d.ts +19 -0
  189. package/types/role.d.ts +11 -0
  190. package/types/select.d.ts +4 -0
  191. package/types/service-provider.d.ts +15 -0
  192. package/types/site.d.ts +20 -0
  193. package/types/subscription.d.ts +23 -0
  194. package/types/user.d.ts +19 -0
  195. package/types/verification.d.ts +20 -0
  196. package/types/visitor.d.ts +42 -0
  197. package/types/work-order.d.ts +42 -0
  198. package/utils/phoneMasks.ts +1703 -0
@@ -0,0 +1,588 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12" class="mb-2">
4
+ <v-row no-gutters align="center" justify="space-between">
5
+ <v-btn
6
+ class="text-none text-capitalize"
7
+ rounded="pill"
8
+ variant="tonal"
9
+ size="large"
10
+ @click="showCreateDialog = true"
11
+ v-if="canCreateFeedback"
12
+ >
13
+ Create Feedback
14
+ </v-btn>
15
+
16
+ <v-text-field
17
+ v-model="searchText"
18
+ placeholder="Search feedback..."
19
+ variant="outlined"
20
+ density="comfortable"
21
+ clearable
22
+ hide-details
23
+ class="ml-2"
24
+ style="max-width: 250px"
25
+ />
26
+ </v-row>
27
+ </v-col>
28
+
29
+ <v-col cols="12">
30
+ <v-card
31
+ width="100%"
32
+ variant="outlined"
33
+ border="thin"
34
+ rounded="lg"
35
+ :loading="loading"
36
+ >
37
+ <v-toolbar density="compact" color="grey-lighten-4">
38
+ <template #prepend>
39
+ <v-btn fab icon density="comfortable" @click="updatePage(1)">
40
+ <v-icon>mdi-refresh</v-icon>
41
+ </v-btn>
42
+ </template>
43
+
44
+ <template #append>
45
+ <v-row no-gutters justify="end" align="center">
46
+ <span class="mr-2 text-caption text-fontgray">
47
+ {{ pageRange }}
48
+ </span>
49
+ <local-pagination
50
+ v-model="page"
51
+ :length="pages"
52
+ @update:value="updatePage"
53
+ />
54
+ </v-row>
55
+ </template>
56
+ </v-toolbar>
57
+
58
+ <v-data-table
59
+ :headers="headers"
60
+ :items="items"
61
+ item-value="_id"
62
+ items-per-page="20"
63
+ fixed-header
64
+ hide-default-footer
65
+ :hide-default-header="false"
66
+ @click:row="tableRowClickHandler"
67
+ style="max-height: calc(100vh - (200px))"
68
+ >
69
+ </v-data-table>
70
+ </v-card>
71
+ </v-col>
72
+ </v-row>
73
+
74
+ <FeedbackForm
75
+ v-model="showCreateDialog"
76
+ :feedback="_feedback"
77
+ :is-edit-mode="isEditMode"
78
+ :loading="submitting"
79
+ :categories="serviceProviders"
80
+ :errored-images="erroredImages"
81
+ :max-files="5"
82
+ :message-fn="showMessage"
83
+ @close="handleCloseDialog"
84
+ @submit="submitFeedback"
85
+ @file-added="handleFileAdded"
86
+ @file-deleted="deleteFile"
87
+ />
88
+
89
+ <ConfirmDialog
90
+ v-model="showDeleteDialog"
91
+ :loading="submitting"
92
+ @submit="submitDelete"
93
+ :image-src="'/images/icons/delete-icon.png'"
94
+ >
95
+ <template #image>
96
+ <v-img
97
+ height="120"
98
+ src="/images/icons/delete-icon.png"
99
+ alt="Delete Icon"
100
+ contain
101
+ />
102
+ </template>
103
+
104
+ <template #title> Are you sure you want to delete this feedback? </template>
105
+
106
+ <template #footer>
107
+ <v-btn
108
+ color="primary-button"
109
+ variant="flat"
110
+ class="font-weight-bold py-5 d-flex align-center justify-center"
111
+ @click="submitDelete"
112
+ :loading="submitting"
113
+ block
114
+ >
115
+ Confirm
116
+ </v-btn>
117
+ </template>
118
+ </ConfirmDialog>
119
+
120
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
121
+
122
+ <!-- Preview Dialog -->
123
+ <v-dialog
124
+ v-model="dialogPreview"
125
+ width="450"
126
+ persistent
127
+ v-if="canViewFeedback"
128
+ >
129
+ <v-card width="100%">
130
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
131
+ <v-row no-gutters v-if="selectedFeedback" class="mb-4">
132
+ <v-col cols="12">
133
+ <strong>Subject:</strong> {{ selectedFeedback.subject }}
134
+ </v-col>
135
+ <v-col cols="12">
136
+ <strong>Category:</strong>
137
+ {{ selectedFeedback.category }}
138
+ </v-col>
139
+ <v-col cols="12">
140
+ <strong>Status:</strong>
141
+ {{ selectedFeedback.status }}
142
+ </v-col>
143
+ </v-row>
144
+ </v-card-text>
145
+ <v-toolbar class="pa-0" density="compact">
146
+ <v-row no-gutters>
147
+ <v-col cols="6" class="pa-0">
148
+ <v-btn
149
+ block
150
+ variant="text"
151
+ class="text-none"
152
+ size="large"
153
+ @click="dialogPreview = false"
154
+ height="48"
155
+ >
156
+ Close
157
+ </v-btn>
158
+ </v-col>
159
+ <v-col cols="6" class="pa-0">
160
+ <v-menu>
161
+ <template #activator="{ props }">
162
+ <v-btn
163
+ block
164
+ variant="flat"
165
+ color="black"
166
+ class="text-none"
167
+ height="48"
168
+ v-bind="props"
169
+ tile
170
+ >
171
+ More actions
172
+ </v-btn>
173
+ </template>
174
+
175
+ <v-list class="pa-0">
176
+ <v-list-item
177
+ v-if="canViewFeedbackDetails"
178
+ @click="onViewFeedback(selectedFeedback)"
179
+ >
180
+ <v-list-item-title class="text-subtitle-2">
181
+ View
182
+ </v-list-item-title>
183
+ </v-list-item>
184
+ <v-list-item
185
+ v-if="canUpdateFeedback"
186
+ @click="editFeedback(selectedFeedback)"
187
+ >
188
+ <v-list-item-title class="text-subtitle-2">
189
+ Edit
190
+ </v-list-item-title>
191
+ </v-list-item>
192
+
193
+ <v-list-item
194
+ v-if="canDeleteFeedback"
195
+ @click="confirmDeleteFeedback(selectedFeedback)"
196
+ class="text-red"
197
+ >
198
+ <v-list-item-title class="text-subtitle-2">
199
+ Delete
200
+ </v-list-item-title>
201
+ </v-list-item>
202
+ </v-list>
203
+ </v-menu>
204
+ </v-col>
205
+ </v-row>
206
+ </v-toolbar>
207
+ </v-card></v-dialog
208
+ >
209
+ </template>
210
+
211
+ <script setup lang="ts">
212
+ const props = defineProps({
213
+ detailRoute: {
214
+ type: String,
215
+ default: "index",
216
+ },
217
+ permissions: {
218
+ type: Object,
219
+ required: true,
220
+ default: () => ({}),
221
+ },
222
+ orgId: {
223
+ type: String,
224
+ default: "",
225
+ },
226
+ customerId: {
227
+ type: String,
228
+ default: "",
229
+ },
230
+ siteId: {
231
+ type: String,
232
+ default: "",
233
+ },
234
+ canCreateFeedback: {
235
+ type: Boolean,
236
+ default: false,
237
+ },
238
+ canViewFeedback: {
239
+ type: Boolean,
240
+ default: false,
241
+ },
242
+ canViewFeedbackDetails: {
243
+ type: Boolean,
244
+ default: false,
245
+ },
246
+ canDeleteFeedback: {
247
+ type: Boolean,
248
+ default: false,
249
+ },
250
+ canUpdateFeedback: {
251
+ type: Boolean,
252
+ default: false,
253
+ },
254
+ category: {
255
+ type: String,
256
+ default: "",
257
+ },
258
+ });
259
+
260
+ const isEditMode = ref(false);
261
+ const showCreateDialog = ref(false);
262
+ const showDeleteDialog = ref(false);
263
+ const submitting = ref(false);
264
+ const message = ref("");
265
+ const messageColor = ref("");
266
+ const messageSnackbar = ref(false);
267
+
268
+ const { getUserFromCookie } = useLocal();
269
+
270
+ const serviceProviders = ref<
271
+ Array<{ title: string; value: string; subtitle: string }>
272
+ >([]);
273
+ // const { getAll: getAllServiceProvider } = useServiceProvider();
274
+
275
+ // const { data: getAllReq } = useLazyAsyncData("get-all-service-providers", () =>
276
+ // getAllServiceProvider({
277
+ // siteId: useRoute().params.site as string,
278
+ // })
279
+ // );
280
+
281
+ const { getBySiteAsServiceProvider } = useCustomerSite();
282
+
283
+ const { data: getAllReq } = useLazyAsyncData(
284
+ "get-by-site-as-service-provider",
285
+ () => getBySiteAsServiceProvider(useRoute().params.site as string)
286
+ );
287
+
288
+ watchEffect(() => {
289
+ if (getAllReq.value) {
290
+ serviceProviders.value = getAllReq.value.map((i: any) => ({
291
+ title: i.nature.replace(/_/g, " "),
292
+ subtitle: i.title,
293
+ value: i.nature,
294
+ }));
295
+ }
296
+ });
297
+
298
+ const _feedback = ref({
299
+ subject: "",
300
+ category: "",
301
+ location: "",
302
+ description: "",
303
+ highPriority: false,
304
+ attachments: [] as string[],
305
+ serviceProvider: "",
306
+ assignee: "",
307
+ organization: "",
308
+ site: "",
309
+ });
310
+
311
+ const selected = ref<string[]>([]);
312
+
313
+ const { getColorStatus } = useUtils();
314
+
315
+ const headers: Array<Record<string, any>> = [
316
+ { title: "Name", value: "createdByName", align: "start" },
317
+ { title: "Subject", value: "subject", align: "start" },
318
+ { title: "Category", value: "category", align: "start" },
319
+ { title: "Status", value: "status", align: "start" },
320
+ { title: "", value: "action-table", align: "end" },
321
+ ];
322
+
323
+ // const loading = ref(false);
324
+ const erroredImages = ref<string[]>([]);
325
+ const route = useRoute();
326
+ const { customers } = useCustomer();
327
+
328
+ const {
329
+ getFeedbacks: _getFeedbacks,
330
+ deleteFeedback,
331
+ createFeedback,
332
+ getFeedbackById,
333
+ updateFeedback,
334
+ } = useFeedback();
335
+
336
+ const page = ref(1);
337
+ const pages = ref(0);
338
+ const pageRange = ref("-- - -- of --");
339
+ const items = ref<Array<Record<string, any>>>([]);
340
+
341
+ const {
342
+ data: getAllFeedbackReq,
343
+ status: getAllReqStatus,
344
+ refresh: getAllReqRefresh,
345
+ } = useLazyAsyncData("get-all-feedbacks-" + props.category, () =>
346
+ _getFeedbacks({
347
+ page: page.value,
348
+ site: route.params.site as string,
349
+ category: props.category,
350
+ })
351
+ );
352
+
353
+ const loading = computed(() => getAllReqStatus.value === "pending");
354
+ const onNextPrevPageLoading = ref(false);
355
+
356
+ watchEffect(() => {
357
+ if (getAllFeedbackReq.value) {
358
+ items.value = getAllFeedbackReq.value.items;
359
+ pages.value = getAllFeedbackReq.value.pages;
360
+ pageRange.value = getAllFeedbackReq.value.pageRange;
361
+ }
362
+ });
363
+
364
+ async function updatePage(pageVal: any) {
365
+ onNextPrevPageLoading.value = true;
366
+ page.value = pageVal;
367
+ const response = await _getFeedbacks({
368
+ page: page.value,
369
+ site: route.params.site as string,
370
+ category: props.category,
371
+ });
372
+ if (response) {
373
+ items.value = response.items;
374
+ pages.value = response.pages;
375
+ pageRange.value = response.pageRange;
376
+ onNextPrevPageLoading.value = false;
377
+ }
378
+ }
379
+
380
+ function onSelectedUpdate(newSelected: string[]) {
381
+ selected.value = newSelected;
382
+ }
383
+
384
+ async function editFeedback(item: any) {
385
+ try {
386
+ const _feedbacks = await getFeedbackById(item._id);
387
+ _feedback.value = {
388
+ attachments: (_feedbacks.attachments || []) as string[],
389
+ category: _feedbacks.category || "",
390
+ subject: _feedbacks.subject || "",
391
+ location: _feedbacks.location || "",
392
+ description: _feedbacks.description || "",
393
+ organization: _feedbacks.organization || "",
394
+ site: _feedbacks.site || "",
395
+ highPriority: _feedbacks.highPriority || false,
396
+ serviceProvider: _feedbacks.serviceProvider || "",
397
+ assignee: _feedbacks.assignee || "",
398
+ };
399
+ _feedbackId.value = item._id;
400
+ isEditMode.value = true;
401
+ showCreateDialog.value = true;
402
+ dialogPreview.value = false;
403
+ } catch (error) {
404
+ showMessage("Failed to load full feedback", "error");
405
+ }
406
+ }
407
+
408
+ function onViewFeedback(item: any) {
409
+ const route = useRoute();
410
+ const org = route.params.org;
411
+ const customer = route.params.customer;
412
+ const site = route.params.site;
413
+ const id = item._id;
414
+ useRouter().push({
415
+ name: props.detailRoute,
416
+ params: { org, site, id },
417
+ });
418
+ dialogPreview.value = false;
419
+ }
420
+
421
+ function confirmDeleteFeedback(item: any) {
422
+ feedbackToDelete.value = item;
423
+ showDeleteDialog.value = true;
424
+ }
425
+
426
+ const feedbackToDelete = ref<any>(null);
427
+
428
+ async function submitDelete() {
429
+ if (!feedbackToDelete.value) return;
430
+ submitting.value = true;
431
+ try {
432
+ const response = await deleteFeedback(feedbackToDelete.value._id);
433
+ showMessage(response.message, "success");
434
+ await getAllReqRefresh();
435
+ } catch (error) {
436
+ console.error("Failed to delete feedback:", error);
437
+ showMessage("Failed to delete feedback!", "error");
438
+ } finally {
439
+ submitting.value = false;
440
+ showDeleteDialog.value = false;
441
+ feedbackToDelete.value = null;
442
+ dialogPreview.value = false;
443
+ }
444
+ }
445
+
446
+ const resetFeedbackForm = () => {
447
+ _feedback.value = {
448
+ attachments: [] as string[],
449
+ category: "",
450
+ subject: "",
451
+ location: "",
452
+ description: "",
453
+ highPriority: false,
454
+ serviceProvider: "",
455
+ assignee: "",
456
+ organization: "",
457
+ site: "",
458
+ };
459
+ };
460
+
461
+ function handleCloseDialog() {
462
+ resetFeedbackForm();
463
+ isEditMode.value = false;
464
+ showCreateDialog.value = false;
465
+ }
466
+ const _feedbackId = ref<string | null>(null);
467
+
468
+ async function _createFeedback(organization: string, site: string) {
469
+ const userId = getUserFromCookie();
470
+
471
+ const createPayload: TFeedbackCreate = {
472
+ subject: _feedback.value.subject,
473
+ category: _feedback.value.category,
474
+ location: _feedback.value.location,
475
+ description: _feedback.value.description,
476
+ highPriority: _feedback.value.highPriority ?? false,
477
+ attachments: _feedback.value.attachments || [],
478
+ serviceProvider: _feedback.value.serviceProvider || "",
479
+ assignee: _feedback.value.assignee || "",
480
+ organization,
481
+ site,
482
+ ...(userId !== null ? { createdBy: userId } : {}),
483
+ };
484
+
485
+ const response = await createFeedback(createPayload);
486
+ showMessage(response?.message || "Feedback created successfully", "success");
487
+ }
488
+
489
+ async function _updateFeedback() {
490
+ if (!_feedbackId.value) return;
491
+
492
+ const updatePayload: TFeedbackUpdate = {
493
+ subject: _feedback.value.subject,
494
+ category: _feedback.value.category,
495
+ location: _feedback.value.location,
496
+ description: _feedback.value.description,
497
+ attachments: _feedback.value.attachments || [],
498
+ };
499
+
500
+ const response = await updateFeedback(_feedbackId.value, updatePayload);
501
+ showMessage(response?.message || "Feedback updated successfully", "success");
502
+ }
503
+
504
+ const submitFeedback = async () => {
505
+ submitting.value = true;
506
+
507
+ try {
508
+ const organization = route.params.org as string;
509
+ const site = route.params.site as string;
510
+
511
+ if (isEditMode.value) {
512
+ await _updateFeedback();
513
+ } else {
514
+ await _createFeedback(organization, site);
515
+ }
516
+
517
+ showCreateDialog.value = false;
518
+ await getAllReqRefresh();
519
+ resetFeedbackForm();
520
+ } catch (error) {
521
+ console.error(error);
522
+ showMessage("Something went wrong", "error");
523
+ } finally {
524
+ submitting.value = false;
525
+ }
526
+ };
527
+
528
+ function showMessage(msg: string, color: string) {
529
+ message.value = msg;
530
+ messageColor.value = color;
531
+ messageSnackbar.value = true;
532
+ }
533
+
534
+ const { addFile, deleteFile: _deleteFile } = useFile();
535
+
536
+ const API_DO_STORAGE_ENDPOINT =
537
+ useRuntimeConfig().public.API_DO_STORAGE_ENDPOINT;
538
+
539
+ async function handleFileAdded(file: File) {
540
+ try {
541
+ const res = await addFile(file);
542
+
543
+ const uploadedId = res?.id;
544
+ if (uploadedId) {
545
+ const url = `${uploadedId}`;
546
+ _feedback.value.attachments = _feedback.value.attachments ?? [];
547
+ _feedback.value.attachments.push(url);
548
+ }
549
+ } catch (error) {
550
+ console.error("Error uploading file:", error);
551
+ showMessage("Failed to upload file", "error");
552
+ }
553
+ }
554
+
555
+ async function deleteFile(value: string) {
556
+ try {
557
+ await _deleteFile(value);
558
+ _feedback.value.attachments = (_feedback.value.attachments ?? []).filter(
559
+ (file) => file !== value
560
+ );
561
+ } catch (error) {
562
+ console.log(error);
563
+ showMessage("Failed to delete file", "error");
564
+ }
565
+ }
566
+
567
+ const { serviceProviderCategories, getServiceProviderCategories } =
568
+ useServiceProvider();
569
+
570
+ const categories = computed(() =>
571
+ serviceProviderCategories.value.map((name) => ({
572
+ title: name.charAt(0).toUpperCase() + name.slice(1),
573
+ value: name,
574
+ }))
575
+ );
576
+
577
+ function tableRowClickHandler(_: any, data: any) {
578
+ console.log(data.item);
579
+ selectedFeedback.value = data.item;
580
+ dialogPreview.value = true;
581
+ }
582
+
583
+ const dialogPreview = ref(false);
584
+ const selectedFeedback = ref<TFeedback | null>(null);
585
+ const searchText = ref("");
586
+
587
+ getServiceProviderCategories();
588
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <v-dialog v-model="model" max-width="600" persistent>
3
+ <v-card
4
+ class="px-3 d-flex flex-column"
5
+ :class="`${theme.name.value === 'light' ? 'bg-white' : ''} ${
6
+ mdAndUp ? 'rounded-xl' : ''
7
+ }`"
8
+ :style="{ border: 'none', boxShadow: 'none' }"
9
+ >
10
+ <v-toolbar
11
+ :color="theme.name.value === 'dark' ? 'grey-darken-4' : 'white'"
12
+ class="flex-shrink-0 mb-3"
13
+ style="position: sticky; top: 0; z-index: 10"
14
+ >
15
+ <slot name="title">
16
+ <span class="text-h6 font-weight-medium pt-1 text-capitalize">
17
+ Dialog
18
+ </span>
19
+ </slot>
20
+ <v-spacer />
21
+ <v-btn icon="mdi-close" @click="closeDialog" />
22
+ </v-toolbar>
23
+
24
+ <v-sheet class="flex-grow-1 overflow-y-auto px-3">
25
+ <slot />
26
+ </v-sheet>
27
+
28
+ <v-sheet class="flex-shrink-0 px-3 py-2">
29
+ <slot name="footer" />
30
+ </v-sheet>
31
+ </v-card>
32
+ </v-dialog>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { useTheme, useDisplay } from "vuetify";
37
+
38
+ const theme = useTheme().global;
39
+ const { mdAndUp } = useDisplay();
40
+
41
+ const props = defineProps<{
42
+ modelValue: boolean;
43
+ }>();
44
+
45
+ const emit = defineEmits<{
46
+ (e: "update:modelValue", value: boolean): void;
47
+ (e: "close"): void;
48
+ }>();
49
+
50
+ const model = computed({
51
+ get: () => props.modelValue,
52
+ set: (value) => emit("update:modelValue", value),
53
+ });
54
+
55
+ function closeDialog() {
56
+ model.value = false;
57
+ emit("close");
58
+ }
59
+ </script>
60
+
61
+ <style scoped>
62
+ :deep(.v-dialog .v-overlay__content) {
63
+ margin: auto;
64
+ }
65
+ </style>