@iservice365/layer-common 1.1.0 → 1.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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/CameraForm.vue +264 -0
  3. package/components/CameraMain.vue +352 -0
  4. package/components/Card/DeleteConfirmation.vue +51 -0
  5. package/components/Card/MemberInfoSummary.vue +44 -0
  6. package/components/Chat/Information.vue +28 -11
  7. package/components/Dialog/DeleteConfirmation.vue +51 -0
  8. package/components/Dialog/UpdateMoreAction.vue +99 -0
  9. package/components/Feedback/Form.vue +17 -3
  10. package/components/FeedbackDetail.vue +0 -11
  11. package/components/FeedbackMain.vue +21 -11
  12. package/components/Input/DateTimePicker.vue +5 -1
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +111 -63
  15. package/components/Input/InputPhoneNumberV2.vue +115 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +41 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +420 -0
  21. package/components/TableMain.vue +2 -1
  22. package/components/VehicleUpdateMoreAction.vue +84 -0
  23. package/components/VisitorForm.vue +712 -0
  24. package/components/VisitorFormSelection.vue +53 -0
  25. package/components/VisitorManagement.vue +568 -0
  26. package/components/WorkOrder/Create.vue +70 -46
  27. package/components/WorkOrder/Main.vue +11 -10
  28. package/composables/useBuilding.ts +250 -0
  29. package/composables/useBuildingUnit.ts +116 -0
  30. package/composables/useFeedback.ts +3 -3
  31. package/composables/useFile.ts +7 -9
  32. package/composables/useLocal.ts +67 -0
  33. package/composables/usePeople.ts +48 -0
  34. package/composables/useSecurityUtils.ts +18 -0
  35. package/composables/useSiteSettings.ts +111 -0
  36. package/composables/useUtils.ts +30 -1
  37. package/composables/useVisitor.ts +79 -0
  38. package/package.json +1 -1
  39. package/plugins/vuetify.ts +6 -1
  40. package/types/building.d.ts +19 -0
  41. package/types/camera.d.ts +31 -0
  42. package/types/people.d.ts +22 -0
  43. package/types/select.d.ts +4 -0
  44. package/types/site.d.ts +10 -7
  45. package/types/visitor.d.ts +42 -0
  46. package/utils/phoneMasks.ts +1703 -0
