@7365admin1/layer-common 1.11.23 → 1.11.26
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 +18 -0
- package/components/AreaChecklistHistoryMain.vue +1 -1
- package/components/AttachmentsViewer.vue +266 -0
- package/components/BuildingForm.vue +63 -95
- package/components/BuildingManagement/buildings.vue +11 -12
- package/components/CleaningScheduleMain.vue +10 -1
- package/components/DocumentViewer.vue +75 -0
- package/components/EquipmentItemMain.vue +4 -2
- package/components/EquipmentManagementMain.vue +14 -10
- package/components/ManageChecklistMain.vue +13 -1
- package/components/ScanVisitorQRCode.vue +9 -2
- package/components/ScheduleAreaMain.vue +0 -1
- package/components/VisitorDataFromScannedQRCodeForm.vue +50 -20
- package/components/VisitorForm.vue +6 -8
- package/components/VisitorManagement.vue +438 -55
- package/composables/useVisitor.ts +5 -2
- package/package.json +1 -1
- package/types/building.d.ts +1 -1
|
@@ -109,6 +109,22 @@
|
|
|
109
109
|
>
|
|
110
110
|
</template>
|
|
111
111
|
|
|
112
|
+
<template v-slot:item.email="{ value }">
|
|
113
|
+
{{ value ?? "N/A" }}
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<template v-slot:item.invited-by="{ item }">
|
|
117
|
+
<span class="d-flex align-center ga-2">
|
|
118
|
+
<AvatarMain
|
|
119
|
+
v-if="item?.inviterName"
|
|
120
|
+
:name="item?.inviterName"
|
|
121
|
+
:size="20"
|
|
122
|
+
:id="item?._id"
|
|
123
|
+
/>
|
|
124
|
+
<span class="text-capitalize">{{ item?.inviterName ?? "N/A" }}</span>
|
|
125
|
+
</span>
|
|
126
|
+
</template>
|
|
127
|
+
|
|
112
128
|
<template v-slot:item.type-company="{ item }">
|
|
113
129
|
<span class="d-flex align-center ga-2">
|
|
114
130
|
<v-icon icon="mdi-user" size="15" />
|
|
@@ -297,6 +313,49 @@
|
|
|
297
313
|
</v-row>
|
|
298
314
|
</template>
|
|
299
315
|
|
|
316
|
+
<template v-slot:item.arrival-date-time="{ item }">
|
|
317
|
+
<v-row no-gutters class="d-flex flex-column py-2">
|
|
318
|
+
<span class="d-flex align-center ga-2">
|
|
319
|
+
<v-icon icon="mdi-calendar" size="20" />
|
|
320
|
+
{{ standardFormatDate(item.expectedCheckIn) }}
|
|
321
|
+
</span>
|
|
322
|
+
<span class="d-flex align-center ga-2">
|
|
323
|
+
<v-icon icon="mdi-clock-time-four-outline" size="20" />
|
|
324
|
+
{{ item.arrivalTime ?? "N/A" }}
|
|
325
|
+
</span>
|
|
326
|
+
|
|
327
|
+
<span v-if="!item.checkIn && canUpdateVisitor">
|
|
328
|
+
<span class="d-flex align-center ga-2 cursor-pointer">
|
|
329
|
+
<v-icon
|
|
330
|
+
icon="mdi-clock-time-eight-outline"
|
|
331
|
+
color="success"
|
|
332
|
+
size="20"
|
|
333
|
+
/>
|
|
334
|
+
<v-btn
|
|
335
|
+
size="x-small"
|
|
336
|
+
class="text-capitalize"
|
|
337
|
+
color="success"
|
|
338
|
+
text="Checkin"
|
|
339
|
+
@click.stop="handleCheckin(item)"
|
|
340
|
+
/>
|
|
341
|
+
</span>
|
|
342
|
+
</span>
|
|
343
|
+
</v-row>
|
|
344
|
+
</template>
|
|
345
|
+
|
|
346
|
+
<template v-slot:item.status="{ item }">
|
|
347
|
+
<v-card
|
|
348
|
+
v-if="item?.overnightParking?.status"
|
|
349
|
+
size="small"
|
|
350
|
+
class="rounded-xl text-capitalize text-center elevation-0 py-1 px-2"
|
|
351
|
+
:color="getOvernightParkingRequestStatusColor(item?.overnightParking?.status as string)"
|
|
352
|
+
style="max-width: 150px; margin: auto"
|
|
353
|
+
>
|
|
354
|
+
{{ item?.overnightParking?.status }}
|
|
355
|
+
</v-card>
|
|
356
|
+
<span v-else> N/A </span>
|
|
357
|
+
</template>
|
|
358
|
+
|
|
300
359
|
<template v-slot:item.action="{ item }">
|
|
301
360
|
<v-btn
|
|
302
361
|
v-if="activeTab === 'unregistered' && canAddVisitor"
|
|
@@ -305,6 +364,52 @@
|
|
|
305
364
|
text="Register"
|
|
306
365
|
@click.stop="handleRegistrationUnregisteredVisitor(item)"
|
|
307
366
|
/>
|
|
367
|
+
<v-menu v-else-if="activeTab === 'overnight-parking'">
|
|
368
|
+
<template #activator="{ props }">
|
|
369
|
+
<v-avatar class="rounded-xl border-md">
|
|
370
|
+
<v-icon v-bind="props" icon="mdi-dots-vertical" />
|
|
371
|
+
</v-avatar>
|
|
372
|
+
</template>
|
|
373
|
+
|
|
374
|
+
<v-card>
|
|
375
|
+
<v-list-item
|
|
376
|
+
@click.stop="
|
|
377
|
+
openApproveRejectOverNightParkingRequestDialog(
|
|
378
|
+
item,
|
|
379
|
+
overNightParkingListActions[0].status
|
|
380
|
+
)
|
|
381
|
+
"
|
|
382
|
+
:disabled="
|
|
383
|
+
item?.overnightParking?.status == 'approved' ||
|
|
384
|
+
!overNightParkingListActions[0].disabled
|
|
385
|
+
"
|
|
386
|
+
>
|
|
387
|
+
<template #title>
|
|
388
|
+
<span class="text-caption">
|
|
389
|
+
{{ overNightParkingListActions[0].text }}
|
|
390
|
+
</span>
|
|
391
|
+
</template>
|
|
392
|
+
</v-list-item>
|
|
393
|
+
<v-divider />
|
|
394
|
+
<v-list-item
|
|
395
|
+
v-if="item?.overnightParking?.status !== 'approved'"
|
|
396
|
+
@click="
|
|
397
|
+
openApproveRejectOverNightParkingRequestDialog(
|
|
398
|
+
item,
|
|
399
|
+
overNightParkingListActions[1].status
|
|
400
|
+
)
|
|
401
|
+
"
|
|
402
|
+
:disabled="!overNightParkingListActions[1].disabled"
|
|
403
|
+
>
|
|
404
|
+
<template #title>
|
|
405
|
+
<span class="text-caption">
|
|
406
|
+
{{ overNightParkingListActions[1].text }}
|
|
407
|
+
</span>
|
|
408
|
+
</template>
|
|
409
|
+
</v-list-item>
|
|
410
|
+
<v-divider />
|
|
411
|
+
</v-card>
|
|
412
|
+
</v-menu>
|
|
308
413
|
</template>
|
|
309
414
|
|
|
310
415
|
<template v-slot:item.checkInRemarks="{ item }">
|
|
@@ -371,9 +476,29 @@
|
|
|
371
476
|
<template v-slot:content>
|
|
372
477
|
<v-row no-gutters class="mb-4">
|
|
373
478
|
<v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
|
|
479
|
+
<span
|
|
480
|
+
v-if="
|
|
481
|
+
key === 'checkIn' &&
|
|
482
|
+
!selectedVisitorObject[key] &&
|
|
483
|
+
canUpdateVisitor
|
|
484
|
+
"
|
|
485
|
+
class="d-flex align-center"
|
|
486
|
+
>
|
|
487
|
+
<strong>{{ label }}:</strong>
|
|
488
|
+
<v-btn
|
|
489
|
+
size="x-small"
|
|
490
|
+
class="ml-3 text-capitalize"
|
|
491
|
+
color="success"
|
|
492
|
+
text="Checkin"
|
|
493
|
+
:disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
494
|
+
@click="handleCheckin(selectedVisitorObject)"
|
|
495
|
+
:loading="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
496
|
+
/>
|
|
497
|
+
</span>
|
|
374
498
|
<span
|
|
375
499
|
v-if="
|
|
376
500
|
key === 'checkOut' &&
|
|
501
|
+
selectedVisitorObject['checkIn'] &&
|
|
377
502
|
!selectedVisitorObject[key] &&
|
|
378
503
|
canCheckoutVisitor
|
|
379
504
|
"
|
|
@@ -496,6 +621,25 @@
|
|
|
496
621
|
<p class="text-body-2 mb-3">
|
|
497
622
|
Please ensure all passes and keys are returned before checking out.
|
|
498
623
|
</p>
|
|
624
|
+
|
|
625
|
+
<div>
|
|
626
|
+
<InputLabel class="text-capitalize" title="Full Name" />
|
|
627
|
+
<v-text-field
|
|
628
|
+
v-model="selectedVisitorObject.name"
|
|
629
|
+
density="comfortable"
|
|
630
|
+
readonly
|
|
631
|
+
/>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<div>
|
|
635
|
+
<InputLabel class="text-capitalize" title="Location" />
|
|
636
|
+
<v-text-field
|
|
637
|
+
v-model="selectedVisitorObject.location"
|
|
638
|
+
density="comfortable"
|
|
639
|
+
readonly
|
|
640
|
+
/>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
499
643
|
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
500
644
|
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
501
645
|
<v-row
|
|
@@ -667,12 +811,90 @@
|
|
|
667
811
|
/>
|
|
668
812
|
</v-dialog>
|
|
669
813
|
|
|
814
|
+
<v-dialog
|
|
815
|
+
v-model="dialog.approveRejectOvernightParkingRequestDialog"
|
|
816
|
+
transition="dialog-bottom-transition"
|
|
817
|
+
persistent
|
|
818
|
+
width="60vh"
|
|
819
|
+
>
|
|
820
|
+
<v-card>
|
|
821
|
+
<v-toolbar density="compact">
|
|
822
|
+
<v-row
|
|
823
|
+
no-gutters
|
|
824
|
+
class="d-flex fill-height justify-space-between align-center px-4"
|
|
825
|
+
>
|
|
826
|
+
<span class="font-weight-bold"> Overnight Parking Request </span>
|
|
827
|
+
<v-btn
|
|
828
|
+
icon="mdi-close"
|
|
829
|
+
variant="text"
|
|
830
|
+
@click="dialog.approveRejectOvernightParkingRequestDialog = false"
|
|
831
|
+
/>
|
|
832
|
+
</v-row>
|
|
833
|
+
</v-toolbar>
|
|
834
|
+
<v-card-text class="px-4 pb-2">
|
|
835
|
+
<v-row no-gutters justify="center" align-content="center">
|
|
836
|
+
<v-col
|
|
837
|
+
v-if="
|
|
838
|
+
approveRejectOvernightParkingRequestDialog.status !== 'remarks'
|
|
839
|
+
"
|
|
840
|
+
cols="12"
|
|
841
|
+
class="text-h6 text-center"
|
|
842
|
+
>
|
|
843
|
+
{{
|
|
844
|
+
`Are you sure you want to ${approveRejectOvernightParkingRequestDialog.text} the visitor's overnight parking request?`
|
|
845
|
+
}}
|
|
846
|
+
</v-col>
|
|
847
|
+
|
|
848
|
+
<v-col cols="12" md="8" class="mt-4">
|
|
849
|
+
<v-textarea
|
|
850
|
+
v-model="overNightParkingRequestApproveRejectRemarks"
|
|
851
|
+
label="Remarks"
|
|
852
|
+
placeholder="Enter remarks"
|
|
853
|
+
outlined
|
|
854
|
+
rows="3"
|
|
855
|
+
clearable
|
|
856
|
+
/>
|
|
857
|
+
</v-col>
|
|
858
|
+
</v-row>
|
|
859
|
+
</v-card-text>
|
|
860
|
+
<v-toolbar class="pa-0" density="compact">
|
|
861
|
+
<v-row no-gutters>
|
|
862
|
+
<v-col cols="6">
|
|
863
|
+
<v-btn
|
|
864
|
+
text="No"
|
|
865
|
+
variant="text"
|
|
866
|
+
block
|
|
867
|
+
:disabled="isApprovingRejectingOvernightParkingRequest"
|
|
868
|
+
@click="closeApproveRejectOvernightParkingDialog"
|
|
869
|
+
/>
|
|
870
|
+
</v-col>
|
|
871
|
+
<v-col cols="6">
|
|
872
|
+
<v-btn
|
|
873
|
+
text="Yes"
|
|
874
|
+
color="success"
|
|
875
|
+
variant="flat"
|
|
876
|
+
height="48"
|
|
877
|
+
rounded="0"
|
|
878
|
+
block
|
|
879
|
+
:disabled="
|
|
880
|
+
!overNightParkingRequestApproveRejectRemarks ||
|
|
881
|
+
isApprovingRejectingOvernightParkingRequest
|
|
882
|
+
"
|
|
883
|
+
@click="updateOvernightParkingRequestStatus"
|
|
884
|
+
:loading="isApprovingRejectingOvernightParkingRequest"
|
|
885
|
+
/>
|
|
886
|
+
</v-col>
|
|
887
|
+
</v-row>
|
|
888
|
+
</v-toolbar>
|
|
889
|
+
</v-card>
|
|
890
|
+
</v-dialog>
|
|
891
|
+
|
|
670
892
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
671
893
|
</v-row>
|
|
672
894
|
</template>
|
|
673
895
|
|
|
674
896
|
<script setup lang="ts">
|
|
675
|
-
import
|
|
897
|
+
import moment from "moment-timezone";
|
|
676
898
|
|
|
677
899
|
definePageMeta({
|
|
678
900
|
middleware: ["01-auth", "02-org"],
|
|
@@ -709,10 +931,15 @@ const {
|
|
|
709
931
|
visitorSelection,
|
|
710
932
|
typeFieldMap,
|
|
711
933
|
updateVisitor,
|
|
712
|
-
|
|
934
|
+
visitorDataFromScannedQRCodeCheckIn,
|
|
713
935
|
} = useVisitor();
|
|
714
|
-
const {
|
|
715
|
-
|
|
936
|
+
const {
|
|
937
|
+
debounce,
|
|
938
|
+
formatCamelCaseToWords,
|
|
939
|
+
formatDate,
|
|
940
|
+
UTCToLocalTIme,
|
|
941
|
+
standardFormatDate,
|
|
942
|
+
} = useUtils();
|
|
716
943
|
const { formatLocation } = useSecurityUtils();
|
|
717
944
|
const { getFileUrlAnpr } = useFile();
|
|
718
945
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
@@ -747,6 +974,7 @@ const messageSnackbar = ref(false);
|
|
|
747
974
|
|
|
748
975
|
const loading = reactive({
|
|
749
976
|
fetchingVisitors: false,
|
|
977
|
+
checkingIn: false,
|
|
750
978
|
checkingOut: false,
|
|
751
979
|
savingRemarks: false,
|
|
752
980
|
updatingPassKeys: false,
|
|
@@ -763,6 +991,7 @@ const dialog = reactive({
|
|
|
763
991
|
editPassKey: false,
|
|
764
992
|
scanVisitorQRCode: false,
|
|
765
993
|
showVisitorDataFromScannedQRCode: false,
|
|
994
|
+
approveRejectOvernightParkingRequestDialog: false,
|
|
766
995
|
});
|
|
767
996
|
|
|
768
997
|
const snapshotImageUrl = ref("");
|
|
@@ -770,29 +999,140 @@ const remarksType = ref<"checkIn" | "checkOut">("checkIn");
|
|
|
770
999
|
const remarksInput = ref("");
|
|
771
1000
|
const remarksViewOnly = ref(false);
|
|
772
1001
|
|
|
773
|
-
const headers = computed(() =>
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1002
|
+
const headers = computed(() => {
|
|
1003
|
+
const tab = activeTab.value;
|
|
1004
|
+
|
|
1005
|
+
// 1. Define the specific headers for Overnight Parking
|
|
1006
|
+
if (tab === "overnight-parking") {
|
|
1007
|
+
return [
|
|
1008
|
+
{ title: "Name", value: "name" },
|
|
1009
|
+
{ title: "Email", value: "email" },
|
|
1010
|
+
{ title: "Invited By", value: "invited-by" },
|
|
1011
|
+
{ title: "Arrival Date/Time", value: "arrival-date-time" },
|
|
1012
|
+
{ title: "Check In/Out", value: "checkin-out" },
|
|
1013
|
+
{ title: "Status", value: "status" },
|
|
1014
|
+
{ title: "Action", value: "action" },
|
|
1015
|
+
];
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// 2. Define logic for all other tabs
|
|
1019
|
+
const list = [{ title: "Name", value: "name" }];
|
|
1020
|
+
|
|
1021
|
+
// Standard info for non-parking tabs
|
|
1022
|
+
list.push(
|
|
1023
|
+
{ title: "Type/Company", value: "type-company" },
|
|
1024
|
+
{ title: "Location", value: "location" },
|
|
1025
|
+
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" }
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
// Conditional logic for Guests vs Others
|
|
1029
|
+
if (tab === "guests") {
|
|
1030
|
+
list.push({ title: "Arrival Date/Time", value: "arrival-date-time" });
|
|
1031
|
+
} else {
|
|
1032
|
+
list.push({ title: "Check In/Out", value: "checkin-out" });
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Specific additions
|
|
1036
|
+
if (tab === "resident-transactions") {
|
|
1037
|
+
list.push(
|
|
1038
|
+
{ title: "Check-in Remarks", value: "checkInRemarks" },
|
|
1039
|
+
{ title: "Check-out Remarks", value: "checkOutRemarks" }
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (tab === "unregistered") {
|
|
1044
|
+
list.push({ title: "Action", value: "action" });
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return list;
|
|
1048
|
+
});
|
|
789
1049
|
|
|
790
1050
|
const tabOptions = [
|
|
791
1051
|
{ name: "Registered", value: "registered" },
|
|
792
1052
|
{ name: "Unregistered", value: "unregistered" },
|
|
793
1053
|
{ name: "Guests", value: "guests" },
|
|
794
1054
|
{ name: "Resident Transactions", value: "resident-transactions" },
|
|
1055
|
+
{ name: "Overnight Parking", value: "overnight-parking" },
|
|
1056
|
+
];
|
|
1057
|
+
|
|
1058
|
+
function getOvernightParkingRequestStatusColor(status: string) {
|
|
1059
|
+
switch (status) {
|
|
1060
|
+
case "approved":
|
|
1061
|
+
return "success";
|
|
1062
|
+
case "rejected":
|
|
1063
|
+
return "error";
|
|
1064
|
+
default:
|
|
1065
|
+
return "grey";
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const overNightParkingListActions = [
|
|
1070
|
+
{
|
|
1071
|
+
text: "Approve",
|
|
1072
|
+
status: "approved",
|
|
1073
|
+
disabled: props.canUpdateVisitor,
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
text: "Reject",
|
|
1077
|
+
status: "rejected",
|
|
1078
|
+
disabled: props.canUpdateVisitor,
|
|
1079
|
+
},
|
|
795
1080
|
];
|
|
1081
|
+
const overNightParkingRequestApproveRejectRemarks = ref("");
|
|
1082
|
+
const approveRejectOvernightParkingRequestDialog = ref({
|
|
1083
|
+
text: "",
|
|
1084
|
+
status: "",
|
|
1085
|
+
});
|
|
1086
|
+
const selectedOvernightParkingRequest = ref<Record<string, any>>({});
|
|
1087
|
+
|
|
1088
|
+
function openApproveRejectOverNightParkingRequestDialog(
|
|
1089
|
+
visitor: Record<string, any>,
|
|
1090
|
+
status: string
|
|
1091
|
+
) {
|
|
1092
|
+
selectedOvernightParkingRequest.value = visitor;
|
|
1093
|
+
approveRejectOvernightParkingRequestDialog.value = {
|
|
1094
|
+
text: status === "approved" ? "Approve" : "Reject",
|
|
1095
|
+
status,
|
|
1096
|
+
};
|
|
1097
|
+
dialog.approveRejectOvernightParkingRequestDialog = true;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const isApprovingRejectingOvernightParkingRequest = ref(false);
|
|
1101
|
+
|
|
1102
|
+
async function updateOvernightParkingRequestStatus() {
|
|
1103
|
+
isApprovingRejectingOvernightParkingRequest.value = true;
|
|
1104
|
+
try {
|
|
1105
|
+
await visitorDataFromScannedQRCodeCheckIn({
|
|
1106
|
+
type: "overnight-parking",
|
|
1107
|
+
filter: {
|
|
1108
|
+
_id: selectedOvernightParkingRequest.value._id,
|
|
1109
|
+
},
|
|
1110
|
+
update: {
|
|
1111
|
+
status: approveRejectOvernightParkingRequestDialog.value.status,
|
|
1112
|
+
remarks: overNightParkingRequestApproveRejectRemarks.value ?? "",
|
|
1113
|
+
updatedBy: currentUser.value?._id,
|
|
1114
|
+
},
|
|
1115
|
+
userType: "site",
|
|
1116
|
+
});
|
|
1117
|
+
showMessage(
|
|
1118
|
+
`Visitor's overnight parking request successfully ${approveRejectOvernightParkingRequestDialog.value.status}`,
|
|
1119
|
+
"success"
|
|
1120
|
+
);
|
|
1121
|
+
await getVisitorRefresh();
|
|
1122
|
+
closeApproveRejectOvernightParkingDialog();
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
showMessage(errorConverter(error), "error");
|
|
1125
|
+
} finally {
|
|
1126
|
+
isApprovingRejectingOvernightParkingRequest.value = false;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function closeApproveRejectOvernightParkingDialog() {
|
|
1131
|
+
dialog.approveRejectOvernightParkingRequestDialog = false;
|
|
1132
|
+
selectedVisitorId.value = "";
|
|
1133
|
+
approveRejectOvernightParkingRequestDialog.value = { text: "", status: "" };
|
|
1134
|
+
overNightParkingRequestApproveRejectRemarks.value = "";
|
|
1135
|
+
}
|
|
796
1136
|
|
|
797
1137
|
function normalizeDateOnly(value: string) {
|
|
798
1138
|
if (!value) return "";
|
|
@@ -848,15 +1188,16 @@ function toRoute(tab: any) {
|
|
|
848
1188
|
const obj = tabOptions.find((x) => x.value === tab);
|
|
849
1189
|
if (!obj) return;
|
|
850
1190
|
page.value = 1;
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
}
|
|
1191
|
+
getVisitorRefresh();
|
|
1192
|
+
// navigateTo({
|
|
1193
|
+
// name: routeName,
|
|
1194
|
+
// params: {
|
|
1195
|
+
// org: orgId,
|
|
1196
|
+
// },
|
|
1197
|
+
// // query: {
|
|
1198
|
+
// // tab
|
|
1199
|
+
// // },
|
|
1200
|
+
// });
|
|
860
1201
|
}
|
|
861
1202
|
const {
|
|
862
1203
|
data: getVisitorReq,
|
|
@@ -869,8 +1210,8 @@ const {
|
|
|
869
1210
|
page: page.value,
|
|
870
1211
|
site: siteId,
|
|
871
1212
|
search: searchInput.value,
|
|
872
|
-
dateTo:
|
|
873
|
-
dateFrom:
|
|
1213
|
+
dateTo: moment(dateTo.value).endOf("day").toISOString(),
|
|
1214
|
+
dateFrom: moment(dateFrom.value).startOf("day").toISOString(),
|
|
874
1215
|
type:
|
|
875
1216
|
filterTypes.value.length === 0 && activeTab.value === "registered"
|
|
876
1217
|
? "contractor,delivery,walk-in,pick-up,drop-off,guest"
|
|
@@ -880,10 +1221,12 @@ const {
|
|
|
880
1221
|
|
|
881
1222
|
if (activeTab.value === "guests") {
|
|
882
1223
|
params.type = "guest";
|
|
883
|
-
params.status = "
|
|
1224
|
+
params.status = "approved";
|
|
884
1225
|
} else if (activeTab.value === "resident-transactions") {
|
|
885
1226
|
params.status = "registered";
|
|
886
1227
|
params.type = "resident,tenant";
|
|
1228
|
+
} else if (activeTab.value === "overnight-parking") {
|
|
1229
|
+
params.tab = "Overnight Parking";
|
|
887
1230
|
} else {
|
|
888
1231
|
params.status = activeTab.value;
|
|
889
1232
|
}
|
|
@@ -913,10 +1256,12 @@ watch(getVisitorReq, (newData: any) => {
|
|
|
913
1256
|
}
|
|
914
1257
|
});
|
|
915
1258
|
|
|
916
|
-
const selectedVisitorObject = computed(() => {
|
|
1259
|
+
const selectedVisitorObject = computed<Record<string, any>>(() => {
|
|
917
1260
|
const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
|
|
918
1261
|
if (!obj) return {};
|
|
1262
|
+
|
|
919
1263
|
const type = obj?.type as TVisitorType | undefined;
|
|
1264
|
+
|
|
920
1265
|
let includedKeys: string[] = [
|
|
921
1266
|
"checkIn",
|
|
922
1267
|
"checkOut",
|
|
@@ -925,9 +1270,23 @@ const selectedVisitorObject = computed(() => {
|
|
|
925
1270
|
"snapshotExitImage",
|
|
926
1271
|
];
|
|
927
1272
|
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1273
|
+
|
|
1274
|
+
let locationString = "N/A";
|
|
1275
|
+
|
|
1276
|
+
if (obj?.block || obj?.level || obj?.unitName) {
|
|
1277
|
+
locationString = `${obj.block} / ${obj.level} / ${obj.unitName}`;
|
|
1278
|
+
} else if (obj?.block) {
|
|
1279
|
+
locationString = obj.block;
|
|
1280
|
+
} else {
|
|
1281
|
+
locationString = "N/A";
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return {
|
|
1285
|
+
...Object.fromEntries(
|
|
1286
|
+
Object.entries(obj).filter(([key]) => includedKeys.includes(key))
|
|
1287
|
+
),
|
|
1288
|
+
location: locationString,
|
|
1289
|
+
};
|
|
931
1290
|
});
|
|
932
1291
|
|
|
933
1292
|
function formatValues(key: string, value: any) {
|
|
@@ -947,9 +1306,20 @@ function formatValues(key: string, value: any) {
|
|
|
947
1306
|
const visitorDataFromScannedQRCode = ref<Record<string, any>>({});
|
|
948
1307
|
|
|
949
1308
|
function handleShowVisitorDataFromScannedQRCodeDialog(
|
|
950
|
-
|
|
1309
|
+
scannedData: Record<string, any>
|
|
951
1310
|
) {
|
|
952
|
-
visitorDataFromScannedQRCode.value =
|
|
1311
|
+
visitorDataFromScannedQRCode.value = scannedData;
|
|
1312
|
+
|
|
1313
|
+
const { checkIn, checkOut, _id, visitorPass } = scannedData;
|
|
1314
|
+
const hasPasses = (visitorPass?.length ?? 0) > 0;
|
|
1315
|
+
|
|
1316
|
+
// 1. If they are checked in and HAVE passes/keys, go to the Return screen
|
|
1317
|
+
if (checkIn && !checkOut && hasPasses) {
|
|
1318
|
+
handleCheckout(_id);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// 2. Default Case: Show visitor data (Entry, already checked out, or checked in with no passes)
|
|
953
1323
|
dialog.showVisitorDataFromScannedQRCode = true;
|
|
954
1324
|
}
|
|
955
1325
|
|
|
@@ -961,24 +1331,32 @@ async function handleVisitorDataFromScannedQRCodeCheckInOut(
|
|
|
961
1331
|
) {
|
|
962
1332
|
try {
|
|
963
1333
|
isVisitorDataFromScannedQRCodeCheckingInOut.value = true;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1334
|
+
if (type != "checkOut") {
|
|
1335
|
+
await visitorDataFromScannedQRCodeCheckIn({
|
|
1336
|
+
type,
|
|
1337
|
+
filter: {
|
|
1338
|
+
_id: visitorData._id,
|
|
1339
|
+
},
|
|
1340
|
+
update: {
|
|
1341
|
+
updatedBy: currentUser.value._id,
|
|
1342
|
+
visitorPass: visitorData.visitorPass.find(
|
|
1343
|
+
(i: Record<string, any>) => i.keyId === null
|
|
1344
|
+
)
|
|
1345
|
+
? undefined
|
|
1346
|
+
: visitorData.visitorPass,
|
|
1347
|
+
nric: visitorData.nric ?? "",
|
|
1348
|
+
plateNumber: visitorData.plateNumber ?? "",
|
|
1349
|
+
},
|
|
1350
|
+
userType: "site",
|
|
1351
|
+
});
|
|
1352
|
+
showMessage("Visitor successfully checked-in!", "info");
|
|
1353
|
+
} else {
|
|
1354
|
+
selectedVisitorId.value = visitorData._id;
|
|
1355
|
+
await proceedCheckout();
|
|
1356
|
+
}
|
|
981
1357
|
handleCloseVisitorDataFromScannedQRCodeDialog();
|
|
1358
|
+
getVisitorRefresh();
|
|
1359
|
+
dialog.viewVisitor = false;
|
|
982
1360
|
} catch (error) {
|
|
983
1361
|
showMessage(errorConverter(error), "error");
|
|
984
1362
|
} finally {
|
|
@@ -1171,6 +1549,11 @@ const canConfirmCheckout = computed(() => {
|
|
|
1171
1549
|
});
|
|
1172
1550
|
});
|
|
1173
1551
|
|
|
1552
|
+
function handleCheckin(user: Record<string, any>) {
|
|
1553
|
+
visitorDataFromScannedQRCode.value = user;
|
|
1554
|
+
dialog.showVisitorDataFromScannedQRCode = true;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1174
1557
|
function handleCheckout(userId: string) {
|
|
1175
1558
|
if (!userId) {
|
|
1176
1559
|
showMessage("Invalid userId", "error");
|
|
@@ -90,6 +90,7 @@ export default function () {
|
|
|
90
90
|
status?: string;
|
|
91
91
|
checkedOut?: boolean;
|
|
92
92
|
plateNumber?: string;
|
|
93
|
+
tab?: string;
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
async function getVisitors({
|
|
@@ -106,6 +107,7 @@ export default function () {
|
|
|
106
107
|
// status = "registered"
|
|
107
108
|
plateNumber = "",
|
|
108
109
|
checkedOut,
|
|
110
|
+
tab,
|
|
109
111
|
}: GetVisitorsParams = {}) {
|
|
110
112
|
return await useNuxtApp().$api<Record<string, any>>(
|
|
111
113
|
"/api/visitor-transactions",
|
|
@@ -124,6 +126,7 @@ export default function () {
|
|
|
124
126
|
checkedOut,
|
|
125
127
|
plateNumber,
|
|
126
128
|
order,
|
|
129
|
+
tab,
|
|
127
130
|
},
|
|
128
131
|
}
|
|
129
132
|
);
|
|
@@ -174,7 +177,7 @@ export default function () {
|
|
|
174
177
|
);
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
function
|
|
180
|
+
function visitorDataFromScannedQRCodeCheckIn(payload: any) {
|
|
178
181
|
return useNuxtApp().$api<Record<string, any>>("/api/visitors/update-one", {
|
|
179
182
|
method: "PUT",
|
|
180
183
|
body: payload,
|
|
@@ -191,6 +194,6 @@ export default function () {
|
|
|
191
194
|
deleteVisitor,
|
|
192
195
|
validateVisitorQrCode,
|
|
193
196
|
updateVisitorPassKey,
|
|
194
|
-
|
|
197
|
+
visitorDataFromScannedQRCodeCheckIn,
|
|
195
198
|
};
|
|
196
199
|
}
|
package/package.json
CHANGED