@7365admin1/layer-common 3.0.26 → 3.0.27
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 +7 -0
- package/components/AccessCardDetailsDialog.vue +98 -2
- package/components/AccessCardPreviewDialog.vue +31 -3
- package/components/AccessManagement.vue +10 -1
- package/components/EntryPassInformation.vue +55 -17
- package/components/HidReaderForm.vue +10 -16
- package/components/MemberInformation.vue +59 -1
- package/components/VisitorForm.vue +22 -6
- package/components/VisitorManagement.vue +790 -219
- package/composables/useAccessManagement.ts +8 -0
- package/package.json +1 -1
|
@@ -1,51 +1,137 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
<TableMain
|
|
4
|
-
:
|
|
5
|
-
|
|
3
|
+
<TableMain
|
|
4
|
+
:headers="headers"
|
|
5
|
+
:items="items"
|
|
6
|
+
:loading="getVisitorPending"
|
|
7
|
+
:page="page"
|
|
8
|
+
:pages="pages"
|
|
9
|
+
:extension-height="110"
|
|
10
|
+
:offset="300"
|
|
11
|
+
:pageRange="pageRange"
|
|
12
|
+
@refresh="getVisitorRefresh"
|
|
13
|
+
@update:page="handleUpdatePage"
|
|
14
|
+
show-header
|
|
15
|
+
@row-click="handleRowClick"
|
|
16
|
+
>
|
|
6
17
|
<template #actions>
|
|
7
18
|
<v-row no-gutters>
|
|
8
|
-
<v-btn
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
<v-btn
|
|
20
|
+
v-if="canAddVisitor"
|
|
21
|
+
class="text-none"
|
|
22
|
+
rounded="pill"
|
|
23
|
+
variant="tonal"
|
|
24
|
+
size="large"
|
|
25
|
+
@click="handleAddNew"
|
|
26
|
+
text="Add Visitor"
|
|
27
|
+
/>
|
|
28
|
+
<v-btn
|
|
29
|
+
v-if="canScanVisitorQRCode"
|
|
30
|
+
text="Scan QR Code"
|
|
31
|
+
class="text-none ml-2"
|
|
32
|
+
rounded="pill"
|
|
33
|
+
variant="tonal"
|
|
34
|
+
size="large"
|
|
35
|
+
@click="dialog.scanVisitorQRCode = true"
|
|
36
|
+
/>
|
|
12
37
|
<v-menu>
|
|
13
38
|
<template #activator="{ props }">
|
|
14
|
-
<v-btn
|
|
15
|
-
|
|
39
|
+
<v-btn
|
|
40
|
+
v-bind="props"
|
|
41
|
+
text="Download Report"
|
|
42
|
+
class="text-none ml-2"
|
|
43
|
+
prepend-icon="mdi-download"
|
|
44
|
+
rounded="pill"
|
|
45
|
+
variant="tonal"
|
|
46
|
+
size="large"
|
|
47
|
+
/>
|
|
16
48
|
</template>
|
|
17
49
|
<v-list>
|
|
18
|
-
<v-list-item
|
|
19
|
-
|
|
50
|
+
<v-list-item
|
|
51
|
+
prepend-icon="mdi-file-pdf"
|
|
52
|
+
title="Download PDF Report"
|
|
53
|
+
@click="handleDownloadPDFReport"
|
|
54
|
+
/>
|
|
55
|
+
<v-list-item
|
|
56
|
+
prepend-icon="mdi-file-csv"
|
|
57
|
+
title="Download CSV Report"
|
|
58
|
+
@click="handleDownloadCSVReport"
|
|
59
|
+
/>
|
|
20
60
|
</v-list>
|
|
21
61
|
</v-menu>
|
|
22
62
|
</v-row>
|
|
23
63
|
</template>
|
|
24
64
|
<template #extension>
|
|
25
65
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
26
|
-
<v-tabs
|
|
27
|
-
|
|
66
|
+
<v-tabs
|
|
67
|
+
v-model="activeTab"
|
|
68
|
+
color="primary"
|
|
69
|
+
:height="40"
|
|
70
|
+
@update:model-value="toRoute"
|
|
71
|
+
class="w-100"
|
|
72
|
+
>
|
|
73
|
+
<v-tab
|
|
74
|
+
v-for="tab in tabOptions"
|
|
75
|
+
:value="tab.value"
|
|
76
|
+
:key="tab.value"
|
|
77
|
+
class="text-capitalize"
|
|
78
|
+
>
|
|
28
79
|
{{ tab.name }}
|
|
29
80
|
</v-tab>
|
|
30
81
|
</v-tabs>
|
|
31
82
|
|
|
32
|
-
<v-card
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
83
|
+
<v-card
|
|
84
|
+
class="w-100 px-3 d-flex align-center ga-5 py-2"
|
|
85
|
+
flat
|
|
86
|
+
:height="60"
|
|
87
|
+
>
|
|
88
|
+
<v-text-field
|
|
89
|
+
v-model="searchInput"
|
|
90
|
+
density="compact"
|
|
91
|
+
placeholder="Search"
|
|
92
|
+
clearable
|
|
93
|
+
max-width="300"
|
|
94
|
+
append-inner-icon="mdi-magnify"
|
|
95
|
+
hide-details
|
|
96
|
+
/>
|
|
97
|
+
<v-checkbox
|
|
98
|
+
v-model="displayNotCheckedOut"
|
|
99
|
+
class="text-subtitle-2"
|
|
100
|
+
hide-details
|
|
101
|
+
>
|
|
36
102
|
<template #label>
|
|
37
103
|
<span class="text-caption">Not Checked Out</span>
|
|
38
104
|
</template>
|
|
39
105
|
</v-checkbox>
|
|
40
|
-
<InputDatePicker
|
|
106
|
+
<InputDatePicker
|
|
107
|
+
v-model="dateFrom"
|
|
108
|
+
density="compact"
|
|
109
|
+
hide-details
|
|
110
|
+
/>
|
|
41
111
|
<InputDatePicker v-model="dateTo" density="compact" hide-details />
|
|
42
|
-
<v-select
|
|
43
|
-
|
|
44
|
-
|
|
112
|
+
<v-select
|
|
113
|
+
v-if="activeTab == 'registered'"
|
|
114
|
+
v-model="filterTypes"
|
|
115
|
+
label="Filter by types"
|
|
116
|
+
item-title="label"
|
|
117
|
+
item-value="value"
|
|
118
|
+
:items="visitorSelection"
|
|
119
|
+
density="compact"
|
|
120
|
+
clearable
|
|
121
|
+
multiple
|
|
122
|
+
max-width="200"
|
|
123
|
+
hide-details
|
|
124
|
+
>
|
|
45
125
|
<template v-slot:selection="{ item, index }">
|
|
46
126
|
<div class="d-flex align-center text-caption text-nowrap">
|
|
47
|
-
<v-chip
|
|
48
|
-
|
|
127
|
+
<v-chip
|
|
128
|
+
v-if="index === 0"
|
|
129
|
+
color="error"
|
|
130
|
+
:text="filterTypeSelectionLabel()"
|
|
131
|
+
size="x-small"
|
|
132
|
+
variant="tonal"
|
|
133
|
+
class="ml-2"
|
|
134
|
+
/>
|
|
49
135
|
</div>
|
|
50
136
|
</template>
|
|
51
137
|
</v-select>
|
|
@@ -60,17 +146,30 @@
|
|
|
60
146
|
</span>
|
|
61
147
|
<span class="text-capitalize">{{ item?.name }}</span>
|
|
62
148
|
</span>
|
|
63
|
-
<span class="text-grey text-caption" v-if="item?.members?.length > 0"
|
|
64
|
-
members)</span
|
|
149
|
+
<span class="text-grey text-caption" v-if="item?.members?.length > 0"
|
|
150
|
+
>( +{{ item?.members?.length }} members)</span
|
|
151
|
+
>
|
|
65
152
|
</template>
|
|
66
153
|
|
|
67
|
-
<template v-slot:item.email="{
|
|
68
|
-
|
|
154
|
+
<template v-slot:item.email-vehicleNumber="{ item }">
|
|
155
|
+
<span class="d-flex align-center ga-2">
|
|
156
|
+
<v-icon icon="mdi-email" size="15" />
|
|
157
|
+
<span class="text-capitalize">{{ item?.email || "N/A" }}</span>
|
|
158
|
+
</span>
|
|
159
|
+
<span class="d-flex align-center ga-2">
|
|
160
|
+
<v-icon icon="mdi-car-back" size="15" />
|
|
161
|
+
<span class="text-capitalize">{{ item?.plateNumber || "N/A" }}</span>
|
|
162
|
+
</span>
|
|
69
163
|
</template>
|
|
70
164
|
|
|
71
165
|
<template v-slot:item.invited-by="{ item }">
|
|
72
166
|
<span class="d-flex align-center ga-2">
|
|
73
|
-
<AvatarMain
|
|
167
|
+
<AvatarMain
|
|
168
|
+
v-if="item?.inviterName"
|
|
169
|
+
:name="item?.inviterName"
|
|
170
|
+
:size="20"
|
|
171
|
+
:id="item?._id"
|
|
172
|
+
/>
|
|
74
173
|
<span class="text-capitalize">{{ item?.inviterName ?? "N/A" }}</span>
|
|
75
174
|
</span>
|
|
76
175
|
</template>
|
|
@@ -80,7 +179,7 @@
|
|
|
80
179
|
<v-icon icon="mdi-user" size="15" />
|
|
81
180
|
<span v-if="item.type === 'contractor'" class="text-capitalize">{{
|
|
82
181
|
formatCamelCaseToWords(item.contractorType)
|
|
83
|
-
|
|
182
|
+
}}</span>
|
|
84
183
|
<span v-else class="text-capitalize">{{ formatType(item) }}</span>
|
|
85
184
|
</span>
|
|
86
185
|
<span class="d-flex align-center ga-2">
|
|
@@ -116,26 +215,52 @@
|
|
|
116
215
|
<template v-slot:item.checkin-out="{ item }">
|
|
117
216
|
<v-row no-gutters class="d-flex flex-column py-2">
|
|
118
217
|
<span class="d-flex align-center ga-2">
|
|
119
|
-
<v-icon
|
|
218
|
+
<v-icon
|
|
219
|
+
icon="mdi-clock-time-four-outline"
|
|
220
|
+
color="green"
|
|
221
|
+
size="20"
|
|
222
|
+
/>
|
|
120
223
|
<span class="text-capitalize">{{
|
|
121
224
|
UTCToLocalTIme(item.checkIn) || "-"
|
|
122
|
-
|
|
225
|
+
}}</span>
|
|
123
226
|
<span>
|
|
124
|
-
<v-icon
|
|
125
|
-
|
|
227
|
+
<v-icon
|
|
228
|
+
v-if="item?.snapshotEntryImage"
|
|
229
|
+
size="17"
|
|
230
|
+
icon="mdi-image"
|
|
231
|
+
@click.stop="handleViewImage(item.snapshotEntryImage)"
|
|
232
|
+
/>
|
|
126
233
|
</span>
|
|
127
234
|
</span>
|
|
128
235
|
|
|
129
|
-
<span
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
item?.status === '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
236
|
+
<span
|
|
237
|
+
v-if="
|
|
238
|
+
!item.checkOut &&
|
|
239
|
+
(item?.status === 'registered' ||
|
|
240
|
+
item?.status === 'unregistered') &&
|
|
241
|
+
canCheckoutVisitor
|
|
242
|
+
"
|
|
243
|
+
>
|
|
244
|
+
<span
|
|
245
|
+
class="d-flex align-center ga-2"
|
|
246
|
+
v-bind="menuProps"
|
|
247
|
+
style="cursor: pointer"
|
|
248
|
+
>
|
|
249
|
+
<v-icon
|
|
250
|
+
icon="mdi-clock-time-eight-outline"
|
|
251
|
+
color="red"
|
|
252
|
+
size="20"
|
|
253
|
+
/>
|
|
254
|
+
<v-btn
|
|
255
|
+
size="x-small"
|
|
256
|
+
class="text-capitalize"
|
|
257
|
+
color="red"
|
|
258
|
+
text="Checkout"
|
|
259
|
+
:loading="
|
|
260
|
+
loading.checkingOut && item?._id === selectedVisitorId
|
|
261
|
+
"
|
|
262
|
+
@click.stop="handleCheckout(item._id)"
|
|
263
|
+
/>
|
|
139
264
|
</span>
|
|
140
265
|
</span>
|
|
141
266
|
<span v-else class="d-flex align-center ga-2">
|
|
@@ -143,50 +268,96 @@
|
|
|
143
268
|
<template v-if="item.checkOut">
|
|
144
269
|
<span class="text-capitalize">{{
|
|
145
270
|
UTCToLocalTIme(item.checkOut)
|
|
146
|
-
|
|
271
|
+
}}</span>
|
|
147
272
|
<span>
|
|
148
|
-
<v-icon
|
|
149
|
-
|
|
273
|
+
<v-icon
|
|
274
|
+
v-if="item?.snapshotExitImage"
|
|
275
|
+
size="17"
|
|
276
|
+
icon="mdi-image"
|
|
277
|
+
@click.stop="handleViewImage(item.snapshotExitImage)"
|
|
278
|
+
/>
|
|
150
279
|
</span>
|
|
151
280
|
<span v-if="item?.manualCheckout">
|
|
152
|
-
<TooltipInfo
|
|
281
|
+
<TooltipInfo
|
|
282
|
+
text="Manual Checkout"
|
|
283
|
+
density="compact"
|
|
284
|
+
size="x-small"
|
|
285
|
+
/>
|
|
153
286
|
</span>
|
|
154
287
|
</template>
|
|
155
288
|
<template v-else> <span class="mt-1"> N/A </span> </template>
|
|
156
289
|
</span>
|
|
157
290
|
|
|
158
|
-
<div
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
291
|
+
<div
|
|
292
|
+
v-if="showAddPassKeyButton(item)"
|
|
293
|
+
class="d-flex flex-wrap ga-1 mt-1"
|
|
294
|
+
v-bind="menuProps"
|
|
295
|
+
style="cursor: pointer"
|
|
296
|
+
>
|
|
297
|
+
<v-chip
|
|
298
|
+
size="x-small"
|
|
299
|
+
variant="tonal"
|
|
300
|
+
prepend-icon="mdi-key"
|
|
301
|
+
@click.stop="handleOpenAddPassKey(item)"
|
|
302
|
+
>Add Pass/Key</v-chip
|
|
303
|
+
>
|
|
162
304
|
</div>
|
|
163
305
|
|
|
164
306
|
<v-menu :close-on-content-click="true">
|
|
165
307
|
<template v-slot:activator="{ props: menuProps }">
|
|
166
|
-
<div
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
308
|
+
<div
|
|
309
|
+
v-if="
|
|
310
|
+
(item.visitorPass?.length ?? 0) > 0 ||
|
|
311
|
+
(item.passKeys?.length ?? 0) > 0
|
|
312
|
+
"
|
|
313
|
+
class="d-flex flex-wrap ga-1 mt-1"
|
|
314
|
+
v-bind="menuProps"
|
|
315
|
+
style="cursor: pointer"
|
|
316
|
+
>
|
|
317
|
+
<v-chip
|
|
318
|
+
v-for="pass in item.visitorPass"
|
|
319
|
+
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
320
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
321
|
+
size="x-small"
|
|
322
|
+
variant="tonal"
|
|
323
|
+
color="blue"
|
|
324
|
+
>
|
|
172
325
|
{{ (pass as any)?.prefixAndName }}
|
|
173
326
|
</v-chip>
|
|
174
|
-
<v-chip
|
|
175
|
-
|
|
327
|
+
<v-chip
|
|
328
|
+
v-for="key in item.passKeys"
|
|
329
|
+
:key="(key as any)._id ?? (key as any).keyId"
|
|
330
|
+
prepend-icon="mdi-key"
|
|
331
|
+
size="x-small"
|
|
332
|
+
variant="tonal"
|
|
333
|
+
color="orange"
|
|
334
|
+
>
|
|
176
335
|
{{ (key as any)?.prefixAndName }}
|
|
177
336
|
</v-chip>
|
|
178
337
|
</div>
|
|
179
338
|
</template>
|
|
180
339
|
<v-list density="compact">
|
|
181
|
-
<v-list-item
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
(item.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
340
|
+
<v-list-item
|
|
341
|
+
v-if="!item?.checkOut"
|
|
342
|
+
prepend-icon="mdi-logout"
|
|
343
|
+
title="Checkout"
|
|
344
|
+
@click.stop="handleCheckout(item._id)"
|
|
345
|
+
/>
|
|
346
|
+
<v-list-item
|
|
347
|
+
v-if="
|
|
348
|
+
(item.visitorPass?.length ?? 0) > 0 ||
|
|
349
|
+
(item.passKeys?.length ?? 0) > 0
|
|
350
|
+
"
|
|
351
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
352
|
+
:title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
|
|
353
|
+
@click.stop="handleOpenEditPassKey(item)"
|
|
354
|
+
/>
|
|
355
|
+
<v-list-item
|
|
356
|
+
v-if="showAddPassKeyButton(item)"
|
|
357
|
+
prepend-icon="mdi-plus"
|
|
358
|
+
title="Add Pass/Key"
|
|
359
|
+
@click.stop="handleOpenAddPassKey(item)"
|
|
360
|
+
/>
|
|
190
361
|
</v-list>
|
|
191
362
|
</v-menu>
|
|
192
363
|
|
|
@@ -202,7 +373,11 @@
|
|
|
202
373
|
v-bind="nfcMenuProps"
|
|
203
374
|
@click.stop
|
|
204
375
|
>
|
|
205
|
-
<v-icon
|
|
376
|
+
<v-icon
|
|
377
|
+
size="15"
|
|
378
|
+
icon="mdi-credit-card-outline"
|
|
379
|
+
color="purple"
|
|
380
|
+
/>
|
|
206
381
|
<v-chip size="x-small" variant="tonal" color="purple">
|
|
207
382
|
{{ item.accessCards.cardNo }}
|
|
208
383
|
<template v-if="(item.cards?.flat().length ?? 0) > 1">
|
|
@@ -227,6 +402,12 @@
|
|
|
227
402
|
</template>
|
|
228
403
|
</v-list>
|
|
229
404
|
</v-menu>
|
|
405
|
+
|
|
406
|
+
<!-- QRCODE identifier -->
|
|
407
|
+
<div v-if="item.accessCards?.type === 'QRCODE' && item.accessCards?.cardNo" class="d-flex align-center ga-1 mt-1">
|
|
408
|
+
<v-icon size="15" icon="mdi-qrcode" color="teal" />
|
|
409
|
+
<v-chip size="x-small" variant="tonal" color="teal">QR · ({{ item.cards?.flat().length ?? 1 }})</v-chip>
|
|
410
|
+
</div>
|
|
230
411
|
</v-row>
|
|
231
412
|
</template>
|
|
232
413
|
|
|
@@ -243,27 +424,44 @@
|
|
|
243
424
|
|
|
244
425
|
<span v-if="!item.checkIn && canUpdateVisitor">
|
|
245
426
|
<span class="d-flex align-center ga-2 cursor-pointer">
|
|
246
|
-
<v-icon
|
|
247
|
-
|
|
248
|
-
|
|
427
|
+
<v-icon
|
|
428
|
+
icon="mdi-clock-time-eight-outline"
|
|
429
|
+
color="success"
|
|
430
|
+
size="20"
|
|
431
|
+
/>
|
|
432
|
+
<v-btn
|
|
433
|
+
size="x-small"
|
|
434
|
+
class="text-capitalize"
|
|
435
|
+
color="success"
|
|
436
|
+
text="Checkin"
|
|
437
|
+
@click.stop="handleCheckin(item)"
|
|
438
|
+
/>
|
|
249
439
|
</span>
|
|
250
440
|
</span>
|
|
251
441
|
</v-row>
|
|
252
442
|
</template>
|
|
253
443
|
|
|
254
444
|
<template v-slot:item.status="{ item }">
|
|
255
|
-
<v-card
|
|
445
|
+
<v-card
|
|
446
|
+
v-if="item?.overnightParking?.status"
|
|
447
|
+
size="small"
|
|
256
448
|
class="rounded-xl text-capitalize text-center elevation-0 py-1 px-2"
|
|
257
449
|
:color="getOvernightParkingRequestStatusColor(item?.overnightParking?.status as string)"
|
|
258
|
-
style="max-width: 150px; margin: auto"
|
|
450
|
+
style="max-width: 150px; margin: auto"
|
|
451
|
+
>
|
|
259
452
|
{{ item?.overnightParking?.status }}
|
|
260
453
|
</v-card>
|
|
261
454
|
<span v-else> N/A </span>
|
|
262
455
|
</template>
|
|
263
456
|
|
|
264
457
|
<template v-slot:item.action="{ item }">
|
|
265
|
-
<v-btn
|
|
266
|
-
|
|
458
|
+
<v-btn
|
|
459
|
+
v-if="activeTab === 'unregistered' && canAddVisitor"
|
|
460
|
+
size="small"
|
|
461
|
+
color="primary"
|
|
462
|
+
text="Register"
|
|
463
|
+
@click.stop="handleRegistrationUnregisteredVisitor(item)"
|
|
464
|
+
/>
|
|
267
465
|
<v-menu v-else-if="activeTab === 'overnight-parking'">
|
|
268
466
|
<template #activator="{ props }">
|
|
269
467
|
<v-avatar v-bind="props" class="rounded-xl border-md">
|
|
@@ -272,17 +470,20 @@
|
|
|
272
470
|
</template>
|
|
273
471
|
|
|
274
472
|
<v-card>
|
|
275
|
-
<v-list-item
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
473
|
+
<v-list-item
|
|
474
|
+
v-if="
|
|
475
|
+
!overNightParkingListActions[0].disabled ||
|
|
476
|
+
!['approved', 'rejected'].includes(
|
|
477
|
+
item?.overnightParking?.status
|
|
478
|
+
)
|
|
479
|
+
"
|
|
480
|
+
@click.stop="
|
|
281
481
|
openApproveRejectOverNightParkingRequestDialog(
|
|
282
482
|
item,
|
|
283
483
|
overNightParkingListActions[0].status
|
|
284
484
|
)
|
|
285
|
-
|
|
485
|
+
"
|
|
486
|
+
>
|
|
286
487
|
<template #title>
|
|
287
488
|
<span class="text-caption">
|
|
288
489
|
{{ overNightParkingListActions[0].text }}
|
|
@@ -290,30 +491,36 @@
|
|
|
290
491
|
</template>
|
|
291
492
|
</v-list-item>
|
|
292
493
|
<v-divider />
|
|
293
|
-
<v-list-item
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
494
|
+
<v-list-item
|
|
495
|
+
v-if="
|
|
496
|
+
!overNightParkingListActions[1].disabled ||
|
|
497
|
+
!['approved', 'rejected'].includes(
|
|
498
|
+
item?.overnightParking?.status
|
|
499
|
+
)
|
|
500
|
+
"
|
|
501
|
+
@click="
|
|
299
502
|
openApproveRejectOverNightParkingRequestDialog(
|
|
300
503
|
item,
|
|
301
504
|
overNightParkingListActions[1].status
|
|
302
505
|
)
|
|
303
|
-
|
|
506
|
+
"
|
|
507
|
+
>
|
|
304
508
|
<template #title>
|
|
305
509
|
<span class="text-caption">
|
|
306
510
|
{{ overNightParkingListActions[1].text }}
|
|
307
511
|
</span>
|
|
308
512
|
</template>
|
|
309
513
|
</v-list-item>
|
|
310
|
-
<v-list-item
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
514
|
+
<v-list-item
|
|
515
|
+
v-if="item?.overnightParking?.remarks"
|
|
516
|
+
@click="
|
|
517
|
+
openApproveRejectOverNightParkingRequestDialog(
|
|
518
|
+
item,
|
|
519
|
+
overNightParkingListActions[2].status,
|
|
520
|
+
item?.overnightParking?.remarks
|
|
521
|
+
)
|
|
522
|
+
"
|
|
523
|
+
>
|
|
317
524
|
<template #title>
|
|
318
525
|
<span class="text-caption">
|
|
319
526
|
{{ overNightParkingListActions[2].text }}
|
|
@@ -327,78 +534,147 @@
|
|
|
327
534
|
|
|
328
535
|
<template v-slot:item.checkInRemarks="{ item }">
|
|
329
536
|
<span>
|
|
330
|
-
<v-btn
|
|
331
|
-
|
|
332
|
-
|
|
537
|
+
<v-btn
|
|
538
|
+
variant="text"
|
|
539
|
+
color="blue-darken-2"
|
|
540
|
+
density="compact"
|
|
541
|
+
size="small"
|
|
542
|
+
@click.stop="handleOpenRemarks(item, 'checkIn')"
|
|
543
|
+
>{{ item.checkInRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
|
|
544
|
+
>
|
|
333
545
|
</span>
|
|
334
546
|
</template>
|
|
335
547
|
<template v-slot:item.checkOutRemarks="{ item }">
|
|
336
548
|
<span>
|
|
337
|
-
<v-btn
|
|
338
|
-
|
|
339
|
-
|
|
549
|
+
<v-btn
|
|
550
|
+
variant="text"
|
|
551
|
+
color="blue-darken-2"
|
|
552
|
+
density="compact"
|
|
553
|
+
size="small"
|
|
554
|
+
@click.stop="handleOpenRemarks(item, 'checkOut')"
|
|
555
|
+
>{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
|
|
556
|
+
>
|
|
340
557
|
</span>
|
|
341
558
|
</template>
|
|
342
559
|
</TableMain>
|
|
343
560
|
|
|
344
561
|
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
345
|
-
<VisitorFormSelection
|
|
562
|
+
<VisitorFormSelection
|
|
563
|
+
:mode="mode"
|
|
564
|
+
@cancel="dialog.showSelection = false"
|
|
565
|
+
@select="handleSelectVisitorType"
|
|
566
|
+
/>
|
|
346
567
|
</v-dialog>
|
|
347
568
|
|
|
348
|
-
<v-dialog
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
569
|
+
<v-dialog
|
|
570
|
+
v-model="dialog.showForm"
|
|
571
|
+
v-if="activeVisitorFormType"
|
|
572
|
+
width="450"
|
|
573
|
+
persistent
|
|
574
|
+
>
|
|
575
|
+
<VisitorForm
|
|
576
|
+
:mode="mode"
|
|
577
|
+
:org="orgId"
|
|
578
|
+
:site="siteId"
|
|
579
|
+
:visitor-data="selectedVisitorDataObject"
|
|
580
|
+
:current-user="currentUser._id"
|
|
581
|
+
:type="activeVisitorFormType"
|
|
582
|
+
@back="handleClickBack"
|
|
583
|
+
@done="handleVisitorFormDone"
|
|
584
|
+
@done:more="handleVisitorFormCreateMore"
|
|
585
|
+
@close:all="handleCloseAll"
|
|
586
|
+
/>
|
|
352
587
|
</v-dialog>
|
|
353
588
|
|
|
354
589
|
<v-dialog v-model="dialog.viewVisitor" width="450" persistent>
|
|
355
|
-
<VehicleUpdateMoreAction
|
|
356
|
-
|
|
590
|
+
<VehicleUpdateMoreAction
|
|
591
|
+
title="Preview"
|
|
592
|
+
:can-update="false"
|
|
593
|
+
:can-delete="false"
|
|
594
|
+
@close="dialog.viewVisitor = false"
|
|
595
|
+
edit-button-label="Edit Visitor"
|
|
596
|
+
>
|
|
357
597
|
<template v-slot:content>
|
|
358
598
|
<v-row no-gutters class="mb-4">
|
|
359
599
|
<v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
|
|
360
|
-
<span
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
600
|
+
<span
|
|
601
|
+
v-if="
|
|
602
|
+
key === 'checkIn' &&
|
|
603
|
+
!selectedVisitorObject[key] &&
|
|
604
|
+
canUpdateVisitor
|
|
605
|
+
"
|
|
606
|
+
class="d-flex align-center"
|
|
607
|
+
>
|
|
365
608
|
<strong>{{ label }}:</strong>
|
|
366
|
-
<v-btn
|
|
609
|
+
<v-btn
|
|
610
|
+
size="x-small"
|
|
611
|
+
class="ml-3 text-capitalize"
|
|
612
|
+
color="success"
|
|
613
|
+
text="Checkin"
|
|
367
614
|
:disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
368
615
|
@click="handleCheckin(selectedVisitorDataObject)"
|
|
369
|
-
:loading="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
616
|
+
:loading="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
617
|
+
/>
|
|
370
618
|
</span>
|
|
371
|
-
<span
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
619
|
+
<span
|
|
620
|
+
v-if="
|
|
621
|
+
key === 'checkOut' &&
|
|
622
|
+
selectedVisitorObject['checkIn'] &&
|
|
623
|
+
!selectedVisitorObject[key] &&
|
|
624
|
+
canCheckoutVisitor
|
|
625
|
+
"
|
|
626
|
+
class="d-flex align-center"
|
|
627
|
+
>
|
|
377
628
|
<strong>{{ label }}:</strong>
|
|
378
|
-
<v-btn
|
|
379
|
-
|
|
629
|
+
<v-btn
|
|
630
|
+
size="x-small"
|
|
631
|
+
class="ml-3 text-capitalize"
|
|
632
|
+
color="red"
|
|
633
|
+
text="Checkout"
|
|
634
|
+
:disabled="loading.checkingOut"
|
|
635
|
+
@click="handleCheckout(selectedVisitorId as string)"
|
|
636
|
+
/>
|
|
380
637
|
</span>
|
|
381
638
|
|
|
382
|
-
<span
|
|
639
|
+
<span
|
|
640
|
+
v-else-if="key === 'nric' && selectedVisitorObject['nric']"
|
|
641
|
+
class="d-flex align-center"
|
|
642
|
+
>
|
|
383
643
|
<strong>{{ label }}:</strong>
|
|
384
644
|
<span v-tooltip:top="selectedVisitorObject[key]" class="ml-2">{{
|
|
385
645
|
maskNRIC(selectedVisitorObject[key])
|
|
386
|
-
|
|
646
|
+
}}</span>
|
|
387
647
|
</span>
|
|
388
|
-
<span
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
648
|
+
<span
|
|
649
|
+
v-else-if="
|
|
650
|
+
key === 'attachments' &&
|
|
651
|
+
selectedVisitorObject?.[key]?.length > 0
|
|
652
|
+
"
|
|
653
|
+
class="d-flex flex-column"
|
|
654
|
+
>
|
|
392
655
|
<strong class="w-full">{{ label }}:</strong>
|
|
393
656
|
<div class="d-flex flex-wrap ga-2">
|
|
394
|
-
<AttachmentsViewer
|
|
395
|
-
|
|
657
|
+
<AttachmentsViewer
|
|
658
|
+
:files="
|
|
659
|
+
mappedAttachments(selectedVisitorObject.attachments)
|
|
660
|
+
"
|
|
661
|
+
title=""
|
|
662
|
+
show-file-type-title
|
|
663
|
+
/>
|
|
396
664
|
</div>
|
|
397
665
|
</span>
|
|
398
666
|
|
|
399
|
-
<span
|
|
667
|
+
<span
|
|
668
|
+
v-else-if="selectedVisitorObject[key]"
|
|
669
|
+
class="d-flex ga-3 align-start"
|
|
670
|
+
><strong>{{ label }}:</strong>
|
|
400
671
|
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
401
|
-
<TooltipInfo
|
|
672
|
+
<TooltipInfo
|
|
673
|
+
v-if="key === 'checkOut'"
|
|
674
|
+
text="Manual Checkout"
|
|
675
|
+
density="compact"
|
|
676
|
+
size="x-small"
|
|
677
|
+
/>
|
|
402
678
|
</span>
|
|
403
679
|
</v-col>
|
|
404
680
|
</v-row>
|
|
@@ -410,7 +686,11 @@
|
|
|
410
686
|
<v-card>
|
|
411
687
|
<v-card-title class="d-flex justify-space-between align-center">
|
|
412
688
|
<span>Snapshot</span>
|
|
413
|
-
<v-btn
|
|
689
|
+
<v-btn
|
|
690
|
+
icon="mdi-close"
|
|
691
|
+
variant="text"
|
|
692
|
+
@click="dialog.snapshotImage = false"
|
|
693
|
+
/>
|
|
414
694
|
</v-card-title>
|
|
415
695
|
<v-card-text class="pa-2 d-flex justify-center">
|
|
416
696
|
<v-img :src="snapshotImageUrl" max-height="600" contain />
|
|
@@ -420,25 +700,51 @@
|
|
|
420
700
|
|
|
421
701
|
<v-dialog v-model="dialog.remarks" max-width="450" persistent>
|
|
422
702
|
<v-card>
|
|
423
|
-
<v-card-title
|
|
703
|
+
<v-card-title
|
|
704
|
+
class="d-flex justify-space-between align-center pt-4 px-4"
|
|
705
|
+
>
|
|
424
706
|
<span>{{
|
|
425
707
|
remarksType === "checkIn" ? "Check-In Remarks" : "Check-Out Remarks"
|
|
426
|
-
|
|
427
|
-
<v-btn
|
|
708
|
+
}}</span>
|
|
709
|
+
<v-btn
|
|
710
|
+
icon="mdi-close"
|
|
711
|
+
variant="text"
|
|
712
|
+
@click="dialog.remarks = false"
|
|
713
|
+
/>
|
|
428
714
|
</v-card-title>
|
|
429
715
|
<v-card-text class="px-4 pb-2">
|
|
430
|
-
<v-textarea
|
|
431
|
-
|
|
716
|
+
<v-textarea
|
|
717
|
+
v-model="remarksInput"
|
|
718
|
+
label="Remarks"
|
|
719
|
+
rows="4"
|
|
720
|
+
auto-grow
|
|
721
|
+
variant="outlined"
|
|
722
|
+
:readonly="remarksViewOnly"
|
|
723
|
+
/>
|
|
432
724
|
</v-card-text>
|
|
433
725
|
<v-toolbar class="pa-0" density="compact">
|
|
434
726
|
<v-row no-gutters>
|
|
435
727
|
<v-col cols="6">
|
|
436
|
-
<v-btn
|
|
437
|
-
|
|
728
|
+
<v-btn
|
|
729
|
+
variant="text"
|
|
730
|
+
block
|
|
731
|
+
:disabled="loading.savingRemarks"
|
|
732
|
+
@click="dialog.remarks = false"
|
|
733
|
+
>Cancel</v-btn
|
|
734
|
+
>
|
|
438
735
|
</v-col>
|
|
439
736
|
<v-col cols="6">
|
|
440
|
-
<v-btn
|
|
441
|
-
|
|
737
|
+
<v-btn
|
|
738
|
+
color="primary"
|
|
739
|
+
variant="flat"
|
|
740
|
+
height="48"
|
|
741
|
+
rounded="0"
|
|
742
|
+
block
|
|
743
|
+
:loading="loading.savingRemarks"
|
|
744
|
+
:disabled="!remarksInput.trim()"
|
|
745
|
+
@click="handleSaveRemarks"
|
|
746
|
+
>Save</v-btn
|
|
747
|
+
>
|
|
442
748
|
</v-col>
|
|
443
749
|
</v-row>
|
|
444
750
|
</v-toolbar>
|
|
@@ -448,9 +754,16 @@
|
|
|
448
754
|
<v-dialog v-model="dialog.returnPassesKeys" max-width="450" persistent>
|
|
449
755
|
<v-card>
|
|
450
756
|
<v-toolbar density="compact" color="">
|
|
451
|
-
<v-row
|
|
757
|
+
<v-row
|
|
758
|
+
no-gutters
|
|
759
|
+
class="d-flex fill-height justify-space-between align-center px-4"
|
|
760
|
+
>
|
|
452
761
|
<span class="font-weight-bold">Return Passes & Keys</span>
|
|
453
|
-
<v-btn
|
|
762
|
+
<v-btn
|
|
763
|
+
icon="mdi-close"
|
|
764
|
+
variant="text"
|
|
765
|
+
@click="dialog.returnPassesKeys = false"
|
|
766
|
+
/>
|
|
454
767
|
</v-row>
|
|
455
768
|
</v-toolbar>
|
|
456
769
|
<v-card-text class="px-4 pb-2">
|
|
@@ -460,55 +773,174 @@
|
|
|
460
773
|
|
|
461
774
|
<div>
|
|
462
775
|
<InputLabel class="text-capitalize" title="Full Name" />
|
|
463
|
-
<v-text-field
|
|
776
|
+
<v-text-field
|
|
777
|
+
v-model="selectedVisitorObject.name"
|
|
778
|
+
density="comfortable"
|
|
779
|
+
readonly
|
|
780
|
+
/>
|
|
464
781
|
</div>
|
|
465
782
|
|
|
466
783
|
<div>
|
|
467
784
|
<InputLabel class="text-capitalize" title="Location" />
|
|
468
|
-
<v-text-field
|
|
785
|
+
<v-text-field
|
|
786
|
+
v-model="selectedVisitorObject.location"
|
|
787
|
+
density="comfortable"
|
|
788
|
+
readonly
|
|
789
|
+
/>
|
|
469
790
|
</div>
|
|
470
791
|
|
|
471
792
|
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
472
793
|
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
473
|
-
<v-row
|
|
474
|
-
|
|
475
|
-
|
|
794
|
+
<v-row
|
|
795
|
+
no-gutters
|
|
796
|
+
v-for="pass in passReturnStatuses"
|
|
797
|
+
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
798
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
|
|
799
|
+
>
|
|
800
|
+
<v-chip
|
|
801
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
802
|
+
size="small"
|
|
803
|
+
variant="tonal"
|
|
804
|
+
color="blue"
|
|
805
|
+
>
|
|
476
806
|
{{ (pass as any)?.prefixAndName }}
|
|
477
807
|
</v-chip>
|
|
478
|
-
<v-select
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
density="compact"
|
|
808
|
+
<v-select
|
|
809
|
+
hide-details
|
|
810
|
+
max-width="200px"
|
|
811
|
+
density="compact"
|
|
812
|
+
:items="passStatusOptions"
|
|
813
|
+
item-title="label"
|
|
814
|
+
item-value="value"
|
|
815
|
+
v-model="pass.status"
|
|
816
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
817
|
+
></v-select>
|
|
818
|
+
<v-textarea
|
|
819
|
+
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
820
|
+
no-resize
|
|
821
|
+
rows="3"
|
|
822
|
+
class="w-100"
|
|
823
|
+
density="compact"
|
|
824
|
+
v-model="pass.remarks"
|
|
825
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
826
|
+
></v-textarea>
|
|
482
827
|
</v-row>
|
|
483
828
|
</div>
|
|
484
829
|
<div v-if="keyReturnStatuses.length > 0" class="mb-2">
|
|
485
830
|
<p class="text-caption text-medium-emphasis mb-1">Keys</p>
|
|
486
|
-
<v-row
|
|
487
|
-
|
|
488
|
-
|
|
831
|
+
<v-row
|
|
832
|
+
no-gutters
|
|
833
|
+
v-for="key in keyReturnStatuses"
|
|
834
|
+
:key="(key as any)._id ?? (key as any).keyId"
|
|
835
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
|
|
836
|
+
>
|
|
837
|
+
<v-chip
|
|
838
|
+
prepend-icon="mdi-key"
|
|
839
|
+
size="small"
|
|
840
|
+
variant="tonal"
|
|
841
|
+
color="orange"
|
|
842
|
+
>
|
|
489
843
|
{{ (key as any)?.prefixAndName }}
|
|
490
844
|
</v-chip>
|
|
491
|
-
<v-select
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
density="compact"
|
|
845
|
+
<v-select
|
|
846
|
+
hide-details
|
|
847
|
+
max-width="200px"
|
|
848
|
+
density="compact"
|
|
849
|
+
:items="passStatusOptions"
|
|
850
|
+
item-title="label"
|
|
851
|
+
item-value="value"
|
|
852
|
+
v-model="key.status"
|
|
853
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
854
|
+
></v-select>
|
|
855
|
+
<v-textarea
|
|
856
|
+
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
857
|
+
no-resize
|
|
858
|
+
rows="3"
|
|
859
|
+
class="w-100"
|
|
860
|
+
density="compact"
|
|
861
|
+
v-model="key.remarks"
|
|
862
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
863
|
+
></v-textarea>
|
|
495
864
|
</v-row>
|
|
496
865
|
</div>
|
|
497
866
|
|
|
498
867
|
<v-row no-gutters class="my-5">
|
|
499
|
-
<v-btn
|
|
500
|
-
|
|
501
|
-
|
|
868
|
+
<v-btn
|
|
869
|
+
variant="flat"
|
|
870
|
+
color="blue"
|
|
871
|
+
density="comfortable"
|
|
872
|
+
class="text-capitalize"
|
|
873
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
874
|
+
:loading="loading.updatingPassKeys"
|
|
875
|
+
@click.stop="handleUpdatePassKeys"
|
|
876
|
+
>Update Pass/Keys</v-btn
|
|
877
|
+
>
|
|
878
|
+
</v-row>
|
|
879
|
+
</v-card-text>
|
|
880
|
+
<v-toolbar class="pa-0" density="compact">
|
|
881
|
+
<v-row no-gutters>
|
|
882
|
+
<v-col cols="6">
|
|
883
|
+
<v-btn
|
|
884
|
+
variant="text"
|
|
885
|
+
block
|
|
886
|
+
@click="dialog.returnPassesKeys = false"
|
|
887
|
+
>Close</v-btn
|
|
888
|
+
>
|
|
889
|
+
</v-col>
|
|
890
|
+
<v-col cols="6">
|
|
891
|
+
<v-btn
|
|
892
|
+
color="red"
|
|
893
|
+
variant="flat"
|
|
894
|
+
height="48"
|
|
895
|
+
rounded="0"
|
|
896
|
+
block
|
|
897
|
+
:loading="loading.checkingOut"
|
|
898
|
+
:disabled="
|
|
899
|
+
!canConfirmCheckout || selectedVisitorObject.checkOut
|
|
900
|
+
"
|
|
901
|
+
@click="proceedCheckout"
|
|
902
|
+
>Confirm Checkout</v-btn
|
|
903
|
+
>
|
|
904
|
+
</v-col>
|
|
905
|
+
</v-row>
|
|
906
|
+
</v-toolbar>
|
|
907
|
+
</v-card>
|
|
908
|
+
</v-dialog>
|
|
909
|
+
|
|
910
|
+
<v-dialog v-model="dialog.returnNfcCard" max-width="450" persistent>
|
|
911
|
+
<v-card>
|
|
912
|
+
<v-toolbar density="compact" color="">
|
|
913
|
+
<v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
|
|
914
|
+
<span class="font-weight-bold">Return NFC Card</span>
|
|
915
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.returnNfcCard = false" />
|
|
502
916
|
</v-row>
|
|
917
|
+
</v-toolbar>
|
|
918
|
+
<v-card-text class="px-4 pb-2">
|
|
919
|
+
<p class="text-body-2 mb-3">Please indicate the status of the NFC card before checking out.</p>
|
|
920
|
+
<div>
|
|
921
|
+
<InputLabel class="text-capitalize" title="Full Name" />
|
|
922
|
+
<v-text-field v-model="selectedVisitorObject.name" density="comfortable" readonly />
|
|
923
|
+
</div>
|
|
924
|
+
<div>
|
|
925
|
+
<InputLabel class="text-capitalize" title="Location" />
|
|
926
|
+
<v-text-field v-model="selectedVisitorObject.location" density="comfortable" readonly />
|
|
927
|
+
</div>
|
|
928
|
+
<div v-for="(card, idx) in nfcCardReturnStatuses" :key="card.cardId" class="mb-4">
|
|
929
|
+
<div class="d-flex align-center ga-2 mb-2">
|
|
930
|
+
<v-icon size="18" icon="mdi-credit-card-outline" color="purple" />
|
|
931
|
+
<v-chip size="small" variant="tonal" color="purple">{{ card.cardNo }}</v-chip>
|
|
932
|
+
</div>
|
|
933
|
+
<v-select v-model="nfcCardReturnStatuses[idx].status" :items="nfcPassStatusOptions" item-title="label" item-value="value" density="comfortable" hide-details />
|
|
934
|
+
<v-textarea v-if="card.status === 'Lost' || card.status === 'Damage'" v-model="nfcCardReturnStatuses[idx].remarks" label="Remarks (required)" no-resize rows="3" class="mt-2" density="compact" />
|
|
935
|
+
</div>
|
|
503
936
|
</v-card-text>
|
|
504
937
|
<v-toolbar class="pa-0" density="compact">
|
|
505
938
|
<v-row no-gutters>
|
|
506
939
|
<v-col cols="6">
|
|
507
|
-
<v-btn variant="text" block @click="dialog.
|
|
940
|
+
<v-btn variant="text" block @click="dialog.returnNfcCard = false">Close</v-btn>
|
|
508
941
|
</v-col>
|
|
509
942
|
<v-col cols="6">
|
|
510
|
-
<v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut" :disabled="!
|
|
511
|
-
" @click="proceedCheckout">Confirm Checkout</v-btn>
|
|
943
|
+
<v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut" :disabled="!canConfirmNfcCheckout" @click="handleNfcCheckout">Confirm Checkout</v-btn>
|
|
512
944
|
</v-col>
|
|
513
945
|
</v-row>
|
|
514
946
|
</v-toolbar>
|
|
@@ -516,50 +948,87 @@
|
|
|
516
948
|
</v-dialog>
|
|
517
949
|
|
|
518
950
|
<v-dialog v-model="dialog.addPassKey" width="450" persistent>
|
|
519
|
-
<AddPassKeyToVisitor
|
|
520
|
-
|
|
951
|
+
<AddPassKeyToVisitor
|
|
952
|
+
mode="add"
|
|
953
|
+
:visitor="selectedVisitorDataObject"
|
|
954
|
+
:site="siteId"
|
|
955
|
+
@close="dialog.addPassKey = false"
|
|
956
|
+
@done="
|
|
521
957
|
() => {
|
|
522
958
|
dialog.addPassKey = false;
|
|
523
959
|
getVisitorRefresh();
|
|
524
960
|
}
|
|
525
|
-
"
|
|
961
|
+
"
|
|
962
|
+
/>
|
|
526
963
|
</v-dialog>
|
|
527
964
|
|
|
528
965
|
<v-dialog v-model="dialog.editPassKey" width="450" persistent>
|
|
529
|
-
<AddPassKeyToVisitor
|
|
530
|
-
|
|
966
|
+
<AddPassKeyToVisitor
|
|
967
|
+
mode="edit"
|
|
968
|
+
:visitor="selectedVisitorDataObject"
|
|
969
|
+
:site="siteId"
|
|
970
|
+
@close="dialog.editPassKey = false"
|
|
971
|
+
@done="
|
|
531
972
|
() => {
|
|
532
973
|
dialog.editPassKey = false;
|
|
533
974
|
getVisitorRefresh();
|
|
534
975
|
}
|
|
535
|
-
"
|
|
976
|
+
"
|
|
977
|
+
/>
|
|
536
978
|
</v-dialog>
|
|
537
979
|
|
|
538
|
-
<ScanVisitorQRCode
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
980
|
+
<ScanVisitorQRCode
|
|
981
|
+
:dialog="dialog.scanVisitorQRCode"
|
|
982
|
+
:site="siteId"
|
|
983
|
+
@open-visitor-data-from-scanned-qr-code-dialog="
|
|
984
|
+
handleShowVisitorDataFromScannedQRCodeDialog
|
|
985
|
+
"
|
|
986
|
+
@close-dialog="dialog.scanVisitorQRCode = false"
|
|
987
|
+
/>
|
|
988
|
+
|
|
989
|
+
<v-dialog
|
|
990
|
+
v-model="dialog.showVisitorDataFromScannedQRCode"
|
|
991
|
+
width="450"
|
|
992
|
+
persistent
|
|
993
|
+
>
|
|
994
|
+
<VisitorDataFromScannedQRCodeForm
|
|
995
|
+
:site="siteId"
|
|
996
|
+
:visitor-data="visitorDataFromScannedQRCode"
|
|
544
997
|
@check-in-out="handleVisitorDataFromScannedQRCodeCheckInOut"
|
|
545
998
|
:processing="isVisitorDataFromScannedQRCodeCheckingInOut"
|
|
546
|
-
@close="handleCloseVisitorDataFromScannedQRCodeDialog"
|
|
999
|
+
@close="handleCloseVisitorDataFromScannedQRCodeDialog"
|
|
1000
|
+
/>
|
|
547
1001
|
</v-dialog>
|
|
548
1002
|
|
|
549
|
-
<v-dialog
|
|
550
|
-
|
|
1003
|
+
<v-dialog
|
|
1004
|
+
v-model="dialog.approveRejectOvernightParkingRequestDialog"
|
|
1005
|
+
transition="dialog-bottom-transition"
|
|
1006
|
+
persistent
|
|
1007
|
+
width="60vh"
|
|
1008
|
+
>
|
|
551
1009
|
<v-card>
|
|
552
1010
|
<v-toolbar density="compact">
|
|
553
|
-
<v-row
|
|
1011
|
+
<v-row
|
|
1012
|
+
no-gutters
|
|
1013
|
+
class="d-flex fill-height justify-space-between align-center px-4"
|
|
1014
|
+
>
|
|
554
1015
|
<span class="font-weight-bold"> Overnight Parking Request </span>
|
|
555
|
-
<v-btn
|
|
1016
|
+
<v-btn
|
|
1017
|
+
icon="mdi-close"
|
|
1018
|
+
variant="text"
|
|
1019
|
+
@click="dialog.approveRejectOvernightParkingRequestDialog = false"
|
|
1020
|
+
/>
|
|
556
1021
|
</v-row>
|
|
557
1022
|
</v-toolbar>
|
|
558
1023
|
<v-card-text class="px-4 pb-6">
|
|
559
1024
|
<v-row no-gutters justify="center" align-content="center">
|
|
560
|
-
<v-col
|
|
561
|
-
|
|
562
|
-
|
|
1025
|
+
<v-col
|
|
1026
|
+
v-if="
|
|
1027
|
+
approveRejectOvernightParkingRequestDialog.status !== 'remarks'
|
|
1028
|
+
"
|
|
1029
|
+
cols="12"
|
|
1030
|
+
class="text-h6 text-center"
|
|
1031
|
+
>
|
|
563
1032
|
{{
|
|
564
1033
|
`Are you sure you want to ${approveRejectOvernightParkingRequestDialog.text} the visitor's overnight
|
|
565
1034
|
parking
|
|
@@ -568,28 +1037,58 @@
|
|
|
568
1037
|
</v-col>
|
|
569
1038
|
|
|
570
1039
|
<v-col cols="12" md="8" class="mt-4">
|
|
571
|
-
<v-textarea
|
|
572
|
-
|
|
1040
|
+
<v-textarea
|
|
1041
|
+
v-model="overNightParkingRequestApproveRejectRemarks"
|
|
1042
|
+
label="Remarks"
|
|
1043
|
+
placeholder="Enter remarks"
|
|
1044
|
+
outlined
|
|
1045
|
+
rows="3"
|
|
1046
|
+
clearable
|
|
1047
|
+
:hide-details="
|
|
1048
|
+
approveRejectOvernightParkingRequestDialog.status ===
|
|
573
1049
|
'remarks'
|
|
574
|
-
|
|
1050
|
+
"
|
|
1051
|
+
:readonly="
|
|
1052
|
+
approveRejectOvernightParkingRequestDialog.status ===
|
|
575
1053
|
'remarks'
|
|
576
|
-
|
|
1054
|
+
"
|
|
1055
|
+
/>
|
|
577
1056
|
</v-col>
|
|
578
1057
|
</v-row>
|
|
579
1058
|
</v-card-text>
|
|
580
|
-
<v-toolbar
|
|
581
|
-
|
|
1059
|
+
<v-toolbar
|
|
1060
|
+
v-if="approveRejectOvernightParkingRequestDialog.status !== 'remarks'"
|
|
1061
|
+
class="pa-0"
|
|
1062
|
+
density="compact"
|
|
1063
|
+
>
|
|
582
1064
|
<v-row no-gutters>
|
|
583
1065
|
<v-col cols="6">
|
|
584
|
-
<v-btn
|
|
1066
|
+
<v-btn
|
|
1067
|
+
text="No"
|
|
1068
|
+
color="grey-lighten-3"
|
|
1069
|
+
variant="flat"
|
|
1070
|
+
height="48"
|
|
1071
|
+
tile
|
|
1072
|
+
block
|
|
585
1073
|
:disabled="isApprovingRejectingOvernightParkingRequest"
|
|
586
|
-
@click="closeApproveRejectOvernightParkingDialog"
|
|
1074
|
+
@click="closeApproveRejectOvernightParkingDialog"
|
|
1075
|
+
/>
|
|
587
1076
|
</v-col>
|
|
588
1077
|
<v-col cols="6">
|
|
589
|
-
<v-btn
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1078
|
+
<v-btn
|
|
1079
|
+
text="Yes"
|
|
1080
|
+
color="success"
|
|
1081
|
+
variant="flat"
|
|
1082
|
+
height="48"
|
|
1083
|
+
tile
|
|
1084
|
+
block
|
|
1085
|
+
:disabled="
|
|
1086
|
+
!overNightParkingRequestApproveRejectRemarks ||
|
|
1087
|
+
isApprovingRejectingOvernightParkingRequest
|
|
1088
|
+
"
|
|
1089
|
+
@click="updateOvernightParkingRequestStatus"
|
|
1090
|
+
:loading="isApprovingRejectingOvernightParkingRequest"
|
|
1091
|
+
/>
|
|
593
1092
|
</v-col>
|
|
594
1093
|
</v-row>
|
|
595
1094
|
</v-toolbar>
|
|
@@ -645,8 +1144,6 @@ const { org: orgId, site: siteId } = route.params as {
|
|
|
645
1144
|
const { enableUnregistered } = useANPRSettings(siteId);
|
|
646
1145
|
const enableUnregisteredTab = computed(() => enableUnregistered.value);
|
|
647
1146
|
|
|
648
|
-
|
|
649
|
-
|
|
650
1147
|
const {
|
|
651
1148
|
getVisitors,
|
|
652
1149
|
visitorSelection,
|
|
@@ -654,6 +1151,7 @@ const {
|
|
|
654
1151
|
updateVisitor,
|
|
655
1152
|
visitorDataFromScannedQRCodeCheckIn,
|
|
656
1153
|
} = useVisitor();
|
|
1154
|
+
const { visitorCheckout } = useAccessManagement();
|
|
657
1155
|
const {
|
|
658
1156
|
debounce,
|
|
659
1157
|
formatCamelCaseToWords,
|
|
@@ -714,6 +1212,7 @@ const dialog = reactive({
|
|
|
714
1212
|
scanVisitorQRCode: false,
|
|
715
1213
|
showVisitorDataFromScannedQRCode: false,
|
|
716
1214
|
approveRejectOvernightParkingRequestDialog: false,
|
|
1215
|
+
returnNfcCard: false,
|
|
717
1216
|
});
|
|
718
1217
|
|
|
719
1218
|
const snapshotImageUrl = ref("");
|
|
@@ -728,7 +1227,7 @@ const headers = computed(() => {
|
|
|
728
1227
|
if (tab === "overnight-parking") {
|
|
729
1228
|
return [
|
|
730
1229
|
{ title: "Name", value: "name" },
|
|
731
|
-
{ title: "Email", value: "email" },
|
|
1230
|
+
{ title: "Email/Vehicle No.", value: "email-vehicleNumber" },
|
|
732
1231
|
{ title: "Invited By", value: "invited-by" },
|
|
733
1232
|
{ title: "Arrival Date/Time", value: "arrival-date-time" },
|
|
734
1233
|
{ title: "Check In/Out", value: "checkin-out" },
|
|
@@ -774,14 +1273,12 @@ const tabOptions = computed(() => {
|
|
|
774
1273
|
{ name: "Registered", value: "registered" },
|
|
775
1274
|
...(enableUnregisteredTab.value
|
|
776
1275
|
? [{ name: "Unregistered", value: "unregistered" }]
|
|
777
|
-
: []
|
|
778
|
-
),
|
|
1276
|
+
: []),
|
|
779
1277
|
{ name: "Guests", value: "guests" },
|
|
780
1278
|
{ name: "Resident Transactions", value: "resident-transactions" },
|
|
781
1279
|
{ name: "Overnight Parking", value: "overnight-parking" },
|
|
782
1280
|
];
|
|
783
1281
|
|
|
784
|
-
|
|
785
1282
|
return baseTabs;
|
|
786
1283
|
});
|
|
787
1284
|
|
|
@@ -929,6 +1426,13 @@ const passStatusOptions = [
|
|
|
929
1426
|
{ label: "Lost", value: "Lost" },
|
|
930
1427
|
];
|
|
931
1428
|
|
|
1429
|
+
const nfcPassStatusOptions = [
|
|
1430
|
+
{ label: "Returned", value: "Returned" },
|
|
1431
|
+
{ label: "Not Returned", value: "In Use" },
|
|
1432
|
+
{ label: "Damaged", value: "Damage" },
|
|
1433
|
+
{ label: "Lost", value: "Lost" },
|
|
1434
|
+
];
|
|
1435
|
+
|
|
932
1436
|
function toRoute(tab: any) {
|
|
933
1437
|
items.value = [];
|
|
934
1438
|
|
|
@@ -1313,6 +1817,9 @@ const passReturnStatuses = ref<
|
|
|
1313
1817
|
const keyReturnStatuses = ref<
|
|
1314
1818
|
{ keyId: string; status: string; prefixAndName: string; remarks: string }[]
|
|
1315
1819
|
>([]);
|
|
1820
|
+
const nfcCardReturnStatuses = ref<
|
|
1821
|
+
{ cardId: string; cardNo: string; status: string; remarks: string }[]
|
|
1822
|
+
>([]);
|
|
1316
1823
|
|
|
1317
1824
|
const canConfirmCheckout = computed(() => {
|
|
1318
1825
|
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
@@ -1327,6 +1834,15 @@ const canConfirmCheckout = computed(() => {
|
|
|
1327
1834
|
});
|
|
1328
1835
|
});
|
|
1329
1836
|
|
|
1837
|
+
const canConfirmNfcCheckout = computed(() => {
|
|
1838
|
+
if (nfcCardReturnStatuses.value.length === 0) return false;
|
|
1839
|
+
return nfcCardReturnStatuses.value.every(({ status, remarks }) => {
|
|
1840
|
+
if (!status || status === "In Use") return false;
|
|
1841
|
+
if ((status === "Lost" || status === "Damage") && !remarks.trim()) return false;
|
|
1842
|
+
return true;
|
|
1843
|
+
});
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1330
1846
|
function handleCheckin(user: Record<string, any>) {
|
|
1331
1847
|
visitorDataFromScannedQRCode.value = user;
|
|
1332
1848
|
dialog.showVisitorDataFromScannedQRCode = true;
|
|
@@ -1365,6 +1881,22 @@ function handleCheckout(userId: string) {
|
|
|
1365
1881
|
return;
|
|
1366
1882
|
}
|
|
1367
1883
|
|
|
1884
|
+
const hasNfc = visitor?.accessCards?.type === "NFC" && !!visitor?.accessCards?.cardNo;
|
|
1885
|
+
if (hasNfc) {
|
|
1886
|
+
const allCards: any[] = visitor.cards?.flat() ?? [];
|
|
1887
|
+
nfcCardReturnStatuses.value = allCards.map((card: any) => ({
|
|
1888
|
+
cardId: card._id,
|
|
1889
|
+
cardNo: card._id === visitor.accessCards._id ? visitor.accessCards.cardNo : card._id,
|
|
1890
|
+
status: "In Use",
|
|
1891
|
+
remarks: "",
|
|
1892
|
+
}));
|
|
1893
|
+
if (nfcCardReturnStatuses.value.length === 0) {
|
|
1894
|
+
nfcCardReturnStatuses.value = [{ cardId: visitor.accessCards._id, cardNo: visitor.accessCards.cardNo, status: "In Use", remarks: "" }];
|
|
1895
|
+
}
|
|
1896
|
+
dialog.returnNfcCard = true;
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1368
1900
|
proceedCheckout();
|
|
1369
1901
|
}
|
|
1370
1902
|
|
|
@@ -1413,6 +1945,34 @@ async function proceedCheckout() {
|
|
|
1413
1945
|
}
|
|
1414
1946
|
}
|
|
1415
1947
|
|
|
1948
|
+
async function handleNfcCheckout() {
|
|
1949
|
+
const userId = selectedVisitorId.value;
|
|
1950
|
+
if (!userId) return;
|
|
1951
|
+
try {
|
|
1952
|
+
loading.checkingOut = true;
|
|
1953
|
+
const res = await visitorCheckout({
|
|
1954
|
+
cards: nfcCardReturnStatuses.value.map(({ cardId, status, remarks }) => ({
|
|
1955
|
+
cardId,
|
|
1956
|
+
userId,
|
|
1957
|
+
status,
|
|
1958
|
+
remarks: remarks ?? "",
|
|
1959
|
+
})),
|
|
1960
|
+
});
|
|
1961
|
+
if (res) {
|
|
1962
|
+
await updateVisitor(userId as string, { checkOut: new Date().toISOString() });
|
|
1963
|
+
showMessage("Visitor successfully checked-out!", "info");
|
|
1964
|
+
await getVisitorRefresh();
|
|
1965
|
+
dialog.returnNfcCard = false;
|
|
1966
|
+
dialog.viewVisitor = false;
|
|
1967
|
+
}
|
|
1968
|
+
} catch (error: any) {
|
|
1969
|
+
const errorMessage = error?.response?._data?.message;
|
|
1970
|
+
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
1971
|
+
} finally {
|
|
1972
|
+
loading.checkingOut = false;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1416
1976
|
function buildReportParams(): any {
|
|
1417
1977
|
const params: any = {
|
|
1418
1978
|
site: siteId,
|
|
@@ -1466,10 +2026,12 @@ async function handleDownloadPDFReport() {
|
|
|
1466
2026
|
if (displayNotCheckedOut.value) queryParams.append("checkedOut", "true");
|
|
1467
2027
|
|
|
1468
2028
|
const queryString = queryParams.toString();
|
|
1469
|
-
const url = `/${route.params.org}/${siteId}/visitor-management/preview${
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
2029
|
+
const url = `/${route.params.org}/${siteId}/visitor-management/preview${
|
|
2030
|
+
queryString ? "?" + queryString : ""
|
|
2031
|
+
}`;
|
|
2032
|
+
const title = `Visitor-Report-${siteId}-${
|
|
2033
|
+
new Date().toISOString().split("T")[0]
|
|
2034
|
+
}`;
|
|
1473
2035
|
|
|
1474
2036
|
await downloadPDF({ url, title, orientation: "P", appKey: "SECURITY" });
|
|
1475
2037
|
showMessage("PDF report downloaded successfully!", "success");
|
|
@@ -1633,8 +2195,8 @@ onMounted(() => {
|
|
|
1633
2195
|
activeTab.value = enableUnregisteredTab.value
|
|
1634
2196
|
? requestedTab
|
|
1635
2197
|
: requestedTab === "unregistered"
|
|
1636
|
-
|
|
1637
|
-
|
|
2198
|
+
? "registered"
|
|
2199
|
+
: requestedTab;
|
|
1638
2200
|
searchInput.value = (route.query.search as string) || "";
|
|
1639
2201
|
if (route.query.dateFrom) {
|
|
1640
2202
|
dateFrom.value = normalizeDateOnly(route.query.dateFrom as string);
|
|
@@ -1666,13 +2228,23 @@ watch(
|
|
|
1666
2228
|
);
|
|
1667
2229
|
|
|
1668
2230
|
watchEffect(async () => {
|
|
1669
|
-
if (
|
|
2231
|
+
if (
|
|
2232
|
+
visitorSocketData.value &&
|
|
2233
|
+
visitorSocketData.value.reload &&
|
|
2234
|
+
activeTab.value === "unregistered"
|
|
2235
|
+
) {
|
|
1670
2236
|
await getVisitorRefresh();
|
|
1671
2237
|
visitorSocketData.value = null;
|
|
1672
2238
|
socketUnregisteredVisitorTrigger.value = false;
|
|
1673
2239
|
}
|
|
1674
2240
|
|
|
1675
|
-
if (
|
|
2241
|
+
if (
|
|
2242
|
+
visitorSocketData.value &&
|
|
2243
|
+
visitorSocketData.value.plateNumber &&
|
|
2244
|
+
socketUnregisteredVisitorTrigger.value &&
|
|
2245
|
+
activeTab.value === "unregistered" &&
|
|
2246
|
+
dialog.showSelection === false
|
|
2247
|
+
) {
|
|
1676
2248
|
// await getVisitorRefresh();
|
|
1677
2249
|
// visitorSocketData.value = null;
|
|
1678
2250
|
// socketUnregisteredVisitorTrigger.value = false;
|
|
@@ -1683,11 +2255,10 @@ watchEffect(async () => {
|
|
|
1683
2255
|
|
|
1684
2256
|
setTimeout(() => {
|
|
1685
2257
|
visitorSocketData.value = null;
|
|
1686
|
-
}, 1000)
|
|
1687
|
-
}, 1000)
|
|
2258
|
+
}, 1000);
|
|
2259
|
+
}, 1000);
|
|
1688
2260
|
}
|
|
1689
|
-
})
|
|
1690
|
-
|
|
2261
|
+
});
|
|
1691
2262
|
</script>
|
|
1692
2263
|
|
|
1693
2264
|
<style scoped></style>
|