@@ -0,0 +1,712 @@
1
+ <template>
2
+ <v-card width="100%" :loading="processing">
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
+ {{ prop.mode }} {{ formatVisitorType(type) }}
7
+ </span>
8
+ </v-row>
9
+ </v-toolbar>
10
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-3">
11
+ <div class="w-100 d-flex justify-space-between ga-2">
12
+ <span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
13
+ formTitle
14
+ }}</span>
15
+ <span
16
+ v-if="prop.type === 'contractor'"
17
+ class="text-subtitle-2 font-weight-bold"
18
+ style="text-wrap: nowrap"
19
+ >Step <span class="text-primary-button">{{ contractorStep }}</span
20
+ >/3</span
21
+ >
22
+ </div>
23
+ <v-form
24
+ ref="formRef"
25
+ v-model="validForm"
26
+ :disabled="processing"
27
+ @click="errorMessage = ''"
28
+ >
29
+ <v-row no-gutters class="pt-4">
30
+ <v-col v-if="shouldShowField('contractorType')" cols="12">
31
+ <InputLabel
32
+ class="text-capitalize"
33
+ title="Contractor Type"
34
+ required
35
+ />
36
+ <v-combobox
37
+ v-model="contractorTypeObj"
38
+ v-model:search="contractorTypeInput"
39
+ :hide-no-data="false"
40
+ @update:focused="handleFocusedContractorType"
41
+ :items="contractorTypes"
42
+ :rules="[requiredRule]"
43
+ item-value="value"
44
+ @update:model-value="handleSelectContractorType"
45
+ variant="outlined"
46
+ density="comfortable"
47
+ persistent-hint
48
+ small-chips
49
+ >
50
+ <template v-slot:no-data>
51
+ <v-list-item>
52
+ <v-list-item-title>
53
+ No results matching "<strong>{{
54
+ contractorTypeInput
55
+ }}</strong
56
+ >". This value will be added as new option.
57
+ </v-list-item-title>
58
+ </v-list-item>
59
+ </template>
60
+ </v-combobox>
61
+ </v-col>
62
+
63
+ <v-col v-if="shouldShowField('attachments')" class="w-100">
64
+ <InputLabel class="text-capitalize" title="Attach Image" />
65
+ <InputFileV2 v-model="visitor.attachments" multiple :max-length="3" />
66
+ </v-col>
67
+
68
+ <v-col v-if="shouldShowField('name')" cols="12">
69
+ <v-row>
70
+ <v-col cols="12">
71
+ <InputLabel
72
+ class="text-capitalize"
73
+ title="Full Name"
74
+ required
75
+ />
76
+ <v-text-field
77
+ v-model.trim="visitor.name"
78
+ density="comfortable"
79
+ :rules="[requiredRule]"
80
+ />
81
+ </v-col>
82
+ </v-row>
83
+ </v-col>
84
+
85
+ <v-col v-if="shouldShowField('deliveryType')" cols="12">
86
+ <v-row>
87
+ <v-col cols="12">
88
+ <v-radio-group
89
+ v-model="visitor.deliveryType"
90
+ inline
91
+ color="primary"
92
+ :rules="[requiredRule]"
93
+ >
94
+ <v-radio label="Food" value="Food"></v-radio>
95
+ <v-radio label="Parcel" value="Parcel"></v-radio>
96
+ </v-radio-group>
97
+ </v-col>
98
+ </v-row>
99
+ </v-col>
100
+
101
+ <template v-if="shouldShowField('company')">
102
+ <v-col v-if="prop.type === 'delivery'" cols="12">
103
+ <InputLabel
104
+ class="text-capitalize"
105
+ title="Company Name"
106
+ required
107
+ />
108
+ <v-combobox
109
+ v-model="companyNameObj"
110
+ v-model:search="companyNameInput"
111
+ :hide-no-data="false"
112
+ @update:focused="handleFocusedCompanyName"
113
+ :items="companyNames"
114
+ :rules="[requiredRule]"
115
+ item-value="value"
116
+ @update:model-value="handleSelectCompanyName"
117
+ variant="outlined"
118
+ density="comfortable"
119
+ persistent-hint
120
+ small-chips
121
+ >
122
+ <template v-slot:no-data>
123
+ <v-list-item>
124
+ <v-list-item-title>
125
+ No results matching "<strong>{{
126
+ contractorTypeInput
127
+ }}</strong
128
+ >". This value will be added as new option.
129
+ </v-list-item-title>
130
+ </v-list-item>
131
+ </template>
132
+ </v-combobox>
133
+ </v-col>
134
+
135
+ <v-col v-else cols="12">
136
+ <v-row>
137
+ <v-col cols="12">
138
+ <InputLabel
139
+ class="text-capitalize"
140
+ title="Company Name"
141
+ required
142
+ />
143
+ <v-text-field
144
+ v-model.trim="visitor.company"
145
+ density="comfortable"
146
+ :rules="[requiredRule]"
147
+ />
148
+ </v-col>
149
+ </v-row>
150
+ </v-col>
151
+ </template>
152
+
153
+ <v-col v-if="shouldShowField('nric')" cols="12">
154
+ <v-row>
155
+ <v-col cols="12">
156
+ <InputLabel
157
+ class="text-capitalize"
158
+ title="NRIC/Passport/ID No."
159
+ required
160
+ />
161
+ <InputNRICNumber
162
+ v-model.trim="visitor.nric"
163
+ density="comfortable"
164
+ :rules="[requiredRule]"
165
+ />
166
+ </v-col>
167
+ </v-row>
168
+ </v-col>
169
+
170
+ <v-col v-if="shouldShowField('contact')" cols="12">
171
+ <InputLabel class="text-capitalize" title="Phone Number" required />
172
+ <InputPhoneNumberV2 v-model="visitor.contact" :rules="[requiredRule]" density="comfortable"/>
173
+ </v-col>
174
+
175
+ <v-col v-if="shouldShowField('plateNumber')" cols="12">
176
+ <v-row>
177
+ <v-col cols="12">
178
+ <InputLabel
179
+ class="text-capitalize"
180
+ title="Vehicle Number"
181
+ required
182
+ />
183
+ <!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
184
+ <InputVehicleNumber
185
+ v-model.trim="visitor.plateNumber"
186
+ density="comfortable"
187
+ :rules="[requiredRule]"
188
+ />
189
+ </v-col>
190
+ </v-row>
191
+ </v-col>
192
+
193
+ <v-col v-if="shouldShowField('block')" cols="12">
194
+ <InputLabel class="text-capitalize" title="Block" required />
195
+ <v-select
196
+ v-model="visitor.block"
197
+ :items="blocksArray"
198
+ item-value="value"
199
+ item-title="title"
200
+ :loading="blockStatus === 'pending'"
201
+ @update:model-value="handleChangeBlock"
202
+ density="comfortable"
203
+ :rules="[requiredRule]"
204
+ />
205
+ </v-col>
206
+
207
+ <v-col v-if="shouldShowField('level')" cols="12">
208
+ <InputLabel class="text-capitalize" title="Level" required />
209
+ <v-select
210
+ v-model="visitor.level"
211
+ :items="levelsArray"
212
+ density="comfortable"
213
+ :disabled="!visitor.block"
214
+ :loading="levelsStatus === 'pending'"
215
+ @update:model-value="handleChangeLevel"
216
+ :rules="[requiredRule]"
217
+ />
218
+ </v-col>
219
+
220
+ <v-col v-if="shouldShowField('unit')" cols="12">
221
+ <InputLabel class="text-capitalize" title="Unit" required />
222
+ <v-select
223
+ v-model="visitor.unit"
224
+ :items="unitsArray"
225
+ density="comfortable"
226
+ :disabled="!visitor.level"
227
+ :loading="unitsStatus === 'pending'"
228
+ :rules="[requiredRule]"
229
+ @update:model-value="handleUpdateUnit"
230
+ />
231
+ </v-col>
232
+
233
+ <v-col v-if="shouldShowField('remarks')" cols="12">
234
+ <InputLabel class="text-capitalize" title="Remarks" />
235
+ <v-textarea
236
+ v-model="visitor.remarks"
237
+ density="comfortable"
238
+ :rows="3"
239
+ no-resize
240
+ />
241
+ </v-col>
242
+
243
+ <v-col
244
+ v-if="prop.type === 'contractor' && contractorStep === 2"
245
+ cols="12"
246
+ >
247
+ <PassInformation />
248
+ </v-col>
249
+
250
+ <v-col
251
+ v-if="prop.type === 'contractor' && contractorStep === 3"
252
+ cols="12"
253
+ >
254
+ <MemberInformation v-model="visitor.members" />
255
+ </v-col>
256
+
257
+ <v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
258
+ <v-checkbox v-model="createMore" density="comfortable" hide-details>
259
+ <template #label>
260
+ <span class="text-subtitle-2 font-weight-bold">
261
+ Create more
262
+ </span>
263
+ </template>
264
+ </v-checkbox>
265
+ </v-col>
266
+ </v-row>
267
+ </v-form>
268
+ </v-card-text>
269
+
270
+ <v-row no-gutters class="w-100" v-if="errorMessage">
271
+ <p class="text-error w-100 text-center text-subtitle-2">
272
+ {{ errorMessage }}
273
+ </p>
274
+ </v-row>
275
+ <v-toolbar density="compact">
276
+ <v-row no-gutters>
277
+ <v-col cols="6">
278
+ <v-btn
279
+ v-if="
280
+ prop.mode === 'add' &&
281
+ prop.type === 'contractor' &&
282
+ contractorStep > 1
283
+ "
284
+ tile
285
+ block
286
+ variant="text"
287
+ class="text-none"
288
+ size="48"
289
+ @click="handleGoToPreviousPage"
290
+ text="Back"
291
+ />
292
+ <v-btn
293
+ v-else-if="prop.mode === 'add'"
294
+ tile
295
+ block
296
+ variant="text"
297
+ class="text-none"
298
+ size="48"
299
+ @click="backToSelection"
300
+ text="Back to Selection"
301
+ />
302
+ <v-btn
303
+ v-else
304
+ tile
305
+ block
306
+ variant="text"
307
+ class="text-none"
308
+ size="48"
309
+ @click="close"
310
+ text="Close"
311
+ />
312
+ </v-col>
313
+ <v-col cols="6">
314
+ <v-btn
315
+ v-if="prop.type === 'contractor'"
316
+ tile
317
+ block
318
+ variant="flat"
319
+ color="primary-button"
320
+ class="text-none"
321
+ size="48"
322
+ :disabled="processing"
323
+ @click="handleNextPage"
324
+ :text="contractorStep === 3 ? 'Submit' : 'Next'"
325
+ />
326
+ <v-btn
327
+ v-else
328
+ tile
329
+ block
330
+ variant="flat"
331
+ color="black"
332
+ class="text-none"
333
+ size="48"
334
+ :disabled="!validForm || processing"
335
+ @click="submit"
336
+ :text="prop.mode == 'add' ? 'Submit' : 'Update'"
337
+ />
338
+ </v-col>
339
+ </v-row>
340
+ </v-toolbar>
341
+ </v-card>
342
+ </template>
343
+
344
+ <script setup lang="ts">
345
+
346
+ const prop = defineProps({
347
+ type: {
348
+ type: String as PropType<TVisitorType>,
349
+ required: true,
350
+ },
351
+ org: {
352
+ type: String,
353
+ required: true,
354
+ },
355
+ site: {
356
+ type: String,
357
+ required: true,
358
+ },
359
+ mode: {
360
+ type: String as PropType<"add" | "edit">,
361
+ default: "add",
362
+ },
363
+ visitorData: {
364
+ type: Object as PropType<Partial<TVisitor> | null>,
365
+ default: null,
366
+ },
367
+ });
368
+
369
+ const { requiredRule, formatDateISO8601 } = useUtils();
370
+ const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
371
+ const { createVisitor, typeFieldMap, contractorTypes } = useVisitor();
372
+
373
+ const emit = defineEmits([
374
+ "back",
375
+ "select",
376
+ "done",
377
+ "done:more",
378
+ "error",
379
+ "close",
380
+ ]);
381
+
382
+ const visitor = reactive<Partial<TVisitorPayload>>({
383
+ contractorType: "",
384
+ name: "",
385
+ deliveryType: "",
386
+ company: "",
387
+ nric: "",
388
+ contact: "",
389
+ plateNumber: "",
390
+ block: "",
391
+ level: "",
392
+ unit: "",
393
+ unitName: "",
394
+ remarks: "",
395
+ attachments: [],
396
+ members: [],
397
+ });
398
+
399
+ const validForm = ref(false);
400
+ const formRef = ref<HTMLFormElement | null>(null);
401
+ const processing = ref(false);
402
+ const message = ref("");
403
+ const errorMessage = ref("");
404
+ const createMore = ref(false);
405
+
406
+ const contractorTypeInput = ref("");
407
+ const contractorTypeObj = ref<TDefaultOptionObj | null>(null);
408
+ const contractorStep = ref(1);
409
+
410
+ const companyNameInput = ref("");
411
+ const companyNameObj = ref<TDefaultOptionObj | null>(null);
412
+
413
+
414
+ const blocksArray = ref<TDefaultOptionObj[]>([]);
415
+ const levelsArray = ref<TDefaultOptionObj[]>([]);
416
+ const unitsArray = ref<TDefaultOptionObj[]>([]);
417
+
418
+ const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
419
+ if (prop.type !== "contractor" || contractorStep.value === 1) {
420
+ const visibleFields = typeFieldMap[prop.type];
421
+ return visibleFields?.includes(fieldKey);
422
+ }
423
+ };
424
+
425
+ const companyNames = computed(() => {
426
+ const arr: string[] = [
427
+ "Foodpanda",
428
+ "GrabFood",
429
+ "Deliveroo",
430
+ "Lazada",
431
+ "Amazon",
432
+ "RedMart",
433
+ "J&T",
434
+ "NTUC FairPrice",
435
+ "NinjaVan",
436
+ "Lalamove",
437
+ "SingPost",
438
+ "EasyParcel",
439
+ "DHL",
440
+ "FedEx",
441
+ "Oddle",
442
+ "UParcel",
443
+ "UPS",
444
+ "Park N Parcel",
445
+ "ParkXL",
446
+ "Airpak Express",
447
+ "Pandago",
448
+ "Qdelivery",
449
+ "iXpress Logistics",
450
+ "Blue Dart",
451
+ "GogoX",
452
+ "Others (Type in remarks)",
453
+ ];
454
+ return arr.map((x) => ({
455
+ title: x,
456
+ value: x,
457
+ }));
458
+ });
459
+
460
+ const formTitle = computed(() => {
461
+ const isContractorForm = prop.type === "contractor";
462
+ const step = contractorStep.value;
463
+ if (isContractorForm && step === 2) {
464
+ return "Pass & Keys Information";
465
+ } else if (isContractorForm && step === 3) {
466
+ return "Members Information";
467
+ } else return "General Information";
468
+ });
469
+
470
+ function handleSelectContractorType(obj: { title: string; value: string }) {
471
+ visitor.contractorType = obj.value ?? "";
472
+ }
473
+
474
+ function handleFocusedContractorType() {
475
+ const obj = contractorTypeObj.value;
476
+ const matched = contractorTypes.some(
477
+ (x) => x.value === obj?.value && x.title === obj?.title
478
+ );
479
+ if (!matched) {
480
+ contractorTypeObj.value = null;
481
+ contractorTypeInput.value = "";
482
+ visitor.contractorType = "";
483
+ }
484
+ }
485
+
486
+ function handleSelectCompanyName(obj: { title: string; value: string }) {
487
+ visitor.company = obj.value ?? "";
488
+ }
489
+
490
+ function handleFocusedCompanyName() {
491
+ const obj = companyNameObj.value;
492
+ const matched = companyNames.value.some(
493
+ (x) => x.value === obj?.value && x.title === obj?.title
494
+ );
495
+ if (!matched) {
496
+ companyNameObj.value = null;
497
+ companyNameInput.value = "";
498
+ visitor.company = "";
499
+ }
500
+ }
501
+
502
+ const {
503
+ data: siteData,
504
+ refresh: refreshSiteData,
505
+ status: blockStatus,
506
+ } = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
507
+ getSiteById(prop.site)
508
+ );
509
+
510
+ const {
511
+ data: levelsData,
512
+ refresh: refreshLevelsData,
513
+ status: levelsStatus,
514
+ } = useLazyAsyncData(
515
+ `fetch-levels-data-${prop.site}-${visitor.block}`,
516
+ async () => {
517
+ if (!visitor.block) return Promise.resolve(null);
518
+ return await getSiteLevels(prop.site, { block: Number(visitor.block) });
519
+ }
520
+ );
521
+
522
+ const {
523
+ data: unitsData,
524
+ refresh: refreshUnitsData,
525
+ status: unitsStatus,
526
+ } = useLazyAsyncData(
527
+ `fetch-units-data-${prop.site}-${visitor.level}`,
528
+ async () => {
529
+ if (!visitor.level) return Promise.resolve(null);
530
+ return await getSiteUnits(prop.site, Number(visitor.block), visitor.level);
531
+ }
532
+ );
533
+
534
+ watch(
535
+ siteData,
536
+ (newVal) => {
537
+ const siteDataValue = newVal as any;
538
+ if (siteDataValue) {
539
+ const numberOfBlocks = siteDataValue.metadata?.block || 0;
540
+ for (let i = 1; i <= numberOfBlocks; i++) {
541
+ blocksArray.value.push({
542
+ title: `Block ${i}`,
543
+ value: i,
544
+ });
545
+ }
546
+ } else {
547
+ blocksArray.value = [];
548
+ }
549
+ },
550
+ { immediate: true }
551
+ );
552
+
553
+ watch(
554
+ levelsData,
555
+ (newVal: any) => {
556
+ if (newVal) {
557
+ const arr = newVal.levels || [];
558
+ levelsArray.value = arr?.map((level: any) => ({
559
+ title: level,
560
+ value: level,
561
+ }));
562
+ } else {
563
+ levelsArray.value = [];
564
+ }
565
+ },
566
+ { immediate: true }
567
+ );
568
+
569
+ watch(
570
+ unitsData,
571
+ (newVal: any) => {
572
+ if (newVal && Array.isArray(newVal)) {
573
+ const arr = newVal || [];
574
+ unitsArray.value = arr?.map((unit: any) => ({
575
+ title: unit?.name,
576
+ value: unit?._id,
577
+ }));
578
+ } else {
579
+ unitsArray.value = [];
580
+ }
581
+ },
582
+ { immediate: true }
583
+ );
584
+
585
+
586
+ function handleChangeBlock(value: any) {
587
+ visitor.level = "";
588
+ visitor.unit = "";
589
+ refreshLevelsData();
590
+ }
591
+
592
+ function handleChangeLevel(value: any) {
593
+ visitor.unit = "";
594
+ refreshUnitsData();
595
+ }
596
+
597
+ function handleUpdateUnit(value: any) {
598
+ const selectedUnit = unitsArray.value?.find((x: any) => x.value === value);
599
+ visitor.unitName = selectedUnit?.title || "";
600
+ }
601
+
602
+ function backToSelection() {
603
+ emit("back");
604
+ message.value = "";
605
+ }
606
+
607
+ function handleGoToPreviousPage() {
608
+ if (contractorStep.value > 1) {
609
+ contractorStep.value--;
610
+ }
611
+ }
612
+
613
+ function close() {
614
+ emit("close");
615
+ message.value = "";
616
+ }
617
+
618
+ function formatVisitorType(type: TVisitorType): string {
619
+ switch (type) {
620
+ case "contractor":
621
+ return "Contractor";
622
+ case "delivery":
623
+ return "Delivery";
624
+ case "walk-in":
625
+ return "Walk-In";
626
+ case "pick-up":
627
+ return "Pick-Up";
628
+ case "drop-off":
629
+ return "Drop-Off";
630
+ default:
631
+ return "";
632
+ }
633
+ }
634
+
635
+ async function handleNextPage() {
636
+ errorMessage.value = "";
637
+ formRef.value!.validate();
638
+ if (!validForm.value) {
639
+ errorMessage.value = "Please complete all required fields *";
640
+ return;
641
+ }
642
+ const step = contractorStep.value;
643
+ if (step < 3) contractorStep.value++;
644
+ else await submit();
645
+ }
646
+
647
+ async function submit() {
648
+ formRef.value!.validate();
649
+ errorMessage.value = "";
650
+ processing.value = true;
651
+
652
+ let payload: Partial<TVisitorPayload> = {
653
+ type: prop.type,
654
+ org: prop.org,
655
+ site: prop.site,
656
+ };
657
+
658
+ if (prop.mode === "add") {
659
+ const allowedFields = typeFieldMap[prop.type];
660
+ for (const key of allowedFields as (keyof TVisitorPayload)[]) {
661
+ if (visitor[key] !== undefined) {
662
+ payload = {
663
+ ...payload,
664
+ [key]: visitor[key],
665
+ };
666
+ }
667
+ }
668
+
669
+ if (prop.type === "contractor") {
670
+ // contractor type logic payload
671
+ payload = {
672
+ ...payload,
673
+ members: visitor.members,
674
+ };
675
+ }
676
+ } else if (prop.mode === "edit") {
677
+ }
678
+ try {
679
+ const res = await createVisitor(payload);
680
+ if (res) {
681
+ if (createMore.value) {
682
+ emit("done:more");
683
+ } else emit("done");
684
+ }
685
+ } catch (error: any) {
686
+ const err = error?.data?.message;
687
+ errorMessage.value =
688
+ err ||
689
+ `Failed to ${
690
+ prop.mode === "add" ? "add" : "update"
691
+ } visitor. Please try again.`;
692
+ } finally {
693
+ processing.value = false;
694
+ }
695
+ }
696
+
697
+ watch(
698
+ () => visitor.plateNumber,
699
+ (newVal) => {
700
+ visitor.plateNumber = newVal?.toUpperCase();
701
+ }
702
+ );
703
+
704
+ onMounted(() => {
705
+ contractorStep.value = 1;
706
+ });
707
+ </script>
708
+ <style scoped>
709
+ .button-outline-class {
710
+ border: 1px solid rgba(var(--v-theme-primary));
711
+ }
712
+ </style>