@7365admin1/layer-common 1.11.28 → 1.11.29

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,95 +1,51 @@
1
1
  <template>
2
2
  <v-row no-gutters>
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
- :canCreate="canAddVisitor"
13
- :canScanVisitorQRCode="canScanVisitorQRCode"
14
- @refresh="getVisitorRefresh"
15
- @update:page="handleUpdatePage"
16
- createLabel="Add Visitor"
17
- show-header
18
- @row-click="handleRowClick"
19
- @create="handleAddNew"
20
- @scan="dialog.scanVisitorQRCode = true"
21
- >
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">
6
+ <template #actions>
7
+ <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" />
12
+ <v-menu>
13
+ <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" />
16
+ </template>
17
+ <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" />
20
+ </v-list>
21
+ </v-menu>
22
+ </v-row>
23
+ </template>
22
24
  <template #extension>
23
25
  <v-row no-gutters class="w-100 d-flex flex-column">
24
- <v-tabs
25
- v-model="activeTab"
26
- color="primary"
27
- :height="40"
28
- @update:model-value="toRoute"
29
- class="w-100"
30
- >
31
- <v-tab
32
- v-for="tab in tabOptions"
33
- :value="tab.value"
34
- :key="tab.value"
35
- class="text-capitalize"
36
- >
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">
37
28
  {{ tab.name }}
38
29
  </v-tab>
39
30
  </v-tabs>
40
31
 
41
- <v-card
42
- class="w-100 px-3 d-flex align-center ga-5 py-2"
43
- flat
44
- :height="60"
45
- >
46
- <v-text-field
47
- v-model="searchInput"
48
- density="compact"
49
- placeholder="Search"
50
- clearable
51
- max-width="300"
52
- append-inner-icon="mdi-magnify"
53
- hide-details
54
- />
55
- <v-checkbox
56
- v-model="displayNotCheckedOut"
57
- class="text-subtitle-2"
58
- hide-details
59
- >
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>
60
36
  <template #label>
61
37
  <span class="text-caption">Not Checked Out</span>
62
38
  </template>
63
39
  </v-checkbox>
64
- <InputDatePicker
65
- v-model="dateFrom"
66
- density="compact"
67
- hide-details
68
- />
40
+ <InputDatePicker v-model="dateFrom" density="compact" hide-details />
69
41
  <InputDatePicker v-model="dateTo" density="compact" hide-details />
70
- <v-select
71
- v-if="activeTab == 'registered'"
72
- v-model="filterTypes"
73
- label="Filter by types"
74
- item-title="label"
75
- item-value="value"
76
- :items="visitorSelection"
77
- density="compact"
78
- clearable
79
- multiple
80
- max-width="200"
81
- hide-details
82
- >
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>
83
45
  <template v-slot:selection="{ item, index }">
84
46
  <div class="d-flex align-center text-caption text-nowrap">
85
- <v-chip
86
- v-if="index === 0"
87
- color="error"
88
- :text="filterTypeSelectionLabel()"
89
- size="x-small"
90
- variant="tonal"
91
- class="ml-2"
92
- />
47
+ <v-chip v-if="index === 0" color="error" :text="filterTypeSelectionLabel()" size="x-small"
48
+ variant="tonal" class="ml-2" />
93
49
  </div>
94
50
  </template>
95
51
  </v-select>
@@ -104,9 +60,8 @@
104
60
  </span>
105
61
  <span class="text-capitalize">{{ item?.name }}</span>
106
62
  </span>
107
- <span class="text-grey text-caption" v-if="item?.members?.length > 0"
108
- >( +{{ item?.members?.length }} members)</span
109
- >
63
+ <span class="text-grey text-caption" v-if="item?.members?.length > 0">( +{{ item?.members?.length }}
64
+ members)</span>
110
65
  </template>
111
66
 
112
67
  <template v-slot:item.email="{ value }">
@@ -115,12 +70,7 @@
115
70
 
116
71
  <template v-slot:item.invited-by="{ item }">
117
72
  <span class="d-flex align-center ga-2">
118
- <AvatarMain
119
- v-if="item?.inviterName"
120
- :name="item?.inviterName"
121
- :size="20"
122
- :id="item?._id"
123
- />
73
+ <AvatarMain v-if="item?.inviterName" :name="item?.inviterName" :size="20" :id="item?._id" />
124
74
  <span class="text-capitalize">{{ item?.inviterName ?? "N/A" }}</span>
125
75
  </span>
126
76
  </template>
@@ -130,7 +80,7 @@
130
80
  <v-icon icon="mdi-user" size="15" />
131
81
  <span v-if="item.type === 'contractor'" class="text-capitalize">{{
132
82
  formatCamelCaseToWords(item.contractorType)
133
- }}</span>
83
+ }}</span>
134
84
  <span v-else class="text-capitalize">{{ formatType(item) }}</span>
135
85
  </span>
136
86
  <span class="d-flex align-center ga-2">
@@ -166,52 +116,26 @@
166
116
  <template v-slot:item.checkin-out="{ item }">
167
117
  <v-row no-gutters class="d-flex flex-column py-2">
168
118
  <span class="d-flex align-center ga-2">
169
- <v-icon
170
- icon="mdi-clock-time-four-outline"
171
- color="green"
172
- size="20"
173
- />
119
+ <v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
174
120
  <span class="text-capitalize">{{
175
121
  UTCToLocalTIme(item.checkIn) || "-"
176
- }}</span>
122
+ }}</span>
177
123
  <span>
178
- <v-icon
179
- v-if="item?.snapshotEntryImage"
180
- size="17"
181
- icon="mdi-image"
182
- @click.stop="handleViewImage(item.snapshotEntryImage)"
183
- />
124
+ <v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
125
+ @click.stop="handleViewImage(item.snapshotEntryImage)" />
184
126
  </span>
185
127
  </span>
186
128
 
