@7365admin1/layer-common 1.10.6 → 1.10.7
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 +6 -0
- package/components/AccessCardQrTagging.vue +314 -34
- package/components/AccessCardQrTaggingPrintQr.vue +75 -0
- package/components/AreaChecklistHistoryLogs.vue +9 -0
- package/components/BuildingForm.vue +36 -5
- package/components/BuildingManagement/buildings.vue +18 -9
- package/components/BuildingManagement/units.vue +12 -114
- package/components/BuildingUnitFormAdd.vue +39 -30
- package/components/BuildingUnitFormEdit.vue +265 -116
- package/components/CleaningScheduleMain.vue +60 -13
- package/components/Dialog/DeleteConfirmation.vue +2 -2
- package/components/Dialog/UpdateMoreAction.vue +2 -2
- package/components/EntryPassInformation.vue +215 -0
- package/components/Input/InputPhoneNumberV2.vue +8 -0
- package/components/ManageChecklistMain.vue +29 -3
- package/components/ScheduleAreaMain.vue +56 -0
- package/components/TableHygiene.vue +27 -0
- package/components/UnitPersonCard.vue +63 -0
- package/components/VehicleAddSelection.vue +2 -2
- package/components/VehicleForm.vue +78 -19
- package/components/VehicleManagement.vue +164 -40
- package/components/VisitorForm.vue +19 -0
- package/composables/useAccessManagement.ts +52 -0
- package/composables/usePeople.ts +4 -3
- package/composables/useVehicle.ts +15 -1
- package/package.json +3 -2
- package/types/cleaner-schedule.d.ts +1 -0
- package/types/html2pdf.d.ts +19 -0
- package/types/people.d.ts +2 -1
- package/types/site.d.ts +8 -0
- package/types/vehicle.d.ts +2 -3
- package/.playground/app.vue +0 -41
- package/.playground/eslint.config.mjs +0 -6
- package/.playground/nuxt.config.ts +0 -22
- package/.playground/pages/feedback.vue +0 -30
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<v-row>
|
|
42
42
|
<v-col cols="12">
|
|
43
43
|
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
44
|
-
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]"
|
|
44
|
+
<v-text-field v-model.trim="vehicle.name" density="comfortable" :rules="[requiredRule]" disabled />
|
|
45
45
|
</v-col>
|
|
46
46
|
</v-row>
|
|
47
47
|
</v-col>
|
|
@@ -75,10 +75,44 @@
|
|
|
75
75
|
<InputLabel class="text-capitalize" title="Vehicle Numbers" required />
|
|
76
76
|
<!-- <v-text-field v-model="vehicle.plateNumber" density="comfortable" :rules="[requiredRule]" /> -->
|
|
77
77
|
<template v-for="plate in vehicle.plates" :key="plate.plateNumber">
|
|
78
|
-
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]" class="mb-2"
|
|
78
|
+
<v-text-field v-model="plate.plateNumber" density="comfortable" :rules="[requiredRule]" class="mb-2" readonly>
|
|
79
|
+
<template #append-inner>
|
|
80
|
+
<v-chip size="small" class="ma-0" :color="formatVehicleStatus(plate?.status)?.color as string">{{ formatVehicleStatus(plate?.status)?.label }}</v-chip>
|
|
81
|
+
</template>
|
|
82
|
+
</v-text-field>
|
|
79
83
|
</template>
|
|
80
84
|
|
|
81
|
-
<
|
|
85
|
+
<template v-for="(plate, index) in newPlateNumbers" :key="index">
|
|
86
|
+
<v-row class="" :class="`${index === newPlateNumbers.length - 1 ? 'mb-1' : 'mb-5'}`" no-gutters>
|
|
87
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '11' : '12'">
|
|
88
|
+
<InputVehicleNumber
|
|
89
|
+
v-model="newPlateNumbers[index]"
|
|
90
|
+
density="comfortable"
|
|
91
|
+
placeholder="New Vehicle Number"
|
|
92
|
+
:rules="[requiredRule]"
|
|
93
|
+
/>
|
|
94
|
+
</v-col>
|
|
95
|
+
|
|
96
|
+
<v-col :cols="newPlateNumbers.length > 1 ? '1' : '0'" class="d-flex align-start">
|
|
97
|
+
<v-btn
|
|
98
|
+
icon="mdi-delete-outline"
|
|
99
|
+
variant="text"
|
|
100
|
+
color="error"
|
|
101
|
+
v-if="newPlateNumbers.length > 1"
|
|
102
|
+
@click="removePlate(index)"
|
|
103
|
+
/>
|
|
104
|
+
</v-col>
|
|
105
|
+
</v-row>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<v-btn
|
|
109
|
+
prepend-icon="mdi-plus"
|
|
110
|
+
variant="outlined"
|
|
111
|
+
class="text-none"
|
|
112
|
+
@click="addPlate"
|
|
113
|
+
>
|
|
114
|
+
Add Plate
|
|
115
|
+
</v-btn>
|
|
82
116
|
</v-col>
|
|
83
117
|
|
|
84
118
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
@@ -157,17 +191,24 @@
|
|
|
157
191
|
</v-toolbar>
|
|
158
192
|
|
|
159
193
|
<v-dialog v-model="showMatchingPeopleDialog" max-width="700">
|
|
160
|
-
<v-card>
|
|
194
|
+
<v-card :loading="checkingNRIC">
|
|
161
195
|
<v-toolbar>
|
|
162
196
|
<v-toolbar-title>
|
|
163
|
-
|
|
197
|
+
<v-row no-gutters class="d-flex align-center justify-space-between">
|
|
198
|
+
<span class="font-weight-bold">
|
|
199
|
+
Matching Records for NRIC: {{ vehicle.nric }}
|
|
200
|
+
</span>
|
|
201
|
+
<span>
|
|
202
|
+
<v-btn icon="mdi-close" variant="text" @click="showMatchingPeopleDialog = false" />
|
|
203
|
+
</span>
|
|
204
|
+
</v-row>
|
|
164
205
|
</v-toolbar-title>
|
|
165
206
|
</v-toolbar>
|
|
166
207
|
|
|
167
208
|
<v-card-text>
|
|
168
209
|
|
|
169
210
|
<v-list lines="three">
|
|
170
|
-
<v-list-item v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
|
|
211
|
+
<v-list-item v-if="matchingPeople.length > 0 || checkingNRIC" v-for="v in matchingPeople" :key="v._id" class="cursor-pointer">
|
|
171
212
|
<v-list-item-title>
|
|
172
213
|
{{ v.name }}
|
|
173
214
|
</v-list-item-title>
|
|
@@ -187,6 +228,14 @@
|
|
|
187
228
|
</template>
|
|
188
229
|
|
|
189
230
|
</v-list-item>
|
|
231
|
+
<v-empty-state v-else
|
|
232
|
+
icon="mdi-account-off"
|
|
233
|
+
title="No matching records found"
|
|
234
|
+
subtitle="The NRIC you entered does not match any existing records.">
|
|
235
|
+
<template #actions>
|
|
236
|
+
<v-btn variant="flat" color="primary" @click="showMatchingPeopleDialog = false">Close</v-btn>
|
|
237
|
+
</template>
|
|
238
|
+
</v-empty-state>
|
|
190
239
|
</v-list>
|
|
191
240
|
|
|
192
241
|
</v-card-text>
|
|
@@ -202,7 +251,7 @@
|
|
|
202
251
|
|
|
203
252
|
const prop = defineProps({
|
|
204
253
|
type: {
|
|
205
|
-
type: String as PropType<TVehicleType>,
|
|
254
|
+
type: String as PropType<TVehicleType | 'seasonpass'>,
|
|
206
255
|
required: true
|
|
207
256
|
},
|
|
208
257
|
org: {
|
|
@@ -224,14 +273,14 @@ const prop = defineProps({
|
|
|
224
273
|
});
|
|
225
274
|
|
|
226
275
|
const { requiredRule, formatDateISO8601, debounce } = useUtils();
|
|
227
|
-
const { addVehicle, getCustomSeasonPassTypes, updateVehicle,
|
|
276
|
+
const { addVehicle, getCustomSeasonPassTypes, updateVehicle, formatVehicleStatus } = useVehicle();
|
|
228
277
|
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
229
278
|
const { findPersonByNRICMultipleResult } = usePeople();
|
|
230
279
|
|
|
231
280
|
const emit = defineEmits(['back', 'select', 'done', 'error', 'close', 'close:all']);
|
|
232
281
|
|
|
233
282
|
|
|
234
|
-
const vehicle = reactive<
|
|
283
|
+
const vehicle = reactive<Omit<ExceptPartial<TVehicle>, 'type'> & { type: TVehicleType | 'seasonpass' }>({
|
|
235
284
|
plates: [],
|
|
236
285
|
type: prop.type,
|
|
237
286
|
category: "resident",
|
|
@@ -250,7 +299,7 @@ const vehicle = reactive<Partial<TVehicle>>({
|
|
|
250
299
|
_id: '',
|
|
251
300
|
});
|
|
252
301
|
|
|
253
|
-
const
|
|
302
|
+
const newPlateNumbers = ref<string[]>(['']);
|
|
254
303
|
const disablePrefilledInputs = ref(true);
|
|
255
304
|
|
|
256
305
|
const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
@@ -277,7 +326,7 @@ const defaultSeasonPassTypeArray = computed(() => {
|
|
|
277
326
|
});
|
|
278
327
|
|
|
279
328
|
|
|
280
|
-
const typeFieldMap: Record<TVehicleType, string[]> = {
|
|
329
|
+
const typeFieldMap: Record<TVehicleType | 'seasonpass', string[]> = {
|
|
281
330
|
seasonpass: ['seasonPassType', 'name', 'phone', 'plateNumber', 'block', 'level', 'unit', 'start', 'end'],
|
|
282
331
|
blocklist: ['name', 'nric', 'phone', 'plateNumber', 'remarks'],
|
|
283
332
|
whitelist: ['name', 'nric', 'phone', 'plateNumber', 'block', 'level', 'unit']
|
|
@@ -302,7 +351,7 @@ const errorMessage = ref('');
|
|
|
302
351
|
|
|
303
352
|
|
|
304
353
|
// fetch existing vehicle data if in edit mode
|
|
305
|
-
if (prop.
|
|
354
|
+
if (prop.vehicleData) {
|
|
306
355
|
const existingVehicleData = JSON.parse(JSON.stringify(prop.vehicleData || {}));
|
|
307
356
|
Object.assign(vehicle, existingVehicleData);
|
|
308
357
|
}
|
|
@@ -416,7 +465,14 @@ function handleChangeLevel(value: any) {
|
|
|
416
465
|
refreshUnitsData();
|
|
417
466
|
}
|
|
418
467
|
|
|
468
|
+
function addPlate() {
|
|
469
|
+
newPlateNumbers.value.push('');
|
|
470
|
+
}
|
|
471
|
+
|
|
419
472
|
|
|
473
|
+
function removePlate(index: number) {
|
|
474
|
+
newPlateNumbers.value.splice(index, 1);
|
|
475
|
+
}
|
|
420
476
|
|
|
421
477
|
function back() {
|
|
422
478
|
emit("back");
|
|
@@ -432,12 +488,13 @@ function close() {
|
|
|
432
488
|
selectedSubscriptionDuration.value = null;
|
|
433
489
|
}
|
|
434
490
|
|
|
435
|
-
function formatVehicleType(type: TVehicleType): string {
|
|
491
|
+
function formatVehicleType(type: TVehicleType | 'seasonpass'): string {
|
|
436
492
|
switch (type) {
|
|
437
493
|
case 'whitelist':
|
|
438
494
|
return 'Whitelist';
|
|
439
495
|
case 'blocklist':
|
|
440
496
|
return 'Blocklist';
|
|
497
|
+
|
|
441
498
|
case 'seasonpass':
|
|
442
499
|
return 'Season Pass';
|
|
443
500
|
default:
|
|
@@ -463,15 +520,16 @@ async function submit() {
|
|
|
463
520
|
const { plateNumber, type, category, name, phoneNumber, block, level, unit, nric, remarks, seasonPassType, start, end, site, org } = vehicle
|
|
464
521
|
|
|
465
522
|
let payload: Partial<TVehiclePayload> = {
|
|
466
|
-
plateNumber,
|
|
523
|
+
plateNumber: newPlateNumbers.value,
|
|
467
524
|
name,
|
|
468
525
|
phoneNumber,
|
|
526
|
+
type: type === 'seasonpass' ? 'whitelist' : type, // season pass will be created as whitelist type in backend with additional seasonPassType field,
|
|
527
|
+
nric
|
|
469
528
|
};
|
|
470
529
|
|
|
471
530
|
if (prop.mode === 'add') {
|
|
472
531
|
payload = {
|
|
473
532
|
...payload,
|
|
474
|
-
type,
|
|
475
533
|
category,
|
|
476
534
|
site,
|
|
477
535
|
org
|
|
@@ -498,7 +556,6 @@ async function submit() {
|
|
|
498
556
|
} else if (vehicle.type === 'blocklist') {
|
|
499
557
|
payload = {
|
|
500
558
|
...payload,
|
|
501
|
-
nric,
|
|
502
559
|
remarks,
|
|
503
560
|
};
|
|
504
561
|
}
|
|
@@ -609,18 +666,17 @@ async function checkNRIC() {
|
|
|
609
666
|
if (!vehicle.nric || vehicle.nric.length < 5) return;
|
|
610
667
|
|
|
611
668
|
checkingNRIC.value = true;
|
|
612
|
-
|
|
669
|
+
showMatchingPeopleDialog.value = true;
|
|
613
670
|
try {
|
|
614
671
|
const res = await findPersonByNRICMultipleResult(vehicle.nric, prop.site) as { items: TPeople[] } | null;
|
|
615
672
|
|
|
616
673
|
if (res?.items && res.items.length > 0) {
|
|
617
674
|
matchingPeople.value = res.items || []
|
|
618
|
-
showMatchingPeopleDialog.value = true;
|
|
619
675
|
} else {
|
|
620
676
|
matchingPeople.value = [];
|
|
621
|
-
showMatchingPeopleDialog.value = false;
|
|
622
677
|
}
|
|
623
678
|
|
|
679
|
+
|
|
624
680
|
} catch (error) {
|
|
625
681
|
console.error("NRIC search failed:", error);
|
|
626
682
|
} finally {
|
|
@@ -634,6 +690,7 @@ watch(
|
|
|
634
690
|
() => vehicle.nric,
|
|
635
691
|
async (newNRIC) => {
|
|
636
692
|
resetVehicleDetails();
|
|
693
|
+
matchingPeople.value = [];
|
|
637
694
|
if (!newNRIC || newNRIC.length < 3) return;
|
|
638
695
|
|
|
639
696
|
debounceedCheckNRIC();
|
|
@@ -651,6 +708,8 @@ const resetVehicleDetails = () => {
|
|
|
651
708
|
disablePrefilledInputs.value = false;
|
|
652
709
|
}
|
|
653
710
|
|
|
711
|
+
watch()
|
|
712
|
+
|
|
654
713
|
|
|
655
714
|
|
|
656
715
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<v-row no-gutters>
|
|
3
3
|
<TableMain :headers="headers" :items="items" :loading="loading || getVehiclesPending" :page="page" :pages="pages"
|
|
4
4
|
:pageRange="pageRange" :extension-height="70" :canCreate="canCreateVehicle" @refresh="getVehiclesRefresh"
|
|
5
|
-
createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="
|
|
5
|
+
createLabel="Add Vehicle" show-header @row-click="handleRowClick" @create="handleAddVehicleClick"
|
|
6
6
|
@update:page="handleUpdatePage">
|
|
7
7
|
|
|
8
8
|
<template #extension>
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
<template #item.block="{ value }">
|
|
18
18
|
{{ value ? `Blk ${value}` : "" }}
|
|
19
19
|
</template>
|
|
20
|
-
<template #item.status="{ value }">
|
|
20
|
+
<!-- <template #item.status="{ value }">
|
|
21
21
|
<v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
|
|
22
22
|
{{ formatVehicleStatus(value).label }}
|
|
23
23
|
</v-chip>
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
</template>
|
|
26
|
+
</template> -->
|
|
27
27
|
|
|
28
28
|
<template #item.plates="{ value, item }">
|
|
29
|
-
|
|
29
|
+
<PlateNumberDisplay :plate-numbers="value" :default-value="item.plateNumber" />
|
|
30
30
|
</template>
|
|
31
31
|
</TableMain>
|
|
32
32
|
|
|
@@ -37,33 +37,51 @@
|
|
|
37
37
|
</v-dialog>
|
|
38
38
|
|
|
39
39
|
<v-dialog v-model="dialog.createVehicle" v-if="vehicleType" width="450" persistent>
|
|
40
|
-
<VehicleForm :type="vehicleType" mode="add" @back="handleBackToSelection" @done="handleAddVehicleComplete"
|
|
40
|
+
<VehicleForm :type="vehicleType" mode="add" :vehicle-data="selectedVehicleObject" @back="handleBackToSelection" @done="handleAddVehicleComplete"
|
|
41
41
|
:org="org" :site="props.site" @close:all="handleCloseAll" />
|
|
42
42
|
</v-dialog>
|
|
43
43
|
|
|
44
|
-
<v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
|
|
44
|
+
<!-- <v-dialog v-model="dialog.updateVehicle" v-if="vehicleType" width="450" persistent>
|
|
45
45
|
<VehicleForm :type="vehicleType" mode="edit" :vehicle-data="selectedVehicleObject"
|
|
46
46
|
@back="dialog.updateVehicle = false" @close="dialog.updateVehicle = false" @done="handleUpdateVehicleComplete"
|
|
47
47
|
:org="org" :site="site" @close:all="handleCloseAll" />
|
|
48
|
-
</v-dialog>
|
|
48
|
+
</v-dialog> -->
|
|
49
49
|
|
|
50
|
-
<v-dialog v-if="canViewVehicleDetails" v-model="dialog.showMoreActions" width="
|
|
51
|
-
<DialogUpdateMoreAction title="Preview" :can-update="
|
|
52
|
-
@close="dialog.showMoreActions = false"
|
|
53
|
-
@delete="handleDeleteVehicleAction" @edit="handleEditVehicleAction">
|
|
50
|
+
<v-dialog v-if="canViewVehicleDetails" v-model="dialog.showMoreActions" width="600" persistent>
|
|
51
|
+
<DialogUpdateMoreAction title="Preview" :can-update="false" :can-delete="false"
|
|
52
|
+
@close="dialog.showMoreActions = false">
|
|
54
53
|
<template v-slot:content>
|
|
55
54
|
<v-row no-gutters class="ga-1 mb-5">
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
<template v-for="(label, key) in formattedFields" :key="key">
|
|
58
|
-
<v-col v-if="key === 'plates'" class="d-flex ga-2">
|
|
57
|
+
<v-col v-if="key === 'plates'" class="d-flex flex-column ga-2">
|
|
59
58
|
<span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong></span>
|
|
60
|
-
<PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" />
|
|
59
|
+
<!-- <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" /> -->
|
|
60
|
+
<v-col cols="12">
|
|
61
|
+
<v-card flat outlined class="pa-2" border="sm black">
|
|
62
|
+
<v-data-table :items="selectedVehicleObject[key]" :headers="plateHeaders" hide-default-footer>
|
|
63
|
+
<template #item.status="{ value }">
|
|
64
|
+
<v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
|
|
65
|
+
{{ formatVehicleStatus(value).label }}
|
|
66
|
+
</v-chip>
|
|
67
|
+
</template>
|
|
68
|
+
<template #item.action="{ item }">
|
|
69
|
+
<v-btn v-if="canDeleteVehicle && (item as TPlateNumber)?.status == 'active'" text="Delete" color="error" flat size="x-small" @click="handleDeleteVehicleAction(item as TPlateNumber)"/>
|
|
70
|
+
<v-btn v-if="props.app === 'property_management_agency' && (item as TPlateNumber)?.status == 'pending'" text="Approve" color="success" flat size="x-small" @click="handleApproveVehicle(item as TPlateNumber)"/>
|
|
71
|
+
<v-btn v-if=" (item as TPlateNumber)?.status == 'deleted'" text="Restore" color="orange" flat size="x-small" @click="handleRestoreVehicle(item as TPlateNumber)"/>
|
|
72
|
+
</template>
|
|
73
|
+
</v-data-table>
|
|
74
|
+
<v-btn text="Add Vehicle Number" class="mt-6 text-capitalize" prepend-icon="mdi-plus"
|
|
75
|
+
@click="addNewPlateNumbers" color="primary" />
|
|
76
|
+
</v-card>
|
|
77
|
+
</v-col>å
|
|
78
|
+
|
|
61
79
|
</v-col>
|
|
62
80
|
|
|
63
81
|
<v-col v-else-if="selectedVehicleObject[key]" cols="12">
|
|
64
82
|
<span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ formatValues(key,
|
|
65
83
|
selectedVehicleObject[key])
|
|
66
|
-
|
|
84
|
+
}}</span>
|
|
67
85
|
</v-col>
|
|
68
86
|
</template>
|
|
69
87
|
</v-row>
|
|
@@ -71,13 +89,22 @@
|
|
|
71
89
|
</DialogUpdateMoreAction>
|
|
72
90
|
</v-dialog>
|
|
73
91
|
<v-dialog v-model="dialog.deleteVehicle" persistent width="540">
|
|
74
|
-
<DialogDeleteConfirmation :message="message" prompt-title="Are you sure want to delete this vehicle
|
|
92
|
+
<DialogDeleteConfirmation :message="message" :loading="deletingVehicle" :prompt-title="`Are you sure want to delete this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
75
93
|
@delete="submitDelete" @close="closeDeleteDialog" />
|
|
76
94
|
</v-dialog>
|
|
95
|
+
<v-dialog v-model="dialog.approveVehicle" persistent width="540">
|
|
96
|
+
<DialogReusablePrompt :message="message" :loading="approvingVehicle" :prompt-title="`Are you sure want to approve this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
97
|
+
@approve="submitApprove" @close="dialog.approveVehicle = false" />
|
|
98
|
+
</v-dialog>
|
|
99
|
+
<v-dialog v-model="dialog.restoreVehicle" persistent width="540">
|
|
100
|
+
<DialogReusablePrompt :message="message" :loading="restoringVehicle" :prompt-title="`Are you sure want to restore this vehicle - ${selectedPlateNumberObject?.plateNumber}?`"
|
|
101
|
+
@approve="submitRestore" @close="dialog.restoreVehicle = false" />
|
|
102
|
+
</v-dialog>
|
|
77
103
|
</v-row>
|
|
78
104
|
</template>
|
|
79
105
|
|
|
80
106
|
<script lang="ts" setup>
|
|
107
|
+
import useUtils from '../composables/useUtils';
|
|
81
108
|
import useVehicle from '../composables/useVehicle';
|
|
82
109
|
|
|
83
110
|
definePageMeta({
|
|
@@ -93,26 +120,33 @@ const props = defineProps({
|
|
|
93
120
|
canViewVehicleDetails: { type: Boolean, default: true },
|
|
94
121
|
site: { type: String, required: true },
|
|
95
122
|
org: { type: String, required: true },
|
|
123
|
+
app: { type: String, required: true },
|
|
96
124
|
});
|
|
97
125
|
|
|
98
126
|
|
|
99
127
|
const headers = [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
{ title: "Name", value: "name" },
|
|
129
|
+
{ title: "Vehicle Numbers", value: "plates" },
|
|
130
|
+
{ title: "NRIC", value: "nric" },
|
|
131
|
+
{ title: "Block", value: "block" },
|
|
132
|
+
{ title: "Floor", value: "level" },
|
|
133
|
+
{ title: "Unit", value: "unitName" },
|
|
134
|
+
{ title: "Category", value: "category" },
|
|
135
|
+
// { title: "Type", value: "type" },
|
|
136
|
+
// { title: "Status", value: "status" },
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
const plateHeaders = [
|
|
140
|
+
{ title: "Plate Number", value: "plateNumber" },
|
|
141
|
+
{ title: "Type", value: "type" },
|
|
142
|
+
{ title: "Status", value: "status" },
|
|
143
|
+
{ title: "", value: "action" },
|
|
144
|
+
]
|
|
111
145
|
|
|
112
146
|
|
|
113
147
|
|
|
114
148
|
const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
|
|
115
|
-
const { getVehicles, deleteVehicle, formatVehicleStatus } = useVehicle();
|
|
149
|
+
const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle } = useVehicle();
|
|
116
150
|
|
|
117
151
|
const items = ref<Array<Record<string, any>>>([]);
|
|
118
152
|
const page = ref(1);
|
|
@@ -122,9 +156,15 @@ const pageRange = ref("-- - -- of --");
|
|
|
122
156
|
const searchInput = ref("")
|
|
123
157
|
|
|
124
158
|
const loading = ref(false);
|
|
159
|
+
const deletingVehicle = ref(false);
|
|
160
|
+
const approvingVehicle = ref(false);
|
|
161
|
+
const restoringVehicle = ref(false);
|
|
162
|
+
|
|
125
163
|
const selectedVehicleId = ref<string | null>(null)
|
|
126
164
|
const vehicleType = ref<TVehicleType | null>(null);
|
|
127
|
-
const
|
|
165
|
+
const vehicleTypeFilter = ref<TVehicleType | null>(null);
|
|
166
|
+
|
|
167
|
+
const selectedPlateNumberObject = ref<TPlateNumber | null>(null);
|
|
128
168
|
|
|
129
169
|
const message = ref("");
|
|
130
170
|
const messageColor = ref("");
|
|
@@ -143,17 +183,18 @@ const dialog = reactive({
|
|
|
143
183
|
showSelection: false,
|
|
144
184
|
deleteVehicle: false,
|
|
145
185
|
showMoreActions: false,
|
|
186
|
+
approveVehicle: false,
|
|
187
|
+
restoreVehicle: false
|
|
146
188
|
});
|
|
147
189
|
|
|
190
|
+
|
|
148
191
|
const typeOptions = [
|
|
149
192
|
{ label: "Whitelist", value: "whitelist" },
|
|
150
193
|
{ label: "Blocklist", value: "blocklist" },
|
|
151
|
-
{ label: "Season Pass", value: "seasonPass" },
|
|
152
194
|
]
|
|
153
195
|
|
|
154
196
|
const formattedFields: Partial<Record<keyof TVehicle, string>> = {
|
|
155
197
|
name: "Name",
|
|
156
|
-
plates: "Vehicle Numbers",
|
|
157
198
|
phoneNumber: "Phone Number",
|
|
158
199
|
nric: "NRIC",
|
|
159
200
|
block: "Block",
|
|
@@ -164,6 +205,7 @@ const formattedFields: Partial<Record<keyof TVehicle, string>> = {
|
|
|
164
205
|
category: "Category",
|
|
165
206
|
type: "Type",
|
|
166
207
|
remarks: "Remarks",
|
|
208
|
+
plates: "Vehicle Numbers",
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
function formatValues(key: string, value: any) {
|
|
@@ -212,9 +254,9 @@ const { data: getVehiclesReq, refresh: getVehiclesRefresh, pending: getVehiclesP
|
|
|
212
254
|
|
|
213
255
|
|
|
214
256
|
watch(getVehiclesReq, (newData: any) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
257
|
+
items.value = newData?.items || [];
|
|
258
|
+
pages.value = newData?.pages || 0;
|
|
259
|
+
pageRange.value = newData?.pageRange || "-- - -- of --";
|
|
218
260
|
})
|
|
219
261
|
|
|
220
262
|
function handleRowClick(data: any) {
|
|
@@ -223,17 +265,33 @@ function handleRowClick(data: any) {
|
|
|
223
265
|
message.value = "";
|
|
224
266
|
}
|
|
225
267
|
|
|
268
|
+
function addNewPlateNumbers() {
|
|
269
|
+
if (!selectedVehicleId.value) return;
|
|
270
|
+
dialog.showSelection = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
226
273
|
function handleEditVehicleAction() {
|
|
227
274
|
vehicleType.value = selectedVehicleObject.value?.type || null;
|
|
275
|
+
console.log("Selected Vehicle Object: ", selectedVehicleObject.value);
|
|
228
276
|
dialog.showSelection = false;
|
|
277
|
+
dialog.updateVehicle = false;
|
|
229
278
|
dialog.updateVehicle = true;
|
|
230
279
|
}
|
|
231
280
|
|
|
232
|
-
function handleDeleteVehicleAction() {
|
|
233
|
-
|
|
281
|
+
function handleDeleteVehicleAction(item: TPlateNumber) {
|
|
282
|
+
selectedPlateNumberObject.value = item || null;
|
|
234
283
|
dialog.deleteVehicle = true;
|
|
235
284
|
}
|
|
236
285
|
|
|
286
|
+
function handleApproveVehicle(item: TPlateNumber) {
|
|
287
|
+
selectedPlateNumberObject.value = item || null;
|
|
288
|
+
dialog.approveVehicle = true;
|
|
289
|
+
}
|
|
290
|
+
function handleRestoreVehicle(item: TPlateNumber) {
|
|
291
|
+
selectedPlateNumberObject.value = item || null;
|
|
292
|
+
dialog.restoreVehicle = true;
|
|
293
|
+
}
|
|
294
|
+
|
|
237
295
|
function handleSelectVehicleStatus(value: TVehicleType) {
|
|
238
296
|
vehicleType.value = value;
|
|
239
297
|
dialog.showSelection = false;
|
|
@@ -261,10 +319,16 @@ function handleUpdateVehicleComplete() {
|
|
|
261
319
|
getVehiclesRefresh();
|
|
262
320
|
}
|
|
263
321
|
|
|
322
|
+
|
|
264
323
|
function handleUpdatePage(newPageNum: number) {
|
|
265
324
|
page.value = newPageNum;
|
|
266
325
|
}
|
|
267
326
|
|
|
327
|
+
function handleAddVehicleClick() {
|
|
328
|
+
dialog.showSelection = true;
|
|
329
|
+
selectedVehicleId.value = null;
|
|
330
|
+
}
|
|
331
|
+
|
|
268
332
|
const formatPlateNumbers = (value: any) => {
|
|
269
333
|
if (!value || value.length === 0) return "";
|
|
270
334
|
|
|
@@ -273,20 +337,80 @@ const formatPlateNumbers = (value: any) => {
|
|
|
273
337
|
};
|
|
274
338
|
|
|
275
339
|
async function submitDelete() {
|
|
276
|
-
|
|
340
|
+
|
|
341
|
+
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
342
|
+
const type = selectedPlateNumberObject.value?.type as TVehicleType;
|
|
343
|
+
if (!plateNumberId) {
|
|
344
|
+
showMessage("Invalid plate number selected for deletion.", "error");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
277
347
|
|
|
278
348
|
try {
|
|
279
|
-
|
|
349
|
+
deletingVehicle.value = true;
|
|
350
|
+
const res = await deleteVehicle({ site: props.site, id: plateNumberId as string, recno: selectedVehicleObject.value.recno, type }
|
|
280
351
|
);
|
|
281
352
|
dialog.deleteVehicle = false;
|
|
282
|
-
dialog.showMoreActions = false;
|
|
283
353
|
selectedVehicleId.value = null
|
|
284
354
|
showMessage(res.message, "success");
|
|
285
355
|
getVehiclesRefresh();
|
|
286
356
|
} catch (error: any) {
|
|
287
357
|
console.error("Error deleting vehicle:", error);
|
|
288
358
|
message.value = error.response._data.message;
|
|
289
|
-
|
|
359
|
+
} finally {
|
|
360
|
+
deletingVehicle.value = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
async function submitRestore() {
|
|
367
|
+
|
|
368
|
+
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
369
|
+
if (!plateNumberId) {
|
|
370
|
+
showMessage("Invalid plate number selected for restoration.", "error");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
restoringVehicle.value = true;
|
|
376
|
+
// reactivate and restore will use the same endpoint, just with different payload
|
|
377
|
+
const res = await approveVehicle({ site: props.site, org: props.org, id: plateNumberId as string }
|
|
378
|
+
);
|
|
379
|
+
dialog.restoreVehicle = false;
|
|
380
|
+
selectedVehicleId.value = null
|
|
381
|
+
showMessage(res.message, "success");
|
|
382
|
+
getVehiclesRefresh();
|
|
383
|
+
} catch (error: any) {
|
|
384
|
+
console.error("Error restoring vehicle:", error);
|
|
385
|
+
message.value = error.response._data.message;
|
|
386
|
+
} finally {
|
|
387
|
+
restoringVehicle.value = false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async function submitApprove() {
|
|
393
|
+
|
|
394
|
+
const plateNumberId = selectedPlateNumberObject.value?._id;
|
|
395
|
+
if (!plateNumberId) {
|
|
396
|
+
showMessage("Invalid plate number selected for approval.", "error");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
approvingVehicle.value = true;
|
|
402
|
+
const res = await approveVehicle({ site: props.site, org: props.org, id: plateNumberId as string }
|
|
403
|
+
|
|
404
|
+
);
|
|
405
|
+
dialog.approveVehicle = false;
|
|
406
|
+
selectedVehicleId.value = null
|
|
407
|
+
showMessage(res.message, "success");
|
|
408
|
+
getVehiclesRefresh();
|
|
409
|
+
} catch (error: any) {
|
|
410
|
+
console.error("Error approving vehicle:", error);
|
|
411
|
+
message.value = error.response._data.message;
|
|
412
|
+
} finally {
|
|
413
|
+
approvingVehicle.value = false;
|
|
290
414
|
}
|
|
291
415
|
}
|
|
292
416
|
|
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
|
|
147
147
|
<v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
|
|
148
148
|
<PassInformation />
|
|
149
|
+
<EntryPassInformation v-if="entryPassSettings?.data?.settings?.nfcPass" v-model="passType" v-model:quantity="passQuantity" v-model:cards="passCards" :settings="entryPassSettings" :loading="entryPassSettingsPending" @scan:barcode="$emit('scan:barcode')" @scan:camera="$emit('scan:camera')" />
|
|
149
150
|
</v-col>
|
|
150
151
|
|
|
151
152
|
<v-col v-if="prop.type === 'contractor' && contractorStep === 3" cols="12">
|
|
@@ -230,6 +231,7 @@ const currentAutofillSource = ref<AutofillSource>(null);
|
|
|
230
231
|
const { requiredRule, debounce } = useUtils();
|
|
231
232
|
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
232
233
|
const { createVisitor, typeFieldMap, contractorTypes } = useVisitor();
|
|
234
|
+
const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
|
|
233
235
|
const { findPersonByNRIC, findPersonByContact, searchCompanyList, findUsersByPlateNumber } = usePeople()
|
|
234
236
|
|
|
235
237
|
const emit = defineEmits([
|
|
@@ -259,6 +261,10 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
259
261
|
members: [],
|
|
260
262
|
});
|
|
261
263
|
|
|
264
|
+
const passType = ref("");
|
|
265
|
+
const passQuantity = ref<number | null>(null);
|
|
266
|
+
const passCards = ref<string[]>([]);
|
|
267
|
+
|
|
262
268
|
const dialog = reactive({
|
|
263
269
|
vehicleNumberUsersList: false,
|
|
264
270
|
});
|
|
@@ -504,6 +510,19 @@ function handleAutofillDataViaVehicleNumber(item: TPeople){
|
|
|
504
510
|
}
|
|
505
511
|
|
|
506
512
|
|
|
513
|
+
const {
|
|
514
|
+
data: entryPassSettings,
|
|
515
|
+
pending: entryPassSettingsPending,
|
|
516
|
+
refresh: refreshEntryPassSettings,
|
|
517
|
+
} = useLazyAsyncData(`fetch-entrypass-settings-${prop.site}`, () => {
|
|
518
|
+
if (contractorStep.value !== 2) return Promise.resolve(null);
|
|
519
|
+
return getEntryPassSettingsBySiteId(prop.site);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
watch(contractorStep, (step) => {
|
|
523
|
+
if (step === 2) refreshEntryPassSettings();
|
|
524
|
+
});
|
|
525
|
+
|
|
507
526
|
const {
|
|
508
527
|
data: siteData,
|
|
509
528
|
refresh: refreshSiteData,
|