@7365admin1/layer-common 1.11.21 → 1.11.23
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/AddPassKeyToVisitor.vue +9 -9
- package/components/AreaMain.vue +10 -8
- package/components/AttendanceMain.vue +6 -6
- package/components/CleaningScheduleMain.vue +8 -7
- package/components/ManageChecklistMain.vue +4 -3
- package/components/MemberInformation.vue +49 -13
- package/components/MyAttendanceMain.vue +6 -3
- package/components/PassInformation.vue +197 -144
- package/components/ScanVisitorQRCode.vue +47 -12
- package/components/ScheduleAreaMain.vue +6 -3
- package/components/ScheduleTaskMain.vue +11 -8
- package/components/UnitMain.vue +10 -8
- package/components/VisitorDataFromScannedQRCodeForm.vue +262 -0
- package/components/VisitorForm.vue +1 -1
- package/components/VisitorManagement.vue +80 -2
- package/components/VisitorPassKeyQRScanner.vue +138 -0
- package/composables/useVisitor.ts +98 -25
- package/package.json +2 -1
- package/types/visitor.d.ts +45 -15
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%" :loading="processing">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row
|
|
5
|
+
no-gutters
|
|
6
|
+
class="fill-height px-6 d-flex justify-space-between align-center"
|
|
7
|
+
align="center"
|
|
8
|
+
>
|
|
9
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
10
|
+
Visitor Information
|
|
11
|
+
</span>
|
|
12
|
+
<ButtonClose @click="emit('close')" />
|
|
13
|
+
</v-row>
|
|
14
|
+
</v-toolbar>
|
|
15
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-3">
|
|
16
|
+
<div class="w-100 d-flex justify-space-between ga-2">
|
|
17
|
+
<!-- <span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
|
|
18
|
+
<span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span> -->
|
|
19
|
+
</div>
|
|
20
|
+
<!-- <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''"> -->
|
|
21
|
+
<v-row no-gutters class="pt-4">
|
|
22
|
+
<v-col cols="12">
|
|
23
|
+
<InputLabel class="text-capitalize" title="Full Name" />
|
|
24
|
+
<v-text-field
|
|
25
|
+
v-model="visitorInfo.name"
|
|
26
|
+
density="comfortable"
|
|
27
|
+
readonly
|
|
28
|
+
/>
|
|
29
|
+
</v-col>
|
|
30
|
+
|
|
31
|
+
<v-col cols="12">
|
|
32
|
+
<InputLabel class="text-capitalize" title="NRIC/Passport/ID No." />
|
|
33
|
+
<v-text-field
|
|
34
|
+
v-if="!visitorInfo.checkIn"
|
|
35
|
+
v-model="visitorInfo.nric"
|
|
36
|
+
density="comfortable"
|
|
37
|
+
hide-details
|
|
38
|
+
clearable
|
|
39
|
+
/>
|
|
40
|
+
<v-text-field
|
|
41
|
+
v-else
|
|
42
|
+
v-model="visitorNric"
|
|
43
|
+
density="comfortable"
|
|
44
|
+
hide-details
|
|
45
|
+
readonly
|
|
46
|
+
/>
|
|
47
|
+
</v-col>
|
|
48
|
+
|
|
49
|
+
<v-col cols="12" class="mt-4">
|
|
50
|
+
<InputLabel
|
|
51
|
+
class="text-capitalize"
|
|
52
|
+
title="Vehicle Number (Optional)"
|
|
53
|
+
/>
|
|
54
|
+
<InputVehicleNumber
|
|
55
|
+
v-if="!visitorInfo.checkIn"
|
|
56
|
+
v-model="visitorInfo.plateNumber"
|
|
57
|
+
density="comfortable"
|
|
58
|
+
hide-details
|
|
59
|
+
clearable
|
|
60
|
+
/>
|
|
61
|
+
<InputVehicleNumber
|
|
62
|
+
v-else
|
|
63
|
+
v-model="visitorPlateNumber"
|
|
64
|
+
density="comfortable"
|
|
65
|
+
hide-details
|
|
66
|
+
readonly
|
|
67
|
+
/>
|
|
68
|
+
</v-col>
|
|
69
|
+
|
|
70
|
+
<v-col cols="12" class="mt-4">
|
|
71
|
+
<InputLabel class="text-capitalize" title="Type" />
|
|
72
|
+
<v-text-field
|
|
73
|
+
v-model="visitorInfo.type"
|
|
74
|
+
density="comfortable"
|
|
75
|
+
hide-details
|
|
76
|
+
readonly
|
|
77
|
+
/>
|
|
78
|
+
</v-col>
|
|
79
|
+
|
|
80
|
+
<v-col cols="12" class="mt-4">
|
|
81
|
+
<InputLabel class="text-capitalize" title="Phone Number" />
|
|
82
|
+
<InputPhoneNumberV2
|
|
83
|
+
v-model="visitorInfo.contact"
|
|
84
|
+
density="comfortable"
|
|
85
|
+
:readonly="true"
|
|
86
|
+
/>
|
|
87
|
+
</v-col>
|
|
88
|
+
|
|
89
|
+
<v-col cols="12">
|
|
90
|
+
<InputLabel class="text-capitalize" title="Purpose" />
|
|
91
|
+
<v-textarea
|
|
92
|
+
v-model="visitorInfo.purpose"
|
|
93
|
+
density="comfortable"
|
|
94
|
+
:rows="3"
|
|
95
|
+
no-resize
|
|
96
|
+
readonly
|
|
97
|
+
/>
|
|
98
|
+
</v-col>
|
|
99
|
+
|
|
100
|
+
<v-col cols="12">
|
|
101
|
+
<InputLabel class="text-capitalize" title="Location" />
|
|
102
|
+
<v-text-field v-model="location" density="comfortable" />
|
|
103
|
+
</v-col>
|
|
104
|
+
|
|
105
|
+
<v-col cols="12">
|
|
106
|
+
<InputLabel class="text-capitalize" title="Visitor Pass" />
|
|
107
|
+
<PassInformation
|
|
108
|
+
v-model:pass="visitorInfo.visitorPass"
|
|
109
|
+
:site="prop.site"
|
|
110
|
+
:type="visitorInfo.type"
|
|
111
|
+
:clearable="true"
|
|
112
|
+
/>
|
|
113
|
+
</v-col>
|
|
114
|
+
|
|
115
|
+
<v-col cols="12" class="mt-4">
|
|
116
|
+
<v-row no-gutters class="pt-2">
|
|
117
|
+
<v-col cols="6" class="pr-1">
|
|
118
|
+
<v-card elevation="3" height="100%" width="100%">
|
|
119
|
+
<v-col cols="12" class="d-flex justify-center pb-0">
|
|
120
|
+
<span
|
|
121
|
+
class="text-h6 font-weight-bold d-flex text-center text-success"
|
|
122
|
+
>
|
|
123
|
+
{{
|
|
124
|
+
visitorInfo.checkIn
|
|
125
|
+
? standardFormatDateTime(visitorInfo.checkIn)
|
|
126
|
+
: "N/A"
|
|
127
|
+
}}
|
|
128
|
+
</span>
|
|
129
|
+
</v-col>
|
|
130
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
131
|
+
Actual Check In
|
|
132
|
+
</v-col>
|
|
133
|
+
</v-card>
|
|
134
|
+
</v-col>
|
|
135
|
+
<v-col cols="6" class="pl-1">
|
|
136
|
+
<v-card elevation="3" height="100%" width="100%">
|
|
137
|
+
<v-col cols="12" class="d-flex justify-center pb-0">
|
|
138
|
+
<span
|
|
139
|
+
class="text-h6 font-weight-bold d-flex text-center text-error"
|
|
140
|
+
>
|
|
141
|
+
{{
|
|
142
|
+
visitorInfo.checkOut
|
|
143
|
+
? standardFormatDateTime(visitorInfo.checkOut)
|
|
144
|
+
: "N/A"
|
|
145
|
+
}}
|
|
146
|
+
</span>
|
|
147
|
+
</v-col>
|
|
148
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
149
|
+
Actual Check Out
|
|
150
|
+
</v-col>
|
|
151
|
+
</v-card>
|
|
152
|
+
</v-col>
|
|
153
|
+
</v-row>
|
|
154
|
+
</v-col>
|
|
155
|
+
</v-row>
|
|
156
|
+
<!-- </v-form> -->
|
|
157
|
+
</v-card-text>
|
|
158
|
+
|
|
159
|
+
<v-toolbar density="compact">
|
|
160
|
+
<v-row no-gutters>
|
|
161
|
+
<v-col cols="6">
|
|
162
|
+
<v-btn text="Close" block size="48" tile @click="emit('close')" />
|
|
163
|
+
</v-col>
|
|
164
|
+
<v-col cols="6">
|
|
165
|
+
<v-btn
|
|
166
|
+
v-if="!visitorInfo.checkIn && !visitorInfo.checkOut"
|
|
167
|
+
block
|
|
168
|
+
text="Check In"
|
|
169
|
+
variant="flat"
|
|
170
|
+
color="success"
|
|
171
|
+
size="48"
|
|
172
|
+
tile
|
|
173
|
+
:disabled="processing"
|
|
174
|
+
@click="emit('check-in-out', 'checkIn', visitorInfo)"
|
|
175
|
+
:loading="processing"
|
|
176
|
+
/>
|
|
177
|
+
<v-btn
|
|
178
|
+
v-if="visitorInfo.checkIn && !visitorInfo.checkOut"
|
|
179
|
+
block
|
|
180
|
+
text="Check Out"
|
|
181
|
+
variant="flat"
|
|
182
|
+
color="error"
|
|
183
|
+
size="48"
|
|
184
|
+
tile
|
|
185
|
+
:disabled="processing"
|
|
186
|
+
@click="emit('check-in-out', 'checkOut', visitorInfo)"
|
|
187
|
+
:loading="processing"
|
|
188
|
+
/>
|
|
189
|
+
</v-col>
|
|
190
|
+
</v-row>
|
|
191
|
+
</v-toolbar>
|
|
192
|
+
</v-card>
|
|
193
|
+
</template>
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
const prop = defineProps({
|
|
196
|
+
site: {
|
|
197
|
+
type: String,
|
|
198
|
+
required: true,
|
|
199
|
+
},
|
|
200
|
+
visitorData: {
|
|
201
|
+
type: Object as PropType<Partial<TVisitor>>,
|
|
202
|
+
default: {},
|
|
203
|
+
},
|
|
204
|
+
processing: {
|
|
205
|
+
type: Boolean,
|
|
206
|
+
required: false,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const emit = defineEmits(["check-in-out", "close"]);
|
|
211
|
+
|
|
212
|
+
const { standardFormatDateTime } = useUtils();
|
|
213
|
+
|
|
214
|
+
const visitorInfo = ref(prop.visitorData);
|
|
215
|
+
|
|
216
|
+
const visitorNric = computed({
|
|
217
|
+
get: () =>
|
|
218
|
+
visitorInfo.value && visitorInfo.value.checkIn
|
|
219
|
+
? visitorInfo.value.nric
|
|
220
|
+
: null,
|
|
221
|
+
set: () => {}, // readonly, do nothing on set
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const visitorPlateNumber = computed({
|
|
225
|
+
get: () =>
|
|
226
|
+
visitorInfo.value && visitorInfo.value.checkIn
|
|
227
|
+
? visitorInfo.value.plateNumber
|
|
228
|
+
: null,
|
|
229
|
+
set: () => {}, // readonly, do nothing on set
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
watchEffect(() => {
|
|
233
|
+
if (visitorInfo.value.plateNumber) {
|
|
234
|
+
visitorInfo.value.type = "guest";
|
|
235
|
+
} else {
|
|
236
|
+
visitorInfo.value.type = "walk-in";
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const location = computed({
|
|
241
|
+
get: () => {
|
|
242
|
+
if (
|
|
243
|
+
visitorInfo.value &&
|
|
244
|
+
visitorInfo.value.block &&
|
|
245
|
+
visitorInfo.value.level &&
|
|
246
|
+
visitorInfo.value.unitName
|
|
247
|
+
) {
|
|
248
|
+
return `${visitorInfo.value.block} / ${visitorInfo.value.level} / ${visitorInfo.value.unitName}`;
|
|
249
|
+
} else if (
|
|
250
|
+
visitorInfo.value &&
|
|
251
|
+
visitorInfo.value.block &&
|
|
252
|
+
!visitorInfo.value.level &&
|
|
253
|
+
!visitorInfo.value.unitName
|
|
254
|
+
) {
|
|
255
|
+
return visitorInfo.value.block;
|
|
256
|
+
} else {
|
|
257
|
+
return "N/A";
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
set: () => {}, // readonly, do nothing on set
|
|
261
|
+
});
|
|
262
|
+
</script>
|
|
@@ -1012,7 +1012,7 @@ const membersFieldIncomplete = computed(() => {
|
|
|
1012
1012
|
});
|
|
1013
1013
|
|
|
1014
1014
|
onMounted(() => {
|
|
1015
|
-
contractorStep.value =
|
|
1015
|
+
contractorStep.value = 1;
|
|
1016
1016
|
currentAutofillSource.value = null;
|
|
1017
1017
|
|
|
1018
1018
|
if (prop.mode === 'register' && prop.visitorData) {
|
|
@@ -194,6 +194,7 @@
|
|
|
194
194
|
:loading="
|
|
195
195
|
loading.checkingOut && item?._id === selectedVisitorId
|
|
196
196
|
"
|
|
197
|
+
@click.stop="handleCheckout(item._id)"
|
|
197
198
|
/>
|
|
198
199
|
</span>
|
|
199
200
|
</span>
|
|
@@ -271,6 +272,7 @@
|
|
|
271
272
|
</template>
|
|
272
273
|
<v-list density="compact">
|
|
273
274
|
<v-list-item
|
|
275
|
+
v-if="!item?.checkOut"
|
|
274
276
|
prepend-icon="mdi-logout"
|
|
275
277
|
title="Checkout"
|
|
276
278
|
@click.stop="handleCheckout(item._id)"
|
|
@@ -644,14 +646,34 @@
|
|
|
644
646
|
|
|
645
647
|
<ScanVisitorQRCode
|
|
646
648
|
:dialog="dialog.scanVisitorQRCode"
|
|
649
|
+
:site="siteId"
|
|
650
|
+
@open-visitor-data-from-scanned-qr-code-dialog="
|
|
651
|
+
handleShowVisitorDataFromScannedQRCodeDialog
|
|
652
|
+
"
|
|
647
653
|
@close-dialog="dialog.scanVisitorQRCode = false"
|
|
648
654
|
/>
|
|
649
655
|
|
|
656
|
+
<v-dialog
|
|
657
|
+
v-model="dialog.showVisitorDataFromScannedQRCode"
|
|
658
|
+
width="450"
|
|
659
|
+
persistent
|
|
660
|
+
>
|
|
661
|
+
<VisitorDataFromScannedQRCodeForm
|
|
662
|
+
:site="siteId"
|
|
663
|
+
:visitor-data="visitorDataFromScannedQRCode"
|
|
664
|
+
@check-in-out="handleVisitorDataFromScannedQRCodeCheckInOut"
|
|
665
|
+
:processing="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
666
|
+
@close="handleCloseVisitorDataFromScannedQRCodeDialog"
|
|
667
|
+
/>
|
|
668
|
+
</v-dialog>
|
|
669
|
+
|
|
650
670
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
651
671
|
</v-row>
|
|
652
672
|
</template>
|
|
653
673
|
|
|
654
674
|
<script setup lang="ts">
|
|
675
|
+
import { errorConverter } from "../utils/data";
|
|
676
|
+
|
|
655
677
|
definePageMeta({
|
|
656
678
|
middleware: ["01-auth", "02-org"],
|
|
657
679
|
});
|
|
@@ -679,8 +701,16 @@ const props = defineProps({
|
|
|
679
701
|
},
|
|
680
702
|
});
|
|
681
703
|
|
|
682
|
-
const {
|
|
683
|
-
|
|
704
|
+
const { authenticate, currentUser } = useLocalAuth();
|
|
705
|
+
authenticate();
|
|
706
|
+
|
|
707
|
+
const {
|
|
708
|
+
getVisitors,
|
|
709
|
+
visitorSelection,
|
|
710
|
+
typeFieldMap,
|
|
711
|
+
updateVisitor,
|
|
712
|
+
visitorDataFromScannedQRCodeCheckInOut,
|
|
713
|
+
} = useVisitor();
|
|
684
714
|
const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
|
|
685
715
|
useUtils();
|
|
686
716
|
const { formatLocation } = useSecurityUtils();
|
|
@@ -732,6 +762,7 @@ const dialog = reactive({
|
|
|
732
762
|
addPassKey: false,
|
|
733
763
|
editPassKey: false,
|
|
734
764
|
scanVisitorQRCode: false,
|
|
765
|
+
showVisitorDataFromScannedQRCode: false,
|
|
735
766
|
});
|
|
736
767
|
|
|
737
768
|
const snapshotImageUrl = ref("");
|
|
@@ -913,6 +944,53 @@ function formatValues(key: string, value: any) {
|
|
|
913
944
|
return value;
|
|
914
945
|
}
|
|
915
946
|
|
|
947
|
+
const visitorDataFromScannedQRCode = ref<Record<string, any>>({});
|
|
948
|
+
|
|
949
|
+
function handleShowVisitorDataFromScannedQRCodeDialog(
|
|
950
|
+
_visitorDataFromScannedQRCode: Record<string, any>
|
|
951
|
+
) {
|
|
952
|
+
visitorDataFromScannedQRCode.value = _visitorDataFromScannedQRCode;
|
|
953
|
+
dialog.showVisitorDataFromScannedQRCode = true;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const isVisitorDataFromScannedQRCodeCheckingInOut = ref(false);
|
|
957
|
+
|
|
958
|
+
async function handleVisitorDataFromScannedQRCodeCheckInOut(
|
|
959
|
+
type: string,
|
|
960
|
+
visitorData: Record<string, any>
|
|
961
|
+
) {
|
|
962
|
+
try {
|
|
963
|
+
isVisitorDataFromScannedQRCodeCheckingInOut.value = true;
|
|
964
|
+
await visitorDataFromScannedQRCodeCheckInOut({
|
|
965
|
+
type,
|
|
966
|
+
filter: {
|
|
967
|
+
_id: visitorData._id,
|
|
968
|
+
},
|
|
969
|
+
update: {
|
|
970
|
+
updatedBy: currentUser.value._id,
|
|
971
|
+
visitorPassIds: visitorData.visitorPass.map(
|
|
972
|
+
(i: Record<string, any>) => i.keyId
|
|
973
|
+
),
|
|
974
|
+
nric: visitorData.nric ?? "",
|
|
975
|
+
plateNumber: visitorData.plateNumber ?? "",
|
|
976
|
+
},
|
|
977
|
+
userType: "site",
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
dialog.scanVisitorQRCode = false;
|
|
981
|
+
handleCloseVisitorDataFromScannedQRCodeDialog();
|
|
982
|
+
} catch (error) {
|
|
983
|
+
showMessage(errorConverter(error), "error");
|
|
984
|
+
} finally {
|
|
985
|
+
isVisitorDataFromScannedQRCodeCheckingInOut.value = false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function handleCloseVisitorDataFromScannedQRCodeDialog() {
|
|
990
|
+
dialog.showVisitorDataFromScannedQRCode = false;
|
|
991
|
+
visitorDataFromScannedQRCode.value = {};
|
|
992
|
+
}
|
|
993
|
+
|
|
916
994
|
function handleAddNew() {
|
|
917
995
|
dialog.showSelection = true;
|
|
918
996
|
activeVisitorFormType.value = null;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog v-model="props.dialog" transition="dialog-bottom-transition" width="700" max-width="700" persistent
|
|
3
|
+
@after-enter="handleScanVisitorQRCode">
|
|
4
|
+
<v-container class="d-flex justify-center" max-height="90vh">
|
|
5
|
+
<!-- QR code reader container -->
|
|
6
|
+
<v-card elevation="2" class="d-flex flex-column align-center pa-2 w-100 h-100">
|
|
7
|
+
<v-toolbar>
|
|
8
|
+
<v-card-title class="text-h5"> Scan QR Code </v-card-title>
|
|
9
|
+
<v-spacer />
|
|
10
|
+
<v-btn color="grey-darken-1" icon="mdi-close" @click="closeDialog()" />
|
|
11
|
+
</v-toolbar>
|
|
12
|
+
|
|
13
|
+
<div id="reader" class="d-flex justify-center align-center w-100 h-100"
|
|
14
|
+
style="height: calc(100vh - 64px)">
|
|
15
|
+
<!-- The QR code scanner box -->
|
|
16
|
+
<!-- This is where the QR code scanner will be displayed -->
|
|
17
|
+
</div>
|
|
18
|
+
<!-- Show Error Messages -->
|
|
19
|
+
<div v-if="showNotFoundMessage" class="text-error mt-8">
|
|
20
|
+
<p>{{ errorMessage }}</p>
|
|
21
|
+
</div>
|
|
22
|
+
<!-- Switch camera button inside the reader box -->
|
|
23
|
+
<v-btn color="primary" icon class="mt-4" @click="switchCamera()">
|
|
24
|
+
<v-icon icon="mdi-camera-switch" large />
|
|
25
|
+
</v-btn>
|
|
26
|
+
</v-card>
|
|
27
|
+
</v-container>
|
|
28
|
+
</v-dialog>
|
|
29
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
30
|
+
</template>
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
const { $Html5Qrcode }: any = useNuxtApp();
|
|
33
|
+
|
|
34
|
+
const props = defineProps({
|
|
35
|
+
dialog: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const scannedValue = defineModel<string>("scannedValue", {
|
|
42
|
+
default: "",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits(["closeDialog"]);
|
|
46
|
+
|
|
47
|
+
const message = ref("");
|
|
48
|
+
const messageColor = ref("");
|
|
49
|
+
const messageSnackbar = ref(false);
|
|
50
|
+
|
|
51
|
+
function showMessage(msg: string, color: string) {
|
|
52
|
+
message.value = msg;
|
|
53
|
+
messageColor.value = color;
|
|
54
|
+
messageSnackbar.value = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const html5QrCodeInstance = ref<any>(null);
|
|
58
|
+
const cameraFacingMode = ref<string>("environment");
|
|
59
|
+
|
|
60
|
+
const showNotFoundMessage = ref<boolean>(false);
|
|
61
|
+
const errorMessage = ref<string>("");
|
|
62
|
+
|
|
63
|
+
const handleScanVisitorQRCode = async () => {
|
|
64
|
+
if (html5QrCodeInstance.value) return;
|
|
65
|
+
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
|
|
66
|
+
html5QrCodeInstance.value = new $Html5Qrcode("reader");
|
|
67
|
+
html5QrCodeInstance.value
|
|
68
|
+
.start(
|
|
69
|
+
{ facingMode: cameraFacingMode.value },
|
|
70
|
+
config,
|
|
71
|
+
async (qrCodeMessage: string) => {
|
|
72
|
+
showNotFoundMessage.value = false;
|
|
73
|
+
scannedValue.value = qrCodeMessage;
|
|
74
|
+
console.log(`QR Code scanned: ${qrCodeMessage}`);
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
closeDialog();
|
|
77
|
+
}, 100);
|
|
78
|
+
},
|
|
79
|
+
(msg: string) => {
|
|
80
|
+
if (msg === "QR Code no longer in front") {
|
|
81
|
+
showNotFoundMessage.value = true;
|
|
82
|
+
errorMessage.value = msg;
|
|
83
|
+
showMessage(msg, "error");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
.catch((err: any) => {
|
|
88
|
+
showMessage(`Unable to start scanning, error: ${err}`, "error");
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const isValidUrl = (string: string) => {
|
|
93
|
+
try {
|
|
94
|
+
new URL(string);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (_) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const switchCamera = async () => {
|
|
102
|
+
await stopScanner();
|
|
103
|
+
cameraFacingMode.value =
|
|
104
|
+
cameraFacingMode.value === "environment" ? "user" : "environment";
|
|
105
|
+
showMessage(
|
|
106
|
+
`Switched to ${cameraFacingMode.value === "environment" ? "Back" : "Front"
|
|
107
|
+
} Camera`,
|
|
108
|
+
"info"
|
|
109
|
+
);
|
|
110
|
+
handleScanVisitorQRCode();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const closeDialog = () => {
|
|
114
|
+
emit("closeDialog");
|
|
115
|
+
stopScanner();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const stopScanner = () => {
|
|
119
|
+
return new Promise<void>((resolve, reject) => {
|
|
120
|
+
if (html5QrCodeInstance.value) {
|
|
121
|
+
html5QrCodeInstance.value
|
|
122
|
+
.stop()
|
|
123
|
+
.then(() => {
|
|
124
|
+
html5QrCodeInstance.value.clear();
|
|
125
|
+
html5QrCodeInstance.value = null;
|
|
126
|
+
showNotFoundMessage.value = false;
|
|
127
|
+
resolve(); // Resolve the promise when the scanner is fully stopped
|
|
128
|
+
})
|
|
129
|
+
.catch((err: any) => {
|
|
130
|
+
showMessage("Unable to stop scanning.", err);
|
|
131
|
+
reject(err); // Reject the promise if there's an error
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
resolve(); // Resolve immediately if no scanner instance exists
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
</script>
|