187
- <span
188
- v-if="
189
- !item.checkOut &&
190
- (item?.status === 'registered' ||
191
- item?.status === 'unregistered') &&
192
- canCheckoutVisitor
193
- "
194
- >
195
- <span
196
- class="d-flex align-center ga-2"
197
- v-bind="menuProps"
198
- style="cursor: pointer"
199
- >
200
- <v-icon
201
- icon="mdi-clock-time-eight-outline"
202
- color="red"
203
- size="20"
204
- />
205
- <v-btn
206
- size="x-small"
207
- class="text-capitalize"
208
- color="red"
209
- text="Checkout"
210
- :loading="
211
- loading.checkingOut && item?._id === selectedVisitorId
212
- "
213
- @click.stop="handleCheckout(item._id)"
214
- />
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)" />
215
139
  </span>
216
140
  </span>
217
141
  <span v-else class="d-flex align-center ga-2">
@@ -219,95 +143,49 @@
219
143
  <template v-if="item.checkOut">
220
144
  <span class="text-capitalize">{{
221
145
  UTCToLocalTIme(item.checkOut) || "-"
222
- }}</span>
146
+ }}</span>
223
147
  <span>
224
- <v-icon
225
- v-if="item?.snapshotExitImage"
226
- size="17"
227
- icon="mdi-image"
228
- @click.stop="handleViewImage(item.snapshotExitImage)"
229
- />
148
+ <v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
149
+ @click.stop="handleViewImage(item.snapshotExitImage)" />
230
150
  </span>
231
151
  <span v-if="item?.manualCheckout">
232
- <TooltipInfo
233
- text="Manual Checkout"
234
- density="compact"
235
- size="x-small"
236
- />
152
+ <TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
237
153
  </span>
238
154
  </template>
239
155
  </span>
240
156
 
241
- <div
242
- v-if="showAddPassKeyButton(item)"
243
- class="d-flex flex-wrap ga-1 mt-1"
244
- v-bind="menuProps"
245
- style="cursor: pointer"
246
- >
247
- <v-chip
248
- size="x-small"
249
- variant="tonal"
250
- prepend-icon="mdi-key"
251
- @click.stop="handleOpenAddPassKey(item)"
252
- >Add Pass/Key</v-chip
253
- >
157
+ <div v-if="showAddPassKeyButton(item)" class="d-flex flex-wrap ga-1 mt-1" v-bind="menuProps"
158
+ style="cursor: pointer">
159
+ <v-chip size="x-small" variant="tonal" prepend-icon="mdi-key" @click.stop="handleOpenAddPassKey(item)">Add
160
+ Pass/Key</v-chip>
254
161
  </div>
255
162
 
256
163
  <v-menu :close-on-content-click="true">
257
164
  <template v-slot:activator="{ props: menuProps }">
258
- <div
259
- v-if="
260
- (item.visitorPass?.length ?? 0) > 0 ||
261
- (item.passKeys?.length ?? 0) > 0
262
- "
263
- class="d-flex flex-wrap ga-1 mt-1"
264
- v-bind="menuProps"
265
- style="cursor: pointer"
266
- >
267
- <v-chip
268
- v-for="pass in item.visitorPass"
269
- :key="(pass as any)._id ?? (pass as any).keyId"
270
- prepend-icon="mdi-card-bulleted-outline"
271
- size="x-small"
272
- variant="tonal"
273
- color="blue"
274
- >
165
+ <div v-if="
166
+ (item.visitorPass?.length ?? 0) > 0 ||
167
+ (item.passKeys?.length ?? 0) > 0
168
+ " class="d-flex flex-wrap ga-1 mt-1" v-bind="menuProps" style="cursor: pointer">
169
+ <v-chip v-for="pass in item.visitorPass" :key="(pass as any)._id ?? (pass as any).keyId"
170
+ prepend-icon="mdi-card-bulleted-outline" size="x-small" variant="tonal" color="blue">
275
171
  {{ (pass as any)?.prefixAndName }}
276
172
  </v-chip>
277
- <v-chip
278
- v-for="key in item.passKeys"
279
- :key="(key as any)._id ?? (key as any).keyId"
280
- prepend-icon="mdi-key"
281
- size="x-small"
282
- variant="tonal"
283
- color="orange"
284
- >
173
+ <v-chip v-for="key in item.passKeys" :key="(key as any)._id ?? (key as any).keyId"
174
+ prepend-icon="mdi-key" size="x-small" variant="tonal" color="orange">
285
175
  {{ (key as any)?.prefixAndName }}
286
176
  </v-chip>
287
177
  </div>
288
178
  </template>
289
179
  <v-list density="compact">
290
- <v-list-item
291
- v-if="!item?.checkOut"
292
- prepend-icon="mdi-logout"
293
- title="Checkout"
294
- @click.stop="handleCheckout(item._id)"
295
- />
296
- <v-list-item
297
- v-if="
298
- (item.visitorPass?.length ?? 0) > 0 ||
299
- (item.passKeys?.length ?? 0) > 0
300
- "
301
- prepend-icon="mdi-card-bulleted-outline"
302
- :title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
303
- @click.stop="handleOpenEditPassKey(item)"
304
- />
305
- <v-list-item
306
- v-if="showAddPassKeyButton(item)"
307
- prepend-icon="mdi-plus"
308
- title="Add Pass/Key"
309
- @click.stop="handleOpenAddPassKey(item)"
310
- />
180
+ <v-list-item v-if="!item?.checkOut" prepend-icon="mdi-logout" title="Checkout"
181
+ @click.stop="handleCheckout(item._id)" />
182
+ <v-list-item v-if="
183
+ (item.visitorPass?.length ?? 0) > 0 ||
184
+ (item.passKeys?.length ?? 0) > 0
185
+ " prepend-icon="mdi-card-bulleted-outline" :title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
186
+ @click.stop="handleOpenEditPassKey(item)" />
187
+ <v-list-item v-if="showAddPassKeyButton(item)" prepend-icon="mdi-plus" title="Add Pass/Key"
188
+ @click.stop="handleOpenAddPassKey(item)" />
311
189
  </v-list>
312
190
  </v-menu>
313
191
  </v-row>
@@ -326,44 +204,27 @@
326
204
 
327
205
  <span v-if="!item.checkIn && canUpdateVisitor">
328
206
  <span class="d-flex align-center ga-2 cursor-pointer">
