@7365admin1/layer-common 4.90.2-staging.491 → 4.90.3

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 (75) hide show
  1. package/CHANGELOG.md +16 -4
  2. package/assets/css/primitives.css +0 -57
  3. package/assets/css/screens.css +4 -8
  4. package/components/AccessCardDetailsDialog.vue +6 -59
  5. package/components/AccessCardPreviewDialog.vue +53 -371
  6. package/components/AppSelect.vue +4 -143
  7. package/components/BuildingUnitFormEdit.vue +0 -6
  8. package/components/Dialog/UpdateMoreAction.vue +1 -11
  9. package/components/DocumentForm.vue +112 -407
  10. package/components/DocumentManagement.vue +12 -213
  11. package/components/Facility/BookingSetup.vue +0 -63
  12. package/components/HidAccessLogDashboard.vue +17 -74
  13. package/components/HidIntercomManagement.vue +14 -183
  14. package/components/HidQrCodeConfiguration.vue +195 -17
  15. package/components/HidReaderManagement.vue +0 -398
  16. package/components/HidReaderUserRoster.vue +3 -44
  17. package/components/HidUserEnrollment.vue +292 -2255
  18. package/components/InventoryItemDetail.vue +1 -2
  19. package/components/InventoryLinesDialog.vue +13 -91
  20. package/components/InventoryPhotoInput.vue +6 -44
  21. package/components/InventoryReportsTab.vue +3 -23
  22. package/components/InventoryRequestsTab.vue +4 -27
  23. package/components/InventoryStockTab.vue +0 -3
  24. package/components/InvitationClientForm.vue +1 -19
  25. package/components/InvitationForm.vue +4 -31
  26. package/components/InvitationMain.vue +22 -124
  27. package/components/SiteSettings.vue +0 -48
  28. package/components/TableMain.vue +7 -22
  29. package/components/VehicleManagement.vue +5 -80
  30. package/components/VisitorForm.vue +0 -12
  31. package/components/VisitorManagement.vue +5 -267
  32. package/composables/useAccessManagement.ts +1 -17
  33. package/composables/useDocument.ts +0 -7
  34. package/composables/useFacility.ts +0 -11
  35. package/composables/useHidAmico.ts +0 -165
  36. package/composables/useHidNavigation.ts +1 -25
  37. package/composables/useInventory.ts +1 -4
  38. package/composables/useMember.ts +0 -12
  39. package/composables/useNFCPatrolReportFilters.ts +3 -21
  40. package/composables/useNFCPatrolRoute.ts +1 -8
  41. package/composables/usePeople.ts +0 -19
  42. package/composables/useServiceProvider.ts +2 -45
  43. package/composables/useSettingsPermission.ts +0 -14
  44. package/composables/useUser.ts +2 -6
  45. package/composables/useVehicle.ts +1 -40
  46. package/composables/useVerification.ts +0 -3
  47. package/middleware/01.auth.ts +7 -3
  48. package/package.json +1 -1
  49. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
  50. package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
  51. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
  52. package/types/document.d.ts +0 -14
  53. package/types/facility.d.ts +0 -4
  54. package/types/inventory.d.ts +0 -27
  55. package/types/member.d.ts +0 -4
  56. package/types/people.d.ts +1 -28
  57. package/types/service-provider.d.ts +0 -5
  58. package/types/site.d.ts +1 -7
  59. package/components/BuildingUnitManagement.vue +0 -298
  60. package/components/HidAccessPermissions.vue +0 -424
  61. package/components/HidProfileQrCode.vue +0 -332
  62. package/components/InventoryPoDialog.vue +0 -111
  63. package/components/VehicleQrStickerDialog.vue +0 -249
  64. package/composables/useHidReaderSelection.ts +0 -50
  65. package/composables/useSiteCategory.ts +0 -45
  66. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
  67. package/utils/hid-enrolment-subject.ts +0 -344
  68. package/utils/hid-permission-assignments.ts +0 -198
  69. package/utils/hid-reader-selection.ts +0 -52
  70. package/utils/inventory-receiving.ts +0 -90
  71. package/utils/invite-group.ts +0 -61
  72. package/utils/invite-sites.ts +0 -25
  73. package/utils/occupancy-role.ts +0 -109
  74. package/utils/service-type.ts +0 -30
  75. package/utils/vehicle-qr-sticker.ts +0 -184
