@7365admin1/layer-common 3.0.27 → 3.0.29
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.
- package/CHANGELOG.md +12 -0
- package/components/AccessCardDetailsDialog.vue +24 -11
- package/components/AccessCardPreviewDialog.vue +7 -3
- package/components/BulletinBoardView.vue +368 -288
- package/components/VehicleForm.vue +570 -285
- package/components/VisitorForm.vue +417 -82
- package/components/VisitorManagement.vue +43 -11
- package/composables/useAccessManagement.ts +1 -1
- package/composables/useVisitor.ts +63 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<v-toolbar>
|
|
4
4
|
<v-row no-gutters class="fill-height px-6 d-flex justify-space-between align-center" align="center">
|
|
5
5
|
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
-
{{
|
|
6
|
+
{{ formMode }} {{ formatVisitorType(type) }}
|
|
7
7
|
</span>
|
|
8
8
|
<ButtonClose @click="emit('close:all')" />
|
|
9
9
|
</v-row>
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
:selected-nfc-cards="passType === 'NFC' ? passCards : []" />
|
|
218
218
|
</v-col>
|
|
219
219
|
|
|
220
|
-
<v-col v-if="
|
|
220
|
+
<v-col v-if="formMode === 'add'" cols="12" class="mt-2">
|
|
221
221
|
<v-checkbox v-model="createMore" density="comfortable" hide-details>
|
|
222
222
|
<template #label>
|
|
223
223
|
<span class="text-subtitle-2 font-weight-bold">
|
|
@@ -239,11 +239,11 @@
|
|
|
239
239
|
<v-row no-gutters>
|
|
240
240
|
<v-col cols="6">
|
|
241
241
|
<v-btn v-if="
|
|
242
|
-
|
|
242
|
+
formMode === 'add' &&
|
|
243
243
|
(withStep2 || withStep3) &&
|
|
244
244
|
contractorStep > 1
|
|
245
245
|
" tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
|
|
246
|
-
<v-btn v-else-if="
|
|
246
|
+
<v-btn v-else-if="formMode === 'add' || formMode === 'register'" tile block variant="text" class="text-none"
|
|
247
247
|
size="48" @click="backToSelection" text="Back to Selection" />
|
|
248
248
|
<v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
|
|
249
249
|
</v-col>
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
:disabled="processing" @click="handleNextPage" :text="'Next'" />
|
|
253
253
|
<v-btn v-else tile block variant="flat" color="black" class="text-none" size="48"
|
|
254
254
|
:disabled="!validForm || processing || membersFieldIncomplete" @click="submit"
|
|
255
|
-
:text="
|
|
255
|
+
:text="formMode == 'add' ? 'Submit' : 'Update'" />
|
|
256
256
|
</v-col>
|
|
257
257
|
</v-row>
|
|
258
258
|
</v-toolbar>
|
|
@@ -264,37 +264,167 @@
|
|
|
264
264
|
@update:people="handleAutofillDataViaVehicleNumber" />
|
|
265
265
|
</v-dialog>
|
|
266
266
|
|
|
267
|
-
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="
|
|
268
|
-
<v-card :loading="loading.checkingOut">
|
|
269
|
-
<v-
|
|
270
|
-
<v-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
</
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
267
|
+
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="1200">
|
|
268
|
+
<v-card :loading="loading.checkingOut || loading.searchingNonCheckedOut">
|
|
269
|
+
<v-card-actions class="justify-end bg-grey-lighten-3 pa-4">
|
|
270
|
+
<v-btn icon="mdi-close" variant="text" size="large" @click="dialog.showNonCheckedOutDialog = false" />
|
|
271
|
+
</v-card-actions>
|
|
272
|
+
|
|
273
|
+
<v-card-text class="pa-6">
|
|
274
|
+
<p class="text-subtitle-1 font-weight-medium mb-8">
|
|
275
|
+
The following list needs to check-out before proceeding to add related to the NRIC/Phone Number below:
|
|
276
|
+
</p>
|
|
277
|
+
|
|
278
|
+
<div class="text-subtitle-1 mb-8">
|
|
279
|
+
<div>NRIC: {{ visitor.nric || "N/A" }}</div>
|
|
280
|
+
<div>Phone Number: {{ visitor.contact || "N/A" }}</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<v-data-table
|
|
284
|
+
class="border rounded"
|
|
285
|
+
:headers="nonCheckedOutVisitorHeaders"
|
|
286
|
+
:items="matchingNonCheckedOutVisitors"
|
|
287
|
+
item-value="_id"
|
|
288
|
+
:items-per-page="5"
|
|
289
|
+
hide-default-footer
|
|
290
|
+
>
|
|
291
|
+
<template #item.name="{ item }">
|
|
292
|
+
<div class="d-flex align-center ga-3">
|
|
293
|
+
<v-avatar color="red" size="32">
|
|
294
|
+
<span class="text-white text-subtitle-2">{{ getVisitorInitial(item.name) }}</span>
|
|
295
|
+
</v-avatar>
|
|
296
|
+
<div>
|
|
297
|
+
<div class="font-weight-medium">{{ item.name || "N/A" }}</div>
|
|
298
|
+
<div class="text-caption">
|
|
299
|
+
<v-icon icon="mdi-card-account-details-outline" size="16" class="mr-1" />
|
|
300
|
+
{{ maskNRIC(item.nric) || "N/A" }}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</template>
|
|
305
|
+
|
|
306
|
+
<template #item.typeCompany="{ item }">
|
|
307
|
+
<div>
|
|
308
|
+
<v-icon icon="mdi-account" size="18" class="mr-2" />
|
|
309
|
+
{{ formatVisitorType(item.type) }}
|
|
310
|
+
</div>
|
|
311
|
+
<div>
|
|
312
|
+
<v-icon icon="mdi-office-building" size="18" class="mr-2" />
|
|
313
|
+
{{ item.company || "N/A" }}
|
|
314
|
+
</div>
|
|
315
|
+
</template>
|
|
316
|
+
|
|
317
|
+
<template #item.location="{ item }">
|
|
318
|
+
<v-icon icon="mdi-calendar-range" size="18" class="mr-2" />
|
|
319
|
+
{{ formatVisitorLocation(item) }}
|
|
320
|
+
</template>
|
|
321
|
+
|
|
322
|
+
<template #item.contactVehicle="{ item }">
|
|
323
|
+
<div>
|
|
324
|
+
<v-icon icon="mdi-phone" size="18" class="mr-2" />
|
|
325
|
+
{{ item.contact || "N/A" }}
|
|
326
|
+
</div>
|
|
327
|
+
<div>
|
|
328
|
+
<v-icon icon="mdi-car" size="18" class="mr-2" />
|
|
329
|
+
{{ item.plateNumber || "N/A" }}
|
|
330
|
+
</div>
|
|
331
|
+
</template>
|
|
332
|
+
|
|
333
|
+
<template #item.checkInOut="{ item }">
|
|
334
|
+
<div class="py-2">
|
|
335
|
+
<div class="mb-2">
|
|
336
|
+
<v-icon icon="mdi-clock-check-outline" color="green" size="20" class="mr-1" />
|
|
337
|
+
{{ formatNonCheckedOutDate(item.checkIn) }}
|
|
338
|
+
</div>
|
|
339
|
+
<v-btn size="small" class="text-none" color="red" variant="flat" :loading="loading.checkingOut && selectedCheckoutVisitorId === item._id" :disabled="!item._id" @click.stop="item._id && handleCheckout(item._id)">
|
|
340
|
+
Check Out
|
|
341
|
+
</v-btn>
|
|
342
|
+
</div>
|
|
343
|
+
</template>
|
|
344
|
+
</v-data-table>
|
|
345
|
+
|
|
346
|
+
<div class="d-flex justify-end mt-8">
|
|
347
|
+
<v-btn color="red" variant="flat" size="large" class="text-none px-10" :loading="loading.searchingNonCheckedOut" @click="handleSkipNricNonCheckedOut">
|
|
348
|
+
Skip NRIC: {{ visitor.nric || "N/A" }}
|
|
349
|
+
</v-btn>
|
|
350
|
+
</div>
|
|
351
|
+
</v-card-text>
|
|
352
|
+
</v-card>
|
|
353
|
+
</v-dialog>
|
|
354
|
+
|
|
355
|
+
<v-dialog
|
|
356
|
+
v-if="enableUnregistered"
|
|
357
|
+
v-model="dialog.showUnregisteredPlateDialog"
|
|
358
|
+
max-width="900"
|
|
359
|
+
persistent
|
|
360
|
+
>
|
|
361
|
+
<v-card color="background" class="pa-4">
|
|
362
|
+
<v-card-actions class="justify-end pa-0">
|
|
363
|
+
<v-btn
|
|
364
|
+
icon="mdi-close"
|
|
365
|
+
variant="text"
|
|
366
|
+
size="large"
|
|
367
|
+
@click="closeUnregisteredPlateDialog"
|
|
368
|
+
/>
|
|
369
|
+
</v-card-actions>
|
|
370
|
+
|
|
371
|
+
<v-card-text class="text-center pt-2">
|
|
372
|
+
<v-icon color="orange" size="96">mdi-alert-circle-outline</v-icon>
|
|
373
|
+
|
|
374
|
+
<div :class="`text-body-${$vuetify.display.xs ? 'mobile' : 'tablet'} mt-8`">
|
|
375
|
+
Vehicle Number {{ unregisteredPlateSearchText }} is similar to below
|
|
376
|
+
<br />
|
|
377
|
+
Unregistered visitors last 24 hours. Please select to
|
|
378
|
+
<br />
|
|
379
|
+
Update
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<v-table class="mt-8 border rounded">
|
|
383
|
+
<thead>
|
|
384
|
+
<tr>
|
|
385
|
+
<th class="text-left">Plate Number</th>
|
|
386
|
+
<th class="text-left">Check In</th>
|
|
387
|
+
<th class="text-center">Action</th>
|
|
388
|
+
</tr>
|
|
389
|
+
</thead>
|
|
390
|
+
<tbody>
|
|
391
|
+
<tr
|
|
392
|
+
v-for="match in unregisteredPlateMatches"
|
|
393
|
+
:key="match._id || match.plateNumber"
|
|
394
|
+
>
|
|
395
|
+
<td class="text-left font-weight-bold text-uppercase">
|
|
396
|
+
{{ match.plateNumber || "N/A" }}
|
|
397
|
+
</td>
|
|
398
|
+
<td class="text-left">
|
|
399
|
+
{{ formatUnregisteredCheckIn(match.checkIn) }}
|
|
400
|
+
</td>
|
|
401
|
+
<td class="text-center">
|
|
402
|
+
<v-btn
|
|
403
|
+
color="red"
|
|
404
|
+
class="text-capitalize"
|
|
405
|
+
density="compact"
|
|
406
|
+
size="large"
|
|
407
|
+
@click="selectUnregisteredPlateMatch(match)"
|
|
408
|
+
>
|
|
409
|
+
Update
|
|
410
|
+
</v-btn>
|
|
411
|
+
</td>
|
|
412
|
+
</tr>
|
|
413
|
+
</tbody>
|
|
414
|
+
</v-table>
|
|
297
415
|
</v-card-text>
|
|
416
|
+
|
|
417
|
+
<v-card-actions>
|
|
418
|
+
<v-btn
|
|
419
|
+
block
|
|
420
|
+
color="red"
|
|
421
|
+
variant="flat"
|
|
422
|
+
size="x-large"
|
|
423
|
+
@click="closeUnregisteredPlateDialog"
|
|
424
|
+
>
|
|
425
|
+
Cancel & Add New
|
|
426
|
+
</v-btn>
|
|
427
|
+
</v-card-actions>
|
|
298
428
|
</v-card>
|
|
299
429
|
</v-dialog>
|
|
300
430
|
</v-card>
|
|
@@ -338,12 +468,16 @@ const prop = defineProps({
|
|
|
338
468
|
type: String,
|
|
339
469
|
default: "",
|
|
340
470
|
},
|
|
471
|
+
enableUnregistered: {
|
|
472
|
+
type: Boolean,
|
|
473
|
+
default: false,
|
|
474
|
+
}
|
|
341
475
|
});
|
|
342
476
|
|
|
343
477
|
type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
|
|
344
478
|
const currentAutofillSource = ref<AutofillSource>(null);
|
|
345
479
|
|
|
346
|
-
const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
|
|
480
|
+
const { requiredRule, debounce, UTCToLocalTIme, maskNRIC } = useUtils();
|
|
347
481
|
const { getSiteById } = useSiteSettings();
|
|
348
482
|
const { currentUser } = useLocalAuth();
|
|
349
483
|
const { getBlocksSelection, getLevelsSelection, getUnitSelection } =
|
|
@@ -352,7 +486,8 @@ const {
|
|
|
352
486
|
createVisitor,
|
|
353
487
|
typeFieldMap,
|
|
354
488
|
contractorTypes,
|
|
355
|
-
|
|
489
|
+
checkSearchUncheckedOutUnregisteredVisitor,
|
|
490
|
+
searchVisitorObject,
|
|
356
491
|
updateVisitor,
|
|
357
492
|
} = useVisitor();
|
|
358
493
|
const { getBySiteId: getEntryPassSettingsBySiteId } =
|
|
@@ -408,10 +543,13 @@ const registeredUnitCompanyName = ref("N/A");
|
|
|
408
543
|
const dialog = reactive({
|
|
409
544
|
vehicleNumberUsersList: false,
|
|
410
545
|
showNonCheckedOutDialog: false,
|
|
546
|
+
showUnregisteredPlateDialog: false,
|
|
411
547
|
});
|
|
412
548
|
|
|
413
549
|
const loading = reactive({
|
|
414
550
|
checkingOut: false,
|
|
551
|
+
checkingUnregisteredPlate: false,
|
|
552
|
+
searchingNonCheckedOut: false,
|
|
415
553
|
});
|
|
416
554
|
|
|
417
555
|
const validForm = ref(false);
|
|
@@ -436,10 +574,28 @@ const deliveryCompany = ref("");
|
|
|
436
574
|
const deliveryCompanyInput = ref("");
|
|
437
575
|
const deliveryCompanyList = ref<string[]>([]);
|
|
438
576
|
|
|
439
|
-
const
|
|
577
|
+
const matchingNonCheckedOutVisitors = ref<TVisitor[]>([]);
|
|
578
|
+
const selectedCheckoutVisitorId = ref<string | null>(null);
|
|
579
|
+
const skipNonCheckedOutNric = ref(false);
|
|
580
|
+
const nonCheckedOutVisitorHeaders = [
|
|
581
|
+
{ title: "Name/NRIC", key: "name", align: "start", sortable: false },
|
|
582
|
+
{ title: "Type / Company", key: "typeCompany", align: "start", sortable: false },
|
|
583
|
+
{ title: "Location", key: "location", align: "start", sortable: false },
|
|
584
|
+
{ title: "Contact / Vehicle No.", key: "contactVehicle", align: "start", sortable: false },
|
|
585
|
+
{ title: "Check In / Out", key: "checkInOut", align: "start", sortable: false, width: "200px" },
|
|
586
|
+
] as const;
|
|
587
|
+
const unregisteredPlateMatches = ref<TVisitor[]>([]);
|
|
588
|
+
const unregisteredPlateSearchText = ref("");
|
|
589
|
+
const selectedUnregisteredVisitor = ref<TVisitor | null>(null);
|
|
590
|
+
const skipUnregisteredPlateCheck = ref(false);
|
|
591
|
+
const pendingUnregisteredAction = ref<"next" | "submit" | null>(null);
|
|
440
592
|
|
|
441
593
|
const vehicleNumberUserItems = ref<TPeople[]>([]);
|
|
442
594
|
|
|
595
|
+
const formMode = computed<"add" | "edit" | "register">(() => {
|
|
596
|
+
return selectedUnregisteredVisitor.value ? "register" : prop.mode;
|
|
597
|
+
});
|
|
598
|
+
|
|
443
599
|
const shouldShowField = (
|
|
444
600
|
fieldKey: keyof TVisitorPayload | "delivery-company"
|
|
445
601
|
) => {
|
|
@@ -467,6 +623,10 @@ const showNextButton = computed(() => {
|
|
|
467
623
|
} else return false;
|
|
468
624
|
});
|
|
469
625
|
|
|
626
|
+
const enableUnregistered = computed(() => {
|
|
627
|
+
return prop.mode === "add" && prop.enableUnregistered
|
|
628
|
+
});
|
|
629
|
+
|
|
470
630
|
const showShowKeysField = computed(() => {
|
|
471
631
|
return ["guest", "contractor", "walk-in"].includes(prop.type);
|
|
472
632
|
});
|
|
@@ -613,6 +773,7 @@ watch(fetchPersonByNRICReq, (obj) => {
|
|
|
613
773
|
watch(
|
|
614
774
|
() => visitor.nric,
|
|
615
775
|
(value) => {
|
|
776
|
+
skipNonCheckedOutNric.value = false;
|
|
616
777
|
if (!value || value.length < 4) {
|
|
617
778
|
nricSearchMenu.value = false;
|
|
618
779
|
nricSearchResults.value = [];
|
|
@@ -620,6 +781,13 @@ watch(
|
|
|
620
781
|
}
|
|
621
782
|
);
|
|
622
783
|
|
|
784
|
+
watch(
|
|
785
|
+
() => visitor.contact,
|
|
786
|
+
() => {
|
|
787
|
+
skipNonCheckedOutNric.value = false;
|
|
788
|
+
}
|
|
789
|
+
);
|
|
790
|
+
|
|
623
791
|
function selectNricSearchResult(item: Partial<TPeople>) {
|
|
624
792
|
nricSearchMenu.value = false;
|
|
625
793
|
nricSearchResults.value = [];
|
|
@@ -660,48 +828,89 @@ watch(fetchCompanyListReq, (arr) => {
|
|
|
660
828
|
companyNames.value = arr?.flatMap((x) => x?.companyName);
|
|
661
829
|
}
|
|
662
830
|
});
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
site: prop.site,
|
|
673
|
-
plateNumber: visitor.plateNumber,
|
|
674
|
-
checkedOut: false,
|
|
675
|
-
status: "registered",
|
|
676
|
-
});
|
|
677
|
-
});
|
|
831
|
+
function getVisitorSearchItems(res: any): TVisitor[] {
|
|
832
|
+
const items =
|
|
833
|
+
res?.items ||
|
|
834
|
+
res?.data?.items ||
|
|
835
|
+
res?.data?.result?.items ||
|
|
836
|
+
res?.result?.items ||
|
|
837
|
+
[];
|
|
838
|
+
return Array.isArray(items) ? items : [];
|
|
839
|
+
}
|
|
678
840
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
841
|
+
function getVisitorInitial(name?: string | null) {
|
|
842
|
+
return name?.trim()?.charAt(0)?.toUpperCase() || "?";
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function formatLocationPart(value: unknown) {
|
|
846
|
+
if (!value) return "";
|
|
847
|
+
if (typeof value === "string") return value;
|
|
848
|
+
if (typeof value === "object" && "name" in value) {
|
|
849
|
+
return String((value as { name?: string }).name || "");
|
|
687
850
|
}
|
|
688
|
-
|
|
851
|
+
return "";
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function formatVisitorLocation(v: TVisitor) {
|
|
855
|
+
return [
|
|
856
|
+
formatLocationPart(v.block),
|
|
857
|
+
formatLocationPart(v.level),
|
|
858
|
+
v.unitName || formatLocationPart(v.unit),
|
|
859
|
+
].filter(Boolean).join(" / ") || "N/A";
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function formatNonCheckedOutDate(date?: string) {
|
|
863
|
+
return date ? UTCToLocalTIme(date) : "N/A";
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
async function checkNonCheckedOutVisitorsBeforeSubmit(
|
|
867
|
+
options: { skipNric?: boolean; showDialog?: boolean } = {}
|
|
868
|
+
) {
|
|
869
|
+
const { skipNric = skipNonCheckedOutNric.value, showDialog = true } = options;
|
|
870
|
+
const nric = skipNric ? null : visitor.nric || null;
|
|
871
|
+
const contact = visitor.contact || null;
|
|
689
872
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
matchingPlateNumberNonCheckedOutArr.value = [];
|
|
873
|
+
if (!nric && !contact) {
|
|
874
|
+
matchingNonCheckedOutVisitors.value = [];
|
|
693
875
|
dialog.showNonCheckedOutDialog = false;
|
|
694
|
-
return;
|
|
876
|
+
return true;
|
|
695
877
|
}
|
|
696
|
-
fetchVisitorListByVehicleNumberRefresh();
|
|
697
|
-
}, 300);
|
|
698
878
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
879
|
+
try {
|
|
880
|
+
loading.searchingNonCheckedOut = true;
|
|
881
|
+
const res = await searchVisitorObject({
|
|
882
|
+
page: 1,
|
|
883
|
+
limit: 1000,
|
|
884
|
+
site: prop.site,
|
|
885
|
+
searchObject: {
|
|
886
|
+
nric,
|
|
887
|
+
contact,
|
|
888
|
+
},
|
|
889
|
+
notCheckedOut: true,
|
|
890
|
+
});
|
|
891
|
+
const items = getVisitorSearchItems(res);
|
|
892
|
+
matchingNonCheckedOutVisitors.value = items;
|
|
893
|
+
dialog.showNonCheckedOutDialog = showDialog && items.length > 0;
|
|
894
|
+
return items.length === 0;
|
|
895
|
+
} catch (error: any) {
|
|
896
|
+
errorMessage.value =
|
|
897
|
+
error?.data?.message ||
|
|
898
|
+
error?.response?._data?.message ||
|
|
899
|
+
"Unable to check unchecked-out visitors. Please try again.";
|
|
900
|
+
return false;
|
|
901
|
+
} finally {
|
|
902
|
+
loading.searchingNonCheckedOut = false;
|
|
703
903
|
}
|
|
704
|
-
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function handleSkipNricNonCheckedOut() {
|
|
907
|
+
const canSubmit = await checkNonCheckedOutVisitorsBeforeSubmit({
|
|
908
|
+
skipNric: true,
|
|
909
|
+
});
|
|
910
|
+
if (!canSubmit) return;
|
|
911
|
+
skipNonCheckedOutNric.value = true;
|
|
912
|
+
dialog.showNonCheckedOutDialog = false;
|
|
913
|
+
}
|
|
705
914
|
|
|
706
915
|
async function handleCheckout(visitorId: string) {
|
|
707
916
|
if (!visitorId) {
|
|
@@ -711,19 +920,21 @@ async function handleCheckout(visitorId: string) {
|
|
|
711
920
|
|
|
712
921
|
try {
|
|
713
922
|
loading.checkingOut = true;
|
|
923
|
+
selectedCheckoutVisitorId.value = visitorId;
|
|
714
924
|
const res = await updateVisitor(visitorId as string, {
|
|
715
925
|
checkOut: new Date().toISOString(),
|
|
716
926
|
});
|
|
717
927
|
if (res) {
|
|
718
|
-
await
|
|
928
|
+
await checkNonCheckedOutVisitorsBeforeSubmit();
|
|
719
929
|
}
|
|
720
930
|
} catch (error: any) {
|
|
721
|
-
const
|
|
931
|
+
const checkoutErrorMessage = error?.response?._data?.message;
|
|
722
932
|
console.log("[ERROR]", error);
|
|
723
933
|
errorMessage.value =
|
|
724
|
-
|
|
934
|
+
checkoutErrorMessage || "An error occurred while checking out the vehicle.";
|
|
725
935
|
} finally {
|
|
726
936
|
loading.checkingOut = false;
|
|
937
|
+
selectedCheckoutVisitorId.value = null;
|
|
727
938
|
}
|
|
728
939
|
}
|
|
729
940
|
|
|
@@ -988,6 +1199,95 @@ function formatVisitorType(type: TVisitorType): string {
|
|
|
988
1199
|
}
|
|
989
1200
|
}
|
|
990
1201
|
|
|
1202
|
+
async function checkUnregisteredPlateBeforeContinue(action: "next" | "submit") {
|
|
1203
|
+
if (
|
|
1204
|
+
!enableUnregistered.value ||
|
|
1205
|
+
skipUnregisteredPlateCheck.value ||
|
|
1206
|
+
selectedUnregisteredVisitor.value ||
|
|
1207
|
+
!visitor.plateNumber
|
|
1208
|
+
) {
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
try {
|
|
1213
|
+
loading.checkingUnregisteredPlate = true;
|
|
1214
|
+
unregisteredPlateSearchText.value = visitor.plateNumber;
|
|
1215
|
+
const res = await checkSearchUncheckedOutUnregisteredVisitor({
|
|
1216
|
+
site: prop.site,
|
|
1217
|
+
plateNumber: visitor.plateNumber,
|
|
1218
|
+
hourAgo: 24,
|
|
1219
|
+
page: 1,
|
|
1220
|
+
limit: 100,
|
|
1221
|
+
});
|
|
1222
|
+
const items = Array.isArray(res?.items)
|
|
1223
|
+
? res.items
|
|
1224
|
+
: Array.isArray(res?.data?.items)
|
|
1225
|
+
? res.data.items
|
|
1226
|
+
: Array.isArray(res?.data)
|
|
1227
|
+
? res.data
|
|
1228
|
+
: Array.isArray(res)
|
|
1229
|
+
? res
|
|
1230
|
+
: [];
|
|
1231
|
+
|
|
1232
|
+
unregisteredPlateMatches.value = items;
|
|
1233
|
+
if (items.length === 0) return true;
|
|
1234
|
+
|
|
1235
|
+
pendingUnregisteredAction.value = action;
|
|
1236
|
+
dialog.showUnregisteredPlateDialog = true;
|
|
1237
|
+
return false;
|
|
1238
|
+
} catch (error: any) {
|
|
1239
|
+
errorMessage.value =
|
|
1240
|
+
error?.data?.message ||
|
|
1241
|
+
error?.response?._data?.message ||
|
|
1242
|
+
"Unable to check unregistered visitors. Please try again.";
|
|
1243
|
+
return false;
|
|
1244
|
+
} finally {
|
|
1245
|
+
loading.checkingUnregisteredPlate = false;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function formatUnregisteredCheckIn(checkIn?: string) {
|
|
1250
|
+
return checkIn ? UTCToLocalTIme(checkIn) : "N/A";
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
async function continuePendingUnregisteredAction() {
|
|
1254
|
+
const action = pendingUnregisteredAction.value;
|
|
1255
|
+
pendingUnregisteredAction.value = null;
|
|
1256
|
+
if (action === "next") {
|
|
1257
|
+
await handleNextPage();
|
|
1258
|
+
} else if (action === "submit") {
|
|
1259
|
+
await submit();
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async function closeUnregisteredPlateDialog() {
|
|
1264
|
+
dialog.showUnregisteredPlateDialog = false;
|
|
1265
|
+
unregisteredPlateMatches.value = [];
|
|
1266
|
+
skipUnregisteredPlateCheck.value = true;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
async function selectUnregisteredPlateMatch(match: TVisitor) {
|
|
1270
|
+
selectedUnregisteredVisitor.value = match;
|
|
1271
|
+
skipUnregisteredPlateCheck.value = true;
|
|
1272
|
+
dialog.showUnregisteredPlateDialog = false;
|
|
1273
|
+
unregisteredPlateMatches.value = [];
|
|
1274
|
+
|
|
1275
|
+
visitor.plateNumber = match.plateNumber;
|
|
1276
|
+
visitor.attachments = match.attachments || visitor.attachments;
|
|
1277
|
+
visitor.block = match.block || visitor.block;
|
|
1278
|
+
visitor.level = match.level || visitor.level;
|
|
1279
|
+
visitor.unit = match.unit || visitor.unit;
|
|
1280
|
+
visitor.company = match.company || visitor.company;
|
|
1281
|
+
visitor.contractorType = match.contractorType || visitor.contractorType;
|
|
1282
|
+
visitor.name = match.name || visitor.name;
|
|
1283
|
+
visitor.contact = match.contact || visitor.contact;
|
|
1284
|
+
visitor.nric = match.nric || visitor.nric;
|
|
1285
|
+
visitor.company = match.company || visitor.company;
|
|
1286
|
+
visitor.remarks = match.remarks || visitor.remarks;
|
|
1287
|
+
|
|
1288
|
+
pendingUnregisteredAction.value = null;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
991
1291
|
async function handleNextPage() {
|
|
992
1292
|
errorMessage.value = "";
|
|
993
1293
|
formRef.value!.validate();
|
|
@@ -1010,11 +1310,12 @@ async function handleNextPage() {
|
|
|
1010
1310
|
errorMessage.value = "Please select at least one NFC card before proceeding.";
|
|
1011
1311
|
return;
|
|
1012
1312
|
}
|
|
1313
|
+
if (!(await checkUnregisteredPlateBeforeContinue("next"))) return;
|
|
1013
1314
|
if (step < 3) contractorStep.value++;
|
|
1014
1315
|
else await submit();
|
|
1015
1316
|
}
|
|
1016
1317
|
|
|
1017
|
-
async function submit() {
|
|
1318
|
+
async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
|
|
1018
1319
|
formRef.value!.validate();
|
|
1019
1320
|
errorMessage.value = "";
|
|
1020
1321
|
if (!validForm.value) {
|
|
@@ -1023,6 +1324,14 @@ async function submit() {
|
|
|
1023
1324
|
}
|
|
1024
1325
|
|
|
1025
1326
|
if (passType.value === "HID_QR" && !validateHidQrPrinterConfig()) return;
|
|
1327
|
+
if (
|
|
1328
|
+
prop.mode === "add" &&
|
|
1329
|
+
!options.skipNonCheckedOutCheck &&
|
|
1330
|
+
!(await checkNonCheckedOutVisitorsBeforeSubmit())
|
|
1331
|
+
) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (!(await checkUnregisteredPlateBeforeContinue("submit"))) return;
|
|
1026
1335
|
|
|
1027
1336
|
processing.value = true;
|
|
1028
1337
|
|
|
@@ -1033,7 +1342,7 @@ async function submit() {
|
|
|
1033
1342
|
createdBy: prop.currentUser,
|
|
1034
1343
|
};
|
|
1035
1344
|
|
|
1036
|
-
if (
|
|
1345
|
+
if (formMode.value === "add" || formMode.value === "register") {
|
|
1037
1346
|
const allowedFields = typeFieldMap[prop.type];
|
|
1038
1347
|
for (const key of allowedFields as (keyof TVisitorPayload)[]) {
|
|
1039
1348
|
if (visitor[key] !== undefined) {
|
|
@@ -1071,7 +1380,7 @@ async function submit() {
|
|
|
1071
1380
|
}
|
|
1072
1381
|
|
|
1073
1382
|
// add register status when registering unregistered visitor
|
|
1074
|
-
if (
|
|
1383
|
+
if (formMode.value === "register") {
|
|
1075
1384
|
payload.status = "registered";
|
|
1076
1385
|
const currentUserId = currentUser.value?._id;
|
|
1077
1386
|
if(currentUserId) {
|
|
@@ -1081,7 +1390,18 @@ async function submit() {
|
|
|
1081
1390
|
|
|
1082
1391
|
try {
|
|
1083
1392
|
let res;
|
|
1084
|
-
if (prop.mode === "add") {
|
|
1393
|
+
if (prop.mode === "add" && selectedUnregisteredVisitor.value?._id) {
|
|
1394
|
+
payload.status = "registered";
|
|
1395
|
+
const currentUserId = currentUser.value?._id;
|
|
1396
|
+
if (currentUserId) {
|
|
1397
|
+
payload.updatedBy = currentUserId;
|
|
1398
|
+
}
|
|
1399
|
+
const { unitName, ...rest } = payload;
|
|
1400
|
+
res = await updateVisitor(
|
|
1401
|
+
selectedUnregisteredVisitor.value._id,
|
|
1402
|
+
rest as TVisitorPayload
|
|
1403
|
+
);
|
|
1404
|
+
} else if (formMode.value === "add") {
|
|
1085
1405
|
res = await createVisitor(payload as TVisitorPayload);
|
|
1086
1406
|
} else if (
|
|
1087
1407
|
(prop.mode === "edit" || prop.mode === "register") &&
|
|
@@ -1224,7 +1544,7 @@ async function submit() {
|
|
|
1224
1544
|
const err = error?.data?.message;
|
|
1225
1545
|
errorMessage.value =
|
|
1226
1546
|
err ||
|
|
1227
|
-
`Failed to ${
|
|
1547
|
+
`Failed to ${formMode.value === "add" ? "add" : "update"
|
|
1228
1548
|
} visitor. Please try again.`;
|
|
1229
1549
|
} finally {
|
|
1230
1550
|
processing.value = false;
|
|
@@ -1368,7 +1688,22 @@ async function printHidQrCodePass(visitorId = "", manageProcessing = true) {
|
|
|
1368
1688
|
watch(
|
|
1369
1689
|
() => visitor.plateNumber,
|
|
1370
1690
|
(newVal) => {
|
|
1371
|
-
|
|
1691
|
+
const normalizedPlateNumber = newVal?.toUpperCase();
|
|
1692
|
+
if (newVal !== normalizedPlateNumber) {
|
|
1693
|
+
visitor.plateNumber = normalizedPlateNumber;
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (
|
|
1698
|
+
selectedUnregisteredVisitor.value &&
|
|
1699
|
+
normalizedPlateNumber !== selectedUnregisteredVisitor.value.plateNumber?.toUpperCase()
|
|
1700
|
+
) {
|
|
1701
|
+
selectedUnregisteredVisitor.value = null;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
skipUnregisteredPlateCheck.value = false;
|
|
1705
|
+
unregisteredPlateMatches.value = [];
|
|
1706
|
+
dialog.showUnregisteredPlateDialog = false;
|
|
1372
1707
|
}
|
|
1373
1708
|
);
|
|
1374
1709
|
|