329
- <v-icon
330
- icon="mdi-clock-time-eight-outline"
331
- color="success"
332
- size="20"
333
- />
334
- <v-btn
335
- size="x-small"
336
- class="text-capitalize"
337
- color="success"
338
- text="Checkin"
339
- @click.stop="handleCheckin(item)"
340
- />
207
+ <v-icon icon="mdi-clock-time-eight-outline" color="success" size="20" />
208
+ <v-btn size="x-small" class="text-capitalize" color="success" text="Checkin"
209
+ @click.stop="handleCheckin(item)" />
341
210
  </span>
342
211
  </span>
343
212
  </v-row>
344
213
  </template>
345
214
 
346
215
  <template v-slot:item.status="{ item }">
347
- <v-card
348
- v-if="item?.overnightParking?.status"
349
- size="small"
216
+ <v-card v-if="item?.overnightParking?.status" size="small"
350
217
  class="rounded-xl text-capitalize text-center elevation-0 py-1 px-2"
351
218
  :color="getOvernightParkingRequestStatusColor(item?.overnightParking?.status as string)"
352
- style="max-width: 150px; margin: auto"
353
- >
219
+ style="max-width: 150px; margin: auto">
354
220
  {{ item?.overnightParking?.status }}
355
221
  </v-card>
356
222
  <span v-else> N/A </span>
357
223
  </template>
358
224
 
359
225
  <template v-slot:item.action="{ item }">
360
- <v-btn
361
- v-if="activeTab === 'unregistered' && canAddVisitor"
362
- size="small"
363
- color="primary"
364
- text="Register"
365
- @click.stop="handleRegistrationUnregisteredVisitor(item)"
366
- />
226
+ <v-btn v-if="activeTab === 'unregistered' && canAddVisitor" size="small" color="primary" text="Register"
227
+ @click.stop="handleRegistrationUnregisteredVisitor(item)" />
367
228
  <v-menu v-else-if="activeTab === 'overnight-parking'">
368
229
  <template #activator="{ props }">
369
230
  <v-avatar v-bind="props" class="rounded-xl border-md">
@@ -372,20 +233,17 @@
372
233
  </template>
373
234
 
374
235
  <v-card>
375
- <v-list-item
376
- v-if="
377
- !overNightParkingListActions[0].disabled ||
378
- !['approved', 'rejected'].includes(
379
- item?.overnightParking?.status
380
- )
381
- "
382
- @click.stop="
383
- openApproveRejectOverNightParkingRequestDialog(
384
- item,
385
- overNightParkingListActions[0].status
386
- )
387
- "
388
- >
236
+ <v-list-item v-if="
237
+ !overNightParkingListActions[0].disabled ||
238
+ !['approved', 'rejected'].includes(
239
+ item?.overnightParking?.status
240
+ )
241
+ " @click.stop="
242
+ openApproveRejectOverNightParkingRequestDialog(
243
+ item,
244
+ overNightParkingListActions[0].status
245
+ )
246
+ ">
389
247
  <template #title>
390
248
  <span class="text-caption">
391
249
  {{ overNightParkingListActions[0].text }}
@@ -393,36 +251,30 @@
393
251
  </template>
394
252
  </v-list-item>
395
253
  <v-divider />
396
- <v-list-item
397
- v-if="
398
- !overNightParkingListActions[1].disabled ||
399
- !['approved', 'rejected'].includes(
400
- item?.overnightParking?.status
401
- )
402
- "
403
- @click="
404
- openApproveRejectOverNightParkingRequestDialog(
405
- item,
406
- overNightParkingListActions[1].status
407
- )
408
- "
409
- >
254
+ <v-list-item v-if="
255
+ !overNightParkingListActions[1].disabled ||
256
+ !['approved', 'rejected'].includes(
257
+ item?.overnightParking?.status
258
+ )
259
+ " @click="
260
+ openApproveRejectOverNightParkingRequestDialog(
261
+ item,
262
+ overNightParkingListActions[1].status
263
+ )
264
+ ">
410
265
  <template #title>
411
266
  <span class="text-caption">
412
267
  {{ overNightParkingListActions[1].text }}
413
268
  </span>
414
269
  </template>
415
270
  </v-list-item>
416
- <v-list-item
417
- v-if="item?.overnightParking?.remarks"
418
- @click="
419
- openApproveRejectOverNightParkingRequestDialog(
420
- item,
421
- overNightParkingListActions[2].status,
422
- item?.overnightParking?.remarks
423
- )
424
- "
425
- >
271
+ <v-list-item v-if="item?.overnightParking?.remarks" @click="
272
+ openApproveRejectOverNightParkingRequestDialog(
273
+ item,
274
+ overNightParkingListActions[2].status,
275
+ item?.overnightParking?.remarks
276
+ )
277
+ ">
426
278
  <template #title>
427
279
  <span class="text-caption">
428
280
  {{ overNightParkingListActions[2].text }}
@@ -436,118 +288,61 @@
436
288
 
437
289
  <template v-slot:item.checkInRemarks="{ item }">
438
290
  <span>
