@7365admin1/layer-common 3.0.31-staging.10 → 3.0.31-staging.12
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/VisitorForm.vue +380 -62
- package/package.json +1 -1
- package/types/people.d.ts +2 -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.
|
|
@@ -157,11 +186,28 @@
|
|
|
157
186
|
<v-col cols="12">
|
|
158
187
|
<InputLabel class="text-capitalize" title="Vehicle Number" :required="isVehicleRequired" />
|
|
159
188
|
<!-- <v-text-field v-model.trim.uppercase="visitor.plateNumber" density="comfortable" /> -->
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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>
|
|
165
211
|
</v-col>
|
|
166
212
|
</v-row>
|
|
167
213
|
</v-col>
|
|
@@ -216,8 +262,8 @@
|
|
|
216
262
|
<MemberInformation v-model="visitor.members" :type="prop.type" :contractor-type="visitor.contractorType"
|
|
217
263
|
:site="prop.site" :selected-visitor-pass="visitor.visitorPass" :clearable="true"
|
|
218
264
|
:settings="entryPassSettings" :settings-loading="entryPassSettingsPending || siteDataPending"
|
|
219
|
-
:unit-id="visitor.unit || null"
|
|
220
|
-
:hid-qr-
|
|
265
|
+
:unit-id="visitor.unit || null" :hid-qr-code-enabled="hidQrCodePassAllowed"
|
|
266
|
+
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
221
267
|
:selected-nfc-cards="passType === 'NFC' ? passCards : []" />
|
|
222
268
|
</v-col>
|
|
223
269
|
|
|
@@ -268,6 +314,47 @@
|
|
|
268
314
|
@update:people="handleAutofillDataViaVehicleNumber" />
|
|
269
315
|
</v-dialog>
|
|
270
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
|
+
|
|
271
358
|
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="1200">
|
|
272
359
|
<v-card :loading="loading.checkingOut || loading.searchingNonCheckedOut">
|
|
273
360
|
<v-card-actions class="justify-end bg-grey-lighten-3 pa-4">
|
|
@@ -284,14 +371,8 @@
|
|
|
284
371
|
<div>Phone Number: {{ visitor.contact || "N/A" }}</div>
|
|
285
372
|
</div>
|
|
286
373
|
|
|
287
|
-
<v-data-table
|
|
288
|
-
|
|
289
|
-
:headers="nonCheckedOutVisitorHeaders"
|
|
290
|
-
:items="matchingNonCheckedOutVisitors"
|
|
291
|
-
item-value="_id"
|
|
292
|
-
:items-per-page="5"
|
|
293
|
-
hide-default-footer
|
|
294
|
-
>
|
|
374
|
+
<v-data-table class="border rounded" :headers="nonCheckedOutVisitorHeaders"
|
|
375
|
+
:items="matchingNonCheckedOutVisitors" item-value="_id" :items-per-page="5" hide-default-footer>
|
|
295
376
|
<template #item.name="{ item }">
|
|
296
377
|
<div class="d-flex align-center ga-3">
|
|
297
378
|
<v-avatar color="red" size="32">
|
|
@@ -340,7 +421,9 @@
|
|
|
340
421
|
<v-icon icon="mdi-clock-check-outline" color="green" size="20" class="mr-1" />
|
|
341
422
|
{{ formatNonCheckedOutDate(item.checkIn) }}
|
|
342
423
|
</div>
|
|
343
|
-
<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)">
|
|
344
427
|
Check Out
|
|
345
428
|
</v-btn>
|
|
346
429
|
</div>
|
|
@@ -348,7 +431,8 @@
|
|
|
348
431
|
</v-data-table>
|
|
349
432
|
|
|
350
433
|
<div class="d-flex justify-end mt-8">
|
|
351
|
-
<v-btn color="red" variant="flat" size="large" class="text-none px-10"
|
|
434
|
+
<v-btn v-if="!skipNonCheckedOutNric" color="red" variant="flat" size="large" class="text-none px-10"
|
|
435
|
+
:loading="loading.searchingNonCheckedOut" @click="handleSkipNricNonCheckedOut">
|
|
352
436
|
Skip NRIC: {{ visitor.nric || "N/A" }}
|
|
353
437
|
</v-btn>
|
|
354
438
|
</div>
|
|
@@ -356,20 +440,10 @@
|
|
|
356
440
|
</v-card>
|
|
357
441
|
</v-dialog>
|
|
358
442
|
|
|
359
|
-
<v-dialog
|
|
360
|
-
v-if="enableUnregistered"
|
|
361
|
-
v-model="dialog.showUnregisteredPlateDialog"
|
|
362
|
-
max-width="900"
|
|
363
|
-
persistent
|
|
364
|
-
>
|
|
443
|
+
<v-dialog v-if="enableUnregistered" v-model="dialog.showUnregisteredPlateDialog" max-width="900" persistent>
|
|
365
444
|
<v-card color="background" class="pa-4">
|
|
366
445
|
<v-card-actions class="justify-end pa-0">
|
|
367
|
-
<v-btn
|
|
368
|
-
icon="mdi-close"
|
|
369
|
-
variant="text"
|
|
370
|
-
size="large"
|
|
371
|
-
@click="closeUnregisteredPlateDialog"
|
|
372
|
-
/>
|
|
446
|
+
<v-btn icon="mdi-close" variant="text" size="large" @click="closeUnregisteredPlateDialog" />
|
|
373
447
|
</v-card-actions>
|
|
374
448
|
|
|
375
449
|
<v-card-text class="text-center pt-2">
|
|
@@ -392,10 +466,7 @@
|
|
|
392
466
|
</tr>
|
|
393
467
|
</thead>
|
|
394
468
|
<tbody>
|
|
395
|
-
<tr
|
|
396
|
-
v-for="match in unregisteredPlateMatches"
|
|
397
|
-
:key="match._id || match.plateNumber"
|
|
398
|
-
>
|
|
469
|
+
<tr v-for="match in unregisteredPlateMatches" :key="match._id || match.plateNumber">
|
|
399
470
|
<td class="text-left font-weight-bold text-uppercase">
|
|
400
471
|
{{ match.plateNumber || "N/A" }}
|
|
401
472
|
</td>
|
|
@@ -403,13 +474,8 @@
|
|
|
403
474
|
{{ formatUnregisteredCheckIn(match.checkIn) }}
|
|
404
475
|
</td>
|
|
405
476
|
<td class="text-center">
|
|
406
|
-
<v-btn
|
|
407
|
-
|
|
408
|
-
class="text-capitalize"
|
|
409
|
-
density="compact"
|
|
410
|
-
size="large"
|
|
411
|
-
@click="selectUnregisteredPlateMatch(match)"
|
|
412
|
-
>
|
|
477
|
+
<v-btn color="red" class="text-capitalize" density="compact" size="large"
|
|
478
|
+
@click="selectUnregisteredPlateMatch(match)">
|
|
413
479
|
Update
|
|
414
480
|
</v-btn>
|
|
415
481
|
</td>
|
|
@@ -419,13 +485,7 @@
|
|
|
419
485
|
</v-card-text>
|
|
420
486
|
|
|
421
487
|
<v-card-actions>
|
|
422
|
-
<v-btn
|
|
423
|
-
block
|
|
424
|
-
color="red"
|
|
425
|
-
variant="flat"
|
|
426
|
-
size="x-large"
|
|
427
|
-
@click="closeUnregisteredPlateDialog"
|
|
428
|
-
>
|
|
488
|
+
<v-btn block color="red" variant="flat" size="x-large" @click="closeUnregisteredPlateDialog">
|
|
429
489
|
Cancel & Add New
|
|
430
490
|
</v-btn>
|
|
431
491
|
</v-card-actions>
|
|
@@ -545,6 +605,7 @@ const hidQrPrinted = ref(false);
|
|
|
545
605
|
const registeredUnitCompanyName = ref("N/A");
|
|
546
606
|
|
|
547
607
|
const dialog = reactive({
|
|
608
|
+
nricAutofillOptions: false,
|
|
548
609
|
vehicleNumberUsersList: false,
|
|
549
610
|
showNonCheckedOutDialog: false,
|
|
550
611
|
showUnregisteredPlateDialog: false,
|
|
@@ -697,6 +758,58 @@ const companyAutofillDataArray = ref<
|
|
|
697
758
|
const nricSearchMenu = ref(false);
|
|
698
759
|
const nricSearchResults = ref<Partial<TPeople>[]>([]);
|
|
699
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
|
+
];
|
|
700
813
|
|
|
701
814
|
const formTitle = computed(() => {
|
|
702
815
|
const isContractorForm = prop.type === "contractor";
|
|
@@ -776,6 +889,29 @@ watch(fetchPersonByNRICReq, (obj) => {
|
|
|
776
889
|
const items = obj?.items ?? [];
|
|
777
890
|
nricSearchResults.value = Array.isArray(items) ? items : [];
|
|
778
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;
|
|
779
915
|
});
|
|
780
916
|
|
|
781
917
|
watch(
|
|
@@ -791,21 +927,68 @@ watch(
|
|
|
791
927
|
|
|
792
928
|
watch(
|
|
793
929
|
() => visitor.contact,
|
|
794
|
-
() => {
|
|
930
|
+
(value) => {
|
|
795
931
|
skipNonCheckedOutNric.value = false;
|
|
932
|
+
if (!value || value.length < 4) {
|
|
933
|
+
phoneSearchMenu.value = false;
|
|
934
|
+
phoneSearchResults.value = [];
|
|
935
|
+
}
|
|
796
936
|
}
|
|
797
937
|
);
|
|
798
938
|
|
|
799
|
-
|
|
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
|
+
) {
|
|
800
978
|
nricSearchMenu.value = false;
|
|
979
|
+
phoneSearchMenu.value = false;
|
|
980
|
+
vehicleSearchMenu.value = false;
|
|
801
981
|
nricSearchResults.value = [];
|
|
982
|
+
phoneSearchResults.value = [];
|
|
983
|
+
vehicleSearchResults.value = [];
|
|
802
984
|
skipNricFetch.value = true;
|
|
803
985
|
|
|
804
|
-
currentAutofillSource.value = "nric";
|
|
986
|
+
currentAutofillSource.value = options.source || "nric";
|
|
805
987
|
|
|
806
988
|
visitor.nric = item.nric || visitor.nric;
|
|
807
989
|
visitor.name = item.name || visitor.name;
|
|
808
|
-
visitor.contact =
|
|
990
|
+
visitor.contact = options.contacts || getPersonContactOptions(item)[0] || visitor.contact;
|
|
991
|
+
visitor.plateNumber = options.plateNumber || getPersonPlateOptions(item)[0] || visitor.plateNumber;
|
|
809
992
|
companyNames.value = item.companyName ?? [];
|
|
810
993
|
if (!visitor.company) {
|
|
811
994
|
visitor.company = companyNames.value?.[0] || visitor.company;
|
|
@@ -821,6 +1004,51 @@ function selectNricSearchResult(item: Partial<TPeople>) {
|
|
|
821
1004
|
}, 1000);
|
|
822
1005
|
}
|
|
823
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
|
+
|
|
824
1052
|
const {
|
|
825
1053
|
data: fetchCompanyListReq,
|
|
826
1054
|
refresh: fetchCompanyListRefresh,
|
|
@@ -1172,6 +1400,44 @@ function handleUpdateNRIC() {
|
|
|
1172
1400
|
debounceFetchNRIC();
|
|
1173
1401
|
}
|
|
1174
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
|
+
|
|
1175
1441
|
function backToSelection() {
|
|
1176
1442
|
emit("back");
|
|
1177
1443
|
message.value = "";
|
|
@@ -1391,7 +1657,7 @@ async function submit(options: { skipNonCheckedOutCheck?: boolean } = {}) {
|
|
|
1391
1657
|
if (formMode.value === "register") {
|
|
1392
1658
|
payload.status = "registered";
|
|
1393
1659
|
const currentUserId = currentUser.value?._id;
|
|
1394
|
-
if(currentUserId) {
|
|
1660
|
+
if (currentUserId) {
|
|
1395
1661
|
payload.updatedBy = currentUserId;
|
|
1396
1662
|
}
|
|
1397
1663
|
}
|
|
@@ -1712,6 +1978,10 @@ watch(
|
|
|
1712
1978
|
skipUnregisteredPlateCheck.value = false;
|
|
1713
1979
|
unregisteredPlateMatches.value = [];
|
|
1714
1980
|
dialog.showUnregisteredPlateDialog = false;
|
|
1981
|
+
if (!normalizedPlateNumber || normalizedPlateNumber.length < 4) {
|
|
1982
|
+
vehicleSearchMenu.value = false;
|
|
1983
|
+
vehicleSearchResults.value = [];
|
|
1984
|
+
}
|
|
1715
1985
|
}
|
|
1716
1986
|
);
|
|
1717
1987
|
|
|
@@ -1764,4 +2034,52 @@ onMounted(() => {
|
|
|
1764
2034
|
.no-pointer {
|
|
1765
2035
|
pointer-events: none;
|
|
1766
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
|
+
}
|
|
1767
2085
|
</style>
|
package/package.json
CHANGED
package/types/people.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ declare type TPeople = {
|
|
|
10
10
|
plateNumber?: string;
|
|
11
11
|
nric?: string;
|
|
12
12
|
contact?: string;
|
|
13
|
+
contacts?: string[] //used in autofill only in VMS
|
|
14
|
+
visitorPlateNumbers?: string [] //used in autofill only in VMS
|
|
13
15
|
remarks?: string;
|
|
14
16
|
start?: string;
|
|
15
17
|
end?: string;
|