@@ -61,37 +61,19 @@
61
61
  <template #item.createdAt="{ item }">
62
62
  {{ item.createdAt ? formatDate(item.createdAt) : "" }}
63
63
  </template>
64
- <template #item.siteLabel="{ item }">
65
- <a
66
- v-if="siteCountOf(item) > 1"
67
- href="#"
68
- class="text-primary"
69
- :title="item.sites?.map((s) => s.siteName).join(', ')"
70
- @click.prevent="detailItem = item"
71
- >
72
- {{ siteLabel(item) }}
73
- </a>
74
- <span v-else>{{ siteLabel(item) }}</span>
75
- </template>
76
64
  <template #item.action-table="{ item }">
77
65
  <v-menu
78
- :close-on-content-click="true"
66
+ :close-on-content-click="false"
79
67
  offset-y
80
- width="170"
81
- v-if="item.status === 'pending' && (props.cancelInvitation || props.inviteMember)"
68
+ width="150"
69
+ v-if="props.cancelInvitation"
82
70
  >
83
71
  <template v-slot:activator="{ props }">
84
72
  <v-icon v-bind="props">mdi-dots-vertical</v-icon>
85
73
  </template>
86
74
  <v-list>
87
- <v-list-item v-if="props.inviteMember" @click="onResend(item)">
88
- Resend
89
- </v-list-item>
90
- <v-list-item
91
- v-if="props.cancelInvitation"
92
- @click="openConfirmDialog(groupIds(item))"
93
- >
94
- {{ siteCountOf(item) > 1 ? "Cancel all" : "Cancel Invite" }}
75
+ <v-list-item @click="openConfirmDialog(item._id ?? '')" v-if="item.status === 'pending'">
76
+ Cancel Invite
95
77
  </v-list-item>
96
78
  </v-list>
97
79
  </v-menu>
@@ -103,34 +85,6 @@
103
85
  </AppCard>
104
86
  </v-col>
105
87
 
106
- <v-dialog :model-value="!!detailItem" max-width="520" @update:model-value="detailItem = null">
107
- <v-card v-if="detailItem" class="pa-4">
108
- <div class="text-h6 mb-1">{{ siteLabel(detailItem) }}</div>
109
- <div class="text-caption mb-3">
110
- {{ detailItem.email }} - one invitation e-mail; accepting it adds every site below.
111
- </div>
112
- <v-list density="compact" style="max-height: 50vh; overflow-y: auto">
113
- <v-list-item v-for="s in detailItem.sites" :key="s._id" :title="s.siteName || 'Site'">
114
- <template #append>
115
- <span class="text-caption me-3">{{ formatStatus(s.status || "") }}</span>
116
- <v-btn
117
- v-if="s.status === 'pending' && props.cancelInvitation"
118
- size="small"
119
- variant="text"
120
- color="error"
121
- @click="openConfirmDialog([s._id])"
122
- >
123
- Cancel
124
- </v-btn>
125
- </template>
126
- </v-list-item>
127
- </v-list>
128
- <div class="d-flex justify-end mt-3">
129
- <v-btn variant="text" @click="detailItem = null">Close</v-btn>
130
- </div>
131
- </v-card>
132
- </v-dialog>
133
-
134
88
  <ConfirmDialog
135
89
  v-model="confirmDialog"
136
90
  :loading="cancelLoading"
@@ -141,12 +95,8 @@
141
95
  </template>
142
96
  <template #description>
143
97
  <p class="text-subtitle-2">
