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

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.
Files changed (31) hide show
  1. package/.github/workflows/publish-staging.yml +11 -0
  2. package/components/AddEqupmentForm.vue +52 -1
  3. package/components/CameraMain.vue +5 -6
  4. package/components/DashboardMain.vue +231 -87
  5. package/components/EquipmentManagementMain.vue +56 -0
  6. package/components/InvitationMain.vue +4 -3
  7. package/components/ManageChecklistMain.vue +44 -38
  8. package/components/Nfc/NFCPatrolReportMain.vue +146 -38
  9. package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
  10. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
  11. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
  12. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +20 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
  14. package/components/Nfc/PatrolReport/ReportDatePicker.vue +61 -0
  15. package/components/Nfc/PatrolReport/ReportFilters.vue +52 -20
  16. package/components/Nfc/PatrolReport/SummaryReportTable.vue +22 -0
  17. package/components/PhotoUpload.vue +19 -4
  18. package/components/ServiceProviderMain.vue +59 -25
  19. package/components/VisitorForm.vue +78 -18
  20. package/components/VisitorManagement.vue +12 -16
  21. package/composables/useCleaningSchedulePermission.ts +16 -51
  22. package/composables/useEquipment.ts +2 -0
  23. package/composables/useLocalAuth.ts +1 -1
  24. package/composables/useNFCPatrolDailyReport.ts +3 -4
  25. package/composables/useNFCPatrolMonthlyReport.ts +62 -30
  26. package/composables/useNFCPatrolReport.ts +2 -0
  27. package/composables/useServiceProvider.ts +21 -0
  28. package/package.json +1 -1
  29. package/types/customer.d.ts +2 -1
  30. package/types/equipment.d.ts +5 -1
  31. package/types/nfc-patrol-report.ts +19 -1
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div class="d-flex flex-column">
3
+ <v-text-field
4
+ v-bind="$attrs"
5
+ :model-value="formattedDate"
6
+ autocomplete="off"
7
+ placeholder="DD/MM/YY"
8
+ readonly
9
+ @click="openDatePicker"
10
+ >
11
+ <template #append-inner>
12
+ <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
13
+ </template>
14
+ </v-text-field>
15
+
16
+ <input
17
+ ref="dateInput"
18
+ v-model="date"
19
+ class="native-date-input"
20
+ type="date"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ defineOptions({ inheritAttrs: false });
27
+
28
+ const date = defineModel<string>({ default: "" });
29
+ const dateInput = ref<HTMLInputElement | null>(null);
30
+
31
+ const formattedDate = computed(() => {
32
+ const match = date.value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
33
+
34
+ if (!match) return "";
35
+
36
+ const [, year, month, day] = match;
37
+ return `${day}/${month}/${year.slice(-2)}`;
38
+ });
39
+
40
+ function openDatePicker() {
41
+ const input = dateInput.value;
42
+
43
+ if (!input) return;
44
+
45
+ if (input.showPicker) {
46
+ input.showPicker();
47
+ } else {
48
+ input.click();
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <style scoped>
54
+ .native-date-input {
55
+ position: absolute;
56
+ width: 1px;
57
+ height: 1px;
58
+ opacity: 0;
59
+ pointer-events: none;
60
+ }
61
+ </style>
@@ -16,7 +16,12 @@
16
16
  />
17
17
  </v-col>
18
18
 
19
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
19
+ <v-col
20
+ v-if="showTimeFilter"
21
+ cols="12"
22
+ md="2"
23
+ class="px-md-2 mb-2 mb-md-0"
24
+ >
20
25
  <v-select
21
26
  :model-value="modelValue.timeRange"
22
27
  :items="options.timeRanges"
@@ -30,10 +35,14 @@
30
35
  />
31
36
  </v-col>
32
37
 
33
- <v-col cols="12" md="2" class="px-md-2 mb-2 mb-md-0">
34
- <v-text-field
38
+ <v-col
39
+ v-if="showDateFilter"
40
+ cols="12"
41
+ md="2"
42
+ class="px-md-2 mb-2 mb-md-0"
43
+ >
44
+ <ReportDatePicker
35
45
  :model-value="modelValue.date"
36
- type="date"
37
46
  variant="outlined"
38
47
  density="comfortable"
39
48
  rounded="lg"
@@ -46,17 +55,31 @@
46
55
  <v-spacer />
47
56
 
48
57
  <v-col cols="12" md="2" class="pl-md-2">
49
- <v-btn
50
- variant="outlined"
51
- block
52
- size="large"
53
- rounded="lg"
54
- class="text-none"
55
- prepend-icon="mdi-export"
56
- @click="$emit('export')"
57
- >
58
- Export
59
- </v-btn>
58
+ <v-menu>
59
+ <template #activator="{ props: menuProps }">
60
+ <v-btn
61
+ v-bind="menuProps"
62
+ variant="outlined"
63
+ block
64
+ size="large"
65
+ rounded="lg"
66
+ class="text-none"
67
+ prepend-icon="mdi-export"
68
+ >
69
+ Export
70
+ </v-btn>
71
+ </template>
72
+
73
+ <v-list density="compact">
74
+ <v-list-item @click="$emit('export', 'pdf')">
75
+ <v-list-item-title>PDF</v-list-item-title>
76
+ </v-list-item>
77
+
78
+ <v-list-item @click="$emit('export', 'csv')">
79
+ <v-list-item-title>CSV</v-list-item-title>
80
+ </v-list-item>
81
+ </v-list>
82
+ </v-menu>
60
83
  </v-col>
61
84
  </v-row>
62
85
  </template>
@@ -66,15 +89,24 @@ import type {
66
89
  NFCPatrolReportFilterOptions,
67
90
  NFCPatrolReportFilters,
68
91
  } from "../../../types/nfc-patrol-report";
92
+ import ReportDatePicker from "./ReportDatePicker.vue";
69
93
 
70
- const props = defineProps<{
71
- modelValue: NFCPatrolReportFilters;
72
- options: NFCPatrolReportFilterOptions;
73
- }>();
94
+ const props = withDefaults(
95
+ defineProps<{
96
+ modelValue: NFCPatrolReportFilters;
97
+ options: NFCPatrolReportFilterOptions;
98
+ showTimeFilter?: boolean;
99
+ showDateFilter?: boolean;
100
+ }>(),
101
+ {
102
+ showTimeFilter: true,
103
+ showDateFilter: true,
104
+ },
105
+ );;
74
106
 
75
107
  const emit = defineEmits<{
76
108
  "update:modelValue": [value: NFCPatrolReportFilters];
77
- export: [];
109
+ export: [type: "pdf" | "csv"];
78
110
  }>();
79
111
 
80
112
  function updateFilter(key: keyof NFCPatrolReportFilters, value: unknown) {
@@ -8,6 +8,8 @@
8
8
  <tr>
9
9
  <th>Patrol Route Name</th>
10
10
  <th>Security Check Schedule</th>
11
+ <th>Security Personnel</th>
12
+ <th>Signature</th>
11
13
  <th>Checked</th>
12
14
  <th>Missed</th>
13
15
  <th>Status</th>
@@ -20,6 +22,17 @@
20
22
  <v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
21
23
  {{ row.securityCheckSchedule }}
22
24
  </td>
25
+ <td>{{ row.guardName || "-" }}</td>
26
+ <td>
27
+ <v-img
28
+ v-if="row.guardSignatureFileId"
29
+ :src="getFileUrl(row.guardSignatureFileId)"
30
+ :alt="`${row.guardName || 'Guard'} signature`"
31
+ class="guard-signature"
32
+ contain
33
+ />
34
+ <span v-else>-</span>
35
+ </td>
23
36
  <td>{{ row.checked }}</td>
24
37
  <td>{{ row.missed }}</td>
25
38
  <td>
@@ -45,7 +58,16 @@ defineProps<{
45
58
  rows: NFCPatrolReportSummaryRow[];
46
59
  }>();
47
60
 
61
+ const { getFileUrl } = useFile();
62
+
48
63
  function statusClass(status: NFCPatrolReportSummaryStatus) {
49
64
  return status === "Completed" ? "text-success" : "text-error";
50
65
  }
51
66
  </script>
67
+
68
+ <style scoped>
69
+ .guard-signature {
70
+ width: 80px;
71
+ height: 40px;
72
+ }
73
+ </style>
@@ -59,6 +59,21 @@
59
59
  </v-col>
60
60
  </v-row>
61
61
 
62
+ <v-dialog v-model="showPhotoPreview" max-width="900">
63
+ <v-card>
64
+ <v-card-actions class="pa-2 justify-end">
65
+ <v-btn
66
+ icon="mdi-close"
67
+ variant="text"
68
+ @click="showPhotoPreview = false"
69
+ />
70
+ </v-card-actions>
71
+ <v-card-text class="pa-2 pt-0">
72
+ <v-img :src="selectedPhoto" contain max-height="80vh" />
73
+ </v-card-text>
74
+ </v-card>
75
+ </v-dialog>
76
+
62
77
  <v-dialog v-model="photoOptionsDialog" max-width="420">
63
78
  <v-card>
64
79
  <v-card-title class="d-flex align-center pa-4">
@@ -196,6 +211,8 @@ const photos = ref<string[]>([...props.modelValue]);
196
211
  const photosIds = ref<string[]>([...props.photoIds]);
197
212
  const photoOptionsDialog = ref(false);
198
213
  const showCameraDialog = ref(false);
214
+ const showPhotoPreview = ref(false);
215
+ const selectedPhoto = ref("");
199
216
  const videoRef = ref<HTMLVideoElement | null>(null);
200
217
  const canvasRef = ref<HTMLCanvasElement | null>(null);
201
218
  const fileInputRef = ref<HTMLInputElement | null>(null);
@@ -205,10 +222,8 @@ const uploading = ref(false);
205
222
  const { addFile } = useFile();
206
223
 
207
224
  function viewPhoto(photoData: string) {
208
- const link = document.createElement("a");
209
- link.href = photoData;
210
- link.target = "_blank";
211
- link.click();
225
+ selectedPhoto.value = photoData;
226
+ showPhotoPreview.value = true;
212
227
  }
213
228
 
214
229
  watch(
@@ -548,7 +548,7 @@
548
548
  variant="flat"
549
549
  class="text-none sp-invite-submit-btn"
550
550
  height="76"
551
- :disabled="!validServiceProvider"
551
+ :disabled="!canSubmitInvite"
552
552
  :loading="disableServiceProvider"
553
553
  @click="submitServiceProviderInvite()"
554
554
  >
@@ -574,6 +574,7 @@ type ListTab = "active" | "pending" | "inactive";
574
574
 
575
575
  type TableRow = {
576
576
  _id: string;
577
+ userId: string;
577
578
  serviceProviderOrgId: string;
578
579
  companyName: string;
579
580
  email: string;
@@ -733,11 +734,10 @@ const {
733
734
  updateStatus: updateServiceProviderStatus,
734
735
  invite,
735
736
  } = useServiceProvider();
736
- const { getAll: getAllMembers } = useMember();
737
+ const { getAll: getAllMembers, getAllByUserId } = useMember();
737
738
 
738
739
  const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
739
740
  useVerification();
740
- const { getOrgsByMembership } = useMember();
741
741
 
742
742
  const existingAccount = ref<any>(null);
743
743
  const checkingExistingAccount = ref(false);
@@ -759,41 +759,77 @@ const createInviteDialog = ref(false);
759
759
 
760
760
  const { getByUserType } = useMember();
761
761
  const { getOrgs } = useOrg();
762
+ const { getByEmail } = useServiceProvider();
762
763
 
763
764
  async function checkExistingAccount(email: string) {
764
765
  existingAccount.value = null;
766
+ messageServiceProvider.value = "";
765
767
 
766
768
  if (!email.trim() || emailRule(email) !== true) return;
767
769
 
768
770
  checkingExistingAccount.value = true;
769
771
 
770
772
  try {
773
+ // Check if user exists
771
774
  const user = await getUserByEmail(email);
772
775
 
773
- if (!user?._id) return;
776
+ if (!user?._id) return;
774
777
 
775
- const orgs = await getOrgs({
776
- user: user._id,
777
- });
778
+ // Check if this service provider already exists in current org/site
779
+ const serviceProviders = await getByEmail({
780
+ email,
781
+ orgId: props.orgId,
782
+ siteId: props.siteId,
783
+ });
784
+
785
+ console.log("serviceProviders", serviceProviders);
786
+
787
+ if (serviceProviders?.length) {
788
+ messageServiceProvider.value =
789
+ "This service provider already exists.";
790
+ return;
791
+ }
792
+
793
+ // Check existing memberships
794
+ const members = await getAllByUserId(user._id);
778
795
 
779
- const matchedOrg = orgs.items?.find(
780
- (org: any) => org.type === serviceProviderInvite.value.app
796
+ const appMember = (members.items ?? []).find(
797
+ (member: any) => member.type === serviceProviderInvite.value.app
781
798
  );
782
799
 
783
- if (matchedOrg) {
784
- existingAccount.value = {
785
- _id: matchedOrg.value,
786
- text: matchedOrg.text,
787
- ...matchedOrg,
788
- };
800
+ if (!appMember) return;
801
+
802
+ // Already belongs to the same org/site
803
+ if (
804
+ appMember.org === props.orgId &&
805
+ (appMember.siteId || "") === (props.siteId || "")
806
+ ) {
807
+ messageServiceProvider.value =
808
+ "This user is already a member of this service provider.";
809
+ return;
789
810
  }
811
+
812
+ // Existing account in another org/site
813
+ existingAccount.value = {
814
+ _id: appMember.org,
815
+ text: appMember.orgName,
816
+ org: appMember.org,
817
+ orgName: appMember.orgName,
818
+ siteId: appMember.siteId,
819
+ siteName: appMember.siteName,
820
+ };
790
821
  } catch (error) {
791
822
  existingAccount.value = null;
792
823
  } finally {
793
824
  checkingExistingAccount.value = false;
794
825
  }
795
826
  }
796
-
827
+ const canSubmitInvite = computed(() => {
828
+ return (
829
+ validServiceProvider.value &&
830
+ !messageServiceProvider.value
831
+ );
832
+ });
797
833
  watch(
798
834
  () => serviceProviderInvite.value.email,
799
835
  async (value) => {
@@ -939,6 +975,7 @@ function formatEmailCell(value: unknown): string {
939
975
  function mapProviderRows(raw: Record<string, any>[]): TableRow[] {
940
976
  return raw.map((row) => ({
941
977
  _id: String(row._id ?? ""),
978
+ userId: String(row.user ?? ""),
942
979
  serviceProviderOrgId: String(row.serviceProviderOrgId ?? ""),
943
980
  companyName: row.name ?? "-",
944
981
  email: formatEmailCell(row.email),
@@ -961,6 +998,7 @@ function inviteTypeLabel(t: string) {
961
998
  function mapInviteRows(raw: Record<string, any>[]): TableRow[] {
962
999
  return raw.map((row) => ({
963
1000
  _id: String(row._id ?? ""),
1001
+ userId: String(row.user ?? ""),
964
1002
  serviceProviderOrgId: String(row.metadata?.serviceProviderOrgId ?? ""),
965
1003
  companyName: row.metadata?.name || "-",
966
1004
  email: formatEmailCell(row.email),
@@ -1029,16 +1067,12 @@ async function openView(row: TableRow) {
1029
1067
 
1030
1068
  loadingMembers.value = true;
1031
1069
  try {
1032
- const data = await getAllMembers({
1033
- page: 1,
1034
- limit: 1000,
1035
- org: row.serviceProviderOrgId,
1036
- status: "active",
1037
- });
1070
+ const data = await getAllByUserId(row.userId);
1071
+
1038
1072
  serviceProviderMembers.value = (data.items ?? []).map((member: any) => ({
1039
- _id: String(member._id ?? ""),
1040
- name: member.name || member.fullName || member.email || "-",
1041
- role: member.roleName || member.role?.name || "-",
1073
+ _id: member._id,
1074
+ name: member.name || "-",
1075
+ role: member.role || "-",
1042
1076
  }));
1043
1077
  } catch (error: any) {
1044
1078
  showMessage(errorConverter(error), "error");
@@ -36,10 +36,17 @@
36
36
  </template>
37
37
 
38
38
  <v-list v-if="nricSearchResults.length" class="pa-1 phone-search-results">
39
- <v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
40
- @click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
41
- <VisitorSearchResultSummary :item="item" />
42
- </v-list-item>
39
+ <div class="search-results-scroll">
40
+ <v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
41
+ @click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
42
+ <VisitorSearchResultSummary :item="item" />
43
+ </v-list-item>
44
+ </div>
45
+ <div class="search-results-close pa-1">
46
+ <v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('nric')">
47
+ Close
48
+ </v-btn>
49
+ </div>
43
50
  </v-list>
44
51
  </v-menu>
45
52
  </v-col>
@@ -58,10 +65,17 @@
58
65
  </template>
59
66
 
60
67
  <v-list v-if="phoneSearchResults.length" class="pa-1 phone-search-results">
61
- <v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
62
- @click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
63
- <VisitorSearchResultSummary :item="item" />
64
- </v-list-item>
68
+ <div class="search-results-scroll">
69
+ <v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
70
+ @click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
71
+ <VisitorSearchResultSummary :item="item" />
72
+ </v-list-item>
73
+ </div>
74
+ <div class="search-results-close pa-1">
75
+ <v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('contact')">
76
+ Close
77
+ </v-btn>
78
+ </div>
65
79
  </v-list>
66
80
  </v-menu>
67
81
  </v-col>
@@ -77,15 +91,23 @@
77
91
  <v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
78
92
  <template #activator="{ props }">
79
93
  <InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
80
- @update:model-value="handleUpdateVehicleNumber" @focus="isPlateNumberFieldFocused = true"
94
+ @update:model-value="handleUpdateVehicleNumber" @focus="handleFocusPlateNumber"
81
95
  @blur="isPlateNumberFieldFocused = false" />
82
96
  </template>
83
97
 
84
98
  <v-list v-if="vehicleSearchResults.length" class="pa-1 phone-search-results">
85
- <v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
86
- @click="selectPlateNumberSearchResult(item)" class="cursor-pointer nric-search-item">
87
- <VisitorSearchResultSummary :item="item" />
88
- </v-list-item>
99
+ <div class="search-results-scroll">
100
+ <v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
101
+ @click="selectPlateNumberSearchResult(item)" class="cursor-pointer nric-search-item">
102
+ <VisitorSearchResultSummary :item="item" />
103
+ </v-list-item>
104
+ </div>
105
+ <div class="search-results-close pa-1">
106
+ <v-btn block variant="flat" color="red"
107
+ @click.stop="closeAutofillSearchResults('vehicleNumber')">
108
+ Close
109
+ </v-btn>
110
+ </div>
89
111
  </v-list>
90
112
  </v-menu>
91
113
  </v-col>
@@ -308,10 +330,10 @@
308
330
 
309
331
  <div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
310
332
  <span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
311
- <span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
333
+ <span v-if="!visitor.nric || currentAutofillSource === 'nric'" class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
312
334
  </div>
313
335
 
314
- <template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
336
+ <template v-if="(!visitor.contact || currentAutofillSource === 'contact') && selectedNricContactOptions.length >= 1">
315
337
  <h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
316
338
  <v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
317
339
  <v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
@@ -319,7 +341,7 @@
319
341
  </v-radio-group>
320
342
  </template>
321
343
 
322
- <template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
344
+ <template v-if="(!visitor.plateNumber || currentAutofillSource === 'vehicleNumber') && selectedNricPlateOptions.length >= 1">
323
345
  <h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
324
346
  <v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
325
347
  <v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
@@ -1044,8 +1066,8 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
1044
1066
  const selectedPlate =
1045
1067
  findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
1046
1068
 
1047
- const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
1048
- const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
1069
+ const needsContactChoice = (!visitor.contact?.trim() || source === 'contact') && contactOptions.length > 1;
1070
+ const needsPlateChoice = (!visitor.plateNumber?.trim() || source === 'vehicleNumber') && plateOptions.length > 1;
1049
1071
 
1050
1072
  if (needsContactChoice || needsPlateChoice) {
1051
1073
  nricSearchMenu.value = false;
@@ -1066,6 +1088,23 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
1066
1088
  });
1067
1089
  }
1068
1090
 
1091
+ function closeAutofillSearchResults(source: Exclude<AutofillSource, null>) {
1092
+ if (source === "nric") {
1093
+ nricSearchMenu.value = false;
1094
+ nricSearchResults.value = [];
1095
+ }
1096
+
1097
+ if (source === "contact") {
1098
+ phoneSearchMenu.value = false;
1099
+ phoneSearchResults.value = [];
1100
+ }
1101
+
1102
+ if (source === "vehicleNumber") {
1103
+ vehicleSearchMenu.value = false;
1104
+ vehicleSearchResults.value = [];
1105
+ }
1106
+ }
1107
+
1069
1108
  function closeNricAutofillOptions() {
1070
1109
  dialog.nricAutofillOptions = false;
1071
1110
  selectedNricSearchResult.value = null;
@@ -1503,6 +1542,15 @@ function handleUpdateVehicleNumber() {
1503
1542
  debounceFetchPlateNumber();
1504
1543
  }
1505
1544
 
1545
+ const handleFocusPlateNumber = () => {
1546
+ isPlateNumberFieldFocused.value = true;
1547
+ if (prop.mode === 'register') {
1548
+
1549
+ handleUpdateVehicleNumber();
1550
+ }
1551
+ };
1552
+
1553
+
1506
1554
  function backToSelection() {
1507
1555
  emit("back");
1508
1556
  message.value = "";
@@ -2108,10 +2156,22 @@ onMounted(() => {
2108
2156
  }
2109
2157
 
2110
2158
  .phone-search-results {
2159
+ display: flex;
2160
+ flex-direction: column;
2111
2161
  max-height: 400px;
2162
+ overflow: hidden;
2163
+ }
2164
+
2165
+ .search-results-scroll {
2166
+ flex: 1 1 auto;
2167
+ min-height: 0;
2112
2168
  overflow-y: auto;
2113
2169
  }
2114
2170
 
2171
+ .search-results-close {
2172
+ flex: 0 0 auto;
2173
+ }
2174
+
2115
2175
  .nric-options-card {
2116
2176
  border-radius: 16px !important;
2117
2177
  }
@@ -825,10 +825,8 @@
825
825
  item-title="label"
826
826
  item-value="value"
827
827
  v-model="pass.status"
828
- :disabled="
829
- selectedVisitorObject.checkOut || pass.status == 'Removed'
830
- "
831
- ></v-select>
828
+ :disabled="pass.status == 'Removed'"
829
+ />
832
830
  <v-textarea
833
831
  v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
834
832
  no-resize
@@ -836,8 +834,7 @@
836
834
  class="w-100"
837
835
  density="compact"
838
836
  v-model="pass.remarks"
839
- :disabled="selectedVisitorObject.checkOut"
840
- ></v-textarea>
837
+ />
841
838
  </v-row>
842
839
  </div>
843
840
  <div v-if="keyReturnStatuses.length > 0" class="mb-2">
@@ -864,10 +861,8 @@
864
861
  item-title="label"
865
862
  item-value="value"
866
863
  v-model="key.status"
867
- :disabled="
868
- selectedVisitorObject.checkOut || key.status == 'Removed'
869
- "
870
- ></v-select>
864
+ :disabled="key.status == 'Removed'"
865
+ />
871
866
  <v-textarea
872
867
  v-if="key.status === 'Lost' || key.status === 'Damaged'"
873
868
  no-resize
@@ -875,8 +870,7 @@
875
870
  class="w-100"
876
871
  density="compact"
877
872
  v-model="key.remarks"
878
- :disabled="selectedVisitorObject.checkOut"
879
- ></v-textarea>
873
+ />
880
874
  </v-row>
881
875
  </div>
882
876
 
@@ -886,11 +880,11 @@
886
880
  color="blue"
887
881
  density="comfortable"
888
882
  class="text-capitalize"
889
- :disabled="selectedVisitorObject.checkOut"
890
883
  :loading="loading.updatingPassKeys"
891
884
  @click.stop="handleUpdatePassKeys"
892
- >Update Pass/Keys</v-btn
893
885
  >
886
+ Update Pass/Keys
887
+ </v-btn>
894
888
  </v-row>
895
889
  </v-card-text>
896
890
  <v-toolbar class="pa-0" density="compact">
@@ -900,8 +894,9 @@
900
894
  variant="text"
901
895
  block
902
896
  @click="dialog.returnPassesKeys = false"
903
- >Close</v-btn
904
897
  >
898
+ Close
899
+ </v-btn>
905
900
  </v-col>
906
901
  <v-col cols="6">
907
902
  <v-btn
@@ -915,8 +910,9 @@
915
910
  !canConfirmCheckout || selectedVisitorObject.checkOut
916
911
  "
917
912
  @click="proceedCheckout"
918
- >Confirm Checkout</v-btn
919
913
  >
914
+ Confirm Checkout
915
+ </v-btn>
920
916
  </v-col>
921
917
  </v-row>
922
918
  </v-toolbar>