@7365admin1/layer-common 3.1.3-staging.39 → 3.1.3-staging.41

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.
@@ -280,8 +280,8 @@ const prop = defineProps({
280
280
  headers: {
281
281
  type: Array as PropType<Array<any>>,
282
282
  default: () => [
283
- { title: "Host", key: "host" },
284
- { title: "Category", key: "category" },
283
+ // { title: "Host", key: "host" },
284
+ // { title: "Category", key: "category" },
285
285
  // { title: "Guard House", key: "guardPost" },
286
286
  // { title: "Actions", key: "action", sortable: false },
287
287
  ],
@@ -294,11 +294,20 @@ const prop = defineProps({
294
294
 
295
295
  const computedHeaders = computed(() => {
296
296
  if (prop.type === "ip") {
297
- const base = [...prop.headers];
298
- base.splice(0, 0, { title: "Name", key: "name" });
299
- return base;
297
+ // const base = [...prop.headers];
298
+ // base.splice(0, 0, { title: "Name", key: "name" });
299
+ // return base;
300
+ return [
301
+ { title: "Camera Name", key: "name" },
302
+ { title: "URL", key: "host" },
303
+ ]
304
+ }else if(prop.type === "anpr"){
305
+ return [
306
+ { title: "URL", key: "host" },
307
+ { title: "Type", key: "direction" },
308
+ ]
300
309
  }
301
- return prop.headers;
310
+ // return prop.headers;
302
311
  });
303
312
 
304
313
  const items = ref<Array<TCamera>>([]);
@@ -2,152 +2,144 @@
2
2
  <v-row no-gutters class="w-100">
3
3
  <v-card class="w-100">
4
4
  <v-card-text>
5
- <v-btn
6
- block
7
- color="primary-button"
8
- :height="40"
9
- text="Scan QR Code"
10
- class="text-capitalize"
11
- prepend-icon="mdi-qrcode"
12
- @click="handleScanQRPass"
13
- />
14
- <v-form
15
- ref="formRef"
16
- v-model="validForm"
17
- :disabled="processing"
18
- @click="errorScanPassMessage = ''"
19
- >
5
+ <v-btn block color="primary-button" :height="40" text="Scan QR Code" class="text-capitalize"
6
+ prepend-icon="mdi-qrcode" @click="handleScanQRPass" />
7
+ <v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorScanPassMessage = ''">
20
8
  <v-row no-gutters class="pt-5 ga-2">
21
9
  <v-col cols="12">
22
10
  <InputLabel class="text-capitalize" title="Full Name" required />
23
- <v-text-field
24
- v-model.trim="memberForm.name"
25
- density="compact"
26
- hide-details
27
- :rules="[requiredRule]"
28
- />
11
+ <v-text-field v-model.trim="memberForm.name" density="compact" hide-details :rules="[requiredRule]" />
29
12
  </v-col>
30
13
 
31
14
  <v-col cols="12">
32
15
  <InputLabel class="text-capitalize" title="NRIC" required />
33
- <InputNRICNumber
34
- v-model.trim="memberForm.nric"
35
- density="compact"
36
- hide-details
37
- :rules="[requiredRule]"
38
- />
16
+ <v-menu v-model="memberNRICSearchMenu" location="bottom" offset-y :close-on-content-click="false">
17
+ <template #activator="{ props: menuProps }">
18
+ <InputNRICNumber v-bind="menuProps" v-model.trim="memberForm.nric" density="compact" hide-details
19
+ :rules="[requiredRule]" :key="memberCurrentAutofillSource + 'member-nric-key'"
20
+ @focus="isMemberNRICFieldFocused = true" @blur="isMemberNRICFieldFocused = false"
21
+ @update:model-value="handleUpdateMemberNRIC" />
22
+ </template>
23
+
24
+ <v-list v-if="memberNRICSearchResults.length" class="pa-1 phone-search-results">
25
+ <v-list-item v-for="(item, index) in memberNRICSearchResults" :key="item._id || index"
26
+ class="cursor-pointer nric-search-item" @click="selectMemberNRICSearchResult(item)">
27
+ <VisitorSearchResultSummary :item="item" />
28
+ </v-list-item>
29
+ </v-list>
30
+ </v-menu>
31
+ </v-col>
32
+
33
+ <v-col cols="12">
34
+ <InputLabel class="text-capitalize" title="Phone Number" required />
35
+ <v-menu v-model="memberPhoneSearchMenu" location="bottom" offset-y :close-on-content-click="false">
36
+ <template #activator="{ props: menuProps }">
37
+ <InputPhoneNumberV2 v-bind="menuProps" v-model="memberForm.contact" density="compact"
38
+ default-country="SG" hide-details :key="memberCurrentAutofillSource + 'member-phone-key'"
39
+ @focus="isMemberPhoneFieldFocused = true" @blur="isMemberPhoneFieldFocused = false"
40
+ @update:model-value="handleUpdateMemberPhoneNumber" />
41
+ </template>
42
+
43
+ <v-list v-if="memberPhoneSearchResults.length" class="pa-1 phone-search-results">
44
+ <v-list-item v-for="(item, index) in memberPhoneSearchResults" :key="item._id || index"
45
+ class="cursor-pointer nric-search-item" @click="selectMemberPhoneNumberSearchResult(item)">
46
+ <VisitorSearchResultSummary :item="item" />
47
+ </v-list-item>
48
+ </v-list>
49
+ </v-menu>
39
50
  </v-col>
40
51
 
41
52
  <v-col cols="12">
42
- <v-autocomplete
43
- v-model="selectedPass"
44
- v-model:search="passInput"
45
- :hide-no-data="false"
46
- class="mt-3"
47
- :items="passItemsFilteredFinal"
48
- item-title="prefixAndName"
49
- item-value="_id"
50
- label="Pass (optional)"
51
- variant="outlined"
52
- density="compact"
53
- :error-messages="errorScanPassMessage"
54
- persistent-hint
55
- small-chips
56
- :clearable="clearable"
57
- @click="fetchPasses"
58
- >
53
+ <v-autocomplete v-model="selectedPass" v-model:search="passInput" :hide-no-data="false" class="mt-3"
54
+ :items="passItemsFilteredFinal" item-title="prefixAndName" item-value="_id" label="Pass (optional)"
55
+ variant="outlined" density="compact" :error-messages="errorScanPassMessage" persistent-hint small-chips
56
+ :clearable="clearable" @click="fetchPasses">
59
57
  <template v-slot:no-data>
60
58
  <v-list-item density="compact">
61
59
  <v-list-item-title v-if="passInput">
62
- No results matching "<strong>{{ passInput }}</strong
63
- >". This value will be added as new option.
60
+ No results matching "<strong>{{ passInput }}</strong>". This value will be added as new option.
64
61
  </v-list-item-title>
65
62
  <v-list-item v-else>No data available</v-list-item>
66
63
  </v-list-item>
67
64
  </template>
68
65
 
69
66
  <template v-slot:chip="{ props, item }">
70
- <v-chip
71
- v-if="selectedPass"
72
- v-bind="props"
73
- prepend-icon="mdi-card-bulleted-outline"
74
- :text="item.raw?.prefixAndName"
75
- ></v-chip>
67
+ <v-chip v-if="selectedPass" v-bind="props" prepend-icon="mdi-card-bulleted-outline"
68
+ :text="item.raw?.prefixAndName"></v-chip>
76
69
  </template>
77
70
  </v-autocomplete>
78
71
  </v-col>
79
72
 
80
73
  <v-col v-if="props.unitId" cols="12">
81
- <EntryPassInformation
82
- v-model="memberPassType"
83
- v-model:quantity="memberPassQuantity"
84
- v-model:cards="memberPassCards"
85
- :settings="props.settings"
86
- :loading="props.settingsLoading"
87
- :site-id="props.site"
88
- :unit-id="props.unitId"
89
- :visitor-type="props.type"
90
- :hid-qr-code-enabled="props.hidQrCodeEnabled"
91
- :hid-qr-printer-configured="props.hidQrPrinterConfigured"
92
- :excluded-card-ids="props.selectedNfcCards.map((c) => c._id)"
93
- />
94
- </v-col>
95
-
96
- <v-col cols="12">
97
- <InputLabel
98
- class="text-capitalize"
99
- title="Phone Number"
100
- required
101
- />
102
- <InputPhoneNumberV2
103
- v-model="memberForm.contact"
104
- density="compact"
105
- default-country="SG"
106
- hide-details
107
- />
74
+ <EntryPassInformation v-model="memberPassType" v-model:quantity="memberPassQuantity"
75
+ v-model:cards="memberPassCards" :settings="props.settings" :loading="props.settingsLoading"
76
+ :site-id="props.site" :unit-id="props.unitId" :visitor-type="props.type"
77
+ :hid-qr-code-enabled="props.hidQrCodeEnabled" :hid-qr-printer-configured="props.hidQrPrinterConfigured"
78
+ :excluded-card-ids="props.selectedNfcCards.map((c) => c._id)" />
108
79
  </v-col>
109
80
 
110
81
  <v-row class="pt-3" justify="space-between">
111
82
  <v-col cols="6">
112
- <v-btn
113
- text="Add Member"
114
- :disabled="!validForm"
115
- prepend-icon="mdi-plus"
116
- color="primary-button"
117
- class="text-capitalize"
118
- @click="handleAddMember"
119
- />
83
+ <v-btn text="Add Member" :disabled="!validForm" prepend-icon="mdi-plus" color="primary-button"
84
+ class="text-capitalize" @click="handleAddMember" />
120
85
  </v-col>
121
86
  <v-col cols="6" align="end">
122
- <v-btn
123
- text="Clear Form"
124
- prepend-icon="mdi-refresh"
125
- color="primary-button"
126
- class="text-capitalize"
127
- @click.stop="handleClearForm"
128
- />
87
+ <v-btn text="Clear Form" prepend-icon="mdi-refresh" color="primary-button" class="text-capitalize"
88
+ @click.stop="handleClearForm" />
129
89
  </v-col>
130
90
  </v-row>
131
91
  </v-row>
132
92
  </v-form>
133
93
  </v-card-text>
134
94
  </v-card>
135
- <visitor-pass-key-q-r-scanner
136
- :dialog="dialog.scanQRCode"
137
- v-model:scannedValue="scannedPassValue"
138
- @close-dialog="dialog.scanQRCode = false"
139
- />
95
+ <visitor-pass-key-q-r-scanner :dialog="dialog.scanQRCode" v-model:scannedValue="scannedPassValue"
96
+ @close-dialog="dialog.scanQRCode = false" />
97
+ <v-dialog v-model="dialog.memberAutofillOptions" max-width="430">
98
+ <v-card v-if="memberSelectedSearchResult" class="nric-options-card">
99
+ <v-card-actions class="justify-end pa-1">
100
+ <v-btn icon="mdi-close" variant="text" size="small" @click="memberCloseAutofillOptions" />
101
+ </v-card-actions>
102
+
103
+ <v-card-text class="pa-4 pt-1">
104
+ <h2 class="nric-options-title font-weight-bold mb-4">Select</h2>
105
+
106
+ <div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
107
+ <span>{{ memberSelectedSearchResult.name || "Unknown" }}</span>
108
+ <span class="text-no-wrap">
109
+ NRIC: {{ memberSelectedSearchResult.nric || "N/A" }}
110
+ </span>
111
+ </div>
112
+
113
+ <template v-if="
114
+ (!memberForm.contact ||
115
+ memberCurrentAutofillSource === 'contact') &&
116
+ memberSelectedContactOptions.length >= 1
117
+ ">
118
+ <h3 class="nric-options-section-title font-weight-bold mb-2">
119
+ Contact
120
+ </h3>
121
+ <v-radio-group v-model="memberSelectedContact" hide-details color="success" class="nric-option-group">
122
+ <v-radio v-for="contact in memberSelectedContactOptions" :key="contact" :label="contact" :value="contact"
123
+ class="nric-option-radio" />
124
+ </v-radio-group>
125
+ </template>
126
+ </v-card-text>
127
+
128
+ <v-card-actions class="justify-end px-4 pb-4">
129
+ <v-btn variant="text" class="text-none" size="small" @click="memberCloseAutofillOptions">
130
+ Cancel
131
+ </v-btn>
132
+ <v-btn color="primary-button" variant="flat" class="text-none px-5" size="small"
133
+ @click="memberConfirmAutofillOptions">
134
+ Select
135
+ </v-btn>
136
+ </v-card-actions>
137
+ </v-card>
138
+ </v-dialog>
140
139
  <v-divider class="my-3" />
141
- <v-row
142
- v-if="committedMembers.length > 0"
143
- no-gutters
144
- class="w-100 mt-5 ga-3"
145
- >
140
+ <v-row v-if="committedMembers.length > 0" no-gutters class="w-100 mt-5 ga-3">
146
141
  <template v-for="(member, index) in committedMembers" :key="member.nric">
147
- <CardMemberInfoSummary
148
- :member="membersDisplayed[index]"
149
- @remove="handleRemoveMember(index)"
150
- />
142
+ <CardMemberInfoSummary :member="membersDisplayed[index]" @remove="handleRemoveMember(index)" />
151
143
  </template>
152
144
  </v-row>
153
145
  <!-- <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" /> -->
@@ -206,8 +198,12 @@ const props = defineProps({
206
198
  },
207
199
  });
208
200
 
209
- const { requiredRule } = useUtils();
201
+ const { requiredRule, debounce } = useUtils();
210
202
  const { getPassKeysByPageSearch } = usePassKey();
203
+ const {
204
+ findPersonByNRICMultipleResult,
205
+ findPersonByPhoneNumberMultipleResult,
206
+ } = usePeople();
211
207
 
212
208
  const memberForm = reactive<TMemberInfo>({
213
209
  name: "",
@@ -238,6 +234,7 @@ const scannedPassValue = ref<string>("");
238
234
 
239
235
  const dialog = reactive({
240
236
  scanQRCode: false,
237
+ memberAutofillOptions: false,
241
238
  });
242
239
 
243
240
  const selectedPass = ref<string>("");
@@ -248,6 +245,23 @@ const memberPassCards = ref<{ _id: string; cardNo: string }[]>([]);
248
245
  const members = defineModel<TMemberInfo[]>({ required: true, default: [] });
249
246
  const committedMembers = ref<TMemberInfo[]>([]);
250
247
 
248
+ type AutofillSource = "nric" | "contact" | null;
249
+ type SearchPerson = Omit<Partial<TPeople>, "contact"> & {
250
+ contact?: string | string[];
251
+ contacts?: string | string[];
252
+ };
253
+
254
+ const memberPhoneSearchMenu = ref(false);
255
+ const memberNRICSearchMenu = ref(false);
256
+ const isMemberPhoneFieldFocused = ref(false);
257
+ const isMemberNRICFieldFocused = ref(false);
258
+ const memberPhoneSearchResults = ref<Partial<TPeople>[]>([]);
259
+ const memberNRICSearchResults = ref<Partial<TPeople>[]>([]);
260
+ const memberCurrentAutofillSource = ref<AutofillSource>(null);
261
+ const memberSelectedSearchResult = ref<Partial<TPeople> | null>(null);
262
+ const memberSelectedContact = ref("");
263
+ const skipMemberNRICFetch = ref(false);
264
+
251
265
  // Always keep the model in sync: committed members + current form draft
252
266
  watch(
253
267
  () => memberForm,
@@ -352,9 +366,219 @@ function resetAccessCard() {
352
366
  memberPassCards.value = [];
353
367
  }
354
368
 
369
+ function getPeopleSearchItems(res: any): Partial<TPeople>[] {
370
+ if (Array.isArray(res?.items)) return res.items;
371
+ if (Array.isArray(res?.data)) return res.data;
372
+ if (Array.isArray(res?.data?.items)) return res.data.items;
373
+ if (res?.data) return [res.data];
374
+ if (res && !res?.message) return [res];
375
+ return [];
376
+ }
377
+
378
+ function toUniqueStringArray(value: unknown) {
379
+ const rawValues = Array.isArray(value) ? value : value ? [value] : [];
380
+ return [...new Set(rawValues.map((v) => String(v).trim()).filter(Boolean))];
381
+ }
382
+
383
+ function getPersonContactOptions(item: Partial<TPeople>) {
384
+ const person = item as SearchPerson;
385
+ return toUniqueStringArray(person.contacts || person.contact);
386
+ }
387
+
388
+ function findMatchingContactOption(options: string[], value?: string | null) {
389
+ const normalizedValue = value?.trim();
390
+ if (!normalizedValue) return "";
391
+ const compactValue = normalizedValue.replace(/\s/g, "");
392
+
393
+ return (
394
+ options.find((option) => option.trim() === normalizedValue) ||
395
+ options.find((option) => option.replace(/\s/g, "") === compactValue) ||
396
+ options.find((option) => option.trim().includes(normalizedValue)) ||
397
+ options.find((option) =>
398
+ option.replace(/\s/g, "").includes(compactValue)
399
+ ) ||
400
+ ""
401
+ );
402
+ }
403
+
404
+ const memberSelectedContactOptions = computed(() => {
405
+ return memberSelectedSearchResult.value
406
+ ? getPersonContactOptions(memberSelectedSearchResult.value)
407
+ : [];
408
+ });
409
+
410
+ const {
411
+ data: fetchMemberByPhoneNumber,
412
+ refresh: fetchMemberByPhoneNumberRefresh,
413
+ } = useLazyAsyncData(`fetch-member-phone-number-${props.site}`, () => {
414
+ const contact = memberForm.contact?.trim() || "";
415
+ if (contact.length < 4) return Promise.resolve(null);
416
+ const type = props.type === "walk-in" ? "guest" : props.type;
417
+ return findPersonByPhoneNumberMultipleResult(contact, props.site, type);
418
+ });
419
+
420
+ watch(fetchMemberByPhoneNumber, (obj) => {
421
+ memberPhoneSearchResults.value = getPeopleSearchItems(obj);
422
+ memberPhoneSearchMenu.value = memberPhoneSearchResults.value.length > 0;
423
+ });
424
+
425
+ const debounceFetchMemberPhoneNumber = debounce(
426
+ fetchMemberByPhoneNumberRefresh,
427
+ 500
428
+ );
429
+
430
+ function handleUpdateMemberPhoneNumber() {
431
+ if (!isMemberPhoneFieldFocused.value) return;
432
+
433
+ const search = memberForm.contact?.trim() || "";
434
+ if (search.length < 4) {
435
+ memberPhoneSearchResults.value = [];
436
+ memberPhoneSearchMenu.value = false;
437
+ return;
438
+ }
439
+
440
+ if (props.type === "drop-off" || props.type === "pick-up") return;
441
+ debounceFetchMemberPhoneNumber();
442
+ }
443
+
444
+ const {
445
+ data: fetchMemberByNRIC,
446
+ refresh: fetchMemberByNRICRefresh,
447
+ } = useLazyAsyncData(`fetch-member-nric-${props.site}`, () => {
448
+ const nric = memberForm.nric?.trim() || "";
449
+ if (nric.length < 4) return Promise.resolve(null);
450
+ const type = props.type === "walk-in" ? "guest" : props.type;
451
+ return findPersonByNRICMultipleResult(nric, props.site, type);
452
+ });
453
+
454
+ watch(fetchMemberByNRIC, (obj) => {
455
+ memberNRICSearchResults.value = getPeopleSearchItems(obj);
456
+ memberNRICSearchMenu.value = memberNRICSearchResults.value.length > 0;
457
+ });
458
+
459
+ const debounceFetchMemberNRIC = debounce(fetchMemberByNRICRefresh, 500);
460
+
461
+ function handleUpdateMemberNRIC() {
462
+ if (skipMemberNRICFetch.value) return;
463
+ if (!isMemberNRICFieldFocused.value) return;
464
+
465
+ const search = memberForm.nric?.trim() || "";
466
+ if (search.length < 4) {
467
+ memberNRICSearchResults.value = [];
468
+ memberNRICSearchMenu.value = false;
469
+ return;
470
+ }
471
+
472
+ if (props.type === "drop-off" || props.type === "pick-up") return;
473
+ debounceFetchMemberNRIC();
474
+ }
475
+
476
+ function selectMemberPhoneNumberSearchResult(item: Partial<TPeople>) {
477
+ memberSelectAutofillSearchResult(item, "contact");
478
+ }
479
+
480
+ function selectMemberNRICSearchResult(item: Partial<TPeople>) {
481
+ memberSelectAutofillSearchResult(item, "nric");
482
+ }
483
+
484
+ function memberSelectAutofillSearchResult(
485
+ item: Partial<TPeople>,
486
+ source: Exclude<AutofillSource, null>
487
+ ) {
488
+ memberCurrentAutofillSource.value = source;
489
+ const contactOptions = getPersonContactOptions(item);
490
+ const selectedContact =
491
+ findMatchingContactOption(contactOptions, memberForm.contact) ||
492
+ contactOptions[0] ||
493
+ "";
494
+
495
+ const needsContactChoice =
496
+ (!memberForm.contact?.trim() || source === "contact") &&
497
+ contactOptions.length > 1;
498
+
499
+ if (needsContactChoice) {
500
+ memberPhoneSearchMenu.value = false;
501
+ memberNRICSearchMenu.value = false;
502
+ memberSelectedSearchResult.value = item;
503
+ memberSelectedContact.value = selectedContact;
504
+ dialog.memberAutofillOptions = true;
505
+ return;
506
+ }
507
+
508
+ applyMemberSearchResult(item, {
509
+ contacts: selectedContact,
510
+ source,
511
+ });
512
+ }
513
+
514
+ function applyMemberSearchResult(
515
+ item: Partial<TPeople>,
516
+ options: {
517
+ contacts?: string;
518
+ source?: Exclude<AutofillSource, null>;
519
+ } = {}
520
+ ) {
521
+ memberNRICSearchMenu.value = false;
522
+ memberPhoneSearchMenu.value = false;
523
+ memberNRICSearchResults.value = [];
524
+ memberPhoneSearchResults.value = [];
525
+ skipMemberNRICFetch.value = true;
526
+ memberCurrentAutofillSource.value = options.source || null;
527
+
528
+ const shouldAutofillField = (fieldSource?: Exclude<AutofillSource, null>) => {
529
+ if (!fieldSource) return false;
530
+ return memberCurrentAutofillSource.value === fieldSource;
531
+ };
532
+ const getAutofillValue = (
533
+ currentValue: string | undefined,
534
+ nextValue: string | undefined,
535
+ fieldSource?: Exclude<AutofillSource, null>
536
+ ) => {
537
+ if (!nextValue) return currentValue;
538
+ if (currentValue && !shouldAutofillField(fieldSource)) return currentValue;
539
+ return nextValue;
540
+ };
541
+
542
+ memberForm.nric =
543
+ getAutofillValue(memberForm.nric, item.nric, "nric") ?? "";
544
+ memberForm.name = getAutofillValue(memberForm.name, item.name) ?? "";
545
+ memberForm.contact =
546
+ getAutofillValue(
547
+ memberForm.contact,
548
+ options.contacts || getPersonContactOptions(item)[0],
549
+ "contact"
550
+ ) ?? "";
551
+
552
+ setTimeout(() => {
553
+ memberCurrentAutofillSource.value = null;
554
+ skipMemberNRICFetch.value = false;
555
+ }, 1000);
556
+ }
557
+
558
+ function memberCloseAutofillOptions() {
559
+ dialog.memberAutofillOptions = false;
560
+ memberSelectedSearchResult.value = null;
561
+ memberSelectedContact.value = "";
562
+ }
563
+
564
+ function memberConfirmAutofillOptions() {
565
+ if (!memberSelectedSearchResult.value) return;
566
+
567
+ applyMemberSearchResult(memberSelectedSearchResult.value, {
568
+ contacts: memberSelectedContact.value,
569
+ source: memberCurrentAutofillSource.value || "nric",
570
+ });
571
+ memberCloseAutofillOptions();
572
+ }
573
+
355
574
  function handleClearForm() {
356
575
  formRef.value?.reset();
357
576
  resetAccessCard();
577
+ memberNRICSearchMenu.value = false;
578
+ memberPhoneSearchMenu.value = false;
579
+ memberNRICSearchResults.value = [];
580
+ memberPhoneSearchResults.value = [];
581
+ memberCloseAutofillOptions();
358
582
  }
359
583
 
360
584
  async function handleAddMember() {
@@ -375,4 +599,45 @@ function handleRemoveMember(index: number) {
375
599
  }
376
600
  </script>
377
601
 
378
- <style scoped></style>
602
+ <style scoped>
603
+ .nric-search-item {
604
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
605
+ border-radius: 4px;
606
+ margin: 4px;
607
+ min-height: 72px;
608
+ }
609
+
610
+ .phone-search-results {
611
+ max-height: 400px;
612
+ overflow-y: auto;
613
+ }
614
+
615
+ .nric-options-card {
616
+ border-radius: 16px !important;
617
+ }
618
+
619
+ .nric-options-title {
620
+ color: #06253b;
621
+ font-size: 1.45rem;
622
+ line-height: 1.2;
623
+ }
624
+
625
+ .nric-options-section-title {
626
+ color: #06253b;
627
+ font-size: 1.05rem;
628
+ line-height: 1.25;
629
+ }
630
+
631
+ .nric-selected-person {
632
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
633
+ border-radius: 12px;
634
+ color: #06253b;
635
+ font-size: 0.95rem;
636
+ min-height: 52px;
637
+ padding: 10px 14px;
638
+ }
639
+
640
+ .nric-option-group {
641
+ border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
642
+ }
643
+ </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.1.3-staging.39",
5
+ "version": "3.1.3-staging.41",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {