@7365admin1/layer-common 3.0.26 → 3.0.28

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.
@@ -1,51 +1,137 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
- <TableMain :headers="headers" :items="items" :loading="getVisitorPending" :page="page" :pages="pages"
4
- :extension-height="110" :offset="300" :pageRange="pageRange" @refresh="getVisitorRefresh"
5
- @update:page="handleUpdatePage" show-header @row-click="handleRowClick">
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 v-if="canAddVisitor" class="text-none" rounded="pill" variant="tonal" size="large"
9
- @click="handleAddNew" text="Add Visitor" />
10
- <v-btn v-if="canScanVisitorQRCode" text="Scan QR Code" class="text-none ml-2" rounded="pill" variant="tonal"
11
- size="large" @click="dialog.scanVisitorQRCode = true" />
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 v-bind="props" text="Download Report" class="text-none ml-2" prepend-icon="mdi-download"
15
- rounded="pill" variant="tonal" size="large" />
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 prepend-icon="mdi-file-pdf" title="Download PDF Report" @click="handleDownloadPDFReport" />
19
- <v-list-item prepend-icon="mdi-file-csv" title="Download CSV Report" @click="handleDownloadCSVReport" />
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 v-model="activeTab" color="primary" :height="40" @update:model-value="toRoute" class="w-100">
27
- <v-tab v-for="tab in tabOptions" :value="tab.value" :key="tab.value" class="text-capitalize">
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 class="w-100 px-3 d-flex align-center ga-5 py-2" flat :height="60">
33
- <v-text-field v-model="searchInput" density="compact" placeholder="Search" clearable max-width="300"
34
- append-inner-icon="mdi-magnify" hide-details />
35
- <v-checkbox v-model="displayNotCheckedOut" class="text-subtitle-2" hide-details>
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 v-model="dateFrom" density="compact" hide-details />
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 v-if="activeTab == 'registered'" v-model="filterTypes" label="Filter by types" item-title="label"
43
- item-value="value" :items="visitorSelection" density="compact" clearable multiple max-width="200"
44
- hide-details>
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 v-if="index === 0" color="error" :text="filterTypeSelectionLabel()" size="x-small"
48
- variant="tonal" class="ml-2" />
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">( +{{ item?.members?.length }}
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="{ value }">
68
- {{ value ?? "N/A" }}
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 v-if="item?.inviterName" :name="item?.inviterName" :size="20" :id="item?._id" />
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
- }}</span>
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 icon="mdi-clock-time-four-outline" color="green" size="20" />
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
- }}</span>
225
+ }}</span>
123
226
  <span>
124
- <v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
125
- @click.stop="handleViewImage(item.snapshotEntryImage)" />
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 v-if="
130
- !item.checkOut &&
131
- (item?.status === 'registered' ||
132
- item?.status === 'unregistered') &&
133
- canCheckoutVisitor
134
- ">
135
- <span class="d-flex align-center ga-2" v-bind="menuProps" style="cursor: pointer">
136
- <v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
137
- <v-btn size="x-small" class="text-capitalize" color="red" text="Checkout" :loading="loading.checkingOut && item?._id === selectedVisitorId
138
- " @click.stop="handleCheckout(item._id)" />
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
- }}</span>
271
+ }}</span>
147
272
  <span>
148
- <v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
149
- @click.stop="handleViewImage(item.snapshotExitImage)" />
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 text="Manual Checkout" density="compact" size="x-small" />
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 v-if="showAddPassKeyButton(item)" class="d-flex flex-wrap ga-1 mt-1" v-bind="menuProps"
159
- style="cursor: pointer">
160
- <v-chip size="x-small" variant="tonal" prepend-icon="mdi-key" @click.stop="handleOpenAddPassKey(item)">Add
161
- Pass/Key</v-chip>
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 v-if="
167
- (item.visitorPass?.length ?? 0) > 0 ||
168
- (item.passKeys?.length ?? 0) > 0
169
- " class="d-flex flex-wrap ga-1 mt-1" v-bind="menuProps" style="cursor: pointer">
170
- <v-chip v-for="pass in item.visitorPass" :key="(pass as any)._id ?? (pass as any).keyId"
171
- prepend-icon="mdi-card-bulleted-outline" size="x-small" variant="tonal" color="blue">
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 v-for="key in item.passKeys" :key="(key as any)._id ?? (key as any).keyId"
175
- prepend-icon="mdi-key" size="x-small" variant="tonal" color="orange">
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 v-if="!item?.checkOut" prepend-icon="mdi-logout" title="Checkout"
182
- @click.stop="handleCheckout(item._id)" />
183
- <v-list-item v-if="
184
- (item.visitorPass?.length ?? 0) > 0 ||
185
- (item.passKeys?.length ?? 0) > 0
186
- " prepend-icon="mdi-card-bulleted-outline" :title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
187
- @click.stop="handleOpenEditPassKey(item)" />
188
- <v-list-item v-if="showAddPassKeyButton(item)" prepend-icon="mdi-plus" title="Add Pass/Key"
189
- @click.stop="handleOpenAddPassKey(item)" />
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 size="15" icon="mdi-credit-card-outline" color="purple" />
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 icon="mdi-clock-time-eight-outline" color="success" size="20" />
247
- <v-btn size="x-small" class="text-capitalize" color="success" text="Checkin"
248
- @click.stop="handleCheckin(item)" />
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 v-if="item?.overnightParking?.status" size="small"
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 v-if="activeTab === 'unregistered' && canAddVisitor" size="small" color="primary" text="Register"
266
- @click.stop="handleRegistrationUnregisteredVisitor(item)" />
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 v-if="
276
- !overNightParkingListActions[0].disabled ||
277
- !['approved', 'rejected'].includes(
278
- item?.overnightParking?.status
279
- )
280
- " @click.stop="
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 v-if="
294
- !overNightParkingListActions[1].disabled ||
295
- !['approved', 'rejected'].includes(
296
- item?.overnightParking?.status
297
- )
298
- " @click="
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 v-if="item?.overnightParking?.remarks" @click="
311
- openApproveRejectOverNightParkingRequestDialog(
312
- item,
313
- overNightParkingListActions[2].status,
314
- item?.overnightParking?.remarks
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 variant="text" color="blue-darken-2" density="compact" size="small"
331
- @click.stop="handleOpenRemarks(item, 'checkIn')">{{ item.checkInRemarks ? "View Remarks" : "Add Remarks"
332
- }}</v-btn>
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 variant="text" color="blue-darken-2" density="compact" size="small"
338
- @click.stop="handleOpenRemarks(item, 'checkOut')">{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks"
339
- }}</v-btn>
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 :mode="mode" @cancel="dialog.showSelection = false" @select="handleSelectVisitorType" />
562
+ <VisitorFormSelection
563
+ :mode="mode"
564
+ @cancel="dialog.showSelection = false"
565
+ @select="handleSelectVisitorType"
566
+ />
346
567
  </v-dialog>
347
568
 
348
- <v-dialog v-model="dialog.showForm" v-if="activeVisitorFormType" width="450" persistent>
349
- <VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject"
350
- :current-user="currentUser._id" :type="activeVisitorFormType" @back="handleClickBack"
351
- @done="handleVisitorFormDone" @done:more="handleVisitorFormCreateMore" @close:all="handleCloseAll" />
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 title="Preview" :can-update="false" :can-delete="false"
356
- @close="dialog.viewVisitor = false" edit-button-label="Edit Visitor">
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 v-if="
361
- key === 'checkIn' &&
362
- !selectedVisitorObject[key] &&
363
- canUpdateVisitor
364
- " class="d-flex align-center">
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 size="x-small" class="ml-3 text-capitalize" color="success" text="Checkin"
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 v-if="
372
- key === 'checkOut' &&
373
- selectedVisitorObject['checkIn'] &&
374
- !selectedVisitorObject[key] &&
375
- canCheckoutVisitor
376
- " class="d-flex align-center">
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 size="x-small" class="ml-3 text-capitalize" color="red" text="Checkout"
379
- :disabled="loading.checkingOut" @click="handleCheckout(selectedVisitorId as string)" />
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 v-else-if="key === 'nric' && selectedVisitorObject['nric']" class="d-flex align-center">
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
- }}</span>
646
+ }}</span>
387
647
  </span>
