@7365admin1/layer-common 1.11.22 → 1.11.24
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/AreaChecklistHistoryMain.vue +1 -1
- package/components/AttendanceMain.vue +6 -6
- package/components/CleaningScheduleMain.vue +18 -8
- package/components/EquipmentItemMain.vue +4 -2
- package/components/EquipmentManagementMain.vue +14 -10
- package/components/ManageChecklistMain.vue +17 -4
- package/components/MemberInformation.vue +49 -13
- package/components/MyAttendanceMain.vue +6 -3
- package/components/PassInformation.vue +197 -144
- package/components/ScanVisitorQRCode.vue +54 -12
- package/components/ScheduleAreaMain.vue +6 -4
- package/components/ScheduleTaskMain.vue +11 -8
- package/components/VisitorDataFromScannedQRCodeForm.vue +292 -0
- package/components/VisitorForm.vue +6 -8
- package/components/VisitorManagement.vue +229 -20
- package/components/VisitorPassKeyQRScanner.vue +138 -0
- package/composables/useVisitor.ts +89 -28
- package/package.json +2 -1
- package/types/visitor.d.ts +45 -15
|
@@ -0,0 +1,292 @@
|
|
|
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 v-if="!visitorInfo.checkIn" 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 v-if="!visitorInfo.checkIn" 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 v-if="!visitorInfo.checkIn" 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="Location" />
|
|
91
|
+
<v-text-field v-model="location" density="comfortable" readonly />
|
|
92
|
+
</v-col>
|
|
93
|
+
|
|
94
|
+
<v-col v-if="!visitorInfo.checkIn" cols="12">
|
|
95
|
+
<InputLabel class="text-capitalize" title="Purpose" />
|
|
96
|
+
<v-textarea
|
|
97
|
+
v-model="visitorInfo.purpose"
|
|
98
|
+
density="comfortable"
|
|
99
|
+
:rows="2"
|
|
100
|
+
no-resize
|
|
101
|
+
readonly
|
|
102
|
+
/>
|
|
103
|
+
</v-col>
|
|
104
|
+
|
|
105
|
+
<v-col v-if="!visitorInfo.checkIn" cols="12">
|
|
106
|
+
<InputLabel class="text-capitalize" title="Remarks" />
|
|
107
|
+
<v-textarea
|
|
108
|
+
v-model="visitorInfo.remarks"
|
|
109
|
+
density="comfortable"
|
|
110
|
+
:rows="2"
|
|
111
|
+
no-resize
|
|
112
|
+
/>
|
|
113
|
+
</v-col>
|
|
114
|
+
|
|
115
|
+
<v-col v-if="!visitorInfo.checkIn" cols="12">
|
|
116
|
+
<InputLabel class="text-capitalize" title="Visitor Pass" />
|
|
117
|
+
<PassInformation
|
|
118
|
+
v-model:pass="visitorInfo.visitorPass"
|
|
119
|
+
:site="prop.site"
|
|
120
|
+
:type="visitorInfo.type"
|
|
121
|
+
:hideKeys="true"
|
|
122
|
+
:clearable="true"
|
|
123
|
+
/>
|
|
124
|
+
</v-col>
|
|
125
|
+
|
|
126
|
+
<v-col v-else cols="12">
|
|
127
|
+
<v-card elevation="4" height="100%" width="100%">
|
|
128
|
+
<v-col cols="12" class="d-flex justify-center pb-0">
|
|
129
|
+
<span
|
|
130
|
+
class="text-h6 font-weight-bold d-flex text-center text-success"
|
|
131
|
+
>
|
|
132
|
+
{{
|
|
133
|
+
visitorInfo.checkIn
|
|
134
|
+
? standardFormatDateTime(visitorInfo.checkIn)
|
|
135
|
+
: "N/A"
|
|
136
|
+
}}
|
|
137
|
+
</span>
|
|
138
|
+
</v-col>
|
|
139
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
140
|
+
Actual Check In
|
|
141
|
+
</v-col>
|
|
142
|
+
</v-card>
|
|
143
|
+
</v-col>
|
|
144
|
+
|
|
145
|
+
<!-- <v-col cols="12" class="mt-4">
|
|
146
|
+
<v-row no-gutters class="pt-2">
|
|
147
|
+
<v-col cols="6" class="pr-1">
|
|
148
|
+
<v-card elevation="3" height="100%" width="100%">
|
|
149
|
+
<v-col cols="12" class="d-flex justify-center pb-0">
|
|
150
|
+
<span
|
|
151
|
+
class="text-h6 font-weight-bold d-flex text-center text-success"
|
|
152
|
+
>
|
|
153
|
+
{{
|
|
154
|
+
visitorInfo.checkIn
|
|
155
|
+
? standardFormatDateTime(visitorInfo.checkIn)
|
|
156
|
+
: "N/A"
|
|
157
|
+
}}
|
|
158
|
+
</span>
|
|
159
|
+
</v-col>
|
|
160
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
161
|
+
Actual Check In
|
|
162
|
+
</v-col>
|
|
163
|
+
</v-card>
|
|
164
|
+
</v-col>
|
|
165
|
+
<v-col cols="6" class="pl-1">
|
|
166
|
+
<v-card elevation="3" height="100%" width="100%">
|
|
167
|
+
<v-col cols="12" class="d-flex justify-center pb-0">
|
|
168
|
+
<span
|
|
169
|
+
class="text-h6 font-weight-bold d-flex text-center text-error"
|
|
170
|
+
>
|
|
171
|
+
{{
|
|
172
|
+
visitorInfo.checkOut
|
|
173
|
+
? standardFormatDateTime(visitorInfo.checkOut)
|
|
174
|
+
: "N/A"
|
|
175
|
+
}}
|
|
176
|
+
</span>
|
|
177
|
+
</v-col>
|
|
178
|
+
<v-col cols="12" class="d-flex justify-center">
|
|
179
|
+
Actual Check Out
|
|
180
|
+
</v-col>
|
|
181
|
+
</v-card>
|
|
182
|
+
</v-col>
|
|
183
|
+
</v-row>
|
|
184
|
+
</v-col> -->
|
|
185
|
+
</v-row>
|
|
186
|
+
<!-- </v-form> -->
|
|
187
|
+
</v-card-text>
|
|
188
|
+
|
|
189
|
+
<v-toolbar density="compact">
|
|
190
|
+
<v-row no-gutters>
|
|
191
|
+
<v-col cols="6">
|
|
192
|
+
<v-btn text="Close" block size="48" tile @click="emit('close')" />
|
|
193
|
+
</v-col>
|
|
194
|
+
<v-col cols="6">
|
|
195
|
+
<v-btn
|
|
196
|
+
v-if="!visitorInfo.checkIn && !visitorInfo.checkOut"
|
|
197
|
+
block
|
|
198
|
+
text="Check In"
|
|
199
|
+
variant="flat"
|
|
200
|
+
color="success"
|
|
201
|
+
size="48"
|
|
202
|
+
tile
|
|
203
|
+
:disabled="processing"
|
|
204
|
+
@click="emit('check-in-out', 'checkIn', visitorInfo)"
|
|
205
|
+
:loading="processing"
|
|
206
|
+
/>
|
|
207
|
+
<v-btn
|
|
208
|
+
v-if="visitorInfo.checkIn && !visitorInfo.checkOut"
|
|
209
|
+
block
|
|
210
|
+
text="Confirm Check Out"
|
|
211
|
+
variant="flat"
|
|
212
|
+
color="error"
|
|
213
|
+
size="48"
|
|
214
|
+
tile
|
|
215
|
+
:disabled="processing"
|
|
216
|
+
@click="emit('check-in-out', 'checkOut', visitorInfo)"
|
|
217
|
+
:loading="processing"
|
|
218
|
+
/>
|
|
219
|
+
</v-col>
|
|
220
|
+
</v-row>
|
|
221
|
+
</v-toolbar>
|
|
222
|
+
</v-card>
|
|
223
|
+
</template>
|
|
224
|
+
<script setup lang="ts">
|
|
225
|
+
const prop = defineProps({
|
|
226
|
+
site: {
|
|
227
|
+
type: String,
|
|
228
|
+
required: true,
|
|
229
|
+
},
|
|
230
|
+
visitorData: {
|
|
231
|
+
type: Object as PropType<Partial<TVisitor>>,
|
|
232
|
+
default: {},
|
|
233
|
+
},
|
|
234
|
+
processing: {
|
|
235
|
+
type: Boolean,
|
|
236
|
+
required: false,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const emit = defineEmits(["check-in-out", "close"]);
|
|
241
|
+
|
|
242
|
+
const { standardFormatDateTime } = useUtils();
|
|
243
|
+
|
|
244
|
+
const visitorInfo = ref(prop.visitorData);
|
|
245
|
+
|
|
246
|
+
const visitorNric = computed({
|
|
247
|
+
get: () =>
|
|
248
|
+
visitorInfo.value && visitorInfo.value.checkIn
|
|
249
|
+
? visitorInfo.value.nric
|
|
250
|
+
: null,
|
|
251
|
+
set: () => {}, // readonly, do nothing on set
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const visitorPlateNumber = computed({
|
|
255
|
+
get: () =>
|
|
256
|
+
visitorInfo.value && visitorInfo.value.checkIn
|
|
257
|
+
? visitorInfo.value.plateNumber
|
|
258
|
+
: null,
|
|
259
|
+
set: () => {}, // readonly, do nothing on set
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// watchEffect(() => {
|
|
263
|
+
// if (visitorInfo.value.plateNumber) {
|
|
264
|
+
// visitorInfo.value.type = "guest";
|
|
265
|
+
// } else {
|
|
266
|
+
// visitorInfo.value.type = "walk-in";
|
|
267
|
+
// }
|
|
268
|
+
// });
|
|
269
|
+
|
|
270
|
+
const location = computed({
|
|
271
|
+
get: () => {
|
|
272
|
+
if (
|
|
273
|
+
visitorInfo.value &&
|
|
274
|
+
visitorInfo.value.block &&
|
|
275
|
+
visitorInfo.value.level &&
|
|
276
|
+
visitorInfo.value.unitName
|
|
277
|
+
) {
|
|
278
|
+
return `${visitorInfo.value.block} / ${visitorInfo.value.level} / ${visitorInfo.value.unitName}`;
|
|
279
|
+
} else if (
|
|
280
|
+
visitorInfo.value &&
|
|
281
|
+
visitorInfo.value.block &&
|
|
282
|
+
!visitorInfo.value.level &&
|
|
283
|
+
!visitorInfo.value.unitName
|
|
284
|
+
) {
|
|
285
|
+
return visitorInfo.value.block;
|
|
286
|
+
} else {
|
|
287
|
+
return "N/A";
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
set: () => {}, // readonly, do nothing on set
|
|
291
|
+
});
|
|
292
|
+
</script>
|
|
@@ -475,15 +475,13 @@ async function handleAddNewContractorType() {
|
|
|
475
475
|
|
|
476
476
|
async function handleSelectCompanyName(company: string) {
|
|
477
477
|
visitor.company = company
|
|
478
|
+
// const selected = companyAutofillDataArray.value.find(x => x.companyName?.includes(company))
|
|
479
|
+
// if (!selected) return
|
|
478
480
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
visitor.
|
|
483
|
-
visitor.level = selected.level || ""
|
|
484
|
-
visitor.unit = selected.unit || ""
|
|
485
|
-
visitor.unitName = selected.unitName || ""
|
|
486
|
-
|
|
481
|
+
// visitor.block = selected.block || ""
|
|
482
|
+
// visitor.level = selected.level || ""
|
|
483
|
+
// visitor.unit = selected.unit || ""
|
|
484
|
+
// visitor.unitName = selected.unitName || ""
|
|
487
485
|
}
|
|
488
486
|
|
|
489
487
|
const companyCombo = ref(null)
|
|
@@ -169,11 +169,33 @@
|
|
|
169
169
|
</span>
|
|
170
170
|
|
|
171
171
|
<span
|
|
172
|
-
v-if="
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
v-if="
|
|
173
|
+
!item.checkOut &&
|
|
174
|
+
(item?.status === 'registered' ||
|
|
175
|
+
item?.status === 'unregistered') &&
|
|
176
|
+
canCheckoutVisitor
|
|
177
|
+
"
|
|
178
|
+
>
|
|
179
|
+
<span
|
|
180
|
+
class="d-flex align-center ga-2"
|
|
181
|
+
v-bind="menuProps"
|
|
182
|
+
style="cursor: pointer"
|
|
183
|
+
>
|
|
184
|
+
<v-icon
|
|
185
|
+
icon="mdi-clock-time-eight-outline"
|
|
186
|
+
color="red"
|
|
187
|
+
size="20"
|
|
188
|
+
/>
|
|
189
|
+
<v-btn
|
|
190
|
+
size="x-small"
|
|
191
|
+
class="text-capitalize"
|
|
192
|
+
color="red"
|
|
193
|
+
text="Checkout"
|
|
194
|
+
:loading="
|
|
195
|
+
loading.checkingOut && item?._id === selectedVisitorId
|
|
196
|
+
"
|
|
197
|
+
@click.stop="handleCheckout(item._id)"
|
|
198
|
+
/>
|
|
177
199
|
</span>
|
|
178
200
|
</span>
|
|
179
201
|
<span v-else class="d-flex align-center ga-2">
|
|
@@ -249,17 +271,62 @@
|
|
|
249
271
|
</div>
|
|
250
272
|
</template>
|
|
251
273
|
<v-list density="compact">
|
|
252
|
-
<v-list-item
|
|
253
|
-
|
|
254
|
-
prepend-icon="mdi-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
274
|
+
<v-list-item
|
|
275
|
+
v-if="!item?.checkOut"
|
|
276
|
+
prepend-icon="mdi-logout"
|
|
277
|
+
title="Checkout"
|
|
278
|
+
@click.stop="handleCheckout(item._id)"
|
|
279
|
+
/>
|
|
280
|
+
<v-list-item
|
|
281
|
+
v-if="
|
|
282
|
+
(item.visitorPass?.length ?? 0) > 0 ||
|
|
283
|
+
(item.passKeys?.length ?? 0) > 0
|
|
284
|
+
"
|
|
285
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
286
|
+
:title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
|
|
287
|
+
@click.stop="handleOpenEditPassKey(item)"
|
|
288
|
+
/>
|
|
289
|
+
<v-list-item
|
|
290
|
+
v-if="showAddPassKeyButton(item)"
|
|
291
|
+
prepend-icon="mdi-plus"
|
|
292
|
+
title="Add Pass/Key"
|
|
293
|
+
@click.stop="handleOpenAddPassKey(item)"
|
|
294
|
+
/>
|
|
258
295
|
</v-list>
|
|
259
296
|
</v-menu>
|
|
260
297
|
</v-row>
|
|
261
298
|
</template>
|
|
262
299
|
|
|
300
|
+
<template v-slot:item.arrival-date-time="{ item }">
|
|
301
|
+
<v-row no-gutters class="d-flex flex-column py-2">
|
|
302
|
+
<span class="d-flex align-center ga-2">
|
|
303
|
+
<v-icon icon="mdi-calendar" size="20" />
|
|
304
|
+
{{ standardFormatDate(item.expectedCheckIn) || "-" }}
|
|
305
|
+
</span>
|
|
306
|
+
<span class="d-flex align-center ga-2">
|
|
307
|
+
<v-icon icon="mdi-clock-time-four-outline" size="20" />
|
|
308
|
+
{{ formatTime(item.expectedCheckIn) || "-" }}
|
|
309
|
+
</span>
|
|
310
|
+
|
|
311
|
+
<span v-if="!item.checkIn && canUpdateVisitor">
|
|
312
|
+
<span class="d-flex align-center ga-2 cursor-pointer">
|
|
313
|
+
<v-icon
|
|
314
|
+
icon="mdi-clock-time-eight-outline"
|
|
315
|
+
color="success"
|
|
316
|
+
size="20"
|
|
317
|
+
/>
|
|
318
|
+
<v-btn
|
|
319
|
+
size="x-small"
|
|
320
|
+
class="text-capitalize"
|
|
321
|
+
color="success"
|
|
322
|
+
text="Checkin"
|
|
323
|
+
@click.stop="handleCheckIn(item)"
|
|
324
|
+
/>
|
|
325
|
+
</span>
|
|
326
|
+
</span>
|
|
327
|
+
</v-row>
|
|
328
|
+
</template>
|
|
329
|
+
|
|
263
330
|
<template v-slot:item.action="{ item }">
|
|
264
331
|
<v-btn
|
|
265
332
|
v-if="activeTab === 'unregistered' && canAddVisitor"
|
|
@@ -459,6 +526,25 @@
|
|
|
459
526
|
<p class="text-body-2 mb-3">
|
|
460
527
|
Please ensure all passes and keys are returned before checking out.
|
|
461
528
|
</p>
|
|
529
|
+
|
|
530
|
+
<div>
|
|
531
|
+
<InputLabel class="text-capitalize" title="Full Name" />
|
|
532
|
+
<v-text-field
|
|
533
|
+
v-model="selectedVisitorObject.name"
|
|
534
|
+
density="comfortable"
|
|
535
|
+
readonly
|
|
536
|
+
/>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<div>
|
|
540
|
+
<InputLabel class="text-capitalize" title="Location" />
|
|
541
|
+
<v-text-field
|
|
542
|
+
v-model="selectedVisitorObject.location"
|
|
543
|
+
density="comfortable"
|
|
544
|
+
readonly
|
|
545
|
+
/>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
462
548
|
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
463
549
|
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
464
550
|
<v-row
|
|
@@ -609,14 +695,34 @@
|
|
|
609
695
|
|
|
610
696
|
<ScanVisitorQRCode
|
|
611
697
|
:dialog="dialog.scanVisitorQRCode"
|
|
698
|
+
:site="siteId"
|
|
699
|
+
@open-visitor-data-from-scanned-qr-code-dialog="
|
|
700
|
+
handleShowVisitorDataFromScannedQRCodeDialog
|
|
701
|
+
"
|
|
612
702
|
@close-dialog="dialog.scanVisitorQRCode = false"
|
|
613
703
|
/>
|
|
614
704
|
|
|
705
|
+
<v-dialog
|
|
706
|
+
v-model="dialog.showVisitorDataFromScannedQRCode"
|
|
707
|
+
width="450"
|
|
708
|
+
persistent
|
|
709
|
+
>
|
|
710
|
+
<VisitorDataFromScannedQRCodeForm
|
|
711
|
+
:site="siteId"
|
|
712
|
+
:visitor-data="visitorDataFromScannedQRCode"
|
|
713
|
+
@check-in-out="handleVisitorDataFromScannedQRCodeCheckInOut"
|
|
714
|
+
:processing="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
715
|
+
@close="handleCloseVisitorDataFromScannedQRCodeDialog"
|
|
716
|
+
/>
|
|
717
|
+
</v-dialog>
|
|
718
|
+
|
|
615
719
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
616
720
|
</v-row>
|
|
617
721
|
</template>
|
|
618
722
|
|
|
619
723
|
<script setup lang="ts">
|
|
724
|
+
import { errorConverter } from "../utils/data";
|
|
725
|
+
|
|
620
726
|
definePageMeta({
|
|
621
727
|
middleware: ["01-auth", "02-org"],
|
|
622
728
|
});
|
|
@@ -644,10 +750,24 @@ const props = defineProps({
|
|
|
644
750
|
},
|
|
645
751
|
});
|
|
646
752
|
|
|
647
|
-
const {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
753
|
+
const { authenticate, currentUser } = useLocalAuth();
|
|
754
|
+
authenticate();
|
|
755
|
+
|
|
756
|
+
const {
|
|
757
|
+
getVisitors,
|
|
758
|
+
visitorSelection,
|
|
759
|
+
typeFieldMap,
|
|
760
|
+
updateVisitor,
|
|
761
|
+
visitorDataFromScannedQRCodeCheckIn,
|
|
762
|
+
} = useVisitor();
|
|
763
|
+
const {
|
|
764
|
+
debounce,
|
|
765
|
+
formatCamelCaseToWords,
|
|
766
|
+
formatDate,
|
|
767
|
+
formatTime,
|
|
768
|
+
UTCToLocalTIme,
|
|
769
|
+
standardFormatDate,
|
|
770
|
+
} = useUtils();
|
|
651
771
|
const { formatLocation } = useSecurityUtils();
|
|
652
772
|
const { getFileUrlAnpr } = useFile();
|
|
653
773
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
@@ -697,6 +817,7 @@ const dialog = reactive({
|
|
|
697
817
|
addPassKey: false,
|
|
698
818
|
editPassKey: false,
|
|
699
819
|
scanVisitorQRCode: false,
|
|
820
|
+
showVisitorDataFromScannedQRCode: false,
|
|
700
821
|
});
|
|
701
822
|
|
|
702
823
|
const snapshotImageUrl = ref("");
|
|
@@ -709,7 +830,9 @@ const headers = computed(() => [
|
|
|
709
830
|
{ title: "Type/Company", value: "type-company" },
|
|
710
831
|
{ title: "Location", value: "location" },
|
|
711
832
|
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" },
|
|
712
|
-
|
|
833
|
+
...(activeTab.value != "guests"
|
|
834
|
+
? [{ title: "Check In/Out", value: "checkin-out" }]
|
|
835
|
+
: [{ title: "Arrival Date / Time", value: "arrival-date-time" }]),
|
|
713
836
|
...(activeTab.value === "resident-transactions"
|
|
714
837
|
? [
|
|
715
838
|
{ title: "Check-in Remarks", value: "checkInRemarks" },
|
|
@@ -814,7 +937,7 @@ const {
|
|
|
814
937
|
|
|
815
938
|
if (activeTab.value === "guests") {
|
|
816
939
|
params.type = "guest";
|
|
817
|
-
params.status = "
|
|
940
|
+
params.status = "approved";
|
|
818
941
|
} else if (activeTab.value === "resident-transactions") {
|
|
819
942
|
params.status = "registered";
|
|
820
943
|
params.type = "resident,tenant";
|
|
@@ -850,7 +973,9 @@ watch(getVisitorReq, (newData: any) => {
|
|
|
850
973
|
const selectedVisitorObject = computed(() => {
|
|
851
974
|
const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
|
|
852
975
|
if (!obj) return {};
|
|
976
|
+
|
|
853
977
|
const type = obj?.type as TVisitorType | undefined;
|
|
978
|
+
|
|
854
979
|
let includedKeys: string[] = [
|
|
855
980
|
"checkIn",
|
|
856
981
|
"checkOut",
|
|
@@ -859,9 +984,23 @@ const selectedVisitorObject = computed(() => {
|
|
|
859
984
|
"snapshotExitImage",
|
|
860
985
|
];
|
|
861
986
|
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
987
|
+
|
|
988
|
+
let locationString = "N/A";
|
|
989
|
+
|
|
990
|
+
if (obj?.block || obj?.level || obj?.unitName) {
|
|
991
|
+
locationString = `${obj.block} / ${obj.level} / ${obj.unitName}`;
|
|
992
|
+
} else if (obj?.block) {
|
|
993
|
+
locationString = obj.block;
|
|
994
|
+
} else {
|
|
995
|
+
locationString = "N/A";
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return {
|
|
999
|
+
...Object.fromEntries(
|
|
1000
|
+
Object.entries(obj).filter(([key]) => includedKeys.includes(key))
|
|
1001
|
+
),
|
|
1002
|
+
location: locationString,
|
|
1003
|
+
};
|
|
865
1004
|
});
|
|
866
1005
|
|
|
867
1006
|
function formatValues(key: string, value: any) {
|
|
@@ -878,6 +1017,71 @@ function formatValues(key: string, value: any) {
|
|
|
878
1017
|
return value;
|
|
879
1018
|
}
|
|
880
1019
|
|
|
1020
|
+
const visitorDataFromScannedQRCode = ref<Record<string, any>>({});
|
|
1021
|
+
|
|
1022
|
+
function handleShowVisitorDataFromScannedQRCodeDialog(
|
|
1023
|
+
scannedData: Record<string, any>
|
|
1024
|
+
) {
|
|
1025
|
+
visitorDataFromScannedQRCode.value = scannedData;
|
|
1026
|
+
|
|
1027
|
+
const { checkIn, checkOut, _id, visitorPass } = scannedData;
|
|
1028
|
+
const hasPasses = (visitorPass?.length ?? 0) > 0;
|
|
1029
|
+
|
|
1030
|
+
// 1. If they are checked in and HAVE passes/keys, go to the Return screen
|
|
1031
|
+
if (checkIn && !checkOut && hasPasses) {
|
|
1032
|
+
handleCheckout(_id);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// 2. Default Case: Show visitor data (Entry, already checked out, or checked in with no passes)
|
|
1037
|
+
dialog.showVisitorDataFromScannedQRCode = true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const isVisitorDataFromScannedQRCodeCheckingInOut = ref(false);
|
|
1041
|
+
|
|
1042
|
+
async function handleVisitorDataFromScannedQRCodeCheckInOut(
|
|
1043
|
+
type: string,
|
|
1044
|
+
visitorData: Record<string, any>
|
|
1045
|
+
) {
|
|
1046
|
+
try {
|
|
1047
|
+
isVisitorDataFromScannedQRCodeCheckingInOut.value = true;
|
|
1048
|
+
if (type != "checkOut") {
|
|
1049
|
+
await visitorDataFromScannedQRCodeCheckIn({
|
|
1050
|
+
type,
|
|
1051
|
+
filter: {
|
|
1052
|
+
_id: visitorData._id,
|
|
1053
|
+
},
|
|
1054
|
+
update: {
|
|
1055
|
+
updatedBy: currentUser.value._id,
|
|
1056
|
+
visitorPass: visitorData.visitorPass.find(
|
|
1057
|
+
(i: Record<string, any>) => i.keyId === null
|
|
1058
|
+
)
|
|
1059
|
+
? undefined
|
|
1060
|
+
: visitorData.visitorPass,
|
|
1061
|
+
nric: visitorData.nric ?? "",
|
|
1062
|
+
plateNumber: visitorData.plateNumber ?? "",
|
|
1063
|
+
},
|
|
1064
|
+
userType: "site",
|
|
1065
|
+
});
|
|
1066
|
+
showMessage("Visitor successfully checked-in!", "info");
|
|
1067
|
+
} else {
|
|
1068
|
+
selectedVisitorId.value = visitorData._id;
|
|
1069
|
+
await proceedCheckout();
|
|
1070
|
+
}
|
|
1071
|
+
handleCloseVisitorDataFromScannedQRCodeDialog();
|
|
1072
|
+
getVisitorRefresh();
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
showMessage(errorConverter(error), "error");
|
|
1075
|
+
} finally {
|
|
1076
|
+
isVisitorDataFromScannedQRCodeCheckingInOut.value = false;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function handleCloseVisitorDataFromScannedQRCodeDialog() {
|
|
1081
|
+
dialog.showVisitorDataFromScannedQRCode = false;
|
|
1082
|
+
visitorDataFromScannedQRCode.value = {};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
881
1085
|
function handleAddNew() {
|
|
882
1086
|
dialog.showSelection = true;
|
|
883
1087
|
activeVisitorFormType.value = null;
|
|
@@ -1058,6 +1262,11 @@ const canConfirmCheckout = computed(() => {
|
|
|
1058
1262
|
});
|
|
1059
1263
|
});
|
|
1060
1264
|
|
|
1265
|
+
async function handleCheckIn(user: Record<string, any>) {
|
|
1266
|
+
visitorDataFromScannedQRCode.value = user;
|
|
1267
|
+
dialog.showVisitorDataFromScannedQRCode = true;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1061
1270
|
function handleCheckout(userId: string) {
|
|
1062
1271
|
if (!userId) {
|
|
1063
1272
|
showMessage("Invalid userId", "error");
|