439
- <v-btn
440
- variant="text"
441
- color="blue-darken-2"
442
- density="compact"
443
- size="small"
444
- @click.stop="handleOpenRemarks(item, 'checkIn')"
445
- >{{ item.checkInRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
446
- >
291
+ <v-btn variant="text" color="blue-darken-2" density="compact" size="small"
292
+ @click.stop="handleOpenRemarks(item, 'checkIn')">{{ item.checkInRemarks ? "View Remarks" : "Add Remarks"
293
+ }}</v-btn>
447
294
  </span>
448
295
  </template>
449
296
  <template v-slot:item.checkOutRemarks="{ item }">
450
297
  <span>
451
- <v-btn
452
- variant="text"
453
- color="blue-darken-2"
454
- density="compact"
455
- size="small"
456
- @click.stop="handleOpenRemarks(item, 'checkOut')"
457
- >{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
458
- >
298
+ <v-btn variant="text" color="blue-darken-2" density="compact" size="small"
299
+ @click.stop="handleOpenRemarks(item, 'checkOut')">{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks"
300
+ }}</v-btn>
459
301
  </span>
460
302
  </template>
461
303
  </TableMain>
462
304
 
463
305
  <v-dialog v-model="dialog.showSelection" width="450" persistent>
464
- <VisitorFormSelection
465
- :mode="mode"
466
- @cancel="dialog.showSelection = false"
467
- @select="handleSelectVisitorType"
468
- />
306
+ <VisitorFormSelection :mode="mode" @cancel="dialog.showSelection = false" @select="handleSelectVisitorType" />
469
307
  </v-dialog>
470
308
 
471
- <v-dialog
472
- v-model="dialog.showForm"
473
- v-if="activeVisitorFormType"
474
- width="450"
475
- persistent
476
- >
477
- <VisitorForm
478
- :mode="mode"
479
- :org="orgId"
480
- :site="siteId"
481
- :visitor-data="selectedVisitorDataObject"
482
- :type="activeVisitorFormType"
483
- @back="handleClickBack"
484
- @done="handleVisitorFormDone"
485
- @done:more="handleVisitorFormCreateMore"
486
- @close:all="handleCloseAll"
487
- />
309
+ <v-dialog v-model="dialog.showForm" v-if="activeVisitorFormType" width="450" persistent>
310
+ <VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject"
311
+ :type="activeVisitorFormType" @back="handleClickBack" @done="handleVisitorFormDone"
312
+ @done:more="handleVisitorFormCreateMore" @close:all="handleCloseAll" />
488
313
  </v-dialog>
489
314
 
490
315
  <v-dialog v-model="dialog.viewVisitor" width="450" persistent>
491
- <VehicleUpdateMoreAction
492
- title="Preview"
493
- :can-update="false"
494
- :can-delete="false"
495
- @close="dialog.viewVisitor = false"
496
- edit-button-label="Edit Visitor"
497
- >
316
+ <VehicleUpdateMoreAction title="Preview" :can-update="false" :can-delete="false"
317
+ @close="dialog.viewVisitor = false" edit-button-label="Edit Visitor">
498
318
  <template v-slot:content>
499
319
  <v-row no-gutters class="mb-4">
500
320
  <v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
501
- <span
502
- v-if="
503
- key === 'checkIn' &&
504
- !selectedVisitorObject[key] &&
505
- canUpdateVisitor
506
- "
507
- class="d-flex align-center"
508
- >
321
+ <span v-if="
322
+ key === 'checkIn' &&
323
+ !selectedVisitorObject[key] &&
324
+ canUpdateVisitor
325
+ " class="d-flex align-center">
509
326
  <strong>{{ label }}:</strong>
510
- <v-btn
511
- size="x-small"
512
- class="ml-3 text-capitalize"
513
- color="success"
514
- text="Checkin"
515
- :disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
516
- @click="handleCheckin(selectedVisitorObject)"
517
- :loading="isVisitorDataFromScannedQRCodeCheckingInOut"
518
- />
327
+ <v-btn size="x-small" class="ml-3 text-capitalize" color="success" text="Checkin"
328
+ :disabled="isVisitorDataFromScannedQRCodeCheckingInOut" @click="handleCheckin(selectedVisitorObject)"
329
+ :loading="isVisitorDataFromScannedQRCodeCheckingInOut" />
519
330
  </span>
520
- <span
521
- v-if="
522
- key === 'checkOut' &&
523
- selectedVisitorObject['checkIn'] &&
524
- !selectedVisitorObject[key] &&
525
- canCheckoutVisitor
526
- "
527
- class="d-flex align-center"
528
- >
331
+ <span v-if="
332
+ key === 'checkOut' &&
333
+ selectedVisitorObject['checkIn'] &&
334
+ !selectedVisitorObject[key] &&
335
+ canCheckoutVisitor
336
+ " class="d-flex align-center">
529
337
  <strong>{{ label }}:</strong>
530
- <v-btn
531
- size="x-small"
532
- class="ml-3 text-capitalize"
533
- color="red"
534
- text="Checkout"
535
- :disabled="loading.checkingOut"
536
- @click="handleCheckout(selectedVisitorId as string)"
537
- />
338
+ <v-btn size="x-small" class="ml-3 text-capitalize" color="red" text="Checkout"
339
+ :disabled="loading.checkingOut" @click="handleCheckout(selectedVisitorId as string)" />
538
340
  </span>
539
341
 
540
- <span
541
- v-else-if="selectedVisitorObject[key]"
542
- class="d-flex ga-3 align-center"
543
- ><strong>{{ label }}:</strong>
342
+ <span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
343
+ }}:</strong>
544
344
  {{ formatValues(key, selectedVisitorObject[key]) }}
545
- <TooltipInfo
546
- v-if="key === 'checkOut'"
547
- text="Manual Checkout"
548
- density="compact"
549
- size="x-small"
550
- />
345
+ <TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
551
346
  </span>
552
347
  </v-col>
553
348
  </v-row>
@@ -559,11 +354,7 @@
559
354
  <v-card>
560
355
  <v-card-title class="d-flex justify-space-between align-center">
561
356
  <span>Snapshot</span>
562
- <v-btn
563
- icon="mdi-close"
564
- variant="text"
565
- @click="dialog.snapshotImage = false"
566
- />
357
+ <v-btn icon="mdi-close" variant="text" @click="dialog.snapshotImage = false" />
567
358
  </v-card-title>
568
359
  <v-card-text class="pa-2 d-flex justify-center">
569
360
  <v-img :src="snapshotImageUrl" max-height="600" contain />
@@ -573,51 +364,25 @@
573
364
 
574
365
  <v-dialog v-model="dialog.remarks" max-width="450" persistent>
575
366
  <v-card>
576
- <v-card-title
577
- class="d-flex justify-space-between align-center pt-4 px-4"
578
- >
367
+ <v-card-title class="d-flex justify-space-between align-center pt-4 px-4">
579
368
  <span>{{
580
369
  remarksType === "checkIn" ? "Check-In Remarks" : "Check-Out Remarks"
581
- }}</span>
582
- <v-btn
583
- icon="mdi-close"
584
- variant="text"
585
- @click="dialog.remarks = false"
586
- />
370
+ }}</span>
371
+ <v-btn icon="mdi-close" variant="text" @click="dialog.remarks = false" />
587
372
  </v-card-title>
