@7365admin1/layer-common 3.2.1-staging.75 → 3.2.2-staging.100

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 (87) hide show
  1. package/.changeset/notification-preferences.md +17 -0
  2. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  3. package/CHANGELOG.md +6 -0
  4. package/assets/css/primitives.css +680 -0
  5. package/assets/css/screens.css +541 -0
  6. package/assets/css/shell.css +258 -0
  7. package/assets/css/tokens.css +220 -0
  8. package/components/AccessCardQrTagging.vue +1 -1
  9. package/components/AccessManagement.vue +53 -72
  10. package/components/AppButton.vue +52 -0
  11. package/components/AppCard.vue +20 -0
  12. package/components/AppDateField.vue +35 -0
  13. package/components/AppField.vue +46 -0
  14. package/components/AppKpiTile.vue +65 -0
  15. package/components/AppSegmented.vue +38 -0
  16. package/components/AppSelect.vue +203 -0
  17. package/components/AreaChecklistHistoryMain.vue +6 -2
  18. package/components/AreaMain.vue +9 -17
  19. package/components/AttendanceMain.vue +4 -3
  20. package/components/Avatar/Main.vue +54 -2
  21. package/components/BillingMain.vue +26 -30
  22. package/components/BuildingManagement/buildings.vue +27 -38
  23. package/components/BuildingManagement/units.vue +26 -42
  24. package/components/BulletinBoardManagement.vue +23 -10
  25. package/components/CardToolbar.vue +37 -0
  26. package/components/CleaningScheduleMain.vue +29 -46
  27. package/components/ClientMain.vue +90 -78
  28. package/components/DashboardEmptyState.vue +11 -1
  29. package/components/DashboardMain.vue +285 -207
  30. package/components/DocumentManagement.vue +129 -93
  31. package/components/EntryPassMain.vue +49 -27
  32. package/components/EquipmentItemMain.vue +4 -6
  33. package/components/EquipmentManagementMain.vue +9 -7
  34. package/components/FeedbackMain.vue +35 -49
  35. package/components/FormDialog.vue +60 -21
  36. package/components/HidIntercomManagement.vue +40 -24
  37. package/components/HidQrCodeConfiguration.vue +24 -2
  38. package/components/HidUserEnrollment.vue +68 -31
  39. package/components/InvitationMain.vue +46 -61
  40. package/components/Layout/Header.vue +289 -37
  41. package/components/Layout/NavigationDrawer.vue +299 -30
  42. package/components/ManageChecklistMain.vue +17 -17
  43. package/components/MemberMain.vue +79 -60
  44. package/components/MyAttendanceMain.vue +3 -5
  45. package/components/Nfc/NFCPatrolRouteMain.vue +64 -96
  46. package/components/NotificationSettings.vue +361 -0
  47. package/components/OnlineFormsConfiguration.vue +1 -1
  48. package/components/PageHeader.vue +43 -0
  49. package/components/PassInformation.vue +104 -45
  50. package/components/RolePermissionMain.vue +184 -91
  51. package/components/ScanVisitorQRCode.vue +35 -7
  52. package/components/ScheduleAreaMain.vue +15 -49
  53. package/components/ScheduleTaskMain.vue +23 -26
  54. package/components/ScheduleTastTicketMain.vue +18 -43
  55. package/components/ServiceProviderMain.vue +113 -168
  56. package/components/SiteSettings.vue +21 -8
  57. package/components/SpecificAttr.vue +10 -2
  58. package/components/StatusChip.vue +93 -0
  59. package/components/SwitchContext.vue +33 -1
  60. package/components/TableHygiene.vue +29 -46
  61. package/components/TableList.vue +1 -1
  62. package/components/TableListSecondary.vue +1 -1
  63. package/components/TableMain.vue +161 -77
  64. package/components/TableV3.vue +61 -66
  65. package/components/UnitMain.vue +9 -17
  66. package/components/VehicleManagement.vue +20 -9
  67. package/components/VisitorForm.vue +100 -7
  68. package/components/VisitorManagement.vue +219 -68
  69. package/components/WorkOrder/Main.vue +42 -51
  70. package/composables/useHidAmico.ts +26 -2
  71. package/composables/useNotificationPreference.ts +87 -0
  72. package/composables/useOrg.ts +1 -1
  73. package/composables/useRole.ts +22 -0
  74. package/composables/useSipWebPhone.ts +28 -3
  75. package/composables/useThemePreference.ts +63 -0
  76. package/composables/useVisitor.ts +6 -1
  77. package/nuxt.config.ts +31 -0
  78. package/package.json +3 -2
  79. package/pages/notification-settings.vue +19 -0
  80. package/plugins/vuetify.ts +32 -1
  81. package/types/org.d.ts +2 -0
  82. package/utils/nav-sections.test.ts +190 -0
  83. package/utils/nav-sections.ts +151 -0
  84. package/utils/status.test.ts +77 -0
  85. package/utils/status.ts +90 -0
  86. package/utils/theme.test.ts +325 -7
  87. package/utils/theme.ts +268 -137
