@7365admin1/layer-common 1.11.17 → 1.11.19
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 +207 -0
- package/components/BuildingUnitFormEdit.vue +41 -1
- package/components/Card/MemberInfoSummary.vue +5 -1
- package/components/Input/FileV2.vue +4 -3
- package/components/MemberInformation.vue +98 -20
- package/components/PassInformation.vue +75 -30
- package/components/VehicleManagement.vue +107 -34
- package/components/VisitorForm.vue +72 -30
- package/components/VisitorManagement.vue +278 -27
- package/composables/useKey.ts +4 -0
- package/composables/useLocalAuth.ts +2 -4
- package/composables/useSiteSettings.ts +14 -0
- package/composables/useTemplateReusable.ts +5 -1
- package/composables/useVehicle.ts +3 -3
- package/package.json +1 -1
- package/types/vehicle.d.ts +2 -1
- package/types/visitor.d.ts +13 -2
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
</span>
|
|
44
44
|
<span class="text-capitalize">{{ item?.name }}</span>
|
|
45
45
|
</span>
|
|
46
|
+
<span class="text-grey text-caption" v-if="item?.members?.length > 0">( +{{ item?.members?.length }}
|
|
47
|
+
members)</span>
|
|
46
48
|
</template>
|
|
47
49
|
|
|
48
50
|
<template v-slot:item.type-company="{ item }">
|
|
@@ -84,36 +86,56 @@
|
|
|
84
86
|
</template>
|
|
85
87
|
|
|
86
88
|
<template v-slot:item.checkin-out="{ item }">
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
UTCToLocalTIme(item.checkIn) || "-"
|
|
91
|
-
}}</span>
|
|
92
|
-
<span>
|
|
93
|
-
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
|
|
94
|
-
@click.stop="handleViewImage(item.snapshotEntryImage)" />
|
|
95
|
-
</span>
|
|
96
|
-
</span>
|
|
97
|
-
<span class="d-flex align-center ga-2">
|
|
98
|
-
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
|
|
99
|
-
<template v-if="item.checkOut">
|
|
89
|
+
<v-row no-gutters class="d-flex flex-column py-2">
|
|
90
|
+
<span class="d-flex align-center ga-2">
|
|
91
|
+
<v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
|
|
100
92
|
<span class="text-capitalize">{{
|
|
101
|
-
UTCToLocalTIme(item.
|
|
102
|
-
|
|
93
|
+
UTCToLocalTIme(item.checkIn) || "-"
|
|
94
|
+
}}</span>
|
|
103
95
|
<span>
|
|
104
|
-
<v-icon v-if="item?.
|
|
105
|
-
@click.stop="handleViewImage(item.
|
|
96
|
+
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
|
|
97
|
+
@click.stop="handleViewImage(item.snapshotEntryImage)" />
|
|
106
98
|
</span>
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
</span>
|
|
100
|
+
<span class="d-flex align-center ga-2">
|
|
101
|
+
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
|
|
102
|
+
<template v-if="item.checkOut">
|
|
103
|
+
<span class="text-capitalize">{{
|
|
104
|
+
UTCToLocalTIme(item.checkOut) || "-"
|
|
105
|
+
}}</span>
|
|
106
|
+
<span>
|
|
107
|
+
<v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
|
|
108
|
+
@click.stop="handleViewImage(item.snapshotExitImage)" />
|
|
109
|
+
</span>
|
|
110
|
+
<span v-if="item?.manualCheckout">
|
|
111
|
+
<TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
|
|
112
|
+
</span>
|
|
113
|
+
</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" />
|
|
109
118
|
</span>
|
|
110
|
-
</template>
|
|
111
|
-
<span v-else-if="!item?.checkOut && (item?.status === 'registered' || item?.status === 'unregistered')">
|
|
112
|
-
<v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
|
|
113
|
-
:loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
|
|
114
|
-
v-if="canCheckoutVisitor" />
|
|
115
119
|
</span>
|
|
116
|
-
|
|
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
|
+
|
|
137
|
+
</div>
|
|
138
|
+
</v-row>
|
|
117
139
|
</template>
|
|
118
140
|
|
|
119
141
|
<template v-slot:item.action="{ item }">
|
|
@@ -121,6 +143,21 @@
|
|
|
121
143
|
@click.stop="handleRegistrationUnregisteredVisitor(item)" />
|
|
122
144
|
</template>
|
|
123
145
|
|
|
146
|
+
<template v-slot:item.checkInRemarks="{ item }">
|
|
147
|
+
<span>
|
|
148
|
+
<v-btn variant="text" color="blue-darken-2" density="compact" size="small"
|
|
149
|
+
@click.stop="handleOpenRemarks(item, 'checkIn')">{{ item.checkInRemarks ? "View Remarks" : "Add Remarks"
|
|
150
|
+
}}</v-btn>
|
|
151
|
+
</span>
|
|
152
|
+
</template>
|
|
153
|
+
<template v-slot:item.checkOutRemarks="{ item }">
|
|
154
|
+
<span>
|
|
155
|
+
<v-btn variant="text" color="blue-darken-2" density="compact" size="small"
|
|
156
|
+
@click.stop="handleOpenRemarks(item, 'checkOut')">{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks"
|
|
157
|
+
}}</v-btn>
|
|
158
|
+
</span>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
124
161
|
</TableMain>
|
|
125
162
|
|
|
126
163
|
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
@@ -179,6 +216,93 @@
|
|
|
179
216
|
</v-card>
|
|
180
217
|
</v-dialog>
|
|
181
218
|
|
|
219
|
+
<v-dialog v-model="dialog.remarks" max-width="450" persistent>
|
|
220
|
+
<v-card>
|
|
221
|
+
<v-card-title class="d-flex justify-space-between align-center pt-4 px-4">
|
|
222
|
+
<span>{{ remarksType === 'checkIn' ? 'Check-In Remarks' : 'Check-Out Remarks' }}</span>
|
|
223
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.remarks = false" />
|
|
224
|
+
</v-card-title>
|
|
225
|
+
<v-card-text class="px-4 pb-2">
|
|
226
|
+
<v-textarea v-model="remarksInput" label="Remarks" rows="4" auto-grow variant="outlined"
|
|
227
|
+
:readonly="remarksViewOnly" />
|
|
228
|
+
</v-card-text>
|
|
229
|
+
<v-toolbar class="pa-0" density="compact">
|
|
230
|
+
<v-row no-gutters>
|
|
231
|
+
<v-col cols="6">
|
|
232
|
+
<v-btn variant="text" block :disabled="loading.savingRemarks"
|
|
233
|
+
@click="dialog.remarks = false">Cancel</v-btn>
|
|
234
|
+
</v-col>
|
|
235
|
+
<v-col cols="6">
|
|
236
|
+
<v-btn color="primary" variant="flat" height="48" rounded="0" block :loading="loading.savingRemarks"
|
|
237
|
+
:disabled="!remarksInput.trim()" @click="handleSaveRemarks">Save</v-btn>
|
|
238
|
+
</v-col>
|
|
239
|
+
</v-row>
|
|
240
|
+
</v-toolbar>
|
|
241
|
+
</v-card>
|
|
242
|
+
</v-dialog>
|
|
243
|
+
|
|
244
|
+
<v-dialog v-model="dialog.returnPassesKeys" max-width="450" persistent>
|
|
245
|
+
<v-card>
|
|
246
|
+
<v-toolbar density="compact" color="">
|
|
247
|
+
<v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
|
|
248
|
+
<span class="font-weight-bold">Return Passes & Keys</span>
|
|
249
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.returnPassesKeys = false" />
|
|
250
|
+
</v-row>
|
|
251
|
+
</v-toolbar>
|
|
252
|
+
<v-card-text class="px-4 pb-2">
|
|
253
|
+
<p class="text-body-2 mb-3">Please ensure all passes and keys are returned before checking out.</p>
|
|
254
|
+
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
255
|
+
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
256
|
+
<v-row no-gutters v-for="pass in passReturnStatuses" :key="(pass as any)._id ?? (pass as any).keyId"
|
|
257
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
|
|
258
|
+
<v-chip prepend-icon="mdi-card-bulleted-outline" size="small" variant="tonal" color="blue">
|
|
259
|
+
{{ (pass as any)?.prefixAndName }}
|
|
260
|
+
</v-chip>
|
|
261
|
+
<v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
|
|
262
|
+
item-value="value" v-model="pass.status" :disabled="selectedVisitorObject.checkOut"></v-select>
|
|
263
|
+
<v-textarea v-if="pass.status === 'Lost' || pass.status === 'Damaged'" no-resize rows="3" class="w-100"
|
|
264
|
+
density="compact" v-model="pass.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
|
|
265
|
+
</v-row>
|
|
266
|
+
</div>
|
|
267
|
+
<div v-if="(keyReturnStatuses.length > 0)" class="mb-2">
|
|
268
|
+
<p class="text-caption text-medium-emphasis mb-1">Keys</p>
|
|
269
|
+
<v-row no-gutters v-for="key in keyReturnStatuses" :key="(key as any)._id ?? (key as any).keyId"
|
|
270
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
|
|
271
|
+
<v-chip prepend-icon="mdi-key" size="small" variant="tonal" color="orange">
|
|
272
|
+
{{ (key as any)?.prefixAndName }}
|
|
273
|
+
</v-chip>
|
|
274
|
+
<v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
|
|
275
|
+
item-value="value" v-model="key.status" :disabled="selectedVisitorObject.checkOut"></v-select>
|
|
276
|
+
<v-textarea v-if="key.status === 'Lost' || key.status === 'Damaged'" no-resize rows="3" class="w-100"
|
|
277
|
+
density="compact" v-model="key.remarks" :disabled="selectedVisitorObject.checkOut"></v-textarea>
|
|
278
|
+
</v-row>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<v-row no-gutters class="my-5">
|
|
282
|
+
<v-btn variant="flat" color="blue" density="comfortable" class="text-capitalize" :disabled="selectedVisitorObject.checkOut"
|
|
283
|
+
:loading="loading.updatingPassKeys" @click.stop="handleUpdatePassKeys" >Update Pass/Keys</v-btn>
|
|
284
|
+
</v-row>
|
|
285
|
+
</v-card-text>
|
|
286
|
+
<v-toolbar class="pa-0" density="compact">
|
|
287
|
+
<v-row no-gutters>
|
|
288
|
+
<v-col cols="6">
|
|
289
|
+
<v-btn variant="text" block
|
|
290
|
+
@click="dialog.returnPassesKeys = false">Close</v-btn>
|
|
291
|
+
</v-col>
|
|
292
|
+
<v-col cols="6">
|
|
293
|
+
<v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut"
|
|
294
|
+
:disabled="!canConfirmCheckout || selectedVisitorObject.checkOut" @click="proceedCheckout">Confirm Checkout</v-btn>
|
|
295
|
+
</v-col>
|
|
296
|
+
</v-row>
|
|
297
|
+
</v-toolbar>
|
|
298
|
+
</v-card>
|
|
299
|
+
</v-dialog>
|
|
300
|
+
|
|
301
|
+
<v-dialog v-model="dialog.addPassKey" width="450" persistent>
|
|
302
|
+
<AddPassKeyToVisitor :visitor="selectedVisitorDataObject" :site="siteId" @close="dialog.addPassKey = false"
|
|
303
|
+
@update="getVisitorRefresh" />
|
|
304
|
+
</v-dialog>
|
|
305
|
+
|
|
182
306
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
183
307
|
</v-row>
|
|
184
308
|
</template>
|
|
@@ -256,6 +380,8 @@ const loading = reactive({
|
|
|
256
380
|
deletingVisitor: false,
|
|
257
381
|
fetchingVisitors: false,
|
|
258
382
|
checkingOut: false,
|
|
383
|
+
savingRemarks: false,
|
|
384
|
+
updatingPassKeys: false,
|
|
259
385
|
});
|
|
260
386
|
|
|
261
387
|
const dialog = reactive({
|
|
@@ -264,9 +390,15 @@ const dialog = reactive({
|
|
|
264
390
|
viewVisitor: false,
|
|
265
391
|
deleteConfirmation: false,
|
|
266
392
|
snapshotImage: false,
|
|
393
|
+
remarks: false,
|
|
394
|
+
returnPassesKeys: false,
|
|
395
|
+
addPassKey: false
|
|
267
396
|
});
|
|
268
397
|
|
|
269
398
|
const snapshotImageUrl = ref("");
|
|
399
|
+
const remarksType = ref<'checkIn' | 'checkOut'>('checkIn');
|
|
400
|
+
const remarksInput = ref("");
|
|
401
|
+
const remarksViewOnly = ref(false);
|
|
270
402
|
|
|
271
403
|
const headers = computed(() => [
|
|
272
404
|
{ title: "Name", value: "name" },
|
|
@@ -274,6 +406,7 @@ const headers = computed(() => [
|
|
|
274
406
|
{ title: "Location", value: "location" },
|
|
275
407
|
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" },
|
|
276
408
|
{ title: "Check In/Out", value: "checkin-out" },
|
|
409
|
+
...(activeTab.value === "resident-transactions" ? [{ title: "Check-in Remarks", value: "checkInRemarks" }, { title: "Check-out Remarks", value: "checkOutRemarks" }] : []),
|
|
277
410
|
...(activeTab.value === 'unregistered' ? [{ title: "Action", value: "action" }] : [])
|
|
278
411
|
])
|
|
279
412
|
|
|
@@ -314,7 +447,15 @@ function filterTypeSelectionLabel() {
|
|
|
314
447
|
return `${length} selected ${length === 1 ? "type" : "types"}` as string;
|
|
315
448
|
}
|
|
316
449
|
|
|
450
|
+
const passStatusOptions = [
|
|
451
|
+
{ label: "Returned", value: "Returned" },
|
|
452
|
+
{ label: "Not Returned", value: "In Use" },
|
|
453
|
+
{ label: "Damaged", value: "Damaged" },
|
|
454
|
+
{ label: "Lost", value: "Lost" },
|
|
455
|
+
]
|
|
456
|
+
|
|
317
457
|
function toRoute(tab: any) {
|
|
458
|
+
items.value = []
|
|
318
459
|
|
|
319
460
|
const obj = tabOptions.find((x) => x.value === tab);
|
|
320
461
|
if (!obj) return;
|
|
@@ -358,7 +499,7 @@ const {
|
|
|
358
499
|
// params.status = "pending"
|
|
359
500
|
// }
|
|
360
501
|
|
|
361
|
-
if(activeTab.value === "guests"){
|
|
502
|
+
if (activeTab.value === "guests") {
|
|
362
503
|
params.type = "guest"
|
|
363
504
|
params.status = "pending"
|
|
364
505
|
} else if (activeTab.value === "resident-transactions") {
|
|
@@ -381,6 +522,7 @@ const {
|
|
|
381
522
|
watch(getVisitorReq, (newData: any) => {
|
|
382
523
|
if (newData) {
|
|
383
524
|
items.value = newData.items ?? [];
|
|
525
|
+
// items.value = [{ _id: "testid", name: "John Doe", type: "Contractor", company: "ABC Corp", location: "Block A, Level 1, Unit 101", contact: "12345678", plateNumber: "SGX1234A", checkIn: new Date().toISOString(), checkOut: null, status: "registered", visitorPass: [{ _id: "1", keyId: "pass1", prefixAndName: "VIP Pass" }, { _id: "2", keyId: "pass2", prefixAndName: "VIP Pass 2" }], passKeys: [{ _id: "1", keyId: "key2", prefixAndName: "Master Key" }] }];
|
|
384
526
|
pages.value = newData.pages ?? 0;
|
|
385
527
|
pageRange.value = newData?.pageRange ?? "-- - -- of --";
|
|
386
528
|
}
|
|
@@ -412,6 +554,8 @@ function formatValues(key: string, value: any) {
|
|
|
412
554
|
return value;
|
|
413
555
|
}
|
|
414
556
|
|
|
557
|
+
|
|
558
|
+
|
|
415
559
|
function handleAddNew() {
|
|
416
560
|
dialog.showSelection = true;
|
|
417
561
|
activeVisitorFormType.value = null;
|
|
@@ -427,6 +571,15 @@ function handleUpdatePage(newPageNum: number) {
|
|
|
427
571
|
page.value = newPageNum;
|
|
428
572
|
}
|
|
429
573
|
|
|
574
|
+
function showAddPassKeyButton(item: any) {
|
|
575
|
+
const hasPasses = (item?.visitorPass?.length ?? 0) > 0;
|
|
576
|
+
const hasKeys = (item?.passKeys?.length ?? 0) > 0;
|
|
577
|
+
|
|
578
|
+
const isTypeWithPassKey = ["contractor", "guest", "walk-in"].includes(item?.type);
|
|
579
|
+
|
|
580
|
+
return !hasPasses && !hasKeys && isTypeWithPassKey && item?.status === "registered" && !item?.checkOut;
|
|
581
|
+
}
|
|
582
|
+
|
|
430
583
|
function handleSelectVisitorType(type: TVisitorType) {
|
|
431
584
|
dialog.showSelection = false;
|
|
432
585
|
dialog.showForm = true;
|
|
@@ -479,6 +632,63 @@ function handleViewImage(imageId: string) {
|
|
|
479
632
|
dialog.snapshotImage = true;
|
|
480
633
|
}
|
|
481
634
|
|
|
635
|
+
function handleOpenAddPassKey(item: any) {
|
|
636
|
+
selectedVisitorId.value = item?._id;
|
|
637
|
+
dialog.addPassKey = true;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function handleOpenRemarks(item: any, type: 'checkIn' | 'checkOut') {
|
|
641
|
+
selectedVisitorId.value = item?._id;
|
|
642
|
+
remarksType.value = type;
|
|
643
|
+
const existingRemarks = type === 'checkIn' ? item?.checkInRemarks : item?.checkOutRemarks;
|
|
644
|
+
remarksInput.value = existingRemarks || "";
|
|
645
|
+
remarksViewOnly.value = !!existingRemarks;
|
|
646
|
+
dialog.remarks = true;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async function handleSaveRemarks() {
|
|
650
|
+
if (!remarksInput.value.trim() || !selectedVisitorId.value) return;
|
|
651
|
+
try {
|
|
652
|
+
loading.savingRemarks = true;
|
|
653
|
+
const payload = remarksType.value === 'checkIn'
|
|
654
|
+
? { checkInRemarks: remarksInput.value.trim() }
|
|
655
|
+
: { checkOutRemarks: remarksInput.value.trim() };
|
|
656
|
+
await updateVisitor(selectedVisitorId.value, payload);
|
|
657
|
+
showMessage("Remarks saved successfully!", "info");
|
|
658
|
+
await getVisitorRefresh();
|
|
659
|
+
dialog.remarks = false;
|
|
660
|
+
} catch (error: any) {
|
|
661
|
+
const errorMessage = error?.response?._data?.message;
|
|
662
|
+
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
663
|
+
} finally {
|
|
664
|
+
loading.savingRemarks = false;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function handleUpdatePassKeys() {
|
|
669
|
+
if (!selectedVisitorId.value) return;
|
|
670
|
+
try {
|
|
671
|
+
loading.updatingPassKeys = true;
|
|
672
|
+
const payload: any = {};
|
|
673
|
+
if (passReturnStatuses.value.length > 0) {
|
|
674
|
+
const passReturnStatusesPayload = passReturnStatuses.value.map(p => ({ keyId: p.keyId, status: p.status, remarks: p.remarks }));
|
|
675
|
+
payload.visitorPass = passReturnStatusesPayload;
|
|
676
|
+
}
|
|
677
|
+
if (keyReturnStatuses.value.length > 0) {
|
|
678
|
+
const keyReturnStatusesPayload = keyReturnStatuses.value.map(k => ({ keyId: k.keyId, status: k.status, remarks: k.remarks }));
|
|
679
|
+
payload.passKeys = keyReturnStatusesPayload;
|
|
680
|
+
}
|
|
681
|
+
await updateVisitor(selectedVisitorId.value, payload);
|
|
682
|
+
showMessage("Pass/Key statuses updated successfully!", "info");
|
|
683
|
+
await getVisitorRefresh();
|
|
684
|
+
} catch (error: any) {
|
|
685
|
+
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
|
+
|
|
482
692
|
|
|
483
693
|
async function handleProceedDeleteVisitor() {
|
|
484
694
|
try {
|
|
@@ -502,22 +712,63 @@ async function handleProceedDeleteVisitor() {
|
|
|
502
712
|
}
|
|
503
713
|
}
|
|
504
714
|
|
|
505
|
-
|
|
715
|
+
|
|
716
|
+
const passReturnStatuses = ref<{ keyId: string, status: string, prefixAndName: string, remarks: string }[]>([]);
|
|
717
|
+
const keyReturnStatuses = ref<{ keyId: string, status: string, prefixAndName: string, remarks: string }[]>([]);
|
|
718
|
+
|
|
719
|
+
const canConfirmCheckout = computed(() => {
|
|
720
|
+
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
721
|
+
return allEntries.every((entry) => {
|
|
722
|
+
if (!entry.status || entry.status === 'In Use') return false;
|
|
723
|
+
if ((entry.status === 'Lost' || entry.status === 'Damaged') && !entry.remarks.trim()) return false;
|
|
724
|
+
return true;
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
function handleCheckout(userId: string) {
|
|
506
731
|
if (!userId) {
|
|
507
732
|
showMessage("Invalid userId", "error");
|
|
508
733
|
return;
|
|
509
734
|
}
|
|
510
735
|
selectedVisitorId.value = userId;
|
|
511
736
|
|
|
737
|
+
const visitor = items.value.find((x: any) => x?._id === userId);
|
|
738
|
+
const hasPasses = (visitor?.visitorPass?.length ?? 0) > 0;
|
|
739
|
+
const hasKeys = (visitor?.passKeys?.length ?? 0) > 0;
|
|
740
|
+
|
|
741
|
+
if (hasPasses || hasKeys) {
|
|
742
|
+
const visitor = items.value.find((x: any) => x?._id === userId);
|
|
743
|
+
passReturnStatuses.value = ((visitor?.visitorPass as any[]) || []).map((p: any) => ({ keyId: p.keyId, status: p.status ?? "In Use", remarks: '', prefixAndName: p.prefixAndName }));
|
|
744
|
+
keyReturnStatuses.value = ((visitor?.passKeys as any[]) || []).map((k: any) => ({ keyId: k.keyId, status: k.status ?? "In Use", remarks: '', prefixAndName: k.prefixAndName }));
|
|
745
|
+
dialog.returnPassesKeys = true;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
proceedCheckout();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async function proceedCheckout() {
|
|
753
|
+
const userId = selectedVisitorId.value;
|
|
754
|
+
if (!userId) return;
|
|
755
|
+
|
|
512
756
|
try {
|
|
513
757
|
loading.checkingOut = true;
|
|
758
|
+
|
|
759
|
+
const passReturnStatusesPayload = passReturnStatuses.value.map(p => ({ keyId: p.keyId, status: p.status, remarks: p.remarks }));
|
|
760
|
+
const keyReturnStatusesPayload = keyReturnStatuses.value.map(k => ({ keyId: k.keyId, status: k.status, remarks: k.remarks }));
|
|
761
|
+
|
|
514
762
|
const res = await updateVisitor(userId as string, {
|
|
515
763
|
checkOut: new Date().toISOString(),
|
|
764
|
+
...(passReturnStatuses.value.length > 0 ? { visitorPass: passReturnStatusesPayload } : {}),
|
|
765
|
+
...(keyReturnStatuses.value.length > 0 ? { passKeys: keyReturnStatusesPayload } : {}),
|
|
516
766
|
});
|
|
517
767
|
if (res) {
|
|
518
768
|
showMessage("Visitor successfully checked-out!", "info");
|
|
519
769
|
await getVisitorRefresh();
|
|
520
770
|
dialog.viewVisitor = false;
|
|
771
|
+
dialog.returnPassesKeys = false;
|
|
521
772
|
}
|
|
522
773
|
} catch (error: any) {
|
|
523
774
|
const errorMessage = error?.response?._data?.message;
|
package/composables/useKey.ts
CHANGED
|
@@ -195,6 +195,7 @@ export default function useKey() {
|
|
|
195
195
|
organization,
|
|
196
196
|
startDateTime,
|
|
197
197
|
endDateTime,
|
|
198
|
+
statuses
|
|
198
199
|
}: {
|
|
199
200
|
page?: number;
|
|
200
201
|
search?: string | ISearch;
|
|
@@ -204,6 +205,7 @@ export default function useKey() {
|
|
|
204
205
|
organization?: string;
|
|
205
206
|
startDateTime?: string;
|
|
206
207
|
endDateTime?: string;
|
|
208
|
+
statuses?: string[];
|
|
207
209
|
} = {}) {
|
|
208
210
|
/*
|
|
209
211
|
search here should accept
|
|
@@ -220,6 +222,7 @@ export default function useKey() {
|
|
|
220
222
|
organization,
|
|
221
223
|
startDateTime,
|
|
222
224
|
endDateTime,
|
|
225
|
+
statuses
|
|
223
226
|
},
|
|
224
227
|
});
|
|
225
228
|
}
|
|
@@ -381,5 +384,6 @@ export default function useKey() {
|
|
|
381
384
|
passTypesStatus,
|
|
382
385
|
getCountTotalKeys,
|
|
383
386
|
getKeyWithSequence,
|
|
387
|
+
getKeysByKeyPageSearch
|
|
384
388
|
};
|
|
385
389
|
}
|
|
@@ -29,7 +29,7 @@ export default function useLocalAuth() {
|
|
|
29
29
|
|
|
30
30
|
const currentUser = useState(
|
|
31
31
|
"currentUser",
|
|
32
|
-
(): TUser => JSON.parse(JSON.stringify(user))
|
|
32
|
+
(): TUser | null => JSON.parse(JSON.stringify(user))
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
async function authenticate() {
|
|
@@ -41,9 +41,7 @@ export default function useLocalAuth() {
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
watchEffect(() => {
|
|
44
|
-
|
|
45
|
-
currentUser.value = getCurrentUserReq.value;
|
|
46
|
-
}
|
|
44
|
+
currentUser.value = getCurrentUserReq.value as TUser | null;
|
|
47
45
|
});
|
|
48
46
|
|
|
49
47
|
watchEffect(() => {
|
|
@@ -98,6 +98,19 @@ export default function () {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
async function updateSiteInformation(
|
|
102
|
+
siteId: string,
|
|
103
|
+
payload: { bgImage: string; description: string; docs: { id: string; name: string }[] }
|
|
104
|
+
) {
|
|
105
|
+
return await useNuxtApp().$api<Record<string, any>>(
|
|
106
|
+
`/api/sites/information/id/${siteId}`,
|
|
107
|
+
{
|
|
108
|
+
method: "PATCH",
|
|
109
|
+
body: payload,
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
async function setSiteGuardPosts(siteId: string, value: number) {
|
|
102
115
|
return await useNuxtApp().$api<Record<string, any>>(
|
|
103
116
|
`/api/sites/guard-post/id/${siteId}`,
|
|
@@ -138,6 +151,7 @@ export default function () {
|
|
|
138
151
|
updateSiteCamera,
|
|
139
152
|
deleteSiteCameraById,
|
|
140
153
|
updateSitebyId,
|
|
154
|
+
updateSiteInformation,
|
|
141
155
|
getOvernightParkingAvailability,
|
|
142
156
|
updateOvernightParkingAvailability
|
|
143
157
|
};
|
|
@@ -269,14 +269,18 @@ export default function useTemplateReusable() {
|
|
|
269
269
|
_id,
|
|
270
270
|
site,
|
|
271
271
|
status,
|
|
272
|
+
userType,
|
|
273
|
+
updatedBy,
|
|
272
274
|
}: {
|
|
273
275
|
_id?: string;
|
|
274
276
|
site?: string;
|
|
275
277
|
status?: string;
|
|
278
|
+
userType?: string;
|
|
279
|
+
updatedBy?: string;
|
|
276
280
|
}) {
|
|
277
281
|
return useNuxtApp().$api("/api/qr-code-templates/v1", {
|
|
278
282
|
method: "PUT",
|
|
279
|
-
body: { _id, site, status },
|
|
283
|
+
body: { _id, site, status, userType, updatedBy },
|
|
280
284
|
});
|
|
281
285
|
}
|
|
282
286
|
|
|
@@ -27,7 +27,7 @@ export default function useVehicle() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function updateVehicle(id: string, payload: Partial<TVehiclePayload>) {
|
|
30
|
-
return await useNuxtApp().$api<Record<string, any>>(`/api/vehicles/${id}`, {
|
|
30
|
+
return await useNuxtApp().$api<Record<string, any>>(`/api/vehicles/id/${id}`, {
|
|
31
31
|
method: "PUT",
|
|
32
32
|
body: payload,
|
|
33
33
|
});
|
|
@@ -51,11 +51,11 @@ export default function useVehicle() {
|
|
|
51
51
|
}
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
interface IDeleteVehicleParams {
|
|
56
56
|
site: string;
|
|
57
57
|
id: string;
|
|
58
|
-
recno
|
|
58
|
+
recno?: string;
|
|
59
59
|
type: "whitelist" | "blocklist";
|
|
60
60
|
}
|
|
61
61
|
|
package/package.json
CHANGED
package/types/vehicle.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare type TVehicle = {
|
|
|
9
9
|
block: number | "";
|
|
10
10
|
level: string;
|
|
11
11
|
unit: string;
|
|
12
|
+
uniName?: string; // For display purposes, the API will return the unit name if available
|
|
12
13
|
nric?: string | null;
|
|
13
14
|
remarks?: string;
|
|
14
15
|
seasonPassType?: string;
|
|
@@ -43,4 +44,4 @@ declare type TVehiclePayload = Pick<
|
|
|
43
44
|
| "site"
|
|
44
45
|
| "org"
|
|
45
46
|
| "peopleId"
|
|
46
|
-
> & { plateNumber
|
|
47
|
+
> & { plateNumber?: string | string[] };
|
package/types/visitor.d.ts
CHANGED
|
@@ -20,11 +20,15 @@ declare type TVisitor = {
|
|
|
20
20
|
org: string;
|
|
21
21
|
site: string,
|
|
22
22
|
manualCheckout?: boolean;
|
|
23
|
+
visitorPass?: TPassKeyPayload[];
|
|
24
|
+
passKeys?: TPassKeyPayload[]
|
|
25
|
+
checkInRemarks?: string;
|
|
26
|
+
checkOutRemarks?: string;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
declare type TVisitorType = "contractor" | "delivery" | "walk-in" | "pick-up" | "drop-off" | "guest";
|
|
26
30
|
|
|
27
|
-
declare type TVisitorPayload = Pick<TVisitor, "name" | "type" | "company" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "checkOut" | "contractorType" | "deliveryType" | "nric" | "contact" | "remarks" | "attachments" | "org" | "site" | "status"> & {
|
|
31
|
+
declare type TVisitorPayload = Pick<TVisitor, "name" | "type" | "company" | "block" | "level" | "unit" | "unitName" | "contact" | "plateNumber" | "checkOut" | "contractorType" | "deliveryType" | "nric" | "contact" | "remarks" | "attachments" | "org" | "site" | "status" | "visitorPass" | "passKeys" | "checkInRemarks" | "checkOutRemarks"> & {
|
|
28
32
|
members?: TMemberInfo[]
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -38,6 +42,13 @@ declare type TDefaultOptionObj = {
|
|
|
38
42
|
declare type TMemberInfo = {
|
|
39
43
|
name: string;
|
|
40
44
|
nric: string;
|
|
41
|
-
visitorPass
|
|
45
|
+
visitorPass?: string;
|
|
46
|
+
passKeys?: TPassKeyPayload[];
|
|
42
47
|
contact: string
|
|
43
48
|
}
|
|
49
|
+
|
|
50
|
+
declare type TPassKeyPayload = {
|
|
51
|
+
keyId: string;
|
|
52
|
+
status?: string;
|
|
53
|
+
remarks?: string;
|
|
54
|
+
}
|