588
373
  <v-card-text class="px-4 pb-2">
589
- <v-textarea
590
- v-model="remarksInput"
591
- label="Remarks"
592
- rows="4"
593
- auto-grow
594
- variant="outlined"
595
- :readonly="remarksViewOnly"
596
- />
374
+ <v-textarea v-model="remarksInput" label="Remarks" rows="4" auto-grow variant="outlined"
375
+ :readonly="remarksViewOnly" />
597
376
  </v-card-text>
598
377
  <v-toolbar class="pa-0" density="compact">
599
378
  <v-row no-gutters>
600
379
  <v-col cols="6">
601
- <v-btn
602
- variant="text"
603
- block
604
- :disabled="loading.savingRemarks"
605
- @click="dialog.remarks = false"
606
- >Cancel</v-btn
607
- >
380
+ <v-btn variant="text" block :disabled="loading.savingRemarks"
381
+ @click="dialog.remarks = false">Cancel</v-btn>
608
382
  </v-col>
609
383
  <v-col cols="6">
610
- <v-btn
611
- color="primary"
612
- variant="flat"
613
- height="48"
614
- rounded="0"
615
- block
616
- :loading="loading.savingRemarks"
617
- :disabled="!remarksInput.trim()"
618
- @click="handleSaveRemarks"
619
- >Save</v-btn
620
- >
384
+ <v-btn color="primary" variant="flat" height="48" rounded="0" block :loading="loading.savingRemarks"
385
+ :disabled="!remarksInput.trim()" @click="handleSaveRemarks">Save</v-btn>
621
386
  </v-col>
622
387
  </v-row>
623
388
  </v-toolbar>
@@ -627,16 +392,9 @@
627
392
  <v-dialog v-model="dialog.returnPassesKeys" max-width="450" persistent>
628
393
  <v-card>
629
394
  <v-toolbar density="compact" color="">
630
- <v-row
631
- no-gutters
632
- class="d-flex fill-height justify-space-between align-center px-4"
633
- >
395
+ <v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
634
396
  <span class="font-weight-bold">Return Passes &amp; Keys</span>
635
- <v-btn
636
- icon="mdi-close"
637
- variant="text"
638
- @click="dialog.returnPassesKeys = false"
639
- />
397
+ <v-btn icon="mdi-close" variant="text" @click="dialog.returnPassesKeys = false" />
640
398
  </v-row>
641
399
  </v-toolbar>
642
400
  <v-card-text class="px-4 pb-2">
@@ -646,134 +404,55 @@
646
404
 
647
405
  <div>
648
406
  <InputLabel class="text-capitalize" title="Full Name" />
649
- <v-text-field
650
- v-model="selectedVisitorObject.name"
651
- density="comfortable"
652
- readonly
653
- />
407
+ <v-text-field v-model="selectedVisitorObject.name" density="comfortable" readonly />
654
408
  </div>
655
409
 
656
410
  <div>
657
411
  <InputLabel class="text-capitalize" title="Location" />
658
- <v-text-field
659
- v-model="selectedVisitorObject.location"
660
- density="comfortable"
661
- readonly
662
- />
412
+ <v-text-field v-model="selectedVisitorObject.location" density="comfortable" readonly />
663
413
  </div>
664
414
 
665
415
  <div v-if="passReturnStatuses.length > 0" class="mb-2">
666
416
  <p class="text-caption text-medium-emphasis mb-1">Passes</p>
667
- <v-row
668
- no-gutters
669
- v-for="pass in passReturnStatuses"
670
- :key="(pass as any)._id ?? (pass as any).keyId"
671
- class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
672
- >
673
- <v-chip
674
- prepend-icon="mdi-card-bulleted-outline"
675
- size="small"
676
- variant="tonal"
677
- color="blue"
678
- >
417
+ <v-row no-gutters v-for="pass in passReturnStatuses" :key="(pass as any)._id ?? (pass as any).keyId"
418
+ class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
419
+ <v-chip prepend-icon="mdi-card-bulleted-outline" size="small" variant="tonal" color="blue">
679
420
  {{ (pass as any)?.prefixAndName }}
680
421
  </v-chip>
681
- <v-select
682
- hide-details
683
- max-width="200px"
684
- density="compact"
685
- :items="passStatusOptions"
686
- item-title="label"
687
- item-value="value"
688
- v-model="pass.status"
689
- :disabled="selectedVisitorObject.checkOut"
690
- ></v-select>
691
- <v-textarea
692
- v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
693
- no-resize
694
- rows="3"
695
- class="w-100"
696
- density="compact"
697
- v-model="pass.remarks"
698
- :disabled="selectedVisitorObject.checkOut"
699
- ></v-textarea>
422
+ <v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
423
+ item-value="value" v-model="pass.status" :disabled="selectedVisitorObject.checkOut"></v-select>
424
+ <v-textarea v-if="pass.status === 'Lost' || pass.status === 'Damaged'" no-resize rows="3" class="w-100"
425
+ density="compact" v-model="pass.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
700
426
  </v-row>
701
427
  </div>
702
428
  <div v-if="keyReturnStatuses.length > 0" class="mb-2">
703
429
  <p class="text-caption text-medium-emphasis mb-1">Keys</p>
704
- <v-row
705
- no-gutters
706
- v-for="key in keyReturnStatuses"
707
- :key="(key as any)._id ?? (key as any).keyId"
708
- class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
709
- >
710
- <v-chip
711
- prepend-icon="mdi-key"
712
- size="small"
713
- variant="tonal"
714
- color="orange"
715
- >
430
+ <v-row no-gutters v-for="key in keyReturnStatuses" :key="(key as any)._id ?? (key as any).keyId"
431
+ class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
432
+ <v-chip prepend-icon="mdi-key" size="small" variant="tonal" color="orange">
716
433
  {{ (key as any)?.prefixAndName }}
717
434
  </v-chip>
