@7365admin1/layer-common 1.11.19 → 1.11.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/components/AddPassKeyToVisitor.vue +114 -53
- package/components/AreaChecklistHistoryMain.vue +29 -1
- package/components/BulletinBoardView.vue +158 -16
- package/components/CleaningScheduleMain.vue +2 -2
- package/components/Input/DateTimePicker.vue +45 -9
- package/components/OvernightParkingAvailability.vue +291 -155
- package/components/ScanVisitorQRCode.vue +157 -0
- package/components/ScheduleAreaMain.vue +6 -6
- package/components/SiteSettings.vue +303 -243
- package/components/TableMain.vue +72 -21
- package/components/VisitorManagement.vue +656 -234
- package/composables/useFeedback.ts +1 -1
- package/composables/useOrg.ts +16 -0
- package/composables/useSiteSettings.ts +30 -1
- package/package.json +1 -1
- package/plugins/html5-qrcode.client.js +8 -0
- package/types/overnight-parking.d.ts +3 -2
|
@@ -1,34 +1,95 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
|
-
<TableMain
|
|
4
|
-
:
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
>
|
|
7
22
|
<template #extension>
|
|
8
23
|
<v-row no-gutters class="w-100 d-flex flex-column">
|
|
9
|
-
<v-tabs
|
|
10
|
-
|
|
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
|
+
>
|
|
11
37
|
{{ tab.name }}
|
|
12
38
|
</v-tab>
|
|
13
39
|
</v-tabs>
|
|
14
40
|
|
|
15
|
-
<v-card
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
>
|
|
19
60
|
<template #label>
|
|
20
61
|
<span class="text-caption">Not Checked Out</span>
|
|
21
62
|
</template>
|
|
22
63
|
</v-checkbox>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
64
|
+
<InputDatePicker
|
|
65
|
+
v-model="dateFrom"
|
|
66
|
+
density="compact"
|
|
67
|
+
hide-details
|
|
68
|
+
/>
|
|
69
|
+
<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
|
+
>
|
|
28
83
|
<template v-slot:selection="{ item, index }">
|
|
29
84
|
<div class="d-flex align-center text-caption text-nowrap">
|
|
30
|
-
<v-chip
|
|
31
|
-
|
|
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
|
+
/>
|
|
32
93
|
</div>
|
|
33
94
|
</template>
|
|
34
95
|
</v-select>
|
|
@@ -43,8 +104,9 @@
|
|
|
43
104
|
</span>
|
|
44
105
|
<span class="text-capitalize">{{ item?.name }}</span>
|
|
45
106
|
</span>
|
|
46
|
-
<span class="text-grey text-caption" v-if="item?.members?.length > 0"
|
|
47
|
-
members)</span
|
|
107
|
+
<span class="text-grey text-caption" v-if="item?.members?.length > 0"
|
|
108
|
+
>( +{{ item?.members?.length }} members)</span
|
|
109
|
+
>
|
|
48
110
|
</template>
|
|
49
111
|
|
|
50
112
|
<template v-slot:item.type-company="{ item }">
|
|
@@ -52,7 +114,7 @@
|
|
|
52
114
|
<v-icon icon="mdi-user" size="15" />
|
|
53
115
|
<span v-if="item.type === 'contractor'" class="text-capitalize">{{
|
|
54
116
|
formatCamelCaseToWords(item.contractorType)
|
|
55
|
-
|
|
117
|
+
}}</span>
|
|
56
118
|
<span v-else class="text-capitalize">{{ formatType(item) }}</span>
|
|
57
119
|
</span>
|
|
58
120
|
<span class="d-flex align-center ga-2">
|
|
@@ -88,109 +150,255 @@
|
|
|
88
150
|
<template v-slot:item.checkin-out="{ item }">
|
|
89
151
|
<v-row no-gutters class="d-flex flex-column py-2">
|
|
90
152
|
<span class="d-flex align-center ga-2">
|
|
91
|
-
<v-icon
|
|
153
|
+
<v-icon
|
|
154
|
+
icon="mdi-clock-time-four-outline"
|
|
155
|
+
color="green"
|
|
156
|
+
size="20"
|
|
157
|
+
/>
|
|
92
158
|
<span class="text-capitalize">{{
|
|
93
159
|
UTCToLocalTIme(item.checkIn) || "-"
|
|
94
160
|
}}</span>
|
|
95
161
|
<span>
|
|
96
|
-
<v-icon
|
|
97
|
-
|
|
162
|
+
<v-icon
|
|
163
|
+
v-if="item?.snapshotEntryImage"
|
|
164
|
+
size="17"
|
|
165
|
+
icon="mdi-image"
|
|
166
|
+
@click.stop="handleViewImage(item.snapshotEntryImage)"
|
|
167
|
+
/>
|
|
98
168
|
</span>
|
|
99
169
|
</span>
|
|
100
|
-
|
|
170
|
+
|
|
171
|
+
<span
|
|
172
|
+
v-if="
|
|
173
|
+
!item.checkOut &&
|
|
174
|
+
(item?.status === 'registered' ||
|
|
175
|
+
item?.status === 'unregistered') &&
|
|
176
|
+
canCheckoutVisitor
|
|
177
|
+
"
|
|
178
|
+
>
|
|
179
|
+
<span
|
|
180
|
+
class="d-flex align-center ga-2"
|
|
181
|
+
v-bind="menuProps"
|
|
182
|
+
style="cursor: pointer"
|
|
183
|
+
>
|
|
184
|
+
<v-icon
|
|
185
|
+
icon="mdi-clock-time-eight-outline"
|
|
186
|
+
color="red"
|
|
187
|
+
size="20"
|
|
188
|
+
/>
|
|
189
|
+
<v-btn
|
|
190
|
+
size="x-small"
|
|
191
|
+
class="text-capitalize"
|
|
192
|
+
color="red"
|
|
193
|
+
text="Checkout"
|
|
194
|
+
:loading="
|
|
195
|
+
loading.checkingOut && item?._id === selectedVisitorId
|
|
196
|
+
"
|
|
197
|
+
/>
|
|
198
|
+
</span>
|
|
199
|
+
</span>
|
|
200
|
+
<span v-else class="d-flex align-center ga-2">
|
|
101
201
|
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
|
|
102
202
|
<template v-if="item.checkOut">
|
|
103
203
|
<span class="text-capitalize">{{
|
|
104
204
|
UTCToLocalTIme(item.checkOut) || "-"
|
|
105
205
|
}}</span>
|
|
106
206
|
<span>
|
|
107
|
-
<v-icon
|
|
108
|
-
|
|
207
|
+
<v-icon
|
|
208
|
+
v-if="item?.snapshotExitImage"
|
|
209
|
+
size="17"
|
|
210
|
+
icon="mdi-image"
|
|
211
|
+
@click.stop="handleViewImage(item.snapshotExitImage)"
|
|
212
|
+
/>
|
|
109
213
|
</span>
|
|
110
214
|
<span v-if="item?.manualCheckout">
|
|
111
|
-
<TooltipInfo
|
|
215
|
+
<TooltipInfo
|
|
216
|
+
text="Manual Checkout"
|
|
217
|
+
density="compact"
|
|
218
|
+
size="x-small"
|
|
219
|
+
/>
|
|
112
220
|
</span>
|
|
113
221
|
</template>
|
|
114
|
-
<span v-else-if="!item?.checkOut && (item?.status === 'registered' || item?.status === 'unregistered')">
|
|
115
|
-
<v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
|
|
116
|
-
:loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
|
|
117
|
-
v-if="canCheckoutVisitor" />
|
|
118
|
-
</span>
|
|
119
222
|
</span>
|
|
120
|
-
<div v-if="(item.visitorPass?.length ?? 0) > 0 || (item.passKeys?.length ?? 0) > 0"
|
|
121
|
-
class="d-flex flex-wrap ga-1 mt-1" @click.stop="handleCheckout(item._id)">
|
|
122
|
-
<v-chip v-for="pass in item.visitorPass" :key="(pass as any)._id ?? (pass as any).keyId"
|
|
123
|
-
prepend-icon="mdi-card-bulleted-outline" size="x-small" variant="tonal" color="blue">
|
|
124
|
-
{{ (pass as any)?.prefixAndName }}
|
|
125
|
-
</v-chip>
|
|
126
|
-
<v-chip v-for="key in item.passKeys" :key="(key as any)._id ?? (key as any).keyId" prepend-icon="mdi-key"
|
|
127
|
-
size="x-small" variant="tonal" color="orange">
|
|
128
|
-
{{ (key as any)?.prefixAndName }}
|
|
129
|
-
</v-chip>
|
|
130
|
-
</div>
|
|
131
|
-
<div v-if="showAddPassKeyButton(item)" class="d-flex flex-wrap ga-1 mt-1 mt-2"
|
|
132
|
-
@click.stop="handleCheckout(item._id)">
|
|
133
|
-
<v-chip size="x-small" variant="tonal" prepend-icon="mdi-key" @click.stop="handleOpenAddPassKey(item)">
|
|
134
|
-
Add Pass/Key
|
|
135
|
-
</v-chip>
|
|
136
223
|
|
|
224
|
+
<div
|
|
225
|
+
v-if="showAddPassKeyButton(item)"
|
|
226
|
+
class="d-flex flex-wrap ga-1 mt-1"
|
|
227
|
+
v-bind="menuProps"
|
|
228
|
+
style="cursor: pointer"
|
|
229
|
+
>
|
|
230
|
+
<v-chip
|
|
231
|
+
size="x-small"
|
|
232
|
+
variant="tonal"
|
|
233
|
+
prepend-icon="mdi-key"
|
|
234
|
+
@click.stop="handleOpenAddPassKey(item)"
|
|
235
|
+
>Add Pass/Key</v-chip
|
|
236
|
+
>
|
|
137
237
|
</div>
|
|
238
|
+
|
|
239
|
+
<v-menu :close-on-content-click="true">
|
|
240
|
+
<template v-slot:activator="{ props: menuProps }">
|
|
241
|
+
<div
|
|
242
|
+
v-if="
|
|
243
|
+
(item.visitorPass?.length ?? 0) > 0 ||
|
|
244
|
+
(item.passKeys?.length ?? 0) > 0
|
|
245
|
+
"
|
|
246
|
+
class="d-flex flex-wrap ga-1 mt-1"
|
|
247
|
+
v-bind="menuProps"
|
|
248
|
+
style="cursor: pointer"
|
|
249
|
+
>
|
|
250
|
+
<v-chip
|
|
251
|
+
v-for="pass in item.visitorPass"
|
|
252
|
+
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
253
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
254
|
+
size="x-small"
|
|
255
|
+
variant="tonal"
|
|
256
|
+
color="blue"
|
|
257
|
+
>
|
|
258
|
+
{{ (pass as any)?.prefixAndName }}
|
|
259
|
+
</v-chip>
|
|
260
|
+
<v-chip
|
|
261
|
+
v-for="key in item.passKeys"
|
|
262
|
+
:key="(key as any)._id ?? (key as any).keyId"
|
|
263
|
+
prepend-icon="mdi-key"
|
|
264
|
+
size="x-small"
|
|
265
|
+
variant="tonal"
|
|
266
|
+
color="orange"
|
|
267
|
+
>
|
|
268
|
+
{{ (key as any)?.prefixAndName }}
|
|
269
|
+
</v-chip>
|
|
270
|
+
</div>
|
|
271
|
+
</template>
|
|
272
|
+
<v-list density="compact">
|
|
273
|
+
<v-list-item
|
|
274
|
+
prepend-icon="mdi-logout"
|
|
275
|
+
title="Checkout"
|
|
276
|
+
@click.stop="handleCheckout(item._id)"
|
|
277
|
+
/>
|
|
278
|
+
<v-list-item
|
|
279
|
+
v-if="
|
|
280
|
+
(item.visitorPass?.length ?? 0) > 0 ||
|
|
281
|
+
(item.passKeys?.length ?? 0) > 0
|
|
282
|
+
"
|
|
283
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
284
|
+
:title="item.checkOut ? 'View Pass/Key' : 'Edit Pass/Key'"
|
|
285
|
+
@click.stop="handleOpenEditPassKey(item)"
|
|
286
|
+
/>
|
|
287
|
+
<v-list-item
|
|
288
|
+
v-if="showAddPassKeyButton(item)"
|
|
289
|
+
prepend-icon="mdi-plus"
|
|
290
|
+
title="Add Pass/Key"
|
|
291
|
+
@click.stop="handleOpenAddPassKey(item)"
|
|
292
|
+
/>
|
|
293
|
+
</v-list>
|
|
294
|
+
</v-menu>
|
|
138
295
|
</v-row>
|
|
139
296
|
</template>
|
|
140
297
|
|
|
141
298
|
<template v-slot:item.action="{ item }">
|
|
142
|
-
<v-btn
|
|
143
|
-
|
|
299
|
+
<v-btn
|
|
300
|
+
v-if="activeTab === 'unregistered' && canAddVisitor"
|
|
301
|
+
size="small"
|
|
302
|
+
color="primary"
|
|
303
|
+
text="Register"
|
|
304
|
+
@click.stop="handleRegistrationUnregisteredVisitor(item)"
|
|
305
|
+
/>
|
|
144
306
|
</template>
|
|
145
307
|
|
|
146
308
|
<template v-slot:item.checkInRemarks="{ item }">
|
|
147
309
|
<span>
|
|
148
|
-
<v-btn
|
|
149
|
-
|
|
150
|
-
|
|
310
|
+
<v-btn
|
|
311
|
+
variant="text"
|
|
312
|
+
color="blue-darken-2"
|
|
313
|
+
density="compact"
|
|
314
|
+
size="small"
|
|
315
|
+
@click.stop="handleOpenRemarks(item, 'checkIn')"
|
|
316
|
+
>{{ item.checkInRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
|
|
317
|
+
>
|
|
151
318
|
</span>
|
|
152
319
|
</template>
|
|
153
320
|
<template v-slot:item.checkOutRemarks="{ item }">
|
|
154
321
|
<span>
|
|
155
|
-
<v-btn
|
|
156
|
-
|
|
157
|
-
|
|
322
|
+
<v-btn
|
|
323
|
+
variant="text"
|
|
324
|
+
color="blue-darken-2"
|
|
325
|
+
density="compact"
|
|
326
|
+
size="small"
|
|
327
|
+
@click.stop="handleOpenRemarks(item, 'checkOut')"
|
|
328
|
+
>{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks" }}</v-btn
|
|
329
|
+
>
|
|
158
330
|
</span>
|
|
159
331
|
</template>
|
|
160
|
-
|
|
161
332
|
</TableMain>
|
|
162
333
|
|
|
163
334
|
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
164
|
-
<VisitorFormSelection
|
|
335
|
+
<VisitorFormSelection
|
|
336
|
+
:mode="mode"
|
|
337
|
+
@cancel="dialog.showSelection = false"
|
|
338
|
+
@select="handleSelectVisitorType"
|
|
339
|
+
/>
|
|
165
340
|
</v-dialog>
|
|
166
341
|
|
|
167
|
-
<v-dialog
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
342
|
+
<v-dialog
|
|
343
|
+
v-model="dialog.showForm"
|
|
344
|
+
v-if="activeVisitorFormType"
|
|
345
|
+
width="450"
|
|
346
|
+
persistent
|
|
347
|
+
>
|
|
348
|
+
<VisitorForm
|
|
349
|
+
:mode="mode"
|
|
350
|
+
:org="orgId"
|
|
351
|
+
:site="siteId"
|
|
352
|
+
:visitor-data="selectedVisitorDataObject"
|
|
353
|
+
:type="activeVisitorFormType"
|
|
354
|
+
@back="handleClickBack"
|
|
355
|
+
@done="handleVisitorFormDone"
|
|
356
|
+
@done:more="handleVisitorFormCreateMore"
|
|
357
|
+
@close:all="handleCloseAll"
|
|
358
|
+
/>
|
|
171
359
|
</v-dialog>
|
|
172
360
|
|
|
173
361
|
<v-dialog v-model="dialog.viewVisitor" width="450" persistent>
|
|
174
|
-
<VehicleUpdateMoreAction
|
|
175
|
-
|
|
176
|
-
|
|
362
|
+
<VehicleUpdateMoreAction
|
|
363
|
+
title="Preview"
|
|
364
|
+
:can-update="false"
|
|
365
|
+
:can-delete="false"
|
|
366
|
+
@close="dialog.viewVisitor = false"
|
|
367
|
+
edit-button-label="Edit Visitor"
|
|
368
|
+
>
|
|
177
369
|
<template v-slot:content>
|
|
178
370
|
<v-row no-gutters class="mb-4">
|
|
179
371
|
<v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
|
|
180
|
-
<span
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
372
|
+
<span
|
|
373
|
+
v-if="
|
|
374
|
+
key === 'checkOut' &&
|
|
375
|
+
!selectedVisitorObject[key] &&
|
|
376
|
+
canCheckoutVisitor
|
|
377
|
+
"
|
|
378
|
+
class="d-flex align-center"
|
|
379
|
+
>
|
|
185
380
|
<strong>{{ label }}:</strong>
|
|
186
|
-
<v-btn
|
|
187
|
-
|
|
381
|
+
<v-btn
|
|
382
|
+
size="x-small"
|
|
383
|
+
class="ml-3 text-capitalize"
|
|
384
|
+
color="red"
|
|
385
|
+
text="Checkout"
|
|
386
|
+
:disabled="loading.checkingOut"
|
|
387
|
+
@click="handleCheckout(selectedVisitorId as string)"
|
|
388
|
+
/>
|
|
188
389
|
</span>
|
|
189
390
|
|
|
190
|
-
<span
|
|
191
|
-
|
|
391
|
+
<span
|
|
392
|
+
v-else-if="selectedVisitorObject[key]"
|
|
393
|
+
class="d-flex ga-3 align-center"
|
|
394
|
+
><strong>{{ label }}:</strong>
|
|
192
395
|
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
193
|
-
<TooltipInfo
|
|
396
|
+
<TooltipInfo
|
|
397
|
+
v-if="key === 'checkOut'"
|
|
398
|
+
text="Manual Checkout"
|
|
399
|
+
density="compact"
|
|
400
|
+
size="x-small"
|
|
401
|
+
/>
|
|
194
402
|
</span>
|
|
195
403
|
</v-col>
|
|
196
404
|
</v-row>
|
|
@@ -198,17 +406,15 @@
|
|
|
198
406
|
</VehicleUpdateMoreAction>
|
|
199
407
|
</v-dialog>
|
|
200
408
|
|
|
201
|
-
<v-dialog v-model="dialog.deleteConfirmation" width="450" persistent>
|
|
202
|
-
<CardDeleteConfirmation prompt-title="Are you sure want to delete this visitor?"
|
|
203
|
-
:loading="loading.deletingVisitor" @close="dialog.deleteConfirmation = false"
|
|
204
|
-
@delete="handleProceedDeleteVisitor" />
|
|
205
|
-
</v-dialog>
|
|
206
|
-
|
|
207
409
|
<v-dialog v-model="dialog.snapshotImage" max-width="700">
|
|
208
410
|
<v-card>
|
|
209
411
|
<v-card-title class="d-flex justify-space-between align-center">
|
|
210
412
|
<span>Snapshot</span>
|
|
211
|
-
<v-btn
|
|
413
|
+
<v-btn
|
|
414
|
+
icon="mdi-close"
|
|
415
|
+
variant="text"
|
|
416
|
+
@click="dialog.snapshotImage = false"
|
|
417
|
+
/>
|
|
212
418
|
</v-card-title>
|
|
213
419
|
<v-card-text class="pa-2 d-flex justify-center">
|
|
214
420
|
<v-img :src="snapshotImageUrl" max-height="600" contain />
|
|
@@ -218,23 +424,51 @@
|
|
|
218
424
|
|
|
219
425
|
<v-dialog v-model="dialog.remarks" max-width="450" persistent>
|
|
220
426
|
<v-card>
|
|
221
|
-
<v-card-title
|
|
222
|
-
|
|
223
|
-
|
|
427
|
+
<v-card-title
|
|
428
|
+
class="d-flex justify-space-between align-center pt-4 px-4"
|
|
429
|
+
>
|
|
430
|
+
<span>{{
|
|
431
|
+
remarksType === "checkIn" ? "Check-In Remarks" : "Check-Out Remarks"
|
|
432
|
+
}}</span>
|
|
433
|
+
<v-btn
|
|
434
|
+
icon="mdi-close"
|
|
435
|
+
variant="text"
|
|
436
|
+
@click="dialog.remarks = false"
|
|
437
|
+
/>
|
|
224
438
|
</v-card-title>
|
|
225
439
|
<v-card-text class="px-4 pb-2">
|
|
226
|
-
<v-textarea
|
|
227
|
-
|
|
440
|
+
<v-textarea
|
|
441
|
+
v-model="remarksInput"
|
|
442
|
+
label="Remarks"
|
|
443
|
+
rows="4"
|
|
444
|
+
auto-grow
|
|
445
|
+
variant="outlined"
|
|
446
|
+
:readonly="remarksViewOnly"
|
|
447
|
+
/>
|
|
228
448
|
</v-card-text>
|
|
229
449
|
<v-toolbar class="pa-0" density="compact">
|
|
230
450
|
<v-row no-gutters>
|
|
231
451
|
<v-col cols="6">
|
|
232
|
-
<v-btn
|
|
233
|
-
|
|
452
|
+
<v-btn
|
|
453
|
+
variant="text"
|
|
454
|
+
block
|
|
455
|
+
:disabled="loading.savingRemarks"
|
|
456
|
+
@click="dialog.remarks = false"
|
|
457
|
+
>Cancel</v-btn
|
|
458
|
+
>
|
|
234
459
|
</v-col>
|
|
235
460
|
<v-col cols="6">
|
|
236
|
-
<v-btn
|
|
237
|
-
|
|
461
|
+
<v-btn
|
|
462
|
+
color="primary"
|
|
463
|
+
variant="flat"
|
|
464
|
+
height="48"
|
|
465
|
+
rounded="0"
|
|
466
|
+
block
|
|
467
|
+
:loading="loading.savingRemarks"
|
|
468
|
+
:disabled="!remarksInput.trim()"
|
|
469
|
+
@click="handleSaveRemarks"
|
|
470
|
+
>Save</v-btn
|
|
471
|
+
>
|
|
238
472
|
</v-col>
|
|
239
473
|
</v-row>
|
|
240
474
|
</v-toolbar>
|
|
@@ -244,54 +478,134 @@
|
|
|
244
478
|
<v-dialog v-model="dialog.returnPassesKeys" max-width="450" persistent>
|
|
245
479
|
<v-card>
|
|
246
480
|
<v-toolbar density="compact" color="">
|
|
247
|
-
<v-row
|
|
481
|
+
<v-row
|
|
482
|
+
no-gutters
|
|
483
|
+
class="d-flex fill-height justify-space-between align-center px-4"
|
|
484
|
+
>
|
|
248
485
|
<span class="font-weight-bold">Return Passes & Keys</span>
|
|
249
|
-
<v-btn
|
|
486
|
+
<v-btn
|
|
487
|
+
icon="mdi-close"
|
|
488
|
+
variant="text"
|
|
489
|
+
@click="dialog.returnPassesKeys = false"
|
|
490
|
+
/>
|
|
250
491
|
</v-row>
|
|
251
492
|
</v-toolbar>
|
|
252
493
|
<v-card-text class="px-4 pb-2">
|
|
253
|
-
<p class="text-body-2 mb-3">
|
|
494
|
+
<p class="text-body-2 mb-3">
|
|
495
|
+
Please ensure all passes and keys are returned before checking out.
|
|
496
|
+
</p>
|
|
254
497
|
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
255
498
|
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
256
|
-
<v-row
|
|
257
|
-
|
|
258
|
-
|
|
499
|
+
<v-row
|
|
500
|
+
no-gutters
|
|
501
|
+
v-for="pass in passReturnStatuses"
|
|
502
|
+
:key="(pass as any)._id ?? (pass as any).keyId"
|
|
503
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
|
|
504
|
+
>
|
|
505
|
+
<v-chip
|
|
506
|
+
prepend-icon="mdi-card-bulleted-outline"
|
|
507
|
+
size="small"
|
|
508
|
+
variant="tonal"
|
|
509
|
+
color="blue"
|
|
510
|
+
>
|
|
259
511
|
{{ (pass as any)?.prefixAndName }}
|
|
260
512
|
</v-chip>
|
|
261
|
-
<v-select
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
density="compact"
|
|
513
|
+
<v-select
|
|
514
|
+
hide-details
|
|
515
|
+
max-width="200px"
|
|
516
|
+
density="compact"
|
|
517
|
+
:items="passStatusOptions"
|
|
518
|
+
item-title="label"
|
|
519
|
+
item-value="value"
|
|
520
|
+
v-model="pass.status"
|
|
521
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
522
|
+
></v-select>
|
|
523
|
+
<v-textarea
|
|
524
|
+
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
525
|
+
no-resize
|
|
526
|
+
rows="3"
|
|
527
|
+
class="w-100"
|
|
528
|
+
density="compact"
|
|
529
|
+
v-model="pass.remarks"
|
|
530
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
531
|
+
></v-textarea>
|
|
265
532
|
</v-row>
|
|
266
533
|
</div>
|
|
267
|
-
<div v-if="
|
|
534
|
+
<div v-if="keyReturnStatuses.length > 0" class="mb-2">
|
|
268
535
|
<p class="text-caption text-medium-emphasis mb-1">Keys</p>
|
|
269
|
-
<v-row
|
|
270
|
-
|
|
271
|
-
|
|
536
|
+
<v-row
|
|
537
|
+
no-gutters
|
|
538
|
+
v-for="key in keyReturnStatuses"
|
|
539
|
+
:key="(key as any)._id ?? (key as any).keyId"
|
|
540
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2"
|
|
541
|
+
>
|
|
542
|
+
<v-chip
|
|
543
|
+
prepend-icon="mdi-key"
|
|
544
|
+
size="small"
|
|
545
|
+
variant="tonal"
|
|
546
|
+
color="orange"
|
|
547
|
+
>
|
|
272
548
|
{{ (key as any)?.prefixAndName }}
|
|
273
549
|
</v-chip>
|
|
274
|
-
<v-select
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
density="compact"
|
|
550
|
+
<v-select
|
|
551
|
+
hide-details
|
|
552
|
+
max-width="200px"
|
|
553
|
+
density="compact"
|
|
554
|
+
:items="passStatusOptions"
|
|
555
|
+
item-title="label"
|
|
556
|
+
item-value="value"
|
|
557
|
+
v-model="key.status"
|
|
558
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
559
|
+
></v-select>
|
|
560
|
+
<v-textarea
|
|
561
|
+
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
562
|
+
no-resize
|
|
563
|
+
rows="3"
|
|
564
|
+
class="w-100"
|
|
565
|
+
density="compact"
|
|
566
|
+
v-model="key.remarks"
|
|
567
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
568
|
+
></v-textarea>
|
|
278
569
|
</v-row>
|
|
279
570
|
</div>
|
|
280
571
|
|
|
281
572
|
<v-row no-gutters class="my-5">
|
|
282
|
-
<v-btn
|
|
283
|
-
|
|
573
|
+
<v-btn
|
|
574
|
+
variant="flat"
|
|
575
|
+
color="blue"
|
|
576
|
+
density="comfortable"
|
|
577
|
+
class="text-capitalize"
|
|
578
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
579
|
+
:loading="loading.updatingPassKeys"
|
|
580
|
+
@click.stop="handleUpdatePassKeys"
|
|
581
|
+
>Update Pass/Keys</v-btn
|
|
582
|
+
>
|
|
284
583
|
</v-row>
|
|
285
584
|
</v-card-text>
|
|
286
585
|
<v-toolbar class="pa-0" density="compact">
|
|
287
586
|
<v-row no-gutters>
|
|
288
587
|
<v-col cols="6">
|
|
289
|
-
<v-btn
|
|
290
|
-
|
|
588
|
+
<v-btn
|
|
589
|
+
variant="text"
|
|
590
|
+
block
|
|
591
|
+
@click="dialog.returnPassesKeys = false"
|
|
592
|
+
>Close</v-btn
|
|
593
|
+
>
|
|
291
594
|
</v-col>
|
|
292
595
|
<v-col cols="6">
|
|
293
|
-
<v-btn
|
|
294
|
-
|
|
596
|
+
<v-btn
|
|
597
|
+
color="red"
|
|
598
|
+
variant="flat"
|
|
599
|
+
height="48"
|
|
600
|
+
rounded="0"
|
|
601
|
+
block
|
|
602
|
+
:loading="loading.checkingOut"
|
|
603
|
+
:disabled="
|
|
604
|
+
!canConfirmCheckout || selectedVisitorObject.checkOut
|
|
605
|
+
"
|
|
606
|
+
@click="proceedCheckout"
|
|
607
|
+
>Confirm Checkout</v-btn
|
|
608
|
+
>
|
|
295
609
|
</v-col>
|
|
296
610
|
</v-row>
|
|
297
611
|
</v-toolbar>
|
|
@@ -299,10 +613,40 @@
|
|
|
299
613
|
</v-dialog>
|
|
300
614
|
|
|
301
615
|
<v-dialog v-model="dialog.addPassKey" width="450" persistent>
|
|
302
|
-
<AddPassKeyToVisitor
|
|
303
|
-
|
|
616
|
+
<AddPassKeyToVisitor
|
|
617
|
+
mode="add"
|
|
618
|
+
:visitor="selectedVisitorDataObject"
|
|
619
|
+
:site="siteId"
|
|
620
|
+
@close="dialog.addPassKey = false"
|
|
621
|
+
@done="
|
|
622
|
+
() => {
|
|
623
|
+
dialog.addPassKey = false;
|
|
624
|
+
getVisitorRefresh();
|
|
625
|
+
}
|
|
626
|
+
"
|
|
627
|
+
/>
|
|
628
|
+
</v-dialog>
|
|
629
|
+
|
|
630
|
+
<v-dialog v-model="dialog.editPassKey" width="450" persistent>
|
|
631
|
+
<AddPassKeyToVisitor
|
|
632
|
+
mode="edit"
|
|
633
|
+
:visitor="selectedVisitorDataObject"
|
|
634
|
+
:site="siteId"
|
|
635
|
+
@close="dialog.editPassKey = false"
|
|
636
|
+
@done="
|
|
637
|
+
() => {
|
|
638
|
+
dialog.editPassKey = false;
|
|
639
|
+
getVisitorRefresh();
|
|
640
|
+
}
|
|
641
|
+
"
|
|
642
|
+
/>
|
|
304
643
|
</v-dialog>
|
|
305
644
|
|
|
645
|
+
<ScanVisitorQRCode
|
|
646
|
+
:dialog="dialog.scanVisitorQRCode"
|
|
647
|
+
@close-dialog="dialog.scanVisitorQRCode = false"
|
|
648
|
+
/>
|
|
649
|
+
|
|
306
650
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
307
651
|
</v-row>
|
|
308
652
|
</template>
|
|
@@ -317,15 +661,15 @@ const props = defineProps({
|
|
|
317
661
|
type: Boolean,
|
|
318
662
|
default: true,
|
|
319
663
|
},
|
|
320
|
-
|
|
664
|
+
canScanVisitorQRCode: {
|
|
321
665
|
type: Boolean,
|
|
322
666
|
default: true,
|
|
323
667
|
},
|
|
324
|
-
|
|
668
|
+
canViewVisitor: {
|
|
325
669
|
type: Boolean,
|
|
326
670
|
default: true,
|
|
327
671
|
},
|
|
328
|
-
|
|
672
|
+
canUpdateVisitor: {
|
|
329
673
|
type: Boolean,
|
|
330
674
|
default: true,
|
|
331
675
|
},
|
|
@@ -335,21 +679,16 @@ const props = defineProps({
|
|
|
335
679
|
},
|
|
336
680
|
});
|
|
337
681
|
|
|
338
|
-
const {
|
|
339
|
-
|
|
340
|
-
visitorSelection,
|
|
341
|
-
typeFieldMap,
|
|
342
|
-
deleteVisitor,
|
|
343
|
-
updateVisitor,
|
|
344
|
-
} = useVisitor();
|
|
682
|
+
const { getVisitors, visitorSelection, typeFieldMap, updateVisitor } =
|
|
683
|
+
useVisitor();
|
|
345
684
|
const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
|
|
346
685
|
useUtils();
|
|
347
686
|
const { formatLocation } = useSecurityUtils();
|
|
348
687
|
const { getFileUrlAnpr } = useFile();
|
|
349
688
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
350
689
|
|
|
351
|
-
const route = useRoute()
|
|
352
|
-
const router = useRouter()
|
|
690
|
+
const route = useRoute();
|
|
691
|
+
const router = useRouter();
|
|
353
692
|
const { org: orgId, site: siteId } = route.params as {
|
|
354
693
|
org: string;
|
|
355
694
|
site: string;
|
|
@@ -377,7 +716,6 @@ const messageColor = ref("");
|
|
|
377
716
|
const messageSnackbar = ref(false);
|
|
378
717
|
|
|
379
718
|
const loading = reactive({
|
|
380
|
-
deletingVisitor: false,
|
|
381
719
|
fetchingVisitors: false,
|
|
382
720
|
checkingOut: false,
|
|
383
721
|
savingRemarks: false,
|
|
@@ -388,15 +726,16 @@ const dialog = reactive({
|
|
|
388
726
|
showSelection: false,
|
|
389
727
|
showForm: false,
|
|
390
728
|
viewVisitor: false,
|
|
391
|
-
deleteConfirmation: false,
|
|
392
729
|
snapshotImage: false,
|
|
393
730
|
remarks: false,
|
|
394
731
|
returnPassesKeys: false,
|
|
395
|
-
addPassKey: false
|
|
732
|
+
addPassKey: false,
|
|
733
|
+
editPassKey: false,
|
|
734
|
+
scanVisitorQRCode: false,
|
|
396
735
|
});
|
|
397
736
|
|
|
398
737
|
const snapshotImageUrl = ref("");
|
|
399
|
-
const remarksType = ref<
|
|
738
|
+
const remarksType = ref<"checkIn" | "checkOut">("checkIn");
|
|
400
739
|
const remarksInput = ref("");
|
|
401
740
|
const remarksViewOnly = ref(false);
|
|
402
741
|
|
|
@@ -406,10 +745,16 @@ const headers = computed(() => [
|
|
|
406
745
|
{ title: "Location", value: "location" },
|
|
407
746
|
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" },
|
|
408
747
|
{ title: "Check In/Out", value: "checkin-out" },
|
|
409
|
-
...(activeTab.value === "resident-transactions"
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
748
|
+
...(activeTab.value === "resident-transactions"
|
|
749
|
+
? [
|
|
750
|
+
{ title: "Check-in Remarks", value: "checkInRemarks" },
|
|
751
|
+
{ title: "Check-out Remarks", value: "checkOutRemarks" },
|
|
752
|
+
]
|
|
753
|
+
: []),
|
|
754
|
+
...(activeTab.value === "unregistered"
|
|
755
|
+
? [{ title: "Action", value: "action" }]
|
|
756
|
+
: []),
|
|
757
|
+
]);
|
|
413
758
|
|
|
414
759
|
const tabOptions = [
|
|
415
760
|
{ name: "Registered", value: "registered" },
|
|
@@ -418,6 +763,19 @@ const tabOptions = [
|
|
|
418
763
|
{ name: "Resident Transactions", value: "resident-transactions" },
|
|
419
764
|
];
|
|
420
765
|
|
|
766
|
+
function normalizeDateOnly(value: string) {
|
|
767
|
+
if (!value) return "";
|
|
768
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
769
|
+
|
|
770
|
+
const parsedDate = new Date(value);
|
|
771
|
+
if (Number.isNaN(parsedDate.getTime())) return "";
|
|
772
|
+
|
|
773
|
+
const year = parsedDate.getFullYear();
|
|
774
|
+
const month = String(parsedDate.getMonth() + 1).padStart(2, "0");
|
|
775
|
+
const day = String(parsedDate.getDate()).padStart(2, "0");
|
|
776
|
+
return `${year}-${month}-${day}`;
|
|
777
|
+
}
|
|
778
|
+
|
|
421
779
|
const selectedVisitorDataObject = computed(() => {
|
|
422
780
|
return items.value.find((x: any) => x?._id === selectedVisitorId.value) || {};
|
|
423
781
|
});
|
|
@@ -439,7 +797,6 @@ const formattedFields = {
|
|
|
439
797
|
checkOut: "Check Out",
|
|
440
798
|
snapshotExitImage: "Exit Image",
|
|
441
799
|
remarks: "Remarks",
|
|
442
|
-
|
|
443
800
|
} as const;
|
|
444
801
|
|
|
445
802
|
function filterTypeSelectionLabel() {
|
|
@@ -452,14 +809,14 @@ const passStatusOptions = [
|
|
|
452
809
|
{ label: "Not Returned", value: "In Use" },
|
|
453
810
|
{ label: "Damaged", value: "Damaged" },
|
|
454
811
|
{ label: "Lost", value: "Lost" },
|
|
455
|
-
]
|
|
812
|
+
];
|
|
456
813
|
|
|
457
814
|
function toRoute(tab: any) {
|
|
458
|
-
items.value = []
|
|
815
|
+
items.value = [];
|
|
459
816
|
|
|
460
817
|
const obj = tabOptions.find((x) => x.value === tab);
|
|
461
818
|
if (!obj) return;
|
|
462
|
-
page.value = 1
|
|
819
|
+
page.value = 1;
|
|
463
820
|
navigateTo({
|
|
464
821
|
name: routeName,
|
|
465
822
|
params: {
|
|
@@ -470,54 +827,51 @@ function toRoute(tab: any) {
|
|
|
470
827
|
// },
|
|
471
828
|
});
|
|
472
829
|
}
|
|
473
|
-
|
|
474
830
|
const {
|
|
475
831
|
data: getVisitorReq,
|
|
476
832
|
refresh: getVisitorRefresh,
|
|
477
833
|
pending: getVisitorPending,
|
|
478
834
|
} = await useLazyAsyncData(
|
|
479
|
-
|
|
835
|
+
"get-all-visitors",
|
|
480
836
|
async () => {
|
|
481
|
-
|
|
482
|
-
// Optional delay (if needed)
|
|
483
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
484
|
-
|
|
485
837
|
const params: any = {
|
|
486
838
|
page: page.value,
|
|
487
839
|
site: siteId,
|
|
488
840
|
search: searchInput.value,
|
|
489
|
-
dateTo: dateTo.value,
|
|
490
|
-
dateFrom: dateFrom.value,
|
|
491
|
-
type:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
// } else if (activeTab.value === "guests") {
|
|
498
|
-
// params.type = "guest"
|
|
499
|
-
// params.status = "pending"
|
|
500
|
-
// }
|
|
841
|
+
dateTo: normalizeDateOnly(dateTo.value),
|
|
842
|
+
dateFrom: normalizeDateOnly(dateFrom.value),
|
|
843
|
+
type:
|
|
844
|
+
filterTypes.value.length === 0 && activeTab.value === "registered"
|
|
845
|
+
? "contractor,delivery,walk-in,pick-up,drop-off,guest"
|
|
846
|
+
: filterTypes.value.filter(Boolean).join(","),
|
|
847
|
+
checkedOut: displayNotCheckedOut.value ? false : undefined,
|
|
848
|
+
};
|
|
501
849
|
|
|
502
850
|
if (activeTab.value === "guests") {
|
|
503
|
-
params.type = "guest"
|
|
504
|
-
params.status = "pending"
|
|
851
|
+
params.type = "guest";
|
|
852
|
+
params.status = "pending";
|
|
505
853
|
} else if (activeTab.value === "resident-transactions") {
|
|
506
|
-
params.status = "registered"
|
|
507
|
-
params.type = "resident,tenant"
|
|
854
|
+
params.status = "registered";
|
|
855
|
+
params.type = "resident,tenant";
|
|
508
856
|
} else {
|
|
509
|
-
params.status = activeTab.value
|
|
857
|
+
params.status = activeTab.value;
|
|
510
858
|
}
|
|
511
859
|
|
|
512
|
-
return await getVisitors(params)
|
|
860
|
+
return await getVisitors(params);
|
|
513
861
|
},
|
|
514
862
|
{
|
|
515
863
|
watch: [
|
|
516
864
|
page,
|
|
865
|
+
searchInput,
|
|
866
|
+
dateFrom,
|
|
867
|
+
dateTo,
|
|
868
|
+
filterTypes,
|
|
869
|
+
activeTab,
|
|
870
|
+
displayNotCheckedOut,
|
|
517
871
|
],
|
|
518
|
-
immediate:
|
|
872
|
+
immediate: true,
|
|
519
873
|
}
|
|
520
|
-
)
|
|
874
|
+
);
|
|
521
875
|
|
|
522
876
|
watch(getVisitorReq, (newData: any) => {
|
|
523
877
|
if (newData) {
|
|
@@ -529,11 +883,16 @@ watch(getVisitorReq, (newData: any) => {
|
|
|
529
883
|
});
|
|
530
884
|
|
|
531
885
|
const selectedVisitorObject = computed(() => {
|
|
532
|
-
|
|
533
886
|
const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
|
|
534
887
|
if (!obj) return {};
|
|
535
888
|
const type = obj?.type as TVisitorType | undefined;
|
|
536
|
-
let includedKeys: string[] = [
|
|
889
|
+
let includedKeys: string[] = [
|
|
890
|
+
"checkIn",
|
|
891
|
+
"checkOut",
|
|
892
|
+
"plateNumber",
|
|
893
|
+
"snapshotEntryImage",
|
|
894
|
+
"snapshotExitImage",
|
|
895
|
+
];
|
|
537
896
|
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
538
897
|
return Object.fromEntries(
|
|
539
898
|
Object.entries(obj).filter(([key]) => includedKeys.includes(key))
|
|
@@ -554,8 +913,6 @@ function formatValues(key: string, value: any) {
|
|
|
554
913
|
return value;
|
|
555
914
|
}
|
|
556
915
|
|
|
557
|
-
|
|
558
|
-
|
|
559
916
|
function handleAddNew() {
|
|
560
917
|
dialog.showSelection = true;
|
|
561
918
|
activeVisitorFormType.value = null;
|
|
@@ -575,9 +932,17 @@ function showAddPassKeyButton(item: any) {
|
|
|
575
932
|
const hasPasses = (item?.visitorPass?.length ?? 0) > 0;
|
|
576
933
|
const hasKeys = (item?.passKeys?.length ?? 0) > 0;
|
|
577
934
|
|
|
578
|
-
const isTypeWithPassKey = ["contractor", "guest", "walk-in"].includes(
|
|
935
|
+
const isTypeWithPassKey = ["contractor", "guest", "walk-in"].includes(
|
|
936
|
+
item?.type
|
|
937
|
+
);
|
|
579
938
|
|
|
580
|
-
return
|
|
939
|
+
return (
|
|
940
|
+
!hasPasses &&
|
|
941
|
+
!hasKeys &&
|
|
942
|
+
isTypeWithPassKey &&
|
|
943
|
+
item?.status === "registered" &&
|
|
944
|
+
!item?.checkOut
|
|
945
|
+
);
|
|
581
946
|
}
|
|
582
947
|
|
|
583
948
|
function handleSelectVisitorType(type: TVisitorType) {
|
|
@@ -603,11 +968,6 @@ function handleVisitorFormCreateMore() {
|
|
|
603
968
|
dialog.showForm = false;
|
|
604
969
|
}
|
|
605
970
|
|
|
606
|
-
function handleDeleteVisitor() {
|
|
607
|
-
dialog.deleteConfirmation = true;
|
|
608
|
-
dialog.viewVisitor = false;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
971
|
function showMessage(msg: string, color: string) {
|
|
612
972
|
message.value = msg;
|
|
613
973
|
messageColor.value = color;
|
|
@@ -637,10 +997,19 @@ function handleOpenAddPassKey(item: any) {
|
|
|
637
997
|
dialog.addPassKey = true;
|
|
638
998
|
}
|
|
639
999
|
|
|
640
|
-
function
|
|
1000
|
+
function handleOpenEditPassKey(item: any) {
|
|
1001
|
+
selectedVisitorId.value = item?._id;
|
|
1002
|
+
if (selectedVisitorDataObject.value.checkOut) {
|
|
1003
|
+
dialog.returnPassesKeys = true;
|
|
1004
|
+
handleCheckout(selectedVisitorId.value as string);
|
|
1005
|
+
} else dialog.editPassKey = true;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function handleOpenRemarks(item: any, type: "checkIn" | "checkOut") {
|
|
641
1009
|
selectedVisitorId.value = item?._id;
|
|
642
1010
|
remarksType.value = type;
|
|
643
|
-
const existingRemarks =
|
|
1011
|
+
const existingRemarks =
|
|
1012
|
+
type === "checkIn" ? item?.checkInRemarks : item?.checkOutRemarks;
|
|
644
1013
|
remarksInput.value = existingRemarks || "";
|
|
645
1014
|
remarksViewOnly.value = !!existingRemarks;
|
|
646
1015
|
dialog.remarks = true;
|
|
@@ -650,16 +1019,20 @@ async function handleSaveRemarks() {
|
|
|
650
1019
|
if (!remarksInput.value.trim() || !selectedVisitorId.value) return;
|
|
651
1020
|
try {
|
|
652
1021
|
loading.savingRemarks = true;
|
|
653
|
-
const payload =
|
|
654
|
-
|
|
655
|
-
|
|
1022
|
+
const payload =
|
|
1023
|
+
remarksType.value === "checkIn"
|
|
1024
|
+
? { checkInRemarks: remarksInput.value.trim() }
|
|
1025
|
+
: { checkOutRemarks: remarksInput.value.trim() };
|
|
656
1026
|
await updateVisitor(selectedVisitorId.value, payload);
|
|
657
1027
|
showMessage("Remarks saved successfully!", "info");
|
|
658
1028
|
await getVisitorRefresh();
|
|
659
1029
|
dialog.remarks = false;
|
|
660
1030
|
} catch (error: any) {
|
|
661
1031
|
const errorMessage = error?.response?._data?.message;
|
|
662
|
-
showMessage(
|
|
1032
|
+
showMessage(
|
|
1033
|
+
errorMessage || "Something went wrong. Please try again later.",
|
|
1034
|
+
"error"
|
|
1035
|
+
);
|
|
663
1036
|
} finally {
|
|
664
1037
|
loading.savingRemarks = false;
|
|
665
1038
|
}
|
|
@@ -671,11 +1044,19 @@ async function handleUpdatePassKeys() {
|
|
|
671
1044
|
loading.updatingPassKeys = true;
|
|
672
1045
|
const payload: any = {};
|
|
673
1046
|
if (passReturnStatuses.value.length > 0) {
|
|
674
|
-
const passReturnStatusesPayload = passReturnStatuses.value.map(p => ({
|
|
1047
|
+
const passReturnStatusesPayload = passReturnStatuses.value.map((p) => ({
|
|
1048
|
+
keyId: p.keyId,
|
|
1049
|
+
status: p.status,
|
|
1050
|
+
remarks: p.remarks,
|
|
1051
|
+
}));
|
|
675
1052
|
payload.visitorPass = passReturnStatusesPayload;
|
|
676
1053
|
}
|
|
677
1054
|
if (keyReturnStatuses.value.length > 0) {
|
|
678
|
-
const keyReturnStatusesPayload = keyReturnStatuses.value.map(k => ({
|
|
1055
|
+
const keyReturnStatusesPayload = keyReturnStatuses.value.map((k) => ({
|
|
1056
|
+
keyId: k.keyId,
|
|
1057
|
+
status: k.status,
|
|
1058
|
+
remarks: k.remarks,
|
|
1059
|
+
}));
|
|
679
1060
|
payload.passKeys = keyReturnStatusesPayload;
|
|
680
1061
|
}
|
|
681
1062
|
await updateVisitor(selectedVisitorId.value, payload);
|
|
@@ -683,50 +1064,35 @@ async function handleUpdatePassKeys() {
|
|
|
683
1064
|
await getVisitorRefresh();
|
|
684
1065
|
} catch (error: any) {
|
|
685
1066
|
const errorMessage = error?.response?._data?.message;
|
|
686
|
-
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
687
|
-
} finally {
|
|
688
|
-
loading.updatingPassKeys = false;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
async function handleProceedDeleteVisitor() {
|
|
694
|
-
try {
|
|
695
|
-
loading.deletingVisitor = true;
|
|
696
|
-
const userId = selectedVisitorId.value;
|
|
697
|
-
const res = await deleteVisitor(userId as string);
|
|
698
|
-
if (res) {
|
|
699
|
-
showMessage("Visitor successfully deleted!", "info");
|
|
700
|
-
await getVisitorRefresh();
|
|
701
|
-
dialog.deleteConfirmation = false;
|
|
702
|
-
}
|
|
703
|
-
} catch (error: any) {
|
|
704
|
-
const errorMessage = error?.response?._data?.message;
|
|
705
|
-
console.log("[ERROR]", error);
|
|
706
1067
|
showMessage(
|
|
707
1068
|
errorMessage || "Something went wrong. Please try again later.",
|
|
708
1069
|
"error"
|
|
709
1070
|
);
|
|
710
1071
|
} finally {
|
|
711
|
-
loading.
|
|
1072
|
+
loading.updatingPassKeys = false;
|
|
712
1073
|
}
|
|
713
1074
|
}
|
|
714
1075
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1076
|
+
const passReturnStatuses = ref<
|
|
1077
|
+
{ keyId: string; status: string; prefixAndName: string; remarks: string }[]
|
|
1078
|
+
>([]);
|
|
1079
|
+
const keyReturnStatuses = ref<
|
|
1080
|
+
{ keyId: string; status: string; prefixAndName: string; remarks: string }[]
|
|
1081
|
+
>([]);
|
|
718
1082
|
|
|
719
1083
|
const canConfirmCheckout = computed(() => {
|
|
720
1084
|
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
721
1085
|
return allEntries.every((entry) => {
|
|
722
|
-
if (!entry.status || entry.status ===
|
|
723
|
-
if (
|
|
1086
|
+
if (!entry.status || entry.status === "In Use") return false;
|
|
1087
|
+
if (
|
|
1088
|
+
(entry.status === "Lost" || entry.status === "Damaged") &&
|
|
1089
|
+
!entry.remarks.trim()
|
|
1090
|
+
)
|
|
1091
|
+
return false;
|
|
724
1092
|
return true;
|
|
725
1093
|
});
|
|
726
1094
|
});
|
|
727
1095
|
|
|
728
|
-
|
|
729
|
-
|
|
730
1096
|
function handleCheckout(userId: string) {
|
|
731
1097
|
if (!userId) {
|
|
732
1098
|
showMessage("Invalid userId", "error");
|
|
@@ -740,8 +1106,22 @@ function handleCheckout(userId: string) {
|
|
|
740
1106
|
|
|
741
1107
|
if (hasPasses || hasKeys) {
|
|
742
1108
|
const visitor = items.value.find((x: any) => x?._id === userId);
|
|
743
|
-
passReturnStatuses.value = ((visitor?.visitorPass as any[]) || []).map(
|
|
744
|
-
|
|
1109
|
+
passReturnStatuses.value = ((visitor?.visitorPass as any[]) || []).map(
|
|
1110
|
+
(p: any) => ({
|
|
1111
|
+
keyId: p.keyId,
|
|
1112
|
+
status: p.status ?? "In Use",
|
|
1113
|
+
remarks: "",
|
|
1114
|
+
prefixAndName: p.prefixAndName,
|
|
1115
|
+
})
|
|
1116
|
+
);
|
|
1117
|
+
keyReturnStatuses.value = ((visitor?.passKeys as any[]) || []).map(
|
|
1118
|
+
(k: any) => ({
|
|
1119
|
+
keyId: k.keyId,
|
|
1120
|
+
status: k.status ?? "In Use",
|
|
1121
|
+
remarks: "",
|
|
1122
|
+
prefixAndName: k.prefixAndName,
|
|
1123
|
+
})
|
|
1124
|
+
);
|
|
745
1125
|
dialog.returnPassesKeys = true;
|
|
746
1126
|
return;
|
|
747
1127
|
}
|
|
@@ -756,13 +1136,25 @@ async function proceedCheckout() {
|
|
|
756
1136
|
try {
|
|
757
1137
|
loading.checkingOut = true;
|
|
758
1138
|
|
|
759
|
-
const passReturnStatusesPayload = passReturnStatuses.value.map(p => ({
|
|
760
|
-
|
|
1139
|
+
const passReturnStatusesPayload = passReturnStatuses.value.map((p) => ({
|
|
1140
|
+
keyId: p.keyId,
|
|
1141
|
+
status: p.status,
|
|
1142
|
+
remarks: p.remarks,
|
|
1143
|
+
}));
|
|
1144
|
+
const keyReturnStatusesPayload = keyReturnStatuses.value.map((k) => ({
|
|
1145
|
+
keyId: k.keyId,
|
|
1146
|
+
status: k.status,
|
|
1147
|
+
remarks: k.remarks,
|
|
1148
|
+
}));
|
|
761
1149
|
|
|
762
1150
|
const res = await updateVisitor(userId as string, {
|
|
763
1151
|
checkOut: new Date().toISOString(),
|
|
764
|
-
...(passReturnStatuses.value.length > 0
|
|
765
|
-
|
|
1152
|
+
...(passReturnStatuses.value.length > 0
|
|
1153
|
+
? { visitorPass: passReturnStatusesPayload }
|
|
1154
|
+
: {}),
|
|
1155
|
+
...(keyReturnStatuses.value.length > 0
|
|
1156
|
+
? { passKeys: keyReturnStatusesPayload }
|
|
1157
|
+
: {}),
|
|
766
1158
|
});
|
|
767
1159
|
if (res) {
|
|
768
1160
|
showMessage("Visitor successfully checked-out!", "info");
|
|
@@ -782,11 +1174,23 @@ async function proceedCheckout() {
|
|
|
782
1174
|
}
|
|
783
1175
|
}
|
|
784
1176
|
|
|
785
|
-
|
|
786
|
-
|
|
787
1177
|
const updateRouteQuery = debounce(
|
|
788
1178
|
// (search: string, from: string, to: string, types: string[], status, checkedOut) => {
|
|
789
|
-
({
|
|
1179
|
+
({
|
|
1180
|
+
search,
|
|
1181
|
+
from,
|
|
1182
|
+
to,
|
|
1183
|
+
types,
|
|
1184
|
+
tab,
|
|
1185
|
+
checkedOut,
|
|
1186
|
+
}: {
|
|
1187
|
+
search: string;
|
|
1188
|
+
from: string;
|
|
1189
|
+
to: string;
|
|
1190
|
+
types: string[];
|
|
1191
|
+
tab: string;
|
|
1192
|
+
checkedOut: boolean;
|
|
1193
|
+
}) => {
|
|
790
1194
|
router.replace({
|
|
791
1195
|
query: {
|
|
792
1196
|
...route.query,
|
|
@@ -795,7 +1199,9 @@ const updateRouteQuery = debounce(
|
|
|
795
1199
|
dateTo: to || undefined,
|
|
796
1200
|
type: types.filter(Boolean).join(",") || undefined,
|
|
797
1201
|
tab: tab || undefined,
|
|
798
|
-
...(checkedOut === true
|
|
1202
|
+
...(checkedOut === true
|
|
1203
|
+
? { checkedOut: "true" }
|
|
1204
|
+
: { checkedOut: undefined }),
|
|
799
1205
|
},
|
|
800
1206
|
});
|
|
801
1207
|
},
|
|
@@ -805,23 +1211,39 @@ const updateRouteQuery = debounce(
|
|
|
805
1211
|
watch(
|
|
806
1212
|
[searchInput, dateFrom, dateTo, filterTypes, activeTab, displayNotCheckedOut],
|
|
807
1213
|
([search, from, to, types, tab, checkedOut]) => {
|
|
1214
|
+
const normalizedFrom = normalizeDateOnly(from);
|
|
1215
|
+
const normalizedTo = normalizeDateOnly(to);
|
|
1216
|
+
dateFrom.value = normalizedFrom;
|
|
1217
|
+
dateTo.value = normalizedTo;
|
|
1218
|
+
|
|
808
1219
|
// updateRouteQuery(search, from, to, types, status, checkedOut)
|
|
809
|
-
updateRouteQuery({
|
|
810
|
-
|
|
1220
|
+
updateRouteQuery({
|
|
1221
|
+
search,
|
|
1222
|
+
from: normalizedFrom,
|
|
1223
|
+
to: normalizedTo,
|
|
1224
|
+
types,
|
|
1225
|
+
tab,
|
|
1226
|
+
checkedOut,
|
|
1227
|
+
});
|
|
1228
|
+
// getVisitorRefresh();
|
|
811
1229
|
},
|
|
812
1230
|
{ deep: true }
|
|
813
1231
|
);
|
|
814
|
-
|
|
815
|
-
|
|
1232
|
+
watch([searchInput, dateFrom, dateTo, filterTypes, activeTab], () => {
|
|
1233
|
+
page.value = 1;
|
|
1234
|
+
});
|
|
816
1235
|
|
|
817
1236
|
onMounted(() => {
|
|
818
|
-
activeTab.value = (route.query.tab as string) || "unregistered"
|
|
1237
|
+
activeTab.value = (route.query.tab as string) || "unregistered";
|
|
819
1238
|
searchInput.value = (route.query.search as string) || "";
|
|
820
|
-
dateFrom.value = (route.query.dateFrom as string) || "";
|
|
821
|
-
dateTo.value = (route.query.dateTo as string) || "";
|
|
822
|
-
filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(
|
|
823
|
-
|
|
824
|
-
|
|
1239
|
+
dateFrom.value = normalizeDateOnly((route.query.dateFrom as string) || "");
|
|
1240
|
+
dateTo.value = normalizeDateOnly((route.query.dateTo as string) || "");
|
|
1241
|
+
filterTypes.value = ((route.query.type as string)?.split(",") || []).filter(
|
|
1242
|
+
Boolean
|
|
1243
|
+
) as TVisitorType[];
|
|
1244
|
+
displayNotCheckedOut.value =
|
|
1245
|
+
(route.query.checkedOut as string) == "true" || false;
|
|
1246
|
+
});
|
|
825
1247
|
</script>
|
|
826
1248
|
|
|
827
1249
|
<style scoped></style>
|