@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.
@@ -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
- }}</span>
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
- }}</strong>" as custom contractor type.
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
- <v-list-item-title>{{ item.name || 'Unknown' }}</v-list-item-title>
69
- <v-list-item-subtitle>NRIC: {{ item.nric }} | Contact No: {{ item.contact }} </v-list-item-subtitle>
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
- <InputPhoneNumberV2 v-model="visitor.contact" :rules="[requiredRule]" density="comfortable"
80
- :key="currentAutofillSource + 'phone-key'" />
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
- }}</strong>" as new company.
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
- }}</strong>" as new company.
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
- <InputVehicleNumber
161
- v-model="visitor.plateNumber"
162
- :rules="[isVehicleRequired ? requiredRule : () => true]"
163
- density="comfortable"
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-code-enabled="hidQrCodePassAllowed" :hid-qr-printer-configured="hasHidQrPrinterConfig"
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
- class="border rounded"
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" :loading="loading.checkingOut && selectedCheckoutVisitorId === item._id" :disabled="!item._id" @click.stop="item._id && handleCheckout(item._id)">
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" :loading="loading.searchingNonCheckedOut" @click="handleSkipNricNonCheckedOut">
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
- color="red"
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
- function selectNricSearchResult(item: Partial<TPeople>) {
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 = item.contact || 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
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.0.31-staging.10",
5
+ "version": "3.0.31-staging.12",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
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;