718
- <v-select
719
- hide-details
720
- max-width="200px"
721
- density="compact"
722
- :items="passStatusOptions"
723
- item-title="label"
724
- item-value="value"
725
- v-model="key.status"
726
- :disabled="selectedVisitorObject.checkOut"
727
- ></v-select>
728
- <v-textarea
729
- v-if="key.status === 'Lost' || key.status === 'Damaged'"
730
- no-resize
731
- rows="3"
732
- class="w-100"
733
- density="compact"
734
- v-model="key.remarks"
735
- :disabled="selectedVisitorObject.checkOut"
736
- ></v-textarea>
435
+ <v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
436
+ item-value="value" v-model="key.status" :disabled="selectedVisitorObject.checkOut"></v-select>
437
+ <v-textarea v-if="key.status === 'Lost' || key.status === 'Damaged'" no-resize rows="3" class="w-100"
438
+ density="compact" v-model="key.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
737
439
  </v-row>
738
440
  </div>
739
441
 
740
442
  <v-row no-gutters class="my-5">
741
- <v-btn
742
- variant="flat"
743
- color="blue"
744
- density="comfortable"
745
- class="text-capitalize"
746
- :disabled="selectedVisitorObject.checkOut"
747
- :loading="loading.updatingPassKeys"
748
- @click.stop="handleUpdatePassKeys"
749
- >Update Pass/Keys</v-btn
750
- >
443
+ <v-btn variant="flat" color="blue" density="comfortable" class="text-capitalize"
444
+ :disabled="selectedVisitorObject.checkOut" :loading="loading.updatingPassKeys"
445
+ @click.stop="handleUpdatePassKeys">Update Pass/Keys</v-btn>
751
446
  </v-row>
752
447
  </v-card-text>
753
448
  <v-toolbar class="pa-0" density="compact">
754
449
  <v-row no-gutters>
755
450
  <v-col cols="6">
756
- <v-btn
757
- variant="text"
758
- block
759
- @click="dialog.returnPassesKeys = false"
760
- >Close</v-btn
761
- >
451
+ <v-btn variant="text" block @click="dialog.returnPassesKeys = false">Close</v-btn>
762
452
  </v-col>
763
453
  <v-col cols="6">
764
- <v-btn
765
- color="red"
766
- variant="flat"
767
- height="48"
768
- rounded="0"
769
- block
770
- :loading="loading.checkingOut"
771
- :disabled="
772
- !canConfirmCheckout || selectedVisitorObject.checkOut
773
- "
774
- @click="proceedCheckout"
775
- >Confirm Checkout</v-btn
776
- >
454
+ <v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut" :disabled="!canConfirmCheckout || selectedVisitorObject.checkOut
455
+ " @click="proceedCheckout">Confirm Checkout</v-btn>
777
456
  </v-col>
778
457
  </v-row>
779
458
  </v-toolbar>
@@ -781,145 +460,80 @@
781
460
  </v-dialog>
782
461
 
783
462
  <v-dialog v-model="dialog.addPassKey" width="450" persistent>
784
- <AddPassKeyToVisitor
785
- mode="add"
786
- :visitor="selectedVisitorDataObject"
787
- :site="siteId"
788
- @close="dialog.addPassKey = false"
789
- @done="
463
+ <AddPassKeyToVisitor mode="add" :visitor="selectedVisitorDataObject" :site="siteId"
464
+ @close="dialog.addPassKey = false" @done="
790
465
  () => {
791
466
  dialog.addPassKey = false;
792
467
  getVisitorRefresh();
793
468
  }
794
- "
795
- />
469
+ " />
796
470
  </v-dialog>
797
471
 
798
472
  <v-dialog v-model="dialog.editPassKey" width="450" persistent>
799
- <AddPassKeyToVisitor
800
- mode="edit"
801
- :visitor="selectedVisitorDataObject"
802
- :site="siteId"
803
- @close="dialog.editPassKey = false"
804
- @done="
473
+ <AddPassKeyToVisitor mode="edit" :visitor="selectedVisitorDataObject" :site="siteId"
474
+ @close="dialog.editPassKey = false" @done="
805
475
  () => {
806
476
  dialog.editPassKey = false;
807
477
  getVisitorRefresh();
808
478
  }
809
- "
810
- />
479
+ " />
811
480
  </v-dialog>
812
481
 
813
- <ScanVisitorQRCode
814
- :dialog="dialog.scanVisitorQRCode"
815
- :site="siteId"
816
- @open-visitor-data-from-scanned-qr-code-dialog="
817
- handleShowVisitorDataFromScannedQRCodeDialog
818
- "
819
- @close-dialog="dialog.scanVisitorQRCode = false"
820
- />
821
-
822
- <v-dialog
823
- v-model="dialog.showVisitorDataFromScannedQRCode"
824
- width="450"
825
- persistent
826
- >
827
- <VisitorDataFromScannedQRCodeForm
828
- :site="siteId"
829
- :visitor-data="visitorDataFromScannedQRCode"
482
+ <ScanVisitorQRCode :dialog="dialog.scanVisitorQRCode" :site="siteId" @open-visitor-data-from-scanned-qr-code-dialog="
483
+ handleShowVisitorDataFromScannedQRCodeDialog
484
+ " @close-dialog="dialog.scanVisitorQRCode = false" />
485
+
486
+ <v-dialog v-model="dialog.showVisitorDataFromScannedQRCode" width="450" persistent>
487
+ <VisitorDataFromScannedQRCodeForm :site="siteId" :visitor-data="visitorDataFromScannedQRCode"
830
488
  @check-in-out="handleVisitorDataFromScannedQRCodeCheckInOut"
831
489
  :processing="isVisitorDataFromScannedQRCodeCheckingInOut"
832
- @close="handleCloseVisitorDataFromScannedQRCodeDialog"
833
- />
490
+ @close="handleCloseVisitorDataFromScannedQRCodeDialog" />
834
491
  </v-dialog>
835
492
 
836
- <v-dialog
837
- v-model="dialog.approveRejectOvernightParkingRequestDialog"
838
- transition="dialog-bottom-transition"
839
- persistent
840
- width="60vh"
841
- >
493
+ <v-dialog v-model="dialog.approveRejectOvernightParkingRequestDialog" transition="dialog-bottom-transition"
494
+ persistent width="60vh">
842
495
  <v-card>