144
- {{
145
- selectedInviteIds.length > 1
146
- ? `Are you sure you want to cancel all ${selectedInviteIds.length} site invitations?`
147
- : "Are you sure you want to cancel this invitation?"
148
- }}
149
- This action cannot be undone.
98
+ Are you sure you want to cancel this invitation? This action cannot be
99
+ undone.
150
100
  </p>
151
101
  </template>
152
102
  <template #footer>
@@ -190,13 +140,6 @@
190
140
  <script setup lang="ts">
191
141
  // Relative import: this layer's `utils/` is not auto-imported into consumers.
192
142
  import { invitationsEmptyText } from "../utils/invitations-empty-text";
193
- import {
194
- groupIds,
195
- resendArgs,
196
- siteCountOf,
197
- siteLabel,
198
- } from "../utils/invite-group";
199
- import useUser from "../composables/useUser";
200
143
 
201
144
  /** The same two tabs, same labels, same routes. */
202
145
  const invitationTabs = [
@@ -262,8 +205,7 @@ const headers = computed(() => {
262
205
  },
263
206
  {
264
207
  title: "Site Invited To",
265
- value: "siteLabel",
266
- sortable: false,
208
+ value: "metadata.siteName",
267
209
  },
268
210
  {
269
211
  title: "Status",
@@ -272,7 +214,7 @@ const headers = computed(() => {
272
214
 
273
215
  ];
274
216
 
275
- if (props.status === "pending" && (props.cancelInvitation || props.inviteMember)) {
217
+ if (props.status === "pending" && props.cancelInvitation) {
276
218
  baseHeaders.push({
277
219
  title: "Action",
278
220
  value: "action-table",
@@ -284,7 +226,6 @@ const headers = computed(() => {
284
226
 
285
227
  const { getVerifications: _getVerifications, cancelUserInvitation } =
286
228
  useVerification();
287
- const { inviteMemberClient } = useUser();
288
229
 
289
230
  const page = ref(1);
290
231
  const pages = ref(0);
@@ -314,31 +255,20 @@ const {
314
255
  pageRange: string;
315
256
  }>(
316
257
  `get-verifications-${props.app}-${props.org}-${props.siteId}-${props.status}`,
317
- async () => {
318
- const query = {
258
+ () =>
259
+ _getVerifications({
319
260
  page: page.value,
320
261
  status: props.status,
321
262
  search: headerSearch.value,
322
263
  // type: "user-invite,member-invite",
323
264
  type: "member-invite",
324
- // The org console (organization app, no site) lists every invitation of
325
- // the organisation, including the site invitations sent from there
326
- // (owner request 2026-10-02); the server's list scoping is unchanged.
327
- app: props.app === "organization" && !props.siteId ? "" : props.app,
265
+ app: props.app,
328
266
  orgId: props.org,
329
267
  // A site app lists only THIS site's invitations; the server checks the
330
268
  // caller reaches it (owner report 2026-09-28: LakeGarden's invitations
331
269
  // showed on every Wing Tai site). The org console passes no site.
332
270
  siteId: props.siteId,
333
- };
334
- // One row per multi-site invitation. An API without `group` refuses the
335
- // key: fall back to the plain list (one row per site, as before).
336
- try {
337
- return await _getVerifications({ ...query, group: true });
338
- } catch {
339
- return await _getVerifications(query);
340
- }
341
- },
271
+ }),
342
272
  {
343
273
  watch: [() => props.status]
344
274
  }
@@ -376,63 +306,31 @@ watch([headerSearch, () => props.status], () => {
376
306
  });
377
307
 
378
308
  const confirmDialog = ref(false);
379
- const selectedInviteIds = ref<string[]>([]);
309
+ const selectedInviteId = ref<string | null>(null);
380
310
  const cancelLoading = ref(false);
381
- const detailItem = ref<any>(null);
382
- const resending = ref(false);
383
311
 
384
- function openConfirmDialog(ids: string[]) {
385
- selectedInviteIds.value = ids;
312
+ function openConfirmDialog(id: string) {
313
+ selectedInviteId.value = id;
386
314
  confirmDialog.value = true;
387
315
  }
388
316
 
389
317
  async function onConfirmCancel() {
390
- if (!selectedInviteIds.value.length) return;
318
+ if (!selectedInviteId.value) return;
391
319
 
392
320
  cancelLoading.value = true;
393
321
  try {
394
- let res: Record<string, any> = {};
395
- for (const id of selectedInviteIds.value) {
396
- res = await cancelUserInvitation(id);
397
- }
398
-
399
- showMessage(
400
- selectedInviteIds.value.length > 1
401
- ? "All site invitations cancelled"
402
- : res.message || "Invitation cancelled successfully",
403
- "success",
404
- );
405
- detailItem.value = null;
322
+ const res = await cancelUserInvitation(selectedInviteId.value);
323
+
324
+ showMessage(res.message || "Invitation cancelled successfully", "success");
406
325
  await getVerifications();
407
326
  confirmDialog.value = false;
408
- selectedInviteIds.value = [];
327
+ selectedInviteId.value = null;
409
328
  } catch (error: any) {
410
- showMessage(error?.data?.message || error?.message || "Failed to cancel invitation", "error");
411
- await getVerifications();
329
+ showMessage(error?.message || "Failed to cancel invitation", "error");
412
330
  }
413
331
  cancelLoading.value = false;
414
332
  }
415
333
 
416
- // ONE request = ONE e-mail for the whole group (the server re-uses the
417
- // pending rows and names every site).
418
- async function onResend(item: any) {
419
- const args = resendArgs(item);
420
- if (!args) {
421
- showMessage("This invitation cannot be re-sent from here.", "error");
422
- return;
423
- }
424
- if (resending.value) return;
425
- resending.value = true;
426
- try {
427
- await inviteMemberClient(args);
428
- showMessage("Invitation re-sent (one e-mail).", "success");
429
- await getVerifications();
430
- } catch (error: any) {
431
- showMessage(error?.data?.message || error?.message || "Failed to resend invitation", "error");
432
- }
433
- resending.value = false;
434
- }
435
-
436
334
  function handleSuccess(closeForm: boolean) {
437
335
  getVerifications();
438
336
  if (closeForm) {
@@ -199,22 +199,6 @@
199
199
  </v-expansion-panel-text>
200
200
  </v-expansion-panel>
201
201
 
202
- <v-expansion-panel v-if="canManageVehicleQr">
203
- <v-expansion-panel-title>
204
- <v-icon class="mr-2">mdi-qrcode</v-icon>
205
- Vehicle QR stickers
206
- </v-expansion-panel-title>
207
- <v-expansion-panel-text>
208
- <v-switch v-model="vehicleQrEnabled" :loading="vehicleQrSaving" :disabled="vehicleQrSaving"
209
- label="Vehicle QR stickers" color="primary" hide-details data-test="vehicle-qr-switch"
210
- @update:model-value="saveVehicleQr" />
211
- <p class="text-caption text-medium-emphasis mt-2 mb-0">
212
- When on, staff can print a QR sticker for each vehicle from Vehicle Mgmt, and guards can scan it to see
213
- which unit the vehicle belongs to.
214
- </p>
215
- </v-expansion-panel-text>
216
- </v-expansion-panel>
217
-
218
202
  <slot
219
203
  name="panels"
220
204
  :can-manage="canManageSiteSettings"
@@ -231,7 +215,6 @@ import useFile from "../composables/useFile";
231
215
  import useSiteSettings from "../composables/useSiteSettings";
232
216
  import useUtils from "../composables/useUtils";
233
217
  import useANPRSettings from "../composables/useANPRSettings";
234
- import useVehicle from "../composables/useVehicle";
235
218
 
236
219
  const props = defineProps({
237
220
  siteId: { type: String, required: true },
@@ -250,7 +233,6 @@ const { canManageSiteInformation,
250
233
  canManageCCTVCamera,
251
234
  canManageDeliveryCompanies,
252
235
  canManageResidentVisitors,
253
- canManageVehicleQr,
254
236
  } = useSettingsPermission();
255
237
 
256
238
  const { getSiteById, updateSitebyId, updateSiteInformation } =
@@ -423,36 +405,6 @@ function handleUpdateCompanies(value: string[]) {
423
405
 
424
406
  const route = useRoute();
425
407
 
426
- // Vehicle QR stickers switch (off until someone turns it on).
427
- const { getVehicleQrSettings, setVehicleQrSettings } = useVehicle();
428
- const vehicleQrEnabled = ref(false);
429
- const vehicleQrSaving = ref(false);
430
-
431
- await useLazyAsyncData(`vehicle-qr-${props.siteId}`, async () => {
432
- if (!canManageVehicleQr.value) return null;
433
- try {
434
- vehicleQrEnabled.value = !!(await getVehicleQrSettings(props.siteId))?.enabled;
435
- } catch {
436
- vehicleQrEnabled.value = false;
437
- }
438
- return null;
439
- });
440
-
441
- async function saveVehicleQr(value: boolean | null) {
442
- const enabled = !!value;
443
- vehicleQrSaving.value = true;
444
- try {
445
- const res = await setVehicleQrSettings(props.siteId, enabled);
446
- vehicleQrEnabled.value = !!res?.enabled;
447
- show(enabled ? "Vehicle QR stickers turned on" : "Vehicle QR stickers turned off", "success");
448
- } catch (e: any) {
449
- vehicleQrEnabled.value = !enabled;
450
- show(e?.data?.message || "Could not change the Vehicle QR stickers setting", "error");
451
- } finally {
452
- vehicleQrSaving.value = false;
453
- }
454
- }
455
-
456
408
  function show(message: string, color: string) {
457
409
  toast.show = true;
458
410
  toast.message = message;
@@ -59,32 +59,17 @@
59
59
  >
60
60
  {{ createLabel }}
61
61
  </v-btn>
62
- <!--
63
- The design's search field. This was a `v-text-field` with only a
64
- `max-width`, so inside the actions row it shrank to its minimum
65
- and showed "Se" and a magnifier, at a different height and
66
- border from the button beside it. Same `search` model; clearing
67
- now sets "" instead of Vuetify's null.
68
- -->
69
- <AppField
62
+ <v-text-field
70
63
  v-if="canSearch"
71
64
  v-model="searchInput"
72
- search
65
+ density="compact"
73
66
  placeholder="Search"
74
- aria-label="Search"
67
+ clearable
68
+ max-width="300"
69
+ append-inner-icon="mdi-magnify"
70
+ hide-details
75
71
  class="table-card__search"
76
- >
77
- <template v-if="searchInput" #trailing>
78
- <button
79
- type="button"
80
- class="app-field__trailing"
81
- aria-label="Clear search"
82
- @click="searchInput = ''"
83
- >
84
- <v-icon icon="mdi-close" size="16" />
85
- </button>
86
- </template>
87
- </AppField>
72
+ />
88
73
  <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
89
74
  <v-btn
90
75
  v-if="canScanVisitorQRCode"
@@ -8,9 +8,6 @@
8
8
  <v-col cols="12">
9
9
  <PageHeader title="Vehicle Mgmt">
10
10
  <template #actions>
11
- <AppButton v-if="qrStickersOn" icon="mdi-qrcode" variant="ghost" @click="openStickerDialog()">
12
- Print QR stickers
13
- </AppButton>
14
11
  <AppButton v-if="canCreateVehicle" icon="mdi-plus" @click="handleAddVehicleClick">
15
12
  Add Vehicle
16
13
  </AppButton>
@@ -85,19 +82,17 @@
85
82
  be drawn.
86
83
  -->
87
84
  <template #item.action="{ item }">
88
- <v-menu v-if="rowHasVehicleActions(item, vehiclePermissions) || qrStickersOn" location="bottom end">
85
+ <v-menu v-if="rowHasVehicleActions(item, vehiclePermissions)" location="bottom end">
89
86
  <template #activator="{ props: menuProps }">
90
87
  <AppButton v-bind="menuProps" variant="row" icon="mdi-dots-vertical" aria-label="Vehicle actions"
91
88
  @click.stop />
92
89
  </template>
93
90
  <v-list density="compact">
94
91
  <template v-for="plate in rowPlates(item)" :key="plate._id ?? plate.plateNumber">
95
- <template v-if="vehiclePlateActions(plate, vehiclePermissions).length || qrStickersOn">
92
+ <template v-if="vehiclePlateActions(plate, vehiclePermissions).length">
96
93
  <v-list-subheader v-if="rowPlates(item).length > 1">
97
94
  {{ plate.plateNumber }}
98
95
  </v-list-subheader>
99
- <v-list-item v-if="qrStickersOn" title="Re-issue sticker" prepend-icon="mdi-qrcode-edit"
100
- @click="askReissue(plate as TPlateNumber)" />
101
96
  <v-list-item v-for="action in vehiclePlateActions(plate, vehiclePermissions)" :key="action.key"
102
97
  :title="action.title" :prepend-icon="action.icon" :base-color="action.color"
103
98
  @click="runVehicleAction(action.key, plate as TPlateNumber)" />
@@ -217,13 +212,11 @@
217
212
  v-tooltip:top="'History'" @click="openHistory(plateNumberItem as TPlateNumber)" />
218
213
  </template>
219
214
  <template #item.action="{ item: plateNumberItem }">
220
- <v-menu v-if="vehiclePlateActions(plateNumberItem, vehiclePermissions).length || qrStickersOn">
215
+ <v-menu v-if="vehiclePlateActions(plateNumberItem, vehiclePermissions).length">
221
216
  <template #activator="{ props: menuProps }">
222
217
  <v-btn icon="mdi-dots-vertical" v-bind="menuProps" flat size="x-small" />
223
218
  </template>
224
219
  <v-list density="compact">
225
- <v-list-item v-if="qrStickersOn" title="Re-issue sticker" prepend-icon="mdi-qrcode-edit"
226
- @click="askReissue(plateNumberItem as TPlateNumber)" />
227
220
  <v-list-item
228
221
  v-for="action in vehiclePlateActions(plateNumberItem, vehiclePermissions)"
229
222
  :key="action.key" :title="action.title" :prepend-icon="action.icon"
@@ -348,26 +341,6 @@
348
341
  </v-card-text>
349
342
  </v-card>
350
343
  </v-dialog>
351
- <VehicleQrStickerDialog v-if="qrStickersOn" v-model="dialog.qrStickers" :site="props.site" :rows="items"
352
- :view="activeTab" :search="searchInput" :vehicle-ids="qrVehicleIds" />
353
-
354
- <v-dialog v-model="dialog.qrReissue" width="460">
355
- <v-card>
356
- <v-card-title>Re-issue sticker?</v-card-title>
357
- <v-card-text>
358
- A new QR sticker will be made for {{ qrReissuePlate?.plateNumber }}. The old sticker will stop working, so
359
- the new one must be pasted on the car.
360
- <v-alert v-if="qrReissueError" type="error" variant="tonal" density="compact" class="mt-3">{{ qrReissueError }}</v-alert>
361
- </v-card-text>
362
- <v-card-actions>
363
- <v-spacer />
364
- <v-btn variant="text" class="text-none" @click="dialog.qrReissue = false">Cancel</v-btn>
365
- <v-btn color="primary" variant="flat" class="text-none" :loading="qrReissuing" @click="confirmReissue">
366
- Re-issue
367
- </v-btn>
368
- </v-card-actions>
369
- </v-card>
370
- </v-dialog>
371
344
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
372
345
  </v-row>
373
346
  </template>
@@ -487,53 +460,7 @@ const activeTab = ref<TVehicleTab>("residents");
487
460
 
488
461
 
489
462
  const { formatCamelCaseToWords, formatDate, debounce, maskNRIC } = useUtils();
490
- const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, blockVehicle, unblockVehicle, getSpecificVehicleById, getActiveCameraCounts, getVehicleQrSettings, regenerateVehicleQr } = useVehicle();
491
-
492
- // Vehicle QR stickers: the site's switch (Settings) decides whether the print
493
- // and re-issue actions exist; only people who may update vehicles get them.
494
- const qrEnabled = ref(false);
495
- const qrStickersOn = computed(() => qrEnabled.value && props.canUpdateVehicle);
496
- const qrVehicleIds = ref<string[]>([]);
497
- const qrReissuePlate = ref<TPlateNumber | null>(null);
498
- const qrReissuing = ref(false);
499
- const qrReissueError = ref("");
500
-
501
- async function loadQrSetting() {
502
- try {
503
- qrEnabled.value = !!(await getVehicleQrSettings(props.site))?.enabled;
504
- } catch {
505
- qrEnabled.value = false;
506
- }
507
- }
508
- onMounted(loadQrSetting);
509
-
510
- function openStickerDialog(ids: string[] = []) {
511
- qrVehicleIds.value = ids;
512
- dialog.qrStickers = true;
513
- }
514
-
515
- function askReissue(plate: TPlateNumber) {
516
- qrReissuePlate.value = plate || null;
517
- qrReissueError.value = "";
518
- dialog.qrReissue = true;
519
- }
520
-
521
- async function confirmReissue() {
522
- const id = qrReissuePlate.value?._id;
523
- if (!id) return;
524
- qrReissuing.value = true;
525
- qrReissueError.value = "";
526
- try {
527
- await regenerateVehicleQr(String(id));
528
- dialog.qrReissue = false;
529
- dialog.showMoreActions = false;
530
- openStickerDialog([String(id)]);
531
- } catch (e: any) {
532
- qrReissueError.value = errorConverter(e);
533
- } finally {
534
- qrReissuing.value = false;
535
- }
536
- }
463
+ const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, blockVehicle, unblockVehicle, getSpecificVehicleById, getActiveCameraCounts } = useVehicle();
537
464
 
538
465
  /**
539
466
  * Active ANPR cameras per list at the site. A live plate on fewer cameras
@@ -616,9 +543,7 @@ const dialog = reactive({
616
543
  blockVehicle: false,
617
544
  unblockVehicle: false,
618
545
  history: false,
619
- bulkVehicle: false,
620
- qrStickers: false,
621
- qrReissue: false,
546
+ bulkVehicle: false
622
547
  });
623
548
 
624
549
 
@@ -943,20 +943,8 @@ const hidQrReaderId = computed(() => (
943
943
  hidQrConfiguredReaderId.value || String(hidQrReader.value?._id || "")
944
944
  ));
945
945
 
946
- /*
947
- * Visitor QR passes are hidden at the manager's request (2026-09-23).
948
- *
949
- * A flag rather than a deletion: issuing, the preview dialog, printing and the
950
- * regenerate handler below are all intact and untouched, so bringing the
951
- * feature back is this one line. Every surface that offers a Visitor QR reads
952
- * `hidQrCodePassAllowed`, which is why the switch belongs here and not in each
953
- * of them.
954
- */
955
- const HID_VISITOR_QR_ENABLED = false;
956
-
957
946
  const hidQrCodePassAllowed = computed(() => {
958
947
  return (
959
- HID_VISITOR_QR_ENABLED &&
960
948
  hidQrCodePassEnabled.value &&
961
949
  ["guest", "contractor", "walk-in"].includes(prop.type)
962
950
  );