@@ -8,8 +8,8 @@
8
8
  :height="40"
9
9
  text="Scan QR Code"
10
10
  class="text-capitalize"
11
- disabled
12
11
  prepend-icon="mdi-qrcode"
12
+ @click="handleScanQRCode"
13
13
  />
14
14
  <v-autocomplete
15
15
  v-model="selectedPass"
@@ -88,6 +88,11 @@
88
88
  </v-card-text>
89
89
  </v-card>
90
90
  </v-row>
91
+ <visitor-pass-key-q-r-scanner
92
+ :dialog="dialog.scanQRCode"
93
+ v-model:scannedValue="scannedValue"
94
+ @close-dialog="dialog.scanQRCode = false"
95
+ />
91
96
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
92
97
  </template>
93
98
 
@@ -95,46 +100,36 @@
95
100
  import type { PropType } from "vue";
96
101
  import type { ValidationRule } from "vuetify/lib/types.mjs";
97
102
  import usePassKey from "../composables/usePassKey";
98
- import useKey from "../composables/useKey";
99
- import { string } from "zod/v4";
103
+ import { errorConverter } from "../utils/data";
104
+ import VisitorPassKeyQRScanner from "./VisitorPassKeyQRScanner.vue";
100
105
 
101
- const props = defineProps({
102
- passRules: {
103
- type: Array as PropType<ValidationRule[]>,
104
- default: [],
105
- },
106
- countRules: {
107
- type: Array as PropType<ValidationRule[]>,
108
- default: [],
109
- },
110
- site: {
111
- type: String,
112
- required: true,
113
- },
114
- type: {
115
- type: String as PropType<TVisitorType>,
116
- required: true,
117
- },
118
- contractorType: {
119
- type: String,
120
- default: "",
121
- },
122
- passKeys: {
123
- type: Array,
124
- default: [],
125
- },
126
- hideKeys: {
127
- type: Boolean,
128
- default: false,
129
- },
130
- clearable: {
131
- type: Boolean,
132
- default: false,
133
- },
106
+ type PassInformationProps = {
107
+ passRules?: ValidationRule[];
108
+ countRules?: ValidationRule[];
109
+ site: string;
110
+ type: TVisitorType;
111
+ contractorType?: string;
112
+ passKeys?: unknown[];
113
+ hideKeys?: boolean;
114
+ clearable?: boolean;
115
+ pass?: TPassKeyPayload[];
116
+ keys?: TPassKeyPayload[];
117
+ };
118
+
119
+ const props = withDefaults(defineProps<PassInformationProps>(), {
120
+ passRules: [],
121
+ countRules: [],
122
+ contractorType: "",
123
+ passKeys: [],
124
+ hideKeys: false,
125
+ clearable: false,
126
+ pass: [],
127
+ keys: [],
134
128
  });
135
129
 
136
- const pass = defineModel<TPassKeyPayload[]>("pass", { default: [] });
137
- const keys = defineModel<TPassKeyPayload[]>("keys", { default: [] });
130
+ const emit = defineEmits(["update:pass", "update:keys"]);
131
+ const pass = ref<TPassKeyPayload[]>(props.pass);
132
+ const keys = ref<TPassKeyPayload[]>(props.keys);
138
133
  const selectedPass = ref<string>("");
139
134
  const selectedKeys = ref<string[]>([]);
140
135
  const passInput = ref("");
@@ -144,6 +139,11 @@ const keyItems = ref<any[]>([]);
144
139
  const selectedType = ref<"qr-pass" | "nfc-card">();
145
140
  const count = ref(1);
146
141
 
142
+ const dialog = reactive({
143
+ scanQRCode: false,
144
+ });
145
+ const scannedValue = ref("");
146
+
147
147
  const { getPassKeysByPageSearch } = usePassKey();
148
148
 
149
149
  const typeItems = [
@@ -179,6 +179,23 @@ function showMessage(msg: string, color: string) {
179
179
  messageSnackbar.value = true;
180
180
  }
181
181
 
182
+ function getItemLabel(item: any) {
183
+ return (item?.prefixAndName ?? item?.raw?.prefixAndName ?? "")
184
+ .toString()
185
+ .trim();
186
+ }
187
+
188
+ function handleScanQRCode() {
189
+ scannedValue.value = "";
190
+ dialog.scanQRCode = true;
191
+ if (passItems.value.length === 0) {
192
+ fetchPasses();
193
+ }
194
+ if (!props.hideKeys && keyItems.value.length === 0) {
195
+ fetchKeys();
196
+ }
197
+ }
198
+
182
199
  const isPassListLoading = ref(false);
183
200
 
184
201
  async function fetchPasses() {
@@ -207,7 +224,7 @@ const isKeyListLoading = ref(false);
207
224
  async function fetchKeys() {
208
225
  isKeyListLoading.value = true;
209
226
  try {
210
- const keys: any = await getPassKeysByPageSearch({
227
+ const result: any = await getPassKeysByPageSearch({
211
228
  page: 1,
212
229
  sites: [props.site],
213
230
  limit: 10000,
@@ -223,7 +240,7 @@ async function fetchKeys() {
223
240
  return false;
224
241
  });
225
242
 
226
- const filteredArray = keys.items.filter((item: any) => {
243
+ const filteredArray = result.items.filter((item: any) => {
227
244
  if (Array.isArray(props?.passKeys) && props.passKeys.length) {
228
245
  return !props.passKeys.includes(item._id);
229
246
  }
@@ -231,18 +248,60 @@ async function fetchKeys() {
231
248
  });
232
249
  keyItems.value = [...previouslySelectedKeys, ...filteredArray];
233
250
  } catch (error) {
234
- showMessage(errorConverter(error as string), "error");
251
+ showMessage(errorConverter(error), "error");
235
252
  } finally {
236
253
  isKeyListLoading.value = false;
237
254
  }
238
255
  }
239
256
 
240
257
  watch(selectedPass, (newVal) => {
241
- pass.value = [{ keyId: newVal }];
258
+ const updated = newVal ? [{ keyId: newVal }] : [];
259
+ pass.value = updated;
260
+ emit("update:pass", updated);
242
261
  });
243
262
 
244
263
  watch(selectedKeys, (newVal) => {
245
- keys.value = newVal.map((key) => ({ keyId: key }));
264
+ const updated = newVal.map((key) => ({ keyId: key }));
265
+ keys.value = updated;
266
+ emit("update:keys", updated);
267
+ });
268
+
269
+ watch(scannedValue, async (newValue) => {
270
+ if (!newValue) return;
271
+
272
+ const scanned = String(newValue).trim();
273
+ if (!scanned) return;
274
+
275
+ if (passItems.value.length === 0) {
276
+ await fetchPasses();
277
+ }
278
+ if (!props.hideKeys && keyItems.value.length === 0) {
279
+ await fetchKeys();
280
+ }
281
+
282
+ const matchedPass = passItems.value.find((item) => {
283
+ return getItemLabel(item).toLowerCase() === scanned.toLowerCase();
284
+ });
285
+
286
+ if (matchedPass && matchedPass._id) {
287
+ selectedPass.value = matchedPass._id;
288
+ return;
289
+ }
290
+
291
+ if (!props.hideKeys) {
292
+ const matchedKey = keyItems.value.find((item) => {
293
+ return getItemLabel(item).toLowerCase() === scanned.toLowerCase();
294
+ });
295
+
296
+ if (matchedKey && matchedKey._id) {
297
+ if (!selectedKeys.value.includes(matchedKey._id)) {
298
+ selectedKeys.value = [...selectedKeys.value, matchedKey._id];
299
+ }
300
+ return;
301
+ }
302
+ }
303
+
304
+ showMessage(`Scanned pass or key "${scanned}" not found`, "error");
246
305
  });
247
306
 
248
307
  //prevent negative value;
@@ -254,10 +313,10 @@ watch(count, (newCount) => {
254
313
 
255
314
  onMounted(() => {
256
315
  if (pass.value.length > 0) {
257
- selectedPass.value = pass.value[0].keyId;
316
+ selectedPass.value = pass.value[0]?.keyId ?? "";
258
317
  }
259
318
  if (keys.value.length > 0) {
260
- selectedKeys.value = keys.value.map((k) => k.keyId);
319
+ selectedKeys.value = keys.value.map((k) => k.keyId ?? "");
261
320
  }
262
321
  });
263
322
  </script>
@@ -1,49 +1,46 @@
1
1
  <template>
2
- <v-row no-gutters>
3
- <v-col cols="12" class="mb-2">
4
- <v-row no-gutters>
5
- <v-btn
6
- class="text-none"
7
- rounded="pill"
8
- variant="tonal"
9
- @click="createDialog = true"
10
- size="large"
11
- v-if="canCreateRole"
12
- >
2
+ <div>
3
+ <!--
4
+ THE DESIGN'S PAGE SCAFFOLD (same shape as Service Providers).
5
+
6
+ `Create role` moves from a bare row above the card onto the title line,
7
+ on the right, where the design puts a screen's primary action. It is the
8
+ same button under the same `canCreateRole` permission opening the same
9
+ dialog. The refresh, the range, the pager and the table are unchanged.
10
+ -->
11
+ <PageHeader title="Roles and Permissions">
12
+ <template #actions>
13
+ <AppButton v-if="canCreateRole" @click="createDialog = true">
13
14
  Create role
14
- </v-btn>
15
- </v-row>
16
- </v-col>
17
- <v-col cols="12">
18
- <v-card
19
- width="100%"
20
- variant="outlined"
21
- border="thin"
22
- rounded="lg"
23
- :loading="loading"
24
- >
25
- <v-toolbar density="compact" color="grey-lighten-4">
26
- <template #prepend>
27
- <v-btn fab icon density="comfortable" @click="getRoles()">
28
- <v-icon>mdi-refresh</v-icon>
29
- </v-btn>
30
- </template>
31
-
32
- <template #append>
33
- <v-row no-gutters justify="end" align="center">
34
- <span class="mr-2 text-caption text-fontgray">
35
- {{ pageRange }}
36
- </span>
37
- <local-pagination
38
- v-model="page"
39
- :length="pages"
40
- @update:value="getRoles()"
41
- />
42
- </v-row>
43
- </template>
44
- </v-toolbar>
45
-
46
- <v-data-table
15
+ </AppButton>
16
+ </template>
17
+ </PageHeader>
18
+
19
+ <AppCard class="table-card">
20
+ <!-- `v-card`'s `:loading` bar, drawn explicitly: a loading screen must
21
+ still say so now that the card is not a `v-card`. -->
22
+ <v-progress-linear v-if="loading" indeterminate height="2" />
23
+
24
+ <CardToolbar :count="pageRange">
25
+ <template #left>
26
+ <AppButton
27
+ variant="icon"
28
+ icon="mdi-refresh"
29
+ aria-label="Refresh"
30
+ @click="getRoles()"
31
+ />
32
+ </template>
33
+
34
+ <template #right>
35
+ <local-pagination
36
+ v-model="page"
37
+ :length="pages"
38
+ @update:value="getRoles()"
39
+ />
40
+ </template>
41
+ </CardToolbar>
42
+
43
+ <v-data-table
47
44
  :headers="props.headers"
48
45
  :items="items"
49
46
  item-value="_id"
@@ -58,7 +55,11 @@
58
55
  <span class="text-caption font-weight-bold text-capitalize">
59
56
  permissions
60
57
  </span>
61
- <v-chip>{{ getPermissionCount(value) }}</v-chip>
58
+ <StatusChip
59
+ :dot="false"
60
+ tone="info"
61
+ :label="String(getPermissionCount(value))"
62
+ />
62
63
  </template>
63
64
 
64
65
  <template #item.action-table="{ item }" v-if="canDeleteRole">
@@ -73,9 +74,8 @@
73
74
  </v-list>
74
75
  </v-menu>
75
76
  </template>
76
- </v-data-table>
77
- </v-card>
78
- </v-col>
77
+ </v-data-table>
78
+ </AppCard>
79
79
 
80
80
  <!-- dialogs -->
81
81
  <v-dialog v-model="createDialog" width="500" persistent>
@@ -105,45 +105,93 @@
105
105
  />
106
106
  </v-dialog>
107
107
 
108
- <ConfirmDialog
109
- v-model="confirmDialog"
110
- :loading="deleteLoading"
111
- @submit="handleDeleteRole"
112
- >
113
- <template #title>
114
- <span class="font-weight-medium text-h5">Delete Role</span>
115
- </template>
116
-
117
- <template #description>
118
- <p class="text-subtitle-2">
119
- Are you sure you want to delete this role? This action cannot be
120
- undone.
121
- </p>
122
- </template>
108
+ <v-dialog v-model="confirmDialog" max-width="450">
109
+ <v-card>
110
+ <v-toolbar>
111
+ <v-row no-gutters class="fill-height px-6" align="center">
112
+ <span class="font-weight-bold text-h6 text-capitalize">
113
+ Delete Role
114
+ </span>
115
+ </v-row>
116
+ </v-toolbar>
123
117
 
124
- <template #footer>
125
- <div class="d-flex justify-center ga-3 w-100">
126
- <v-btn
127
- variant="text"
128
- @click="confirmDialog = false"
129
- :disabled="deleteLoading"
130
- >
131
- Close
132
- </v-btn>
133
- <v-btn
134
- color="primary"
135
- variant="flat"
136
- @click="handleDeleteRole"
137
- :loading="deleteLoading"
138
- >
139
- Delete Role
140
- </v-btn>
141
- </div>
142
- </template>
143
- </ConfirmDialog>
118
+ <v-card-text>
119
+ <span v-if="!hasAssignedMembers" class="text-subtitle-2">
120
+ Are you sure you want to delete this role? This action cannot be
121
+ undone.
122
+ </span>
123
+ <template v-else>
124
+ <p class="text-subtitle-2 mb-4">
125
+ There are currently
126
+ <span class="text-error font-weight-bold">
127
+ {{ deletionPreview.members.length }}
128
+ {{ deletionPreview.members.length === 1 ? "user" : "users" }}
129
+ </span>
130
+ assigned to this role. Reassign each user before deleting it so
131
+ they do not lose access.
132
+ </p>
133
+
134
+ <v-row
135
+ v-for="member in deletionPreview.members"
136
+ :key="member._id"
137
+ no-gutters
138
+ class="mb-3 pa-3 border rounded-lg"
139
+ align="center"
140
+ >
141
+ <v-col cols="12" sm="5" class="text-body-2 mb-2 mb-sm-0">
142
+ {{ member.name || member.user }}
143
+ </v-col>
144
+ <v-col cols="12" sm="7">
145
+ <v-select
146
+ v-model="memberReassignments[member._id]"
147
+ :items="deletionPreview.replacementRoles"
148
+ item-title="name"
149
+ item-value="_id"
150
+ label="Reassign role"
151
+ density="comfortable"
152
+ hide-details
153
+ />
154
+ </v-col>
155
+ </v-row>
156
+ </template>
157
+ </v-card-text>
158
+
159
+ <v-toolbar class="pa-0" density="compact">
160
+ <v-row no-gutters>
161
+ <v-col cols="6" class="pa-0">
162
+ <v-btn
163
+ block
164
+ variant="text"
165
+ class="text-none"
166
+ size="large"
167
+ height="48"
168
+ @click="closeDeleteDialog"
169
+ :disabled="deleteLoading"
170
+ >
171
+ Cancel
172
+ </v-btn>
173
+ </v-col>
174
+ <v-col cols="6" class="pa-0">
175
+ <v-btn
176
+ block
177
+ variant="flat"
178
+ color="black"
179
+ class="text-none font-weight-bold rounded-0"
180
+ height="48"
181
+ @click="handleDeleteRole"
182
+ :loading="deleteLoading"
183
+ :disabled="hasAssignedMembers && !canConfirmDeletion"
184
+ >
185
+ {{ hasAssignedMembers ? "Confirm" : "Delete" }}
186
+ </v-btn>
187
+ </v-col>
188
+ </v-row>
189
+ </v-toolbar>
190
+ </v-card>
191
+ </v-dialog>
144
192
 
145
193
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
146
- </v-row>
194
+ </div>
147
195
  </template>
148
196
 
149
197
  <script setup lang="ts">
@@ -367,10 +415,49 @@ async function refreshRolesAfterUpdate(savedPermissions: string[]) {
367
415
  const confirmDialog = ref(false);
368
416
  const selectedRoleId = ref<string | null>(null);
369
417
  const deleteLoading = ref(false);
418
+ const deletionPreview = ref({
419
+ members: [] as Array<Record<string, any>>,
420
+ replacementRoles: [] as Array<Record<string, any>>,
421
+ });
422
+ const memberReassignments = ref<Record<string, string>>({});
423
+
424
+ const hasAssignedMembers = computed(
425
+ () => deletionPreview.value.members.length > 0
426
+ );
427
+
428
+ const canConfirmDeletion = computed(() =>
429
+ deletionPreview.value.members.every(
430
+ (member) => Boolean(memberReassignments.value[member._id])
431
+ )
432
+ );
433
+
434
+ const { getDeletionPreview, deleteWithReassignments } = useRole();
370
435
 
371
- function openDeleteDialog(id: string) {
436
+ async function openDeleteDialog(id: string) {
372
437
  selectedRoleId.value = id;
373
- confirmDialog.value = true;
438
+ deletionPreview.value = { members: [], replacementRoles: [] };
439
+ memberReassignments.value = {};
440
+ deleteLoading.value = true;
441
+
442
+ try {
443
+ const res = await getDeletionPreview(id);
444
+ deletionPreview.value = res.data;
445
+ confirmDialog.value = true;
446
+ } catch (error: any) {
447
+ const errorMessage =
448
+ error?.response?._data?.message || "Failed to load role assignments.";
449
+ showMessage(errorMessage, "error");
450
+ selectedRoleId.value = null;
451
+ } finally {
452
+ deleteLoading.value = false;
453
+ }
454
+ }
455
+
456
+ function closeDeleteDialog() {
457
+ confirmDialog.value = false;
458
+ selectedRoleId.value = null;
459
+ deletionPreview.value = { members: [], replacementRoles: [] };
460
+ memberReassignments.value = {};
374
461
  }
375
462
 
376
463
  function showMessage(msg: string, color: string) {
@@ -383,17 +470,23 @@ async function handleDeleteRole() {
383
470
  if (!selectedRoleId.value) return;
384
471
  deleteLoading.value = true;
385
472
  try {
386
- const res = await deleteRole(selectedRoleId.value);
387
-
388
- confirmDialog.value = false;
473
+ const reassignments = deletionPreview.value.members.map((member) => ({
474
+ memberId: member._id,
475
+ roleId: memberReassignments.value[member._id],
476
+ }));
477
+ const res = hasAssignedMembers.value
478
+ ? await deleteWithReassignments(selectedRoleId.value, reassignments)
479
+ : await deleteRole(selectedRoleId.value);
480
+
481
+ closeDeleteDialog();
389
482
  showMessage(res.message, "success");
390
483
  getRoles();
391
484
  } catch (error: any) {
392
- const errorMessage = error?.response?._data?.message;
485
+ const errorMessage =
486
+ error?.response?._data?.message || "Failed to delete role.";
393
487
  showMessage(errorMessage, "error");
394
488
  } finally {
395
489
  deleteLoading.value = false;
396
- selectedRoleId.value = null;
397
490
  }
398
491
  }
399
492
  </script>
@@ -60,7 +60,9 @@ const props = defineProps({
60
60
 
61
61
  const { standardFormatDate } = useUtils();
62
62
 
63
- const { validateVisitorQrCode } = useVisitor();
63
+ const { validateVisitorQrCode, getVisitors } = useVisitor();
64
+
65
+ const { getPassKeysByPageSearch } = usePassKey();
64
66
 
65
67
  const emits = defineEmits([
66
68
  "closeDialog",
@@ -97,8 +99,6 @@ const handleScanVisitorQRCode = async () => {
97
99
  showNotFoundMessage.value = false;
98
100
  // Automatically navigate to the scanned URL if it's a valid link
99
101
  if (isValidUrl(qrCodeMessage)) {
100
- console.log("QR Code value", qrCodeMessage);
101
- console.log("Last _id", qrCodeMessage.split("/").pop());
102
102
  // get the last _id in the url
103
103
  const id = qrCodeMessage.split("/").pop();
104
104
  // check if it's a valid mongodb _id
@@ -108,7 +108,6 @@ const handleScanVisitorQRCode = async () => {
108
108
  props.site as string,
109
109
  id as string
110
110
  );
111
- console.log("validateVisitorQrCode", response);
112
111
  if (response.errorMessage) {
113
112
  showNotFoundMessage.value = true;
114
113
  errorMessage.value =
@@ -122,9 +121,38 @@ const handleScanVisitorQRCode = async () => {
122
121
  emits("openVisitorDataFromScannedQrCodeDialog", response);
123
122
  }
124
123
  } else {
125
- showNotFoundMessage.value = true;
126
- errorMessage.value = "Invalid mongodb _id.";
127
- showMessage(errorMessage.value, "error");
124
+ const result: any = await getPassKeysByPageSearch({
125
+ sites: [props.site as string],
126
+ search: qrCodeMessage,
127
+ });
128
+
129
+ if (result.items?.length < 1) {
130
+ showMessage(`Pass or Key ${qrCodeMessage} not found.`, "error");
131
+ return;
132
+ }
133
+
134
+ const data: any = await getVisitors({
135
+ site: props.site as string,
136
+ passOrKey: {
137
+ keyId: result.items[0]._id,
138
+ type: result.items[0].passType == "pass-key" ? "key" : "pass",
139
+ },
140
+ checkedOut: false,
141
+ });
142
+
143
+ if (data.items?.length < 1) {
144
+ showMessage(
145
+ `There are no unchecked-out visitor transactions for the scanned ${
146
+ result.items[0].passType == "pass-key" ? "Key" : "Pass"
147
+ } ${qrCodeMessage}.`,
148
+ "error"
149
+ );
150
+
151
+ return;
152
+ }
153
+
154
+ closeDialog();
155
+ emits("openVisitorDataFromScannedQrCodeDialog", data.items[0]);
128
156
  }
129
157
 
130
158
  // window.location.href = qrCodeMessage;