843
496
  <v-toolbar density="compact">
844
- <v-row
845
- no-gutters
846
- class="d-flex fill-height justify-space-between align-center px-4"
847
- >
497
+ <v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
848
498
  <span class="font-weight-bold"> Overnight Parking Request </span>
849
- <v-btn
850
- icon="mdi-close"
851
- variant="text"
852
- @click="dialog.approveRejectOvernightParkingRequestDialog = false"
853
- />
499
+ <v-btn icon="mdi-close" variant="text" @click="dialog.approveRejectOvernightParkingRequestDialog = false" />
854
500
  </v-row>
855
501
  </v-toolbar>
856
502
  <v-card-text class="px-4 pb-6">
857
503
  <v-row no-gutters justify="center" align-content="center">
858
- <v-col
859
- v-if="
860
- approveRejectOvernightParkingRequestDialog.status !== 'remarks'
861
- "
862
- cols="12"
863
- class="text-h6 text-center"
864
- >
504
+ <v-col v-if="
505
+ approveRejectOvernightParkingRequestDialog.status !== 'remarks'
506
+ " cols="12" class="text-h6 text-center">
865
507
  {{
866
- `Are you sure you want to ${approveRejectOvernightParkingRequestDialog.text} the visitor's overnight parking request?`
508
+ `Are you sure you want to ${approveRejectOvernightParkingRequestDialog.text} the visitor's overnight
509
+ parking
510
+ request?`
867
511
  }}
868
512
  </v-col>
869
513
 
870
514
  <v-col cols="12" md="8" class="mt-4">
871
- <v-textarea
872
- v-model="overNightParkingRequestApproveRejectRemarks"
873
- label="Remarks"
874
- placeholder="Enter remarks"
875
- outlined
876
- rows="3"
877
- clearable
878
- :hide-details="
879
- approveRejectOvernightParkingRequestDialog.status ===
515
+ <v-textarea v-model="overNightParkingRequestApproveRejectRemarks" label="Remarks"
516
+ placeholder="Enter remarks" outlined rows="3" clearable :hide-details="approveRejectOvernightParkingRequestDialog.status ===
880
517
  'remarks'
881
- "
882
- :readonly="
883
- approveRejectOvernightParkingRequestDialog.status ===
884
- 'remarks'
885
- "
886
- />
518
+ " :readonly="approveRejectOvernightParkingRequestDialog.status ===
519
+ 'remarks'
520
+ " />
887
521
  </v-col>
888
522
  </v-row>
889
523
  </v-card-text>
890
- <v-toolbar
891
- v-if="approveRejectOvernightParkingRequestDialog.status !== 'remarks'"
892
- class="pa-0"
893
- density="compact"
894
- >
524
+ <v-toolbar v-if="approveRejectOvernightParkingRequestDialog.status !== 'remarks'" class="pa-0"
525
+ density="compact">
895
526
  <v-row no-gutters>
896
527
  <v-col cols="6">
897
- <v-btn
898
- text="No"
899
- color="grey-lighten-3"
900
- variant="flat"
901
- height="48"
902
- tile
903
- block
528
+ <v-btn text="No" color="grey-lighten-3" variant="flat" height="48" tile block
904
529
  :disabled="isApprovingRejectingOvernightParkingRequest"
905
- @click="closeApproveRejectOvernightParkingDialog"
906
- />
530
+ @click="closeApproveRejectOvernightParkingDialog" />
907
531
  </v-col>
908
532
  <v-col cols="6">
909
- <v-btn
910
- text="Yes"
911
- color="success"
912
- variant="flat"
913
- height="48"
914
- tile
915
- block
916
- :disabled="
917
- !overNightParkingRequestApproveRejectRemarks ||
918
- isApprovingRejectingOvernightParkingRequest
919
- "
920
- @click="updateOvernightParkingRequestStatus"
921
- :loading="isApprovingRejectingOvernightParkingRequest"
922
- />
533
+ <v-btn text="Yes" color="success" variant="flat" height="48" tile block :disabled="!overNightParkingRequestApproveRejectRemarks ||
534
+ isApprovingRejectingOvernightParkingRequest
535
+ " @click="updateOvernightParkingRequestStatus"
536
+ :loading="isApprovingRejectingOvernightParkingRequest" />
923
537
  </v-col>
924
538
  </v-row>
925
539
  </v-toolbar>
@@ -937,6 +551,8 @@ definePageMeta({
937
551
  middleware: ["01-auth", "02-org"],
938
552
  });
939
553
 
