@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,429 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6" align="center">
5
+ <span class="font-weight-bold text-h5"> Edit Unit </span>
6
+ </v-row>
7
+ </v-toolbar>
8
+ <v-card-text style="max-height: 100vh; overflow-y: auto">
9
+ <v-form v-model="validForm" :disabled="disable">
10
+ <v-row no-gutters>
11
+ <v-col cols="12">
12
+ <v-row>
13
+ <v-col cols="12" class="mt-2">
14
+ <v-row no-gutters>
15
+ <InputLabel
16
+ class="text-capitalize font-weight-bold"
17
+ title="Building"
18
+ />
19
+ <v-col cols="12">
20
+ {{ buildingUnit.buildingName }}
21
+ </v-col>
22
+ </v-row>
23
+ </v-col>
24
+ </v-row>
25
+ </v-col>
26
+
27
+ <v-col cols="12">
28
+ <v-row>
29
+ <v-col cols="12" class="mt-2">
30
+ <v-row no-gutters>
31
+ <InputLabel
32
+ class="text-capitalize font-weight-bold"
33
+ title="Block"
34
+ />
35
+ <v-col cols="12">
36
+ {{ buildingUnit.block }}
37
+ </v-col>
38
+ </v-row>
39
+ </v-col>
40
+ </v-row>
41
+ </v-col>
42
+
43
+ <v-col cols="12">
44
+ <v-row>
45
+ <v-col cols="12" class="mt-2">
46
+ <v-row no-gutters>
47
+ <InputLabel
48
+ class="text-capitalize font-weight-bold"
49
+ title="Unit Name"
50
+ />
51
+ <v-col cols="12">
52
+ <v-text-field
53
+ v-model="buildingUnit.name"
54
+ density="comfortable"
55
+ :rules="[requiredRule]"
56
+ ></v-text-field>
57
+ </v-col>
58
+ </v-row>
59
+ </v-col>
60
+ </v-row>
61
+ </v-col>
62
+
63
+ <v-col cols="12">
64
+ <v-row>
65
+ <v-col cols="12" class="mt-2">
66
+ <v-row no-gutters>
67
+ <InputLabel
68
+ class="text-capitalize font-weight-bold"
69
+ title="Owner"
70
+ />
71
+ <v-col cols="12">
72
+ <v-autocomplete
73
+ v-model="buildingUnit.ownerName"
74
+ :items="peopleItems"
75
+ item-title="name"
76
+ return-object
77
+ density="comfortable">
78
+ </v-autocomplete>
79
+ </v-col>
80
+ </v-row>
81
+ </v-col>
82
+ </v-row>
83
+ </v-col>
84
+
85
+ <v-col cols="12">
86
+ <v-row>
87
+ <v-col cols="12" class="mt-2">
88
+ <v-row no-gutters>
89
+ <InputLabel
90
+ class="text-capitalize"
91
+ title="Company Name"
92
+ required
93
+ />
94
+ <v-col cols="12">
95
+ <v-text-field
96
+ v-model="buildingUnit.companyName"
97
+ density="comfortable"
98
+ :rules="[requiredRule]"
99
+ ></v-text-field>
100
+ </v-col>
101
+ </v-row>
102
+ </v-col>
103
+ </v-row>
104
+ </v-col>
105
+
106
+ <v-col cols="12">
107
+ <v-row>
108
+ <v-col cols="12" class="mt-2">
109
+ <v-row no-gutters>
110
+ <InputLabel
111
+ class="text-capitalize"
112
+ title="Company Registration Number"
113
+ />
114
+ <v-col cols="12">
115
+ <v-text-field
116
+ v-model="buildingUnit.companyRegistrationNumber"
117
+ density="comfortable"
118
+ ></v-text-field>
119
+ </v-col>
120
+ </v-row>
121
+ </v-col>
122
+ </v-row>
123
+ </v-col>
124
+
125
+ <v-col cols="12">
126
+ <v-row>
127
+ <v-col cols="12" class="mt-2">
128
+ <v-row no-gutters>
129
+ <InputLabel
130
+ class="text-capitalize"
131
+ title="Lease Start"
132
+ required
133
+ />
134
+ <v-col cols="12">
135
+ <InputDateTimePicker
136
+ ref="startDateRef"
137
+ v-model="buildingUnit.leaseStart"
138
+ :rules="[requiredRule]"
139
+ />
140
+ </v-col>
141
+ </v-row>
142
+ </v-col>
143
+ </v-row>
144
+ </v-col>
145
+
146
+ <v-col cols="12">
147
+ <v-row>
148
+ <v-col cols="12" class="mt-2">
149
+ <v-row no-gutters>
150
+ <InputLabel
151
+ class="text-capitalize"
152
+ title="Lease End"
153
+ required
154
+ />
155
+ <v-col cols="12">
156
+ <InputDateTimePicker
157
+ ref="endDateRef"
158
+ v-model="buildingUnit.leaseEnd"
159
+ :rules="[requiredRule]"
160
+ />
161
+ </v-col>
162
+ </v-row>
163
+ </v-col>
164
+ </v-row>
165
+ </v-col>
166
+
167
+ <v-col cols="12" class="mt-2">
168
+ <v-row no-gutters>
169
+ <InputLabel
170
+ class="text-capitalize font-weight-bold"
171
+ title="Category"
172
+ required
173
+ />
174
+ <v-col cols="12">
175
+ <v-autocomplete
176
+ v-model="buildingUnit.category"
177
+ :items="unitCategories"
178
+ density="comfortable"
179
+ :rules="[requiredRule]"
180
+ ></v-autocomplete>
181
+ </v-col>
182
+ </v-row>
183
+ </v-col>
184
+
185
+ <v-col cols="12">
186
+ <v-row>
187
+ <v-col cols="6" class="mt-2">
188
+ <v-row no-gutters>
189
+ <InputLabel
190
+ class="text-capitalize font-weight-bold"
191
+ title="Level"
192
+ required
193
+ />
194
+ <v-col cols="12">
195
+ <v-select
196
+ v-model="buildingUnit.level"
197
+ :items="buildingLevels"
198
+ density="comfortable"
199
+ :rules="[requiredRule]"
200
+ ></v-select>
201
+ </v-col>
202
+ </v-row>
203
+ </v-col>
204
+ </v-row>
205
+ </v-col>
206
+
207
+ <v-col cols="12" class="mt-5">
208
+ <InputLabel
209
+ class="text-capitalize"
210
+ title="Upload Files (Lease of Contract, Cert. of Occupancy)"
211
+ />
212
+ <InputFileV2
213
+ v-model="buildingUnit.buildingUnitFiles"
214
+ :multiple="false"
215
+ :max-length="10"
216
+ title="Upload PDF Files"
217
+ accept="application/pdf"
218
+ />
219
+ </v-col>
220
+
221
+ <v-col cols="12" class="my-2">
222
+ <v-row no-gutters>
223
+ <v-col cols="12" class="text-center">
224
+ <span
225
+ class="text-none text-subtitle-2 font-weight-medium text-error"
226
+ >
227
+ {{ message }}
228
+ </span>
229
+ </v-col>
230
+ </v-row>
231
+ </v-col>
232
+ </v-row>
233
+ </v-form>
234
+ </v-card-text>
235
+
236
+ <v-toolbar density="compact">
237
+ <v-row no-gutters>
238
+ <v-col cols="6">
239
+ <v-btn
240
+ tile
241
+ block
242
+ variant="text"
243
+ class="text-none"
244
+ size="48"
245
+ @click="cancel"
246
+ >
247
+ Cancel
248
+ </v-btn>
249
+ </v-col>
250
+
251
+ <v-col cols="6">
252
+ <v-btn
253
+ tile
254
+ block
255
+ variant="flat"
256
+ color="black"
257
+ class="text-none"
258
+ size="48"
259
+ :disabled="!validForm || disable"
260
+ @click="submit"
261
+ :loading="disable"
262
+ >
263
+ Submit
264
+ </v-btn>
265
+ </v-col>
266
+ </v-row>
267
+ </v-toolbar>
268
+ </v-card>
269
+ </template>
270
+
271
+ <script setup lang="ts">
272
+ const prop = defineProps({
273
+ site: {
274
+ type: String,
275
+ default: "",
276
+ },
277
+ roomFacility: {
278
+ type: Object as PropType<TBuildingUnit>,
279
+ default: () => ({
280
+ _id: "",
281
+ site: "",
282
+ name: "",
283
+ owner: "",
284
+ ownerName: "",
285
+ building: "",
286
+ buildingName: "",
287
+ category: "",
288
+ block: null,
289
+ level: 0,
290
+ status: "active",
291
+ buildingUnitFiles: [],
292
+ companyName: "",
293
+ companyRegistrationNumber: "",
294
+ leaseStart: "",
295
+ leaseEnd: "",
296
+ }),
297
+ },
298
+ });
299
+
300
+ const buildingUnit = ref({
301
+ _id: "",
302
+ site: "",
303
+ name: "",
304
+ owner: "",
305
+ ownerName: "",
306
+ building: "",
307
+ buildingName: "",
308
+ level: 0,
309
+ category: "",
310
+ block: null,
311
+ status: "active",
312
+ buildingUnitFiles: [],
313
+ companyName: "",
314
+ companyRegistrationNumber: "",
315
+ leaseStart: "",
316
+ leaseEnd: "",
317
+ });
318
+
319
+ buildingUnit.value = JSON.parse(JSON.stringify(prop.roomFacility));
320
+
321
+ const emit = defineEmits(["cancel", "success", "success:create-more"]);
322
+
323
+ const validForm = ref(false);
324
+
325
+ const { getAll } = useBuilding();
326
+ const { getPeopleByUnit } = usePeople();
327
+
328
+ const buildings = ref<Record<string, any>[]>([]);
329
+ const peopleItems = ref<Array<Record<string, any>>>([]);
330
+
331
+ const { data: getBuildingReq } = useLazyAsyncData(
332
+ "get-all-buildings",
333
+ async () => getAll({ site: prop.site, status: "active" })
334
+ );
335
+
336
+ watchEffect(() => {
337
+ if (getBuildingReq.value) {
338
+ buildings.value = getBuildingReq.value.items.map((building: TBuilding) => ({
339
+ title: building.name,
340
+ value: building._id,
341
+ levels: building.levels,
342
+ }));
343
+ }
344
+ });
345
+
346
+ const { data: getUnitPeople } = useLazyAsyncData(
347
+ "get-unit-people",
348
+ async () => getPeopleByUnit(prop.roomFacility._id as string)
349
+ );
350
+
351
+ watch(getUnitPeople, (newData: any) => {
352
+ if (newData) {
353
+ peopleItems.value = newData ?? [];
354
+ }
355
+ });
356
+
357
+ buildingUnit.value.site = prop.site;
358
+
359
+ const selectedBuilding = computed(() => {
360
+ return buildings.value.find((b) => b.value === buildingUnit.value.building);
361
+ });
362
+
363
+ const buildingLevels = computed(() => {
364
+ return Array.from(
365
+ { length: selectedBuilding.value?.levels || 0 },
366
+ (_, i) => i + 1
367
+ );
368
+ });
369
+
370
+ const disable = ref(false);
371
+
372
+ const { requiredRule } = useUtils();
373
+
374
+ const message = ref("");
375
+
376
+ const { updateById, categories: unitCategories } = useBuildingUnit();
377
+
378
+ function arraysAreEqual(a: any[], b: any[]): boolean {
379
+ return a.length === b.length && a.every((val, index) => val === b[index]);
380
+ }
381
+
382
+ const hasChanges = computed(() => {
383
+ return (
384
+ prop.roomFacility.name !== buildingUnit.value.name ||
385
+ prop.roomFacility.category !== buildingUnit.value.category ||
386
+ prop.roomFacility.level !== buildingUnit.value.level ||
387
+ !arraysAreEqual(
388
+ prop.roomFacility.buildingUnitFiles,
389
+ buildingUnit.value.buildingUnitFiles
390
+ )
391
+ );
392
+ });
393
+
394
+ async function submit() {
395
+ disable.value = true;
396
+ try {
397
+
398
+ if (buildingUnit.value.ownerName) {
399
+ buildingUnit.value.owner = (buildingUnit.value.ownerName as any)._id
400
+ buildingUnit.value.ownerName = (buildingUnit.value.ownerName as any).name
401
+ }
402
+
403
+ await updateById(buildingUnit.value._id ?? "", {
404
+ name: buildingUnit.value.name,
405
+ level: buildingUnit.value.level,
406
+ category: buildingUnit.value.category,
407
+ buildingUnitFiles: buildingUnit.value.buildingUnitFiles || [],
408
+ companyName: buildingUnit.value.companyName,
409
+ companyRegistrationNumber: buildingUnit.value.companyRegistrationNumber || "",
410
+ leaseStart: buildingUnit.value.leaseStart,
411
+ leaseEnd: buildingUnit.value.leaseEnd,
412
+ owner: buildingUnit.value.owner || "",
413
+ ownerName: buildingUnit.value.ownerName || ""
414
+ });
415
+
416
+ emit("success");
417
+ } catch (error: any) {
418
+ message.value =
419
+ error.response?._data?.message || "Failed to create building";
420
+ } finally {
421
+ disable.value = false;
422
+ }
423
+ }
424
+
425
+ function cancel() {
426
+ message.value = "";
427
+ emit("cancel");
428
+ }
429
+ </script>
@@ -0,0 +1,264 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6" align="center">
5
+ <span class="font-weight-bold text-h5 text-capitalize">
6
+ {{ title }}
7
+ </span>
8
+ </v-row>
9
+ </v-toolbar>
10
+
11
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-5">
12
+ <v-form ref="formRef" v-model="validForm" :disabled="disable">
13
+ <v-row no-gutters class="pt-4">
14
+ <v-col cols="12">
15
+ <InputLabel class="text-capitalize" title="URL" required />
16
+ <v-text-field
17
+ v-model.trim="camera.host"
18
+ density="comfortable"
19
+ :rules="[requiredRule, isValidBaseURL]"
20
+ placeholder="Enter camera URL"
21
+ />
22
+ </v-col>
23
+
24
+ <v-col v-if="isANPR" cols="12">
25
+ <InputLabel class="text-capitalize" title="Category" required />
26
+ <v-select
27
+ v-model="camera.category"
28
+ :items="anprCategoryOptions"
29
+ density="comfortable"
30
+ :rules="[requiredRule]"
31
+ />
32
+ </v-col>
33
+
34
+ <v-col v-if="isANPR" cols="12">
35
+ <InputLabel class="text-capitalize" title="Direction" required />
36
+ <v-select
37
+ v-model="camera.direction"
38
+ :items="anprDirectionOptions"
39
+ density="comfortable"
40
+ :rules="[requiredRule]"
41
+ />
42
+ </v-col>
43
+
44
+ <v-col v-if="isANPR" cols="12">
45
+ <InputLabel class="text-capitalize" title="Guard House" />
46
+ <v-select
47
+ v-model="camera.guardPost"
48
+ :items="guardPostOptions"
49
+ density="comfortable"
50
+ />
51
+ </v-col>
52
+
53
+ <v-col cols="12">
54
+ <InputLabel class="text-capitalize" title="User" required />
55
+ <v-text-field
56
+ v-model.trim="camera.username"
57
+ density="comfortable"
58
+ :rules="[requiredRule]"
59
+ placeholder="Enter username"
60
+ />
61
+ </v-col>
62
+
63
+ <v-col cols="12">
64
+ <InputLabel class="text-capitalize" title="Password" required />
65
+ <v-text-field
66
+ v-model.trim="camera.password"
67
+ type="password"
68
+ density="comfortable"
69
+ :rules="[requiredRule]"
70
+ placeholder="Enter password"
71
+ />
72
+ </v-col>
73
+ </v-row>
74
+ </v-form>
75
+ </v-card-text>
76
+
77
+ <v-toolbar density="compact">
78
+ <v-row no-gutters>
79
+ <v-col cols="6">
80
+ <v-btn
81
+ tile
82
+ block
83
+ variant="text"
84
+ class="text-none"
85
+ size="48"
86
+ @click="back"
87
+ text="Cancel"
88
+ />
89
+ </v-col>
90
+ <v-col cols="6">
91
+ <v-btn
92
+ tile
93
+ block
94
+ variant="flat"
95
+ color="black"
96
+ class="text-none"
97
+ size="48"
98
+ :disabled="!validForm || disable || !hasChanges"
99
+ @click="submit"
100
+ :loading="disable"
101
+ text="Submit"
102
+ />
103
+ </v-col>
104
+ </v-row>
105
+ </v-toolbar>
106
+ </v-card>
107
+ </template>
108
+
109
+ <script setup lang="ts">
110
+ import useSiteSettings from '../composables/useSiteSettings';
111
+
112
+ const prop = defineProps({
113
+ title: {
114
+ type: String,
115
+ default: "Site Camera Form",
116
+ },
117
+ site: {
118
+ type: String,
119
+ required: true,
120
+ },
121
+ guardPosts: {
122
+ type: Number,
123
+ default: 0,
124
+ },
125
+ type: {
126
+ type: String as PropType<TSiteCamera["type"]>,
127
+ default: "anpr",
128
+ },
129
+ mode: {
130
+ type: String,
131
+ default: "add",
132
+ },
133
+ camera: {
134
+ type: Object as PropType<TSiteCamera>,
135
+ default: () => ({
136
+ site: "",
137
+ host: "",
138
+ username: "",
139
+ password: "",
140
+ type: "ip",
141
+ category: "standard",
142
+ direction: "none",
143
+ guardPost: null,
144
+ status: "active",
145
+ }),
146
+ },
147
+ });
148
+
149
+ const { requiredRule, isValidBaseURL } = useUtils();
150
+ const emit = defineEmits(["cancel", "select", "success", "error"]);
151
+
152
+ const camera = ref<TSiteCamera>({
153
+ site: "",
154
+ host: "",
155
+ username: "",
156
+ password: "",
157
+ type: "ip",
158
+ category: "standard",
159
+ direction: "none",
160
+ guardPost: null,
161
+ status: "active",
162
+ });
163
+
164
+ camera.value.site = prop.site;
165
+
166
+ if (prop.mode === "edit") {
167
+ camera.value = JSON.parse(JSON.stringify(prop.camera));
168
+ }
169
+
170
+ camera.value.site = prop.site;
171
+ camera.value.type = prop.type;
172
+ camera.value.category = prop.type === "anpr" ? "resident" : "standard";
173
+ camera.value.direction = prop.type === "anpr" ? "both" : "none";
174
+
175
+ const validForm = ref(false);
176
+ const formRef = ref<HTMLFormElement | null>(null);
177
+ const disable = ref(false);
178
+ const message = ref("");
179
+
180
+ const isANPR = computed(() => prop.type === "anpr");
181
+
182
+ const anprCategoryOptions = computed(() => [
183
+ { title: "Resident", value: "resident" },
184
+ { title: "Visitor", value: "visitor" },
185
+ ]);
186
+
187
+ const anprDirectionOptions = computed(() => [
188
+ { title: "Entry", value: "entry" },
189
+ { title: "Exit", value: "exit" },
190
+ { title: "Both", value: "both" },
191
+ { title: "None", value: "none" },
192
+ ]);
193
+
194
+ function back() {
195
+ message.value = "";
196
+
197
+ if (formRef.value) {
198
+ formRef.value.reset();
199
+ }
200
+
201
+ emit("cancel");
202
+ }
203
+
204
+ const hasChanges = computed(() => {
205
+ // Only check for changes in edit mode
206
+ if (prop.mode === "edit") {
207
+ return (
208
+ camera.value.host !== prop.camera.host ||
209
+ camera.value.username !== prop.camera.username ||
210
+ camera.value.password !== prop.camera.password ||
211
+ camera.value.category !== prop.camera.category ||
212
+ camera.value.guardPost !== prop.camera.guardPost ||
213
+ camera.value.direction !== prop.camera.direction
214
+ );
215
+ }
216
+
217
+ // For add mode, always return true (allow submission)
218
+ return true;
219
+ });
220
+
221
+ const { addCamera, updateSiteCamera } = useSiteSettings();
222
+
223
+ async function submit() {
224
+ disable.value = true;
225
+ camera.value.guardPost = camera.value.guardPost ?? 0;
226
+
227
+ try {
228
+ if (prop.mode === "add") {
229
+ await addCamera(camera.value);
230
+ }
231
+
232
+ if (prop.mode === "edit") {
233
+ await updateSiteCamera(camera.value._id ?? "", {
234
+ host: camera.value.host,
235
+ username: camera.value.username,
236
+ password: camera.value.password,
237
+ category: camera.value.category,
238
+ guardPost: camera.value.guardPost,
239
+ direction: camera.value.direction,
240
+ });
241
+ }
242
+
243
+ emit("success", `${prop.title} added successfully!`);
244
+ } catch (error: any) {
245
+ emit("error", error.response?._data?.message || "Failed to add camera");
246
+ } finally {
247
+ disable.value = false;
248
+ }
249
+ }
250
+
251
+ const guardPostOptions = computed(() => {
252
+ const items = [];
253
+
254
+ for (let index = 0; index < prop.guardPosts; index++) {
255
+ items.push({
256
+ title: `Guard House ${index + 1}`,
257
+ value: index + 1,
258
+ });
259
+ }
260
+
261
+ return items;
262
+ });
263
+ </script>
264
+ <style scoped></style>