@7365admin1/layer-common 1.11.16 → 1.11.18
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/Card/MemberInfoSummary.vue +5 -1
- package/components/MemberInformation.vue +92 -20
- package/components/PassInformation.vue +75 -30
- package/components/VehicleManagement.vue +107 -34
- package/components/VisitorForm.vue +72 -30
- package/components/VisitorManagement.vue +245 -30
- package/composables/useKey.ts +4 -0
- package/composables/useLocalAuth.ts +2 -4
- 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
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
<div class="w-100 d-flex justify-space-between ga-2">
|
|
13
13
|
<span class="text-subtitle-1 w-100 font-weight-bold mb-3">{{
|
|
14
14
|
formTitle
|
|
15
|
-
|
|
16
|
-
<span v-if="
|
|
17
|
-
<span class="text-primary-button">{{ contractorStep }}</span>/3</span>
|
|
15
|
+
}}</span>
|
|
16
|
+
<span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
|
|
17
|
+
<span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
|
|
18
18
|
</div>
|
|
19
19
|
<v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
|
|
20
20
|
<v-row no-gutters class="pt-4">
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<v-list-item-title @click.stop="handleAddNewContractorType" class="d-flex align-center ga-1">
|
|
31
31
|
<span><v-icon icon="mdi-plus" /></span> Add "<strong>{{
|
|
32
32
|
contractorTypeInput
|
|
33
|
-
|
|
33
|
+
}}</strong>" as custom contractor type.
|
|
34
34
|
</v-list-item-title>
|
|
35
35
|
</v-list-item>
|
|
36
36
|
</template>
|
|
@@ -115,8 +115,7 @@
|
|
|
115
115
|
<InputLabel class="text-capitalize" title="Delivery Company" />
|
|
116
116
|
<v-combobox v-model="deliveryCompany" v-model:search="deliveryCompanyInput" ref="companyCombo"
|
|
117
117
|
autocomplete="off" :hide-no-data="false" :items="deliveryCompanyList" item-value="value"
|
|
118
|
-
:loading="siteDataPending" variant="outlined"
|
|
119
|
-
density="comfortable" persistent-hint small-chips>
|
|
118
|
+
:loading="siteDataPending" variant="outlined" density="comfortable" persistent-hint small-chips>
|
|
120
119
|
<template v-slot:no-data>
|
|
121
120
|
<v-list-item>
|
|
122
121
|
<v-list-item-title v-if="fetchCompanyListPending">
|
|
@@ -183,22 +182,17 @@
|
|
|
183
182
|
<v-textarea v-model="visitor.remarks" density="comfortable" :rows="3" no-resize />
|
|
184
183
|
</v-col>
|
|
185
184
|
|
|
186
|
-
<v-col v-if="
|
|
187
|
-
<PassInformation :
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
v-model="
|
|
191
|
-
|
|
192
|
-
v-model:cards="passCards"
|
|
193
|
-
:settings="entryPassSettings"
|
|
194
|
-
:loading="entryPassSettingsPending"
|
|
195
|
-
:site-id="prop.site"
|
|
196
|
-
:unit-id="visitor.unit || null"
|
|
197
|
-
/>
|
|
185
|
+
<v-col v-if="(withStep2 || withStep3) && contractorStep === 2" cols="12">
|
|
186
|
+
<PassInformation v-model:pass="visitor.visitorPass" v-model:keys="visitor.passKeys" :site="prop.site"
|
|
187
|
+
:type="prop.type" :contractor-type="visitor.contractorType" :hide-keys="!showShowKeysField" />
|
|
188
|
+
<EntryPassInformation v-if="entryPassSettings?.data?.settings?.nfcPass" v-model="passType"
|
|
189
|
+
v-model:quantity="passQuantity" v-model:cards="passCards" :settings="entryPassSettings"
|
|
190
|
+
:loading="entryPassSettingsPending" :site-id="prop.site" :unit-id="visitor.unit || null" />
|
|
198
191
|
</v-col>
|
|
199
192
|
|
|
200
|
-
<v-col v-if="
|
|
201
|
-
<MemberInformation v-model="visitor.members"
|
|
193
|
+
<v-col v-if="withStep3 && contractorStep === 3" cols="12">
|
|
194
|
+
<MemberInformation v-model="visitor.members" :type="prop.type" :contractor-type="visitor.contractorType"
|
|
195
|
+
:site="prop.site" :selected-visitor-pass="visitor.visitorPass" />
|
|
202
196
|
</v-col>
|
|
203
197
|
|
|
204
198
|
<v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
|
|
@@ -224,7 +218,7 @@
|
|
|
224
218
|
<v-col cols="6">
|
|
225
219
|
<v-btn v-if="
|
|
226
220
|
prop.mode === 'add' &&
|
|
227
|
-
|
|
221
|
+
(withStep2 || withStep3) &&
|
|
228
222
|
contractorStep > 1
|
|
229
223
|
" tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
|
|
230
224
|
<v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none"
|
|
@@ -232,10 +226,11 @@
|
|
|
232
226
|
<v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
|
|
233
227
|
</v-col>
|
|
234
228
|
<v-col cols="6">
|
|
235
|
-
<v-btn v-if="
|
|
236
|
-
|
|
229
|
+
<v-btn v-if="showNextButton" tile block variant="flat" color="primary-button" class="text-none" size="48"
|
|
230
|
+
:disabled="processing" @click="handleNextPage" :text="'Next'" />
|
|
237
231
|
<v-btn v-else tile block variant="flat" color="black" class="text-none" size="48"
|
|
238
|
-
:disabled="!validForm || processing" @click="submit"
|
|
232
|
+
:disabled="!validForm || processing || membersFieldIncomplete" @click="submit"
|
|
233
|
+
:text="prop.mode == 'add' ? 'Submit' : 'Update'" />
|
|
239
234
|
</v-col>
|
|
240
235
|
</v-row>
|
|
241
236
|
</v-toolbar>
|
|
@@ -353,6 +348,8 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
353
348
|
remarks: "",
|
|
354
349
|
attachments: [],
|
|
355
350
|
members: [],
|
|
351
|
+
visitorPass: [],
|
|
352
|
+
passKeys: []
|
|
356
353
|
});
|
|
357
354
|
|
|
358
355
|
const passType = ref<string | null>(null);
|
|
@@ -400,12 +397,34 @@ const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
|
400
397
|
|
|
401
398
|
|
|
402
399
|
const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') => {
|
|
403
|
-
if (
|
|
400
|
+
if ((!withStep2.value && !withStep3.value) || contractorStep.value === 1) {
|
|
404
401
|
const visibleFields = typeFieldMap[prop.type];
|
|
405
402
|
return visibleFields?.includes(fieldKey);
|
|
406
403
|
}
|
|
407
404
|
};
|
|
408
405
|
|
|
406
|
+
const withStep2 = computed(() => {
|
|
407
|
+
const step2Types = ["guest", "walk-in"];
|
|
408
|
+
return step2Types.includes(prop.type);
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
const withStep3 = computed(() => {
|
|
412
|
+
const step3Types = ["contractor"];
|
|
413
|
+
return step3Types.includes(prop.type);
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const showNextButton = computed(() => {
|
|
417
|
+
if (withStep3.value) {
|
|
418
|
+
return contractorStep.value === 1 || contractorStep.value === 2
|
|
419
|
+
} else if (withStep2.value) {
|
|
420
|
+
return contractorStep.value === 1
|
|
421
|
+
} else return false
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
const showShowKeysField = computed(() => {
|
|
425
|
+
return prop.type === "contractor"
|
|
426
|
+
})
|
|
427
|
+
|
|
409
428
|
const requireNRIC = computed(() => {
|
|
410
429
|
return prop.type !== 'walk-in' && prop.type !== 'guest';
|
|
411
430
|
})
|
|
@@ -415,8 +434,10 @@ const companyAutofillDataArray = ref<{ companyName: string[], unit: string, bloc
|
|
|
415
434
|
|
|
416
435
|
const formTitle = computed(() => {
|
|
417
436
|
const isContractorForm = prop.type === "contractor";
|
|
437
|
+
const isDriveInForm = prop.type === "guest";
|
|
438
|
+
const isWalkInForm = prop.type === "walk-in";
|
|
418
439
|
const step = contractorStep.value;
|
|
419
|
-
if (isContractorForm && step === 2) {
|
|
440
|
+
if ((isContractorForm || isDriveInForm || isWalkInForm) && step === 2) {
|
|
420
441
|
return "Pass & Keys Information";
|
|
421
442
|
} else if (isContractorForm && step === 3) {
|
|
422
443
|
return "Members Information";
|
|
@@ -519,7 +540,7 @@ const {
|
|
|
519
540
|
}
|
|
520
541
|
);
|
|
521
542
|
|
|
522
|
-
watch(fetchPersonByContactReq, (obj) => {
|
|
543
|
+
watch(fetchPersonByContactReq, (obj: Partial<TPerson>) => {
|
|
523
544
|
if (obj) {
|
|
524
545
|
currentAutofillSource.value = "contact";
|
|
525
546
|
companyNames.value = obj.companyName ?? []
|
|
@@ -568,7 +589,7 @@ watch(fetchVisitorListByVehicleNumberReq, (arr: any) => {
|
|
|
568
589
|
const itemsArray = arr?.items || []
|
|
569
590
|
const isValidArray = Array.isArray(itemsArray)
|
|
570
591
|
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : []
|
|
571
|
-
if(matchingPlateNumberNonCheckedOutArr.value.length > 0) {
|
|
592
|
+
if (matchingPlateNumberNonCheckedOutArr.value.length > 0) {
|
|
572
593
|
dialog.showNonCheckedOutDialog = true;
|
|
573
594
|
} else {
|
|
574
595
|
dialog.showNonCheckedOutDialog = false;
|
|
@@ -603,7 +624,7 @@ async function handleCheckout(visitorId: string) {
|
|
|
603
624
|
checkOut: new Date().toISOString(),
|
|
604
625
|
});
|
|
605
626
|
if (res) {
|
|
606
|
-
await fetchVisitorListByVehicleNumberRefresh();
|
|
627
|
+
await fetchVisitorListByVehicleNumberRefresh();
|
|
607
628
|
|
|
608
629
|
}
|
|
609
630
|
} catch (error: any) {
|
|
@@ -876,6 +897,15 @@ async function submit() {
|
|
|
876
897
|
};
|
|
877
898
|
}
|
|
878
899
|
}
|
|
900
|
+
|
|
901
|
+
const isGuest = prop.type === "guest";
|
|
902
|
+
const isWalkIn = prop.type === "walk-in";
|
|
903
|
+
const isContractor = prop.type === "contractor";
|
|
904
|
+
if (isGuest || isWalkIn || isContractor) {
|
|
905
|
+
payload.visitorPass = visitor.visitorPass;
|
|
906
|
+
payload.passKeys = visitor.passKeys;
|
|
907
|
+
}
|
|
908
|
+
|
|
879
909
|
}
|
|
880
910
|
|
|
881
911
|
|
|
@@ -888,7 +918,7 @@ async function submit() {
|
|
|
888
918
|
};
|
|
889
919
|
}
|
|
890
920
|
|
|
891
|
-
if(prop.type === "delivery"){
|
|
921
|
+
if (prop.type === "delivery") {
|
|
892
922
|
payload = {
|
|
893
923
|
...payload,
|
|
894
924
|
company: deliveryCompany.value
|
|
@@ -969,6 +999,18 @@ watch(
|
|
|
969
999
|
}
|
|
970
1000
|
);
|
|
971
1001
|
|
|
1002
|
+
const membersFieldIncomplete = computed(() => {
|
|
1003
|
+
if (!visitor.members || visitor.members.length === 0) return false;
|
|
1004
|
+
|
|
1005
|
+
return visitor.members.some((member) => {
|
|
1006
|
+
const hasName = !!member.name;
|
|
1007
|
+
const hasNric = !!member.nric;
|
|
1008
|
+
const hasContact = !!member.contact;
|
|
1009
|
+
|
|
1010
|
+
return !hasName || !hasNric || !hasContact;
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
|
|
972
1014
|
onMounted(() => {
|
|
973
1015
|
contractorStep.value = 2;
|
|
974
1016
|
currentAutofillSource.value = null;
|
|
@@ -43,6 +43,7 @@
|
|
|
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 }} members)</span>
|
|
46
47
|
</template>
|
|
47
48
|
|
|
48
49
|
<template v-slot:item.type-company="{ item }">
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
<v-icon icon="mdi-user" size="15" />
|
|
51
52
|
<span v-if="item.type === 'contractor'" class="text-capitalize">{{
|
|
52
53
|
formatCamelCaseToWords(item.contractorType)
|
|
53
|
-
|
|
54
|
+
}}</span>
|
|
54
55
|
<span v-else class="text-capitalize">{{ formatType(item) }}</span>
|
|
55
56
|
</span>
|
|
56
57
|
<span class="d-flex align-center ga-2">
|
|
@@ -84,36 +85,49 @@
|
|
|
84
85
|
</template>
|
|
85
86
|
|
|
86
87
|
<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">
|
|
88
|
+
<v-row no-gutters class="d-flex flex-column py-2">
|
|
89
|
+
<span class="d-flex align-center ga-2">
|
|
90
|
+
<v-icon icon="mdi-clock-time-four-outline" color="green" size="20" />
|
|
100
91
|
<span class="text-capitalize">{{
|
|
101
|
-
UTCToLocalTIme(item.
|
|
92
|
+
UTCToLocalTIme(item.checkIn) || "-"
|
|
102
93
|
}}</span>
|
|
103
94
|
<span>
|
|
104
|
-
<v-icon v-if="item?.
|
|
105
|
-
@click.stop="handleViewImage(item.
|
|
95
|
+
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image"
|
|
96
|
+
@click.stop="handleViewImage(item.snapshotEntryImage)" />
|
|
106
97
|
</span>
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
</span>
|
|
99
|
+
<span class="d-flex align-center ga-2">
|
|
100
|
+
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
|
|
101
|
+
<template v-if="item.checkOut">
|
|
102
|
+
<span class="text-capitalize">{{
|
|
103
|
+
UTCToLocalTIme(item.checkOut) || "-"
|
|
104
|
+
}}</span>
|
|
105
|
+
<span>
|
|
106
|
+
<v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image"
|
|
107
|
+
@click.stop="handleViewImage(item.snapshotExitImage)" />
|
|
108
|
+
</span>
|
|
109
|
+
<span v-if="item?.manualCheckout">
|
|
110
|
+
<TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
|
|
111
|
+
</span>
|
|
112
|
+
</template>
|
|
113
|
+
<span v-else-if="!item?.checkOut && (item?.status === 'registered' || item?.status === 'unregistered')">
|
|
114
|
+
<v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
|
|
115
|
+
:loading="loading.checkingOut && item?._id === selectedVisitorId" @click.stop="handleCheckout(item._id)"
|
|
116
|
+
v-if="canCheckoutVisitor" />
|
|
109
117
|
</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
118
|
</span>
|
|
116
|
-
|
|
119
|
+
<div v-if="(item.visitorPass?.length ?? 0) > 0 || (item.passKeys?.length ?? 0) > 0"
|
|
120
|
+
class="d-flex flex-wrap ga-1 mt-1" @click.stop="handleCheckout(item._id)">
|
|
121
|
+
<v-chip v-for="pass in item.visitorPass" :key="(pass as any)._id ?? (pass as any).keyId"
|
|
122
|
+
prepend-icon="mdi-card-bulleted-outline" size="x-small" variant="tonal" color="blue">
|
|
123
|
+
{{ (pass as any)?.prefixAndName }}
|
|
124
|
+
</v-chip>
|
|
125
|
+
<v-chip v-for="key in item.passKeys" :key="(key as any)._id ?? (key as any).keyId" prepend-icon="mdi-key"
|
|
126
|
+
size="x-small" variant="tonal" color="orange">
|
|
127
|
+
{{ (key as any)?.prefixAndName }}
|
|
128
|
+
</v-chip>
|
|
129
|
+
</div>
|
|
130
|
+
</v-row>
|
|
117
131
|
</template>
|
|
118
132
|
|
|
119
133
|
<template v-slot:item.action="{ item }">
|
|
@@ -121,6 +135,21 @@
|
|
|
121
135
|
@click.stop="handleRegistrationUnregisteredVisitor(item)" />
|
|
122
136
|
</template>
|
|
123
137
|
|
|
138
|
+
<template v-slot:item.checkInRemarks="{ item }">
|
|
139
|
+
<span>
|
|
140
|
+
<v-btn variant="text" color="blue-darken-2" density="compact" size="small"
|
|
141
|
+
@click.stop="handleOpenRemarks(item, 'checkIn')">{{ item.checkInRemarks ? "View Remarks" : "Add Remarks"
|
|
142
|
+
}}</v-btn>
|
|
143
|
+
</span>
|
|
144
|
+
</template>
|
|
145
|
+
<template v-slot:item.checkOutRemarks="{ item }">
|
|
146
|
+
<span>
|
|
147
|
+
<v-btn variant="text" color="blue-darken-2" density="compact" size="small"
|
|
148
|
+
@click.stop="handleOpenRemarks(item, 'checkOut')">{{ item.checkOutRemarks ? "View Remarks" : "Add Remarks"
|
|
149
|
+
}}</v-btn>
|
|
150
|
+
</span>
|
|
151
|
+
</template>
|
|
152
|
+
|
|
124
153
|
</TableMain>
|
|
125
154
|
|
|
126
155
|
<v-dialog v-model="dialog.showSelection" width="450" persistent>
|
|
@@ -151,7 +180,7 @@
|
|
|
151
180
|
</span>
|
|
152
181
|
|
|
153
182
|
<span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
|
|
154
|
-
|
|
183
|
+
}}:</strong>
|
|
155
184
|
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
156
185
|
<TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
|
|
157
186
|
</span>
|
|
@@ -179,6 +208,88 @@
|
|
|
179
208
|
</v-card>
|
|
180
209
|
</v-dialog>
|
|
181
210
|
|
|
211
|
+
<v-dialog v-model="dialog.remarks" max-width="450" persistent>
|
|
212
|
+
<v-card>
|
|
213
|
+
<v-card-title class="d-flex justify-space-between align-center pt-4 px-4">
|
|
214
|
+
<span>{{ remarksType === 'checkIn' ? 'Check-In Remarks' : 'Check-Out Remarks' }}</span>
|
|
215
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.remarks = false" />
|
|
216
|
+
</v-card-title>
|
|
217
|
+
<v-card-text class="px-4 pb-2">
|
|
218
|
+
<v-textarea v-model="remarksInput" label="Remarks" rows="4" auto-grow variant="outlined"
|
|
219
|
+
:readonly="remarksViewOnly" />
|
|
220
|
+
</v-card-text>
|
|
221
|
+
<v-toolbar class="pa-0" density="compact">
|
|
222
|
+
<v-row no-gutters>
|
|
223
|
+
<v-col cols="6">
|
|
224
|
+
<v-btn variant="text" block :disabled="loading.savingRemarks"
|
|
225
|
+
@click="dialog.remarks = false">Cancel</v-btn>
|
|
226
|
+
</v-col>
|
|
227
|
+
<v-col cols="6">
|
|
228
|
+
<v-btn color="primary" variant="flat" height="48" rounded="0" block :loading="loading.savingRemarks"
|
|
229
|
+
:disabled="!remarksInput.trim()" @click="handleSaveRemarks">Save</v-btn>
|
|
230
|
+
</v-col>
|
|
231
|
+
</v-row>
|
|
232
|
+
</v-toolbar>
|
|
233
|
+
</v-card>
|
|
234
|
+
</v-dialog>
|
|
235
|
+
|
|
236
|
+
<v-dialog v-model="dialog.returnPassesKeys" max-width="450" persistent>
|
|
237
|
+
<v-card>
|
|
238
|
+
<v-toolbar density="compact" color="">
|
|
239
|
+
<v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
|
|
240
|
+
<span class="font-weight-bold">Return Passes & Keys</span>
|
|
241
|
+
<v-btn icon="mdi-close" variant="text" @click="dialog.returnPassesKeys = false" />
|
|
242
|
+
</v-row>
|
|
243
|
+
</v-toolbar>
|
|
244
|
+
<v-card-text class="px-4 pb-2">
|
|
245
|
+
<p class="text-body-2 mb-3">Please ensure all passes and keys are returned before checking out.</p>
|
|
246
|
+
<div v-if="passReturnStatuses.length > 0" class="mb-2">
|
|
247
|
+
<p class="text-caption text-medium-emphasis mb-1">Passes</p>
|
|
248
|
+
<v-row no-gutters v-for="pass in passReturnStatuses" :key="(pass as any)._id ?? (pass as any).keyId"
|
|
249
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
|
|
250
|
+
<v-chip prepend-icon="mdi-card-bulleted-outline" size="small" variant="tonal" color="blue">
|
|
251
|
+
{{ (pass as any)?.prefixAndName }}
|
|
252
|
+
</v-chip>
|
|
253
|
+
<v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
|
|
254
|
+
item-value="value" v-model="pass.status"></v-select>
|
|
255
|
+
<v-textarea v-if="pass.status === 'Lost' || pass.status === 'Damaged'" no-resize rows="3" class="w-100"
|
|
256
|
+
density="compact" v-model="pass.remarks"></v-textarea>
|
|
257
|
+
</v-row>
|
|
258
|
+
</div>
|
|
259
|
+
<div v-if="(keyReturnStatuses.length > 0)" class="mb-2">
|
|
260
|
+
<p class="text-caption text-medium-emphasis mb-1">Keys</p>
|
|
261
|
+
<v-row no-gutters v-for="key in keyReturnStatuses" :key="(key as any)._id ?? (key as any).keyId"
|
|
262
|
+
class="d-flex flex-wrap justify-space-between align-center ga-5 mb-2">
|
|
263
|
+
<v-chip prepend-icon="mdi-key" size="small" variant="tonal" color="orange">
|
|
264
|
+
{{ (key as any)?.prefixAndName }}
|
|
265
|
+
</v-chip>
|
|
266
|
+
<v-select hide-details max-width="200px" density="compact" :items="passStatusOptions" item-title="label"
|
|
267
|
+
item-value="value" v-model="key.status">
|
|
268
|
+
</v-select>
|
|
269
|
+
<v-textarea v-if="key.status === 'Lost' || key.status === 'Damaged'" no-resize rows="3" class="w-100"
|
|
270
|
+
density="compact" v-model="key.remarks"></v-textarea>
|
|
271
|
+
</v-row>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<v-row no-gutters class="my-5">
|
|
275
|
+
<v-btn variant="flat" color="blue" density="comfortable" class="text-capitalize" :loading="loading.updatingPassKeys" @click.stop="handleUpdatePassKeys">Update Pass/Keys</v-btn>
|
|
276
|
+
</v-row>
|
|
277
|
+
</v-card-text>
|
|
278
|
+
<v-toolbar class="pa-0" density="compact">
|
|
279
|
+
<v-row no-gutters>
|
|
280
|
+
<v-col cols="6">
|
|
281
|
+
<v-btn variant="text" block :disabled="loading.checkingOut"
|
|
282
|
+
@click="dialog.returnPassesKeys = false">Close</v-btn>
|
|
283
|
+
</v-col>
|
|
284
|
+
<v-col cols="6">
|
|
285
|
+
<v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut"
|
|
286
|
+
:disabled="!canConfirmCheckout" @click="proceedCheckout">Confirm Checkout</v-btn>
|
|
287
|
+
</v-col>
|
|
288
|
+
</v-row>
|
|
289
|
+
</v-toolbar>
|
|
290
|
+
</v-card>
|
|
291
|
+
</v-dialog>
|
|
292
|
+
|
|
182
293
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
183
294
|
</v-row>
|
|
184
295
|
</template>
|
|
@@ -256,6 +367,8 @@ const loading = reactive({
|
|
|
256
367
|
deletingVisitor: false,
|
|
257
368
|
fetchingVisitors: false,
|
|
258
369
|
checkingOut: false,
|
|
370
|
+
savingRemarks: false,
|
|
371
|
+
updatingPassKeys: false,
|
|
259
372
|
});
|
|
260
373
|
|
|
261
374
|
const dialog = reactive({
|
|
@@ -264,9 +377,14 @@ const dialog = reactive({
|
|
|
264
377
|
viewVisitor: false,
|
|
265
378
|
deleteConfirmation: false,
|
|
266
379
|
snapshotImage: false,
|
|
380
|
+
remarks: false,
|
|
381
|
+
returnPassesKeys: false,
|
|
267
382
|
});
|
|
268
383
|
|
|
269
384
|
const snapshotImageUrl = ref("");
|
|
385
|
+
const remarksType = ref<'checkIn' | 'checkOut'>('checkIn');
|
|
386
|
+
const remarksInput = ref("");
|
|
387
|
+
const remarksViewOnly = ref(false);
|
|
270
388
|
|
|
271
389
|
const headers = computed(() => [
|
|
272
390
|
{ title: "Name", value: "name" },
|
|
@@ -274,6 +392,7 @@ const headers = computed(() => [
|
|
|
274
392
|
{ title: "Location", value: "location" },
|
|
275
393
|
{ title: "Contact/Vehicle No.", value: "contact-vehicleNumber" },
|
|
276
394
|
{ title: "Check In/Out", value: "checkin-out" },
|
|
395
|
+
...(activeTab.value === "resident-transactions" ? [{ title: "Check-in Remarks", value: "checkInRemarks" }, { title: "Check-out Remarks", value: "checkOutRemarks" }] : []),
|
|
277
396
|
...(activeTab.value === 'unregistered' ? [{ title: "Action", value: "action" }] : [])
|
|
278
397
|
])
|
|
279
398
|
|
|
@@ -314,7 +433,15 @@ function filterTypeSelectionLabel() {
|
|
|
314
433
|
return `${length} selected ${length === 1 ? "type" : "types"}` as string;
|
|
315
434
|
}
|
|
316
435
|
|
|
436
|
+
const passStatusOptions = [
|
|
437
|
+
{ label: "Returned", value: "Returned" },
|
|
438
|
+
{ label: "Not Returned", value: "In Use" },
|
|
439
|
+
{ label: "Damaged", value: "Damaged" },
|
|
440
|
+
{ label: "Lost", value: "Lost" },
|
|
441
|
+
]
|
|
442
|
+
|
|
317
443
|
function toRoute(tab: any) {
|
|
444
|
+
items.value = []
|
|
318
445
|
|
|
319
446
|
const obj = tabOptions.find((x) => x.value === tab);
|
|
320
447
|
if (!obj) return;
|
|
@@ -347,7 +474,7 @@ const {
|
|
|
347
474
|
search: searchInput.value,
|
|
348
475
|
dateTo: dateTo.value,
|
|
349
476
|
dateFrom: dateFrom.value,
|
|
350
|
-
type: filterTypes.value.filter(Boolean).join(","),
|
|
477
|
+
type: (filterTypes.value.length === 0 && activeTab.value === 'registered' ? "contractor,delivery,walk-in,pick-up,drop-off,guest" : filterTypes.value.filter(Boolean).join(",")),
|
|
351
478
|
checkedOut: displayNotCheckedOut.value ? false : undefined
|
|
352
479
|
}
|
|
353
480
|
|
|
@@ -358,7 +485,7 @@ const {
|
|
|
358
485
|
// params.status = "pending"
|
|
359
486
|
// }
|
|
360
487
|
|
|
361
|
-
if(activeTab.value === "guests"){
|
|
488
|
+
if (activeTab.value === "guests") {
|
|
362
489
|
params.type = "guest"
|
|
363
490
|
params.status = "pending"
|
|
364
491
|
} else if (activeTab.value === "resident-transactions") {
|
|
@@ -380,7 +507,8 @@ const {
|
|
|
380
507
|
|
|
381
508
|
watch(getVisitorReq, (newData: any) => {
|
|
382
509
|
if (newData) {
|
|
383
|
-
items.value = newData.items ?? [];
|
|
510
|
+
// items.value = newData.items ?? [];
|
|
511
|
+
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
512
|
pages.value = newData.pages ?? 0;
|
|
385
513
|
pageRange.value = newData?.pageRange ?? "-- - -- of --";
|
|
386
514
|
}
|
|
@@ -479,6 +607,56 @@ function handleViewImage(imageId: string) {
|
|
|
479
607
|
dialog.snapshotImage = true;
|
|
480
608
|
}
|
|
481
609
|
|
|
610
|
+
function handleOpenRemarks(item: any, type: 'checkIn' | 'checkOut') {
|
|
611
|
+
selectedVisitorId.value = item?._id;
|
|
612
|
+
remarksType.value = type;
|
|
613
|
+
const existingRemarks = type === 'checkIn' ? item?.checkInRemarks : item?.checkOutRemarks;
|
|
614
|
+
remarksInput.value = existingRemarks || "";
|
|
615
|
+
remarksViewOnly.value = !!existingRemarks;
|
|
616
|
+
dialog.remarks = true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function handleSaveRemarks() {
|
|
620
|
+
if (!remarksInput.value.trim() || !selectedVisitorId.value) return;
|
|
621
|
+
try {
|
|
622
|
+
loading.savingRemarks = true;
|
|
623
|
+
const payload = remarksType.value === 'checkIn'
|
|
624
|
+
? { checkInRemarks: remarksInput.value.trim() }
|
|
625
|
+
: { checkOutRemarks: remarksInput.value.trim() };
|
|
626
|
+
await updateVisitor(selectedVisitorId.value, payload);
|
|
627
|
+
showMessage("Remarks saved successfully!", "info");
|
|
628
|
+
await getVisitorRefresh();
|
|
629
|
+
dialog.remarks = false;
|
|
630
|
+
} catch (error: any) {
|
|
631
|
+
const errorMessage = error?.response?._data?.message;
|
|
632
|
+
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
633
|
+
} finally {
|
|
634
|
+
loading.savingRemarks = false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function handleUpdatePassKeys(){
|
|
639
|
+
if (!selectedVisitorId.value) return;
|
|
640
|
+
try {
|
|
641
|
+
loading.updatingPassKeys = true;
|
|
642
|
+
const payload: any = {};
|
|
643
|
+
if (passReturnStatuses.value.length > 0) {
|
|
644
|
+
payload.visitorPass = passReturnStatuses.value;
|
|
645
|
+
}
|
|
646
|
+
if (keyReturnStatuses.value.length > 0) {
|
|
647
|
+
payload.passKeys = keyReturnStatuses.value;
|
|
648
|
+
}
|
|
649
|
+
await updateVisitor(selectedVisitorId.value, payload);
|
|
650
|
+
showMessage("Pass/Key statuses updated successfully!", "info");
|
|
651
|
+
await getVisitorRefresh();
|
|
652
|
+
} catch (error: any) {
|
|
653
|
+
const errorMessage = error?.response?._data?.message;
|
|
654
|
+
showMessage(errorMessage || "Something went wrong. Please try again later.", "error");
|
|
655
|
+
} finally {
|
|
656
|
+
loading.updatingPassKeys = false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
482
660
|
|
|
483
661
|
async function handleProceedDeleteVisitor() {
|
|
484
662
|
try {
|
|
@@ -502,22 +680,59 @@ async function handleProceedDeleteVisitor() {
|
|
|
502
680
|
}
|
|
503
681
|
}
|
|
504
682
|
|
|
505
|
-
|
|
683
|
+
|
|
684
|
+
const passReturnStatuses = ref<{ keyId: string, status: string, prefixAndName: string, remarks: string }[]>([]);
|
|
685
|
+
const keyReturnStatuses = ref<{ keyId: string, status: string, prefixAndName: string, remarks: string }[]>([]);
|
|
686
|
+
|
|
687
|
+
const canConfirmCheckout = computed(() => {
|
|
688
|
+
const allEntries = [...passReturnStatuses.value, ...keyReturnStatuses.value];
|
|
689
|
+
return allEntries.every((entry) => {
|
|
690
|
+
if (!entry.status || entry.status === 'In Use') return false;
|
|
691
|
+
if ((entry.status === 'Lost' || entry.status === 'Damaged') && !entry.remarks.trim()) return false;
|
|
692
|
+
return true;
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
function handleCheckout(userId: string) {
|
|
506
699
|
if (!userId) {
|
|
507
700
|
showMessage("Invalid userId", "error");
|
|
508
701
|
return;
|
|
509
702
|
}
|
|
510
703
|
selectedVisitorId.value = userId;
|
|
511
704
|
|
|
705
|
+
const visitor = items.value.find((x: any) => x?._id === userId);
|
|
706
|
+
const hasPasses = (visitor?.visitorPass?.length ?? 0) > 0;
|
|
707
|
+
const hasKeys = (visitor?.passKeys?.length ?? 0) > 0;
|
|
708
|
+
|
|
709
|
+
if (hasPasses || hasKeys) {
|
|
710
|
+
const visitor = items.value.find((x: any) => x?._id === userId);
|
|
711
|
+
passReturnStatuses.value = ((visitor?.visitorPass as any[]) || []).map((p: any) => ({ keyId: p.keyId, status: p.status ?? "In Use", remarks: '', prefixAndName: p.prefixAndName }));
|
|
712
|
+
keyReturnStatuses.value = ((visitor?.passKeys as any[]) || []).map((k: any) => ({ keyId: k.keyId, status: k.status ?? "In Use", remarks: '', prefixAndName: k.prefixAndName }));
|
|
713
|
+
dialog.returnPassesKeys = true;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
proceedCheckout();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async function proceedCheckout() {
|
|
721
|
+
const userId = selectedVisitorId.value;
|
|
722
|
+
if (!userId) return;
|
|
723
|
+
|
|
512
724
|
try {
|
|
513
725
|
loading.checkingOut = true;
|
|
514
726
|
const res = await updateVisitor(userId as string, {
|
|
515
727
|
checkOut: new Date().toISOString(),
|
|
728
|
+
...(passReturnStatuses.value.length > 0 ? { visitorPass: passReturnStatuses.value } : {}),
|
|
729
|
+
...(keyReturnStatuses.value.length > 0 ? { passKeys: keyReturnStatuses.value } : {}),
|
|
516
730
|
});
|
|
517
731
|
if (res) {
|
|
518
732
|
showMessage("Visitor successfully checked-out!", "info");
|
|
519
733
|
await getVisitorRefresh();
|
|
520
734
|
dialog.viewVisitor = false;
|
|
735
|
+
dialog.returnPassesKeys = false;
|
|
521
736
|
}
|
|
522
737
|
} catch (error: any) {
|
|
523
738
|
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(() => {
|