554
+ const { downloadPDF } = usePDFDownload();
555
+
940
556
  const props = defineProps({
941
557
  canAddVisitor: {
942
558
  type: Boolean,
@@ -1253,8 +869,8 @@ const {
1253
869
  page: page.value,
1254
870
  site: siteId,
1255
871
  search: searchInput.value,
1256
- dateTo: moment(dateTo.value).endOf("day").toISOString(),
1257
- dateFrom: moment(dateFrom.value).startOf("day").toISOString(),
872
+ dateTo: dateTo.value ? moment(dateTo.value).endOf("day").toISOString() : undefined,
873
+ dateFrom: dateFrom.value ? moment(dateFrom.value).startOf("day").toISOString() : undefined,
1258
874
  type:
1259
875
  filterTypes.value.length === 0 && activeTab.value === "registered"
1260
876
  ? "contractor,delivery,walk-in,pick-up,drop-off,guest"
@@ -1678,6 +1294,146 @@ async function proceedCheckout() {
1678
1294
  }
1679
1295
  }
1680
1296
 
1297
+ function buildReportParams(): any {
1298
+ const params: any = {
1299
+ site: siteId,
1300
+ search: searchInput.value,
1301
+ dateTo: dateTo.value ? moment(dateTo.value).endOf("day").toISOString() : undefined,
1302
+ dateFrom: dateFrom.value ? moment(dateFrom.value).startOf("day").toISOString() : undefined,
1303
+ type:
1304
+ filterTypes.value.length === 0 && activeTab.value === "registered"
1305
+ ? "contractor,delivery,walk-in,pick-up,drop-off,guest"
1306
+ : filterTypes.value.filter(Boolean).join(","),
1307
+ checkedOut: displayNotCheckedOut.value ? false : undefined,
1308
+ limit: 100,
1309
+ };
1310
+
1311
+ if (activeTab.value === "guests") {
1312
+ params.type = "guest";
1313
+ params.status = "approved";
1314
+ } else if (activeTab.value === "resident-transactions") {
1315
+ params.status = "registered";
1316
+ params.type = "resident,tenant";
1317
+ } else if (activeTab.value === "overnight-parking") {
1318
+ params.tab = "Overnight Parking";
1319
+ } else {
1320
+ params.status = activeTab.value;
1321
+ }
1322
+
1323
+ return params;
1324
+ }
1325
+
1326
+
1327
+ async function handleDownloadPDFReport() {
1328
+ try {
1329
+ showMessage("Generating PDF report...", "info");
1330
+
1331
+ // Build URL with query parameters
1332
+ const queryParams = new URLSearchParams();
1333
+ if (searchInput.value) queryParams.append("search", searchInput.value);
1334
+ if (dateFrom.value) queryParams.append("dateFrom", dateFrom.value);
1335
+ if (dateTo.value) queryParams.append("dateTo", dateTo.value);
1336
+ if (filterTypes.value.length > 0) queryParams.append("type", filterTypes.value.join(","));
1337
+ if (activeTab.value) queryParams.append("tab", activeTab.value);
1338
+ if (displayNotCheckedOut.value) queryParams.append("checkedOut", "true");
1339
+
1340
+ const queryString = queryParams.toString();
1341
+ const url = `/${route.params.org}/${siteId}/visitor-management/preview${queryString ? "?" + queryString : ""}`;
1342
+ const title = `Visitor-Report-${siteId}-${new Date().toISOString().split("T")[0]}`;
1343
+
1344
+ await downloadPDF({ url, title, orientation: "P", appKey: "SECURITY" });
1345
+ showMessage("PDF report downloaded successfully!", "success");
1346
+ } catch (error) {
1347
+ showMessage("Error downloading PDF report", "error");
1348
+ }
1349
+ }
1350
+
1351
+ async function handleDownloadCSVReport() {
1352
+ try {
1353
+ showMessage("Generating CSV report...", "info");
1354
+
1355
+ const params = buildReportParams();
1356
+ const reportData = await getVisitors(params);
1357
+ const csvData = generateCSVData(reportData.items ?? []);
1358
+ downloadCSVFile(csvData);
1359
+ showMessage("CSV report downloaded successfully!", "success");
1360
+ } catch (error) {
1361
+ showMessage("Error downloading CSV report", "error");
1362
+ }
1363
+ }
1364
+
1365
+ function generateCSVData(visitors: any[]): string {
1366
+ const headers = [
1367
+ "NAME",
1368
+ "TYPE",
1369
+ "COMPANY",
1370
+ "BLOCK",
1371
+ "LEVEL",
1372
+ "UNIT",
1373
+ "CONTACT",
1374
+ "VEHICLE NO.",
1375
+ "CHECK-IN",
1376
+ "CHECK-OUT",
1377
+ "CHECK-IN IMAGE",
1378
+ "CHECK-OUT IMAGE",
1379
+ ];
1380
+
1381
+ const formatExportTypes = (type: string) => {
1382
+ if (!type || type === "null") return "";
1383
+ if (type === "guest") return "Drive In";
1384
+ const typeParts = type.split("-");
1385
+ return typeParts.map((part) => formatCamelCaseToWords(part)).join("-");
1386
+ };
1387
+
1388
+ const rows = visitors.map((visitor) => [
1389
+ visitor.name || "",
1390
+ formatExportTypes(formatType(visitor)) || "",
1391
+ visitor.company || "",
1392
+ visitor.block || "",
1393
+ visitor.level || "",
1394
+ visitor.unitName || "",
1395
+ visitor.contact || "",
1396
+ visitor.plateNumber || "",
1397
+ formatDate(visitor.checkIn) || "",
1398
+ formatDate(visitor.checkOut) || "",
1399
+ visitor.snapshotEntryImage ? getFileUrlAnpr(`${siteId}/${visitor.snapshotEntryImage}`) : "",
1400
+ visitor.snapshotExitImage ? getFileUrlAnpr(`${siteId}/${visitor.snapshotExitImage}`) : "",
1401
+ ]);
1402
+
1403
+ const csvContent = [
1404
+ headers.map((h) => `"${h}"`).join(","),
1405
+ ...rows.map((row) =>
1406
+ row
1407
+ .map((cell) => {
1408
+ const cellStr = String(cell || "");
1409
+ return `"${cellStr.replace(/"/g, '""')}"`;
1410
+ })
1411
+ .join(",")
1412
+ ),
1413
+ ].join("\n");
1414
+
1415
+ return csvContent;
1416
+ }
1417
+
1418
+ function downloadCSVFile(csvContent: string) {
1419
+ const now = new Date();
1420
+ const dateStr = now.toISOString().split("T")[0];
1421
+ const timeStr = now.toTimeString().split(" ")[0].replace(/:/g, "_");
1422
+ const filename = `VISITOR_MANAGEMENT_CSV_REPORT_${activeTab.value}_${dateStr}_${timeStr}.csv`;
1423
+
1424
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
1425
+ const link = document.createElement("a");
1426
+ const url = URL.createObjectURL(blob);
1427
+
1428
+ link.setAttribute("href", url);
1429
+ link.setAttribute("download", filename);
1430
+ link.style.visibility = "hidden";
1431
+
1432
+ document.body.appendChild(link);
1433
+ link.click();
1434
+ document.body.removeChild(link);
1435
+ }
1436
+
1681
1437
  const updateRouteQuery = debounce(
1682
1438
  // (search: string, from: string, to: string, types: string[], status, checkedOut) => {
1683
1439
  ({
@@ -1748,6 +1504,9 @@ onMounted(() => {
1748
1504
  displayNotCheckedOut.value =
1749
1505
  (route.query.checkedOut as string) == "true" || false;
1750
1506
  });
1507
+
1508
+
1509
+
1751
1510
  </script>
1752
1511
 
1753
1512
  <style scoped></style>