388
- <span v-else-if="
389
- key === 'attachments' &&
390
- selectedVisitorObject?.[key]?.length > 0
391
- " class="d-flex flex-column">
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 :files="mappedAttachments(selectedVisitorObject.attachments)
395
- " title="" show-file-type-title />
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 v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-start"><strong>{{ label }}:</strong>
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 v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
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 icon="mdi-close" variant="text" @click="dialog.snapshotImage = false" />
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 class="d-flex justify-space-between align-center pt-4 px-4">
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
- }}</span>
427
- <v-btn icon="mdi-close" variant="text" @click="dialog.remarks = false" />
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 v-model="remarksInput" label="Remarks" rows="4" auto-grow variant="outlined"
431
- :readonly="remarksViewOnly" />
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 variant="text" block :disabled="loading.savingRemarks"
437
- @click="dialog.remarks = false">Cancel</v-btn>
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 color="primary" variant="flat" height="48" rounded="0" block :loading="loading.savingRemarks"
441
- :disabled="!remarksInput.trim()" @click="handleSaveRemarks">Save</v-btn>
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 no-gutters class="d-flex fill-height justify-space-between align-center px-4">
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 &amp; Keys</span>
453
- <v-btn icon="mdi-close" variant="text" @click="dialog.returnPassesKeys = false" />
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 v-model="selectedVisitorObject.name" density="comfortable" readonly />
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 v-model="selectedVisitorObject.location" density="comfortable" readonly />
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 no-gutters v-for="pass in passReturnStatuses" :key="(pass as any)._id ?? (pass as any).keyId"
474
- class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
475
- <v-chip prepend-icon="mdi-card-bulleted-outline" size="small" variant="tonal" color="blue">
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 hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
479
- item-value="value" v-model="pass.status" :disabled="selectedVisitorObject.checkOut"></v-select>
480
- <v-textarea v-if="pass.status === 'Lost' || pass.status === 'Damaged'" no-resize rows="3" class="w-100"
481
- density="compact" v-model="pass.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
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 no-gutters v-for="key in keyReturnStatuses" :key="(key as any)._id ?? (key as any).keyId"
487
- class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
488
- <v-chip prepend-icon="mdi-key" size="small" variant="tonal" color="orange">
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 hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
492
- item-value="value" v-model="key.status" :disabled="selectedVisitorObject.checkOut"></v-select>
493
- <v-textarea v-if="key.status === 'Lost' || key.status === 'Damaged'" no-resize rows="3" class="w-100"
494
- density="compact" v-model="key.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
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 variant="flat" color="blue" density="comfortable" class="text-capitalize"
500
- :disabled="selectedVisitorObject.checkOut" :loading="loading.updatingPassKeys"
501
- @click.stop="handleUpdatePassKeys">Update Pass/Keys</v-btn>
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.returnPassesKeys = false">Close</v-btn>
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="!canConfirmCheckout || selectedVisitorObject.checkOut
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 mode="add" :visitor="selectedVisitorDataObject" :site="siteId"
520
- @close="dialog.addPassKey = false" @done="
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 mode="edit" :visitor="selectedVisitorDataObject" :site="siteId"
530
- @close="dialog.editPassKey = false" @done="
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 :dialog="dialog.scanVisitorQRCode" :site="siteId" @open-visitor-data-from-scanned-qr-code-dialog="
539
- handleShowVisitorDataFromScannedQRCodeDialog
540
- " @close-dialog="dialog.scanVisitorQRCode = false" />
541
-
542
- <v-dialog v-model="dialog.showVisitorDataFromScannedQRCode" width="450" persistent>
543
- <VisitorDataFromScannedQRCodeForm :site="siteId" :visitor-data="visitorDataFromScannedQRCode"
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 v-model="dialog.approveRejectOvernightParkingRequestDialog" transition="dialog-bottom-transition"
550
- persistent width="60vh">
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 no-gutters class="d-flex fill-height justify-space-between align-center px-4">
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 icon="mdi-close" variant="text" @click="dialog.approveRejectOvernightParkingRequestDialog = false" />
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 v-if="
561
- approveRejectOvernightParkingRequestDialog.status !== 'remarks'
562
- " cols="12" class="text-h6 text-center">
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 v-model="overNightParkingRequestApproveRejectRemarks" label="Remarks"
572
- placeholder="Enter remarks" outlined rows="3" clearable :hide-details="approveRejectOvernightParkingRequestDialog.status ===
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
- " :readonly="approveRejectOvernightParkingRequestDialog.status ===
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 v-if="approveRejectOvernightParkingRequestDialog.status !== 'remarks'" class="pa-0"
581
- density="compact">
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 text="No" color="grey-lighten-3" variant="flat" height="48" tile block
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 text="Yes" color="success" variant="flat" height="48" tile block :disabled="!overNightParkingRequestApproveRejectRemarks ||
590
- isApprovingRejectingOvernightParkingRequest
591
- " @click="updateOvernightParkingRequestStatus"
592
- :loading="isApprovingRejectingOvernightParkingRequest" />
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${queryString ? "?" + queryString : ""
1470
- }`;
1471
- const title = `Visitor-Report-${siteId}-${new Date().toISOString().split("T")[0]
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
- ? "registered"
1637
- : requestedTab;
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 (visitorSocketData.value && visitorSocketData.value.reload && activeTab.value === "unregistered") {
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 (visitorSocketData.value && visitorSocketData.value.plateNumber && socketUnregisteredVisitorTrigger.value && activeTab.value === "unregistered" && dialog.showSelection === false) {
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>