@7365admin1/layer-common 3.0.30 → 3.0.31-staging.11
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/components/AddPassKeyToVisitor.vue +115 -53
- package/components/DashboardMain.vue +29 -33
- package/components/InvitationMain.vue +4 -3
- package/components/ManageChecklistMain.vue +32 -25
- package/components/PhotoUpload.vue +19 -4
- package/components/VisitorForm.vue +385 -59
- package/components/VisitorManagement.vue +109 -30
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/package.json +1 -1
- package/types/customer.d.ts +2 -1
- package/types/people.d.ts +2 -0
- /package/components/{NFC → Nfc}/NFCPatrolReportMain.vue +0 -0
- /package/components/{NFC → Nfc}/NFCPatrolRouteForm.vue +0 -0
- /package/components/{NFC → Nfc}/NFCPatrolRouteMain.vue +0 -0
- /package/components/{NFC → Nfc}/NFCPatrolSettings.vue +0 -0
- /package/components/{NFC → Nfc}/NFCTagForm.vue +0 -0
- /package/components/{NFC → Nfc}/NFCTagMain.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/DailyReportTab.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/MonthlyReportTab.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/PatrolActivityTable.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/PatrolReportTab.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/PatrolRouteSummary.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/RemarksDialog.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/ReportEmptyState.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/ReportFilters.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/ReportLocationHeader.vue +0 -0
- /package/components/{NFC → Nfc}/PatrolReport/SummaryReportTable.vue +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
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
|
-
|
|
15
|
+
}}</span>
|
|
16
16
|
<span v-if="withStep2 || withStep3" class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap">Step
|
|
17
17
|
<span class="text-primary-button">{{ contractorStep }}</span>/{{ withStep3 ? 3 : withStep2 ? 2 : 1 }}</span>
|
|
18
18
|
</div>
|
|
@@ -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>
|
|
@@ -62,11 +62,20 @@
|
|
|
62
62
|
@update:model-value="handleUpdateNRIC" :loading="fetchPersonByNRICPending" />
|
|
63
63
|
</template>
|
|
64
64
|
|
|
65
|
-
<v-list v-if="nricSearchResults.length">
|
|
65
|
+
<v-list v-if="nricSearchResults.length" class="pa-1">
|
|
66
66
|
<v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
|
|
67
|
-
@click="selectNricSearchResult(item)" class="cursor-pointer">
|
|
68
|
-
<
|
|
69
|
-
|
|
67
|
+
@click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
68
|
+
<div class="d-flex align-center justify-space-between ga-4 w-100">
|
|
69
|
+
<span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
|
|
70
|
+
<span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<template v-if="item?.contacts && item?.visitorPlateNumbers">
|
|
73
|
+
<div v-if="item.contacts?.length < 2 && item.visitorPlateNumbers?.length < 2"
|
|
74
|
+
class="d-flex align-center justify-space-between ga-4 w-100">
|
|
75
|
+
<span class="text-subtitle-1">{{ item.contacts?.[0] || 'N/A' }}</span>
|
|
76
|
+
<span class="text-subtitle-1 text-no-wrap">{{ item.visitorPlateNumbers?.[0] || 'N/A' }}</span>
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
70
79
|
</v-list-item>
|
|
71
80
|
</v-list>
|
|
72
81
|
</v-menu>
|
|
@@ -76,8 +85,28 @@
|
|
|
76
85
|
|
|
77
86
|
<v-col v-if="shouldShowField('contact')" cols="12">
|
|
78
87
|
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
79
|
-
<
|
|
80
|
-
|
|
88
|
+
<v-menu v-model="phoneSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
89
|
+
<template #activator="{ props }">
|
|
90
|
+
<InputPhoneNumberV2 v-bind="props" v-model="visitor.contact" :rules="[requiredRule]"
|
|
91
|
+
density="comfortable" :key="currentAutofillSource + 'phone-key'"
|
|
92
|
+
@update:model-value="handleUpdatePhoneNumber" />
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<v-list v-if="phoneSearchResults.length" class="pa-1">
|
|
96
|
+
<v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
|
|
97
|
+
@click="selectAutofillSearchResult(item, 'contact')" class="cursor-pointer nric-search-item">
|
|
98
|
+
<div class="d-flex align-center justify-space-between ga-4 w-100">
|
|
99
|
+
<span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
|
|
100
|
+
<span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
|
|
101
|
+
</div>
|
|
102
|
+
<div v-if="getPersonContactOptions(item).length < 2 && getPersonPlateOptions(item).length < 2"
|
|
103
|
+
class="d-flex align-center justify-space-between ga-4 w-100">
|
|
104
|
+
<span class="text-subtitle-1">{{ getPersonContactOptions(item)[0] || 'N/A' }}</span>
|
|
105
|
+
<span class="text-subtitle-1 text-no-wrap">{{ getPersonPlateOptions(item)[0] || 'N/A' }}</span>
|
|
106
|
+
</div>
|
|
107
|
+
</v-list-item>
|
|
108
|
+
</v-list>
|
|
109
|
+
</v-menu>
|
|
81
110
|
</v-col>
|
|
82
111
|
|
|
83
112
|
<v-col v-if="shouldShowField('deliveryType')" cols="12">
|
|
@@ -108,7 +137,7 @@
|
|
|
108
137
|
class="d-flex align-center ga-1">
|
|
109
138
|
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
|
|
110
139
|
companyNameInput
|
|
111
|
-
|
|
140
|
+
}}</strong>" as new company.
|
|
112
141
|
</v-list-item-title>
|
|
113
142
|
<v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
|
|
114
143
|
Start typing to search for companies.
|
|
@@ -138,7 +167,7 @@
|
|
|
138
167
|
class="d-flex align-center ga-1">
|
|
139
168
|
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{
|
|
140
169
|
companyNameInput
|
|
141
|
-
|
|
170
|
+
}}</strong>" as new company.
|
|
142
171
|
</v-list-item-title>
|
|
143
172
|
<v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
|
|
144
173
|
Start typing to search for companies.
|
|
@@ -155,9 +184,30 @@
|
|
|
155
184
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
156
185
|
<v-row>
|
|
157
186
|
<v-col cols="12">
|
|
158
|
-
<InputLabel class="text-capitalize" title="Vehicle Number" required />
|
|
187
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
|
|
159
188
|
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
160
|
-
<
|
|
189
|
+
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
190
|
+
<template #activator="{ props }">
|
|
191
|
+
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
192
|
+
@update:model-value="handleUpdateVehicleNumber" />
|
|
193
|
+
</template>
|
|
194
|
+
|
|
195
|
+
<v-list v-if="vehicleSearchResults.length" class="pa-1">
|
|
196
|
+
<v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
|
|
197
|
+
@click="selectAutofillSearchResult(item, 'vehicleNumber')"
|
|
198
|
+
class="cursor-pointer nric-search-item">
|
|
199
|
+
<div class="d-flex align-center justify-space-between ga-4 w-100">
|
|
200
|
+
<span class="text-subtitle-1">{{ item.name || 'Unknown' }}</span>
|
|
201
|
+
<span class="text-subtitle-1 text-no-wrap">NRIC: {{ item.nric || 'N/A' }}</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div v-if="getPersonContactOptions(item).length < 2 && getPersonPlateOptions(item).length < 2"
|
|
204
|
+
class="d-flex align-center justify-space-between ga-4 w-100">
|
|
205
|
+
<span class="text-subtitle-1">{{ getPersonContactOptions(item)[0] || 'N/A' }}</span>
|
|
206
|
+
<span class="text-subtitle-1 text-no-wrap">{{ getPersonPlateOptions(item)[0] || 'N/A' }}</span>
|
|
207
|
+
</div>
|
|
208
|
+
</v-list-item>
|
|
209
|
+
</v-list>
|
|
210
|
+
</v-menu>
|
|
161
211
|
</v-col>
|
|
162
212
|
</v-row>
|
|
163
213
|
</v-col>
|
|
@@ -212,8 +262,8 @@
|
|
|
212
262
|
<MemberInformation v-model="visitor.members" :type="prop.type" :contractor-type="visitor.contractorType"
|
|
213
263
|
:site="prop.site" :selected-visitor-pass="visitor.visitorPass" :clearable="true"
|
|
214
264
|
:settings="entryPassSettings" :settings-loading="entryPassSettingsPending || siteDataPending"
|
|
215
|
-
:unit-id="visitor.unit || null"
|
|
216
|
-
:hid-qr-
|
|
265
|
+
:unit-id="visitor.unit || null" :hid-qr-code-enabled="hidQrCodePassAllowed"
|
|
266
|
+
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
217
267
|
:selected-nfc-cards="passType === 'NFC' ? passCards : []" />
|
|
218
268
|
</v-col>
|
|
219
269
|
|
|
@@ -264,6 +314,47 @@
|
|
|
264
314
|
@update:people="handleAutofillDataViaVehicleNumber" />
|
|
265
315
|
</v-dialog>
|
|
266
316
|
|
|
317
|
+
<v-dialog v-model="dialog.nricAutofillOptions" max-width="430">
|
|
318
|
+
<v-card v-if="selectedNricSearchResult" class="nric-options-card">
|
|
319
|
+
<v-card-actions class="justify-end pa-1">
|
|
320
|
+
<v-btn icon="mdi-close" variant="text" size="small" @click="closeNricAutofillOptions" />
|
|
321
|
+
</v-card-actions>
|
|
322
|
+
|
|
323
|
+
<v-card-text class="pa-4 pt-1">
|
|
324
|
+
<h2 class="nric-options-title font-weight-bold mb-4">Select</h2>
|
|
325
|
+
|
|
326
|
+
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
327
|
+
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
328
|
+
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<template v-if="selectedNricContactOptions.length > 1">
|
|
332
|
+
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
333
|
+
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
334
|
+
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
335
|
+
class="nric-option-radio" />
|
|
336
|
+
</v-radio-group>
|
|
337
|
+
</template>
|
|
338
|
+
|
|
339
|
+
<template v-if="selectedNricPlateOptions.length > 1">
|
|
340
|
+
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
341
|
+
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
342
|
+
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
343
|
+
class="nric-option-radio" />
|
|
344
|
+
</v-radio-group>
|
|
345
|
+
</template>
|
|
346
|
+
</v-card-text>
|
|
347
|
+
|
|
348
|
+
<v-card-actions class="justify-end px-4 pb-4">
|
|
349
|
+
<v-btn variant="text" class="text-none" size="small" @click="closeNricAutofillOptions">Cancel</v-btn>
|
|
350
|
+
<v-btn color="primary-button" variant="flat" class="text-none px-5" size="small"
|
|
351
|
+
@click="confirmNricAutofillOptions">
|
|
352
|
+
Select
|
|
353
|
+
</v-btn>
|
|
354
|
+
</v-card-actions>
|
|
355
|
+
</v-card>
|
|
356
|
+
</v-dialog>
|
|
357
|
+
|
|
267
358
|
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="1200">
|
|
268
359
|
<v-card :loading="loading.checkingOut || loading.searchingNonCheckedOut">
|
|
269
360
|
<v-card-actions class="justify-end bg-grey-lighten-3 pa-4">
|
|
@@ -280,14 +371,8 @@
|
|
|
280
371
|
<div>Phone Number: {{ visitor.contact || "N/A" }}</div>
|
|
281
372
|
</div>
|
|
282
373
|
|
|
283
|
-
<v-data-table
|
|
284
|
-
|
|
285
|
-
:headers="nonCheckedOutVisitorHeaders"
|
|
286
|
-
:items="matchingNonCheckedOutVisitors"
|
|
287
|
-
item-value="_id"
|
|
288
|
-
:items-per-page="5"
|
|
289
|
-
hide-default-footer
|
|
290
|
-
>
|
|
374
|
+
<v-data-table class="border rounded" :headers="nonCheckedOutVisitorHeaders"
|
|
375
|
+
:items="matchingNonCheckedOutVisitors" item-value="_id" :items-per-page="5" hide-default-footer>
|
|
291
376
|
<template #item.name="{ item }">
|
|
292
377
|
<div class="d-flex align-center ga-3">
|
|
293
378
|
<v-avatar color="red" size="32">
|
|
@@ -336,7 +421,9 @@
|
|
|
336
421
|
<v-icon icon="mdi-clock-check-outline" color="green" size="20" class="mr-1" />
|
|
337
422
|
{{ formatNonCheckedOutDate(item.checkIn) }}
|
|
338
423
|
</div>
|
|
339
|
-
<v-btn size="small" class="text-none" color="red" variant="flat"
|
|
424
|
+
<v-btn size="small" class="text-none" color="red" variant="flat"
|
|
425
|
+
:loading="loading.checkingOut && selectedCheckoutVisitorId === item._id" :disabled="!item._id"
|
|
426
|
+
@click.stop="item._id && handleCheckout(item._id)">
|
|
340
427
|
Check Out
|
|
341
428
|
</v-btn>
|
|
342
429
|
</div>
|
|
@@ -344,7 +431,8 @@
|
|
|
344
431
|
</v-data-table>
|
|
345
432
|
|
|
346
433
|
<div class="d-flex justify-end mt-8">
|
|
347
|
-
<v-btn
|
|
434
|
+
<v-btn color="red" variant="flat" size="large" class="text-none px-10"
|
|
435
|
+
:loading="loading.searchingNonCheckedOut" @click="handleSkipNricNonCheckedOut">
|
|
348
436
|
Skip NRIC: {{ visitor.nric || "N/A" }}
|
|
349
437
|
</v-btn>
|
|
350
438
|
</div>
|
|
@@ -352,20 +440,10 @@
|
|
|
352
440
|
</v-card>
|
|
353
441
|
</v-dialog>
|
|
354
442
|
|
|
355
|
-
<v-dialog
|
|
356
|
-
v-if="enableUnregistered"
|
|
357
|
-
v-model="dialog.showUnregisteredPlateDialog"
|
|
358
|
-
max-width="900"
|
|
359
|
-
persistent
|
|
360
|
-
>
|
|
443
|
+
<v-dialog v-if="enableUnregistered" v-model="dialog.showUnregisteredPlateDialog" max-width="900" persistent>
|
|
361
444
|
<v-card color="background" class="pa-4">
|
|
362
445
|
<v-card-actions class="justify-end pa-0">
|
|
363
|
-
<v-btn
|
|
364
|
-
icon="mdi-close"
|
|
365
|
-
variant="text"
|
|
366
|
-
size="large"
|
|
367
|
-
@click="closeUnregisteredPlateDialog"
|
|
368
|
-
/>
|
|
446
|
+
<v-btn icon="mdi-close" variant="text" size="large" @click="closeUnregisteredPlateDialog" />
|
|
369
447
|
</v-card-actions>
|
|
370
448
|
|
|
371
449
|
<v-card-text class="text-center pt-2">
|
|
@@ -388,10 +466,7 @@
|
|
|
388
466
|
</tr>
|
|
389
467
|
</thead>
|
|
390
468
|
<tbody>
|
|
391
|
-
<tr
|
|
392
|
-
v-for="match in unregisteredPlateMatches"
|
|
393
|
-
:key="match._id || match.plateNumber"
|
|
394
|
-
>
|
|
469
|
+
<tr v-for="match in unregisteredPlateMatches" :key="match._id || match.plateNumber">
|
|
395
470
|
<td class="text-left font-weight-bold text-uppercase">
|
|
396
471
|
{{ match.plateNumber || "N/A" }}
|
|
397
472
|
</td>
|
|
@@ -399,13 +474,8 @@
|
|
|
399
474
|
{{ formatUnregisteredCheckIn(match.checkIn) }}
|
|
400
475
|
</td>
|
|
401
476
|
<td class="text-center">
|
|
402
|
-
<v-btn
|
|
403
|
-
|
|
404
|
-
class="text-capitalize"
|
|
405
|
-
density="compact"
|
|
406
|
-
size="large"
|
|
407
|
-
@click="selectUnregisteredPlateMatch(match)"
|
|
408
|
-
>
|
|
477
|
+
<v-btn color="red" class="text-capitalize" density="compact" size="large"
|
|
478
|
+
@click="selectUnregisteredPlateMatch(match)">
|
|
409
479
|
Update
|
|
410
480
|
</v-btn>
|
|
411
481
|
</td>
|
|
@@ -415,13 +485,7 @@
|
|
|
415
485
|
</v-card-text>
|
|
416
486
|
|
|
417
487
|
<v-card-actions>
|
|
418
|
-
<v-btn
|
|
419
|
-
block
|
|
420
|
-
color="red"
|
|
421
|
-
variant="flat"
|
|
422
|
-
size="x-large"
|
|
423
|
-
@click="closeUnregisteredPlateDialog"
|
|
424
|
-
>
|
|
488
|
+
<v-btn block color="red" variant="flat" size="x-large" @click="closeUnregisteredPlateDialog">
|
|
425
489
|
Cancel & Add New
|
|
426
490
|
</v-btn>
|
|
427
491
|
</v-card-actions>
|
|
@@ -541,6 +605,7 @@ const hidQrPrinted = ref(false);
|
|
|
541
605
|
const registeredUnitCompanyName = ref("N/A");
|
|
542
606
|
|
|
543
607
|
const dialog = reactive({
|
|
608
|
+
nricAutofillOptions: false,
|
|
544
609
|
vehicleNumberUsersList: false,
|
|
545
610
|
showNonCheckedOutDialog: false,
|
|
546
611
|
showUnregisteredPlateDialog: false,
|
|
@@ -635,6 +700,10 @@ const requireNRIC = computed(() => {
|
|
|
635
700
|
return prop.type !== "walk-in" && prop.type !== "guest";
|
|
636
701
|
});
|
|
637
702
|
|
|
703
|
+
const isVehicleRequired = computed(() => {
|
|
704
|
+
return ["guest", "drop-off", "pick-up"].includes(prop.type);
|
|
705
|
+
});
|
|
706
|
+
|
|
638
707
|
const hidQrCodePassConfig = computed(() => {
|
|
639
708
|
return (siteData.value as any)?.metadata?.hidQrCodePass || {};
|
|
640
709
|
});
|
|
@@ -689,6 +758,58 @@ const companyAutofillDataArray = ref<
|
|
|
689
758
|
const nricSearchMenu = ref(false);
|
|
690
759
|
const nricSearchResults = ref<Partial<TPeople>[]>([]);
|
|
691
760
|
const skipNricFetch = ref(false);
|
|
761
|
+
const phoneSearchMenu = ref(false);
|
|
762
|
+
const phoneSearchResults = ref<Partial<TPeople>[]>([]);
|
|
763
|
+
const vehicleSearchMenu = ref(false);
|
|
764
|
+
const vehicleSearchResults = ref<Partial<TPeople>[]>([]);
|
|
765
|
+
const selectedNricSearchResult = ref<Partial<TPeople> | null>(null);
|
|
766
|
+
const selectedNricContact = ref("");
|
|
767
|
+
const selectedNricPlate = ref("");
|
|
768
|
+
|
|
769
|
+
type NricSearchPerson = Omit<Partial<TPeople>, "contact"> & {
|
|
770
|
+
contact?: string | string[];
|
|
771
|
+
contacts?: string | string[];
|
|
772
|
+
visitorPlateNumbers?: string | string[];
|
|
773
|
+
plateNumber?: string | string[];
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const mockPhoneNumberSearchResults: Partial<NricSearchPerson>[] = [
|
|
777
|
+
{
|
|
778
|
+
_id: "mock-phone-multiple-options",
|
|
779
|
+
name: "James Smith",
|
|
780
|
+
nric: "1323232499",
|
|
781
|
+
contacts: ["653939394", "81234567"],
|
|
782
|
+
visitorPlateNumbers: ["34566", "SMA1234A"],
|
|
783
|
+
companyName: ["Mock Company Pte Ltd"],
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
_id: "mock-phone-single-option",
|
|
787
|
+
name: "Jane Tan",
|
|
788
|
+
nric: "S1234567A",
|
|
789
|
+
contacts: ["91234567"],
|
|
790
|
+
visitorPlateNumbers: ["SGB5678B"],
|
|
791
|
+
companyName: ["Single Option Company"],
|
|
792
|
+
},
|
|
793
|
+
];
|
|
794
|
+
|
|
795
|
+
const mockVehicleNumberSearchResults: Partial<NricSearchPerson>[] = [
|
|
796
|
+
{
|
|
797
|
+
_id: "mock-vehicle-multiple-options",
|
|
798
|
+
name: "James Smith",
|
|
799
|
+
nric: "1323232499",
|
|
800
|
+
contacts: ["653939394", "81234567"],
|
|
801
|
+
visitorPlateNumbers: ["34566", "SMA1234A"],
|
|
802
|
+
companyName: ["Mock Company Pte Ltd"],
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
_id: "mock-vehicle-single-option",
|
|
806
|
+
name: "Ali Lim",
|
|
807
|
+
nric: "T7654321B",
|
|
808
|
+
contacts: ["98765432"],
|
|
809
|
+
visitorPlateNumbers: ["SKX2468C"],
|
|
810
|
+
companyName: ["Vehicle Single Company"],
|
|
811
|
+
},
|
|
812
|
+
];
|
|
692
813
|
|
|
693
814
|
const formTitle = computed(() => {
|
|
694
815
|
const isContractorForm = prop.type === "contractor";
|
|
@@ -768,6 +889,29 @@ watch(fetchPersonByNRICReq, (obj) => {
|
|
|
768
889
|
const items = obj?.items ?? [];
|
|
769
890
|
nricSearchResults.value = Array.isArray(items) ? items : [];
|
|
770
891
|
nricSearchMenu.value = nricSearchResults.value.length > 0;
|
|
892
|
+
|
|
893
|
+
// Mock data for testing NRIC autofill options.
|
|
894
|
+
// Uncomment this block and type at least 4 characters in NRIC to test
|
|
895
|
+
// both the multi-option dialog and immediate single-option autofill.
|
|
896
|
+
// nricSearchResults.value = [
|
|
897
|
+
// {
|
|
898
|
+
// _id: "mock-person-multiple-options",
|
|
899
|
+
// name: "James Smith",
|
|
900
|
+
// nric: "1323232499",
|
|
901
|
+
// contacts: ["653939394", "81234567"],
|
|
902
|
+
// visitorPlateNumbers: ["34566", "SMA1234A"],
|
|
903
|
+
// companyName: ["Mock Company Pte Ltd"],
|
|
904
|
+
// } as Partial<NricSearchPerson>,
|
|
905
|
+
// {
|
|
906
|
+
// _id: "mock-person-single-option",
|
|
907
|
+
// name: "Jane Tan",
|
|
908
|
+
// nric: "S1234567A",
|
|
909
|
+
// contacts: ["91234567"],
|
|
910
|
+
// visitorPlateNumbers: ["SGB5678B"],
|
|
911
|
+
// companyName: ["Single Option Company"],
|
|
912
|
+
// } as Partial<NricSearchPerson>,
|
|
913
|
+
// ];
|
|
914
|
+
// nricSearchMenu.value = true;
|
|
771
915
|
});
|
|
772
916
|
|
|
773
917
|
watch(
|
|
@@ -783,21 +927,68 @@ watch(
|
|
|
783
927
|
|
|
784
928
|
watch(
|
|
785
929
|
() => visitor.contact,
|
|
786
|
-
() => {
|
|
930
|
+
(value) => {
|
|
787
931
|
skipNonCheckedOutNric.value = false;
|
|
932
|
+
if (!value || value.length < 4) {
|
|
933
|
+
phoneSearchMenu.value = false;
|
|
934
|
+
phoneSearchResults.value = [];
|
|
935
|
+
}
|
|
788
936
|
}
|
|
789
937
|
);
|
|
790
938
|
|
|
791
|
-
|
|
939
|
+
const selectedNricContactOptions = computed(() => {
|
|
940
|
+
return selectedNricSearchResult.value
|
|
941
|
+
? getPersonContactOptions(selectedNricSearchResult.value)
|
|
942
|
+
: [];
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
const selectedNricPlateOptions = computed(() => {
|
|
946
|
+
return selectedNricSearchResult.value
|
|
947
|
+
? getPersonPlateOptions(selectedNricSearchResult.value)
|
|
948
|
+
: [];
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
function toUniqueStringArray(value: unknown) {
|
|
952
|
+
const rawValues = Array.isArray(value) ? value : value ? [value] : [];
|
|
953
|
+
return [...new Set(rawValues.map((v) => String(v).trim()).filter(Boolean))];
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function getPersonContactOptions(item: Partial<TPeople>) {
|
|
957
|
+
const person = item as NricSearchPerson;
|
|
958
|
+
return toUniqueStringArray(person.contacts || person.contact);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function getPersonPlateOptions(item: Partial<TPeople>) {
|
|
962
|
+
const person = item as NricSearchPerson;
|
|
963
|
+
const visitorPlateNumbers = toUniqueStringArray(person.visitorPlateNumbers);
|
|
964
|
+
if (visitorPlateNumbers.length) return visitorPlateNumbers;
|
|
965
|
+
|
|
966
|
+
const plateNumbers = toUniqueStringArray(person.plateNumber);
|
|
967
|
+
const plates = Array.isArray(person.plates)
|
|
968
|
+
? person.plates.map((plate) => plate?.plateNumber)
|
|
969
|
+
: [];
|
|
970
|
+
|
|
971
|
+
return toUniqueStringArray([...plateNumbers, ...plates]);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function applyNricSearchResult(
|
|
975
|
+
item: Partial<TPeople>,
|
|
976
|
+
options: { contacts?: string; plateNumber?: string; source?: Exclude<AutofillSource, null> } = {}
|
|
977
|
+
) {
|
|
792
978
|
nricSearchMenu.value = false;
|
|
979
|
+
phoneSearchMenu.value = false;
|
|
980
|
+
vehicleSearchMenu.value = false;
|
|
793
981
|
nricSearchResults.value = [];
|
|
982
|
+
phoneSearchResults.value = [];
|
|
983
|
+
vehicleSearchResults.value = [];
|
|
794
984
|
skipNricFetch.value = true;
|
|
795
985
|
|
|
796
|
-
currentAutofillSource.value = "nric";
|
|
986
|
+
currentAutofillSource.value = options.source || "nric";
|
|
797
987
|
|
|
798
988
|
visitor.nric = item.nric || visitor.nric;
|
|
799
989
|
visitor.name = item.name || visitor.name;
|
|
800
|
-
visitor.contact =
|
|
990
|
+
visitor.contact = options.contacts || getPersonContactOptions(item)[0] || visitor.contact;
|
|
991
|
+
visitor.plateNumber = options.plateNumber || getPersonPlateOptions(item)[0] || visitor.plateNumber;
|
|
801
992
|
companyNames.value = item.companyName ?? [];
|
|
802
993
|
if (!visitor.company) {
|
|
803
994
|
visitor.company = companyNames.value?.[0] || visitor.company;
|
|
@@ -813,6 +1004,51 @@ function selectNricSearchResult(item: Partial<TPeople>) {
|
|
|
813
1004
|
}, 1000);
|
|
814
1005
|
}
|
|
815
1006
|
|
|
1007
|
+
function selectNricSearchResult(item: Partial<TPeople>) {
|
|
1008
|
+
selectAutofillSearchResult(item, "nric");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<AutofillSource, null>) {
|
|
1012
|
+
const contactOptions = getPersonContactOptions(item);
|
|
1013
|
+
const plateOptions = getPersonPlateOptions(item);
|
|
1014
|
+
|
|
1015
|
+
if (contactOptions.length > 1 || plateOptions.length > 1) {
|
|
1016
|
+
nricSearchMenu.value = false;
|
|
1017
|
+
phoneSearchMenu.value = false;
|
|
1018
|
+
vehicleSearchMenu.value = false;
|
|
1019
|
+
currentAutofillSource.value = source;
|
|
1020
|
+
selectedNricSearchResult.value = item;
|
|
1021
|
+
selectedNricContact.value = contactOptions[0] || "";
|
|
1022
|
+
selectedNricPlate.value = plateOptions[0] || "";
|
|
1023
|
+
dialog.nricAutofillOptions = true;
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
applyNricSearchResult(item, {
|
|
1028
|
+
contacts: contactOptions[0],
|
|
1029
|
+
plateNumber: plateOptions[0],
|
|
1030
|
+
source,
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function closeNricAutofillOptions() {
|
|
1035
|
+
dialog.nricAutofillOptions = false;
|
|
1036
|
+
selectedNricSearchResult.value = null;
|
|
1037
|
+
selectedNricContact.value = "";
|
|
1038
|
+
selectedNricPlate.value = "";
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function confirmNricAutofillOptions() {
|
|
1042
|
+
if (!selectedNricSearchResult.value) return;
|
|
1043
|
+
|
|
1044
|
+
applyNricSearchResult(selectedNricSearchResult.value, {
|
|
1045
|
+
contacts: selectedNricContact.value,
|
|
1046
|
+
plateNumber: selectedNricPlate.value,
|
|
1047
|
+
source: currentAutofillSource.value || "nric",
|
|
1048
|
+
});
|
|
1049
|
+
closeNricAutofillOptions();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
816
1052
|
const {
|
|
817
1053
|
data: fetchCompanyListReq,
|
|
818
1054
|
refresh: fetchCompanyListRefresh,
|
|
@@ -1164,6 +1400,44 @@ function handleUpdateNRIC() {
|
|
|
1164
1400
|
debounceFetchNRIC();
|
|
1165
1401
|
}
|
|
1166
1402
|
|
|
1403
|
+
function handleUpdatePhoneNumber() {
|
|
1404
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "contact")
|
|
1405
|
+
return;
|
|
1406
|
+
|
|
1407
|
+
const search = visitor.contact?.trim() || "";
|
|
1408
|
+
if (search.length < 4) {
|
|
1409
|
+
phoneSearchResults.value = [];
|
|
1410
|
+
phoneSearchMenu.value = false;
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
phoneSearchResults.value = mockPhoneNumberSearchResults.filter((item) =>
|
|
1415
|
+
getPersonContactOptions(item as Partial<TPeople>).some((contact) =>
|
|
1416
|
+
contact.includes(search)
|
|
1417
|
+
)
|
|
1418
|
+
) as Partial<TPeople>[];
|
|
1419
|
+
phoneSearchMenu.value = phoneSearchResults.value.length > 0;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function handleUpdateVehicleNumber() {
|
|
1423
|
+
if (currentAutofillSource.value && currentAutofillSource.value !== "vehicleNumber")
|
|
1424
|
+
return;
|
|
1425
|
+
|
|
1426
|
+
const search = visitor.plateNumber?.trim()?.toUpperCase() || "";
|
|
1427
|
+
if (search.length < 4) {
|
|
1428
|
+
vehicleSearchResults.value = [];
|
|
1429
|
+
vehicleSearchMenu.value = false;
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
vehicleSearchResults.value = mockVehicleNumberSearchResults.filter((item) =>
|
|
1434
|
+
getPersonPlateOptions(item as Partial<TPeople>).some((plate) =>
|
|
1435
|
+
plate.toUpperCase().includes(search)
|
|
1436
|
+
)
|
|
1437
|
+
) as Partial<TPeople>[];
|
|
1438
|
+
vehicleSearchMenu.value = vehicleSearchResults.value.length > 0;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1167
1441
|
function backToSelection() {
|
|
1168
1442
|
emit("back");
|
|
1169
1443
|
message.value = "";
|
|
@@ -1383,7 +1657,7 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
|
|
|
1383
1657
|
if (formMode.value === "register") {
|
|
1384
1658
|
payload.status = "registered";
|
|
1385
1659
|
const currentUserId = currentUser.value?._id;
|
|
1386
|
-
if(currentUserId) {
|
|
1660
|
+
if (currentUserId) {
|
|
1387
1661
|
payload.updatedBy = currentUserId;
|
|
1388
1662
|
}
|
|
1389
1663
|
}
|
|
@@ -1704,6 +1978,10 @@ watch(
|
|
|
1704
1978
|
skipUnregisteredPlateCheck.value = false;
|
|
1705
1979
|
unregisteredPlateMatches.value = [];
|
|
1706
1980
|
dialog.showUnregisteredPlateDialog = false;
|
|
1981
|
+
if (!normalizedPlateNumber || normalizedPlateNumber.length < 4) {
|
|
1982
|
+
vehicleSearchMenu.value = false;
|
|
1983
|
+
vehicleSearchResults.value = [];
|
|
1984
|
+
}
|
|
1707
1985
|
}
|
|
1708
1986
|
);
|
|
1709
1987
|
|
|
@@ -1756,4 +2034,52 @@ onMounted(() => {
|
|
|
1756
2034
|
.no-pointer {
|
|
1757
2035
|
pointer-events: none;
|
|
1758
2036
|
}
|
|
2037
|
+
|
|
2038
|
+
.nric-search-item {
|
|
2039
|
+
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
2040
|
+
border-radius: 4px;
|
|
2041
|
+
margin: 4px;
|
|
2042
|
+
min-height: 72px;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
.nric-options-card {
|
|
2046
|
+
border-radius: 16px !important;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.nric-options-title {
|
|
2050
|
+
color: #06253b;
|
|
2051
|
+
font-size: 1.45rem;
|
|
2052
|
+
line-height: 1.2;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
.nric-options-section-title {
|
|
2056
|
+
color: #06253b;
|
|
2057
|
+
font-size: 1.05rem;
|
|
2058
|
+
line-height: 1.25;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
.nric-selected-person {
|
|
2062
|
+
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
2063
|
+
border-radius: 12px;
|
|
2064
|
+
color: #06253b;
|
|
2065
|
+
font-size: 0.95rem;
|
|
2066
|
+
min-height: 52px;
|
|
2067
|
+
padding: 10px 14px;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
.nric-option-group {
|
|
2071
|
+
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
.nric-option-radio {
|
|
2075
|
+
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
2076
|
+
color: #06253b;
|
|
2077
|
+
font-size: 0.95rem;
|
|
2078
|
+
min-height: 46px;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
.nric-option-radio :deep(.v-label) {
|
|
2082
|
+
font-size: 0.95rem;
|
|
2083
|
+
opacity: 1;
|
|
2084
|
+
}
|
|
1759
2085
|
</style>
|