@7365admin1/layer-common 4.35.1-staging.385 → 4.37.0

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 (69) hide show
  1. package/CHANGELOG.md +16 -4
  2. package/assets/css/primitives.css +0 -43
  3. package/components/AccessCardAddForm.vue +4 -5
  4. package/components/AccessCardAssignToUnitForm.vue +2 -3
  5. package/components/AccessCardPreviewDialog.vue +3 -4
  6. package/components/AppSelect.vue +3 -77
  7. package/components/CameraForm.vue +6 -20
  8. package/components/DashboardMain.vue +60 -66
  9. package/components/Dialog/UpdateMoreAction.vue +1 -11
  10. package/components/DocumentForm.vue +112 -407
  11. package/components/DocumentManagement.vue +12 -213
  12. package/components/EntryPass/QrTemplatePreview.vue +25 -21
  13. package/components/EntryPassInformation.vue +24 -119
  14. package/components/Facility/BookingSetup.vue +0 -63
  15. package/components/HidAccessLogDashboard.vue +17 -74
  16. package/components/HidIntercomManagement.vue +14 -180
  17. package/components/HidQrCodeConfiguration.vue +195 -17
  18. package/components/HidReaderManagement.vue +0 -398
  19. package/components/HidReaderUserRoster.vue +3 -44
  20. package/components/HidUserEnrollment.vue +155 -845
  21. package/components/InvitationClientForm.vue +1 -19
  22. package/components/Layout/NavigationDrawer.vue +1 -4
  23. package/components/NotificationBell.vue +7 -59
  24. package/components/RolePermissionFormCreate.vue +3 -4
  25. package/components/RolePermissionFormPreviewUpdate.vue +5 -12
  26. package/components/RolePermissionMain.vue +0 -1
  27. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  28. package/components/ServiceProviderMain.vue +30 -178
  29. package/components/VisitorForm.vue +0 -12
  30. package/components/VisitorManagement.vue +10 -285
  31. package/components/VisitorSocketPopUp.vue +1 -2
  32. package/composables/useANPRSettings.ts +15 -45
  33. package/composables/useAccessManagement.ts +0 -5
  34. package/composables/useCleaningPermission.ts +0 -2
  35. package/composables/useCommonPermission.ts +0 -32
  36. package/composables/useDocument.ts +0 -7
  37. package/composables/useFacility.ts +0 -11
  38. package/composables/useHidAmico.ts +0 -159
  39. package/composables/useHidNavigation.ts +1 -25
  40. package/composables/usePeople.ts +0 -19
  41. package/composables/usePropertyPermission.ts +0 -19
  42. package/composables/useSecurityPermission.ts +0 -19
  43. package/composables/useServiceProvider.ts +0 -3
  44. package/composables/useServiceProviderInvitation.ts +3 -101
  45. package/composables/useSiteEntryPassSettings.ts +0 -26
  46. package/composables/useWebUsb.ts +11 -14
  47. package/package.json +2 -2
  48. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
  49. package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
  50. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
  51. package/plugins/API.ts +0 -11
  52. package/types/document.d.ts +0 -14
  53. package/types/facility.d.ts +0 -4
  54. package/types/people.d.ts +1 -28
  55. package/utils/module-routes.ts +1 -8
  56. package/utils/nav-sections.ts +2 -8
  57. package/utils/notification-target.ts +8 -277
  58. package/utils/permission-labels.ts +2 -20
  59. package/components/ClientInvitations.vue +0 -291
  60. package/components/HidAccessPermissions.vue +0 -424
  61. package/components/HidProfileQrCode.vue +0 -332
  62. package/composables/useHidReaderSelection.ts +0 -50
  63. package/composables/useSiteCategory.ts +0 -45
  64. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
  65. package/utils/dashboard-widget-prefs.ts +0 -54
  66. package/utils/hid-permission-assignments.ts +0 -190
  67. package/utils/hid-reader-selection.ts +0 -52
  68. package/utils/occupancy-role.ts +0 -109
  69. package/utils/sp-inviter-row.ts +0 -97
@@ -21,6 +21,33 @@
21
21
  <div class="config-body">
22
22
  <div class="section-label config-body__title">HID Configuration</div>
23
23
 
24
+ <div class="form-section identification-section">
25
+ <div class="section-label">Enable/disable reader identification method</div>
26
+ <p class="section-description">
27
+ Toggles multi-factor access methods, allowing users to authenticate via biometric, physical card, or credential inputs.
28
+ </p>
29
+ <div class="methods-grid">
30
+ <div
31
+ v-for="method in identificationMethodOptions"
32
+ :key="method.key"
33
+ class="method-control"
34
+ >
35
+ <span>{{ method.label }}</span>
36
+ <v-switch
37
+ v-model="form.identificationMethods[method.key]"
38
+ class="app-switch"
39
+ hide-details
40
+ density="compact"
41
+ :loading="savingSection === `method-${method.key}`"
42
+ :disabled="Boolean(savingSection) || !form.readerId"
43
+ @update:model-value="saveIdentificationMethod(method.key)"
44
+ />
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <v-divider />
50
+
24
51
  <div class="form-section">
25
52
  <div class="section-label">Printer Setup</div>
26
53
  <div class="printer-grid">
@@ -197,11 +224,14 @@
197
224
 
198
225
 
199
226
  <script setup lang="ts">
227
+ type HidIdentificationMethod = keyof NonNullable<THidQrCodePassConfig["identificationMethods"]>;
228
+
200
229
  type HidQrConfig = Omit<
201
230
  THidQrCodePassConfig,
202
231
  "identificationMethods" | "onlineMode" | "updatedAt" | "qrGatePermissions"
203
232
  > & {
204
233
  onlineMode: boolean;
234
+ identificationMethods: Record<HidIdentificationMethod, boolean>;
205
235
  qrGatePermissions: {
206
236
  resident: string[];
207
237
  propertyManagement: string[];
@@ -235,7 +265,11 @@ const runtimeConfig = useRuntimeConfig();
235
265
  const isPermissionManager = computed(() => runtimeConfig.public.APP === "security_agency");
236
266
 
237
267
  const { getSiteById, updateSitebyId } = useSiteSettings();
238
- const { getReaders } = useHidAmico();
268
+ const {
269
+ getReaders,
270
+ getReaderConfiguration,
271
+ setReaderConfiguration,
272
+ } = useHidAmico();
239
273
  const {
240
274
  requestDevice: requestPrinterDevice,
241
275
  testConnection: testPrinterConnection,
@@ -250,6 +284,14 @@ const defaultConfig: HidQrConfig = {
250
284
  propertyManagement: [],
251
285
  },
252
286
  qrFormat: "0",
287
+ identificationMethods: {
288
+ facial: true,
289
+ card: true,
290
+ qrCode: false,
291
+ idPassword: true,
292
+ pin: false,
293
+ bluetooth: false,
294
+ },
253
295
  printer: {
254
296
  vendorId: "",
255
297
  productId: "",
@@ -279,6 +321,16 @@ const visitorQrGateOptions = computed(() => readers.value
279
321
  title: String(reader.name || reader.deviceId || reader._id) + " - " + String(reader.portalName || "Portal"),
280
322
  value: reader._id,
281
323
  })));
324
+ const identificationMethodOptions: Array<{
325
+ key: HidIdentificationMethod;
326
+ label: string;
327
+ }> = [
328
+ { key: "facial", label: "Facial" },
329
+ { key: "card", label: "Card" },
330
+ { key: "qrCode", label: "QR Code" },
331
+ { key: "idPassword", label: "ID Password" },
332
+ { key: "pin", label: "PIN" },
333
+ ];
282
334
  const siteName = computed(() => siteData.value?.name || "Site");
283
335
  const siteLabel = computed(() => siteData.value?.code || siteData.value?.block || props.site);
284
336
  const primaryQrGateReaderId = computed(() => (
@@ -319,6 +371,7 @@ async function loadConfig() {
319
371
  const readerList = readerResponse as HidReaderListResponse;
320
372
  readers.value = readerList.items ?? readerList.data?.items ?? readerList.data?.readers ?? [];
321
373
  const config = siteData.value?.metadata?.hidQrCodePass || {};
374
+ const configuredMethods = config.identificationMethods;
322
375
  Object.assign(form, {
323
376
  enabled: Boolean(config.enabled),
324
377
  // Visitor QR is reader-local. Do not restore a legacy Online Mode value
@@ -339,6 +392,14 @@ async function loadConfig() {
339
392
  || "",
340
393
  ),
341
394
  qrFormat: "0",
395
+ identificationMethods: {
396
+ facial: configuredMethods?.facial ?? true,
397
+ card: configuredMethods?.card ?? true,
398
+ qrCode: configuredMethods?.qrCode ?? Boolean(config.enabled),
399
+ idPassword: configuredMethods?.idPassword ?? true,
400
+ pin: configuredMethods?.pin ?? false,
401
+ bluetooth: false,
402
+ },
342
403
  printer: {
343
404
  vendorId: String(config.printer?.vendorId || ""),
344
405
  productId: String(config.printer?.productId || ""),
@@ -349,25 +410,57 @@ async function loadConfig() {
349
410
  },
350
411
  validityMinutes: config.validityMinutes ? Number(config.validityMinutes) : null,
351
412
  });
413
+
414
+ await loadReaderConfiguration();
415
+ }
416
+
417
+ function isUnknownRecord(value: unknown): value is Record<string, unknown> {
418
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
419
+ }
420
+
421
+ function getConfigurationData(response: unknown) {
422
+ if (!isUnknownRecord(response)) return {};
423
+ return isUnknownRecord(response.data) ? response.data : response;
424
+ }
425
+
426
+ function isEnabledConfiguration(value: unknown) {
427
+ return value === true || value === 1 || value === "1";
428
+ }
429
+
430
+ async function loadReaderConfiguration() {
431
+ if (!form.readerId) return;
432
+
433
+ try {
434
+ const response = await getReaderConfiguration(form.readerId, {
435
+ identifier: [
436
+ "card_identification_enabled",
437
+ "pin_identification_enabled",
438
+ "multi_factor_authentication",
439
+ ],
440
+ });
441
+ const configuration = getConfigurationData(response);
442
+ const identifier = isUnknownRecord(configuration.identifier) ? configuration.identifier : {};
443
+
444
+ if (identifier.pin_identification_enabled !== undefined) {
445
+ const pinEnabled = isEnabledConfiguration(identifier.pin_identification_enabled);
446
+ form.identificationMethods.pin = pinEnabled;
447
+ form.identificationMethods.idPassword = !pinEnabled;
448
+ }
449
+ if (identifier.card_identification_enabled !== undefined) {
450
+ form.identificationMethods.card = isEnabledConfiguration(
451
+ identifier.card_identification_enabled,
452
+ );
453
+ }
454
+ } catch (error: unknown) {
455
+ console.warn("Unable to load HID reader configuration:", errorConverter(error));
456
+ }
352
457
  }
353
458
 
354
459
  function buildPayload() {
355
- /*
356
- * `enabled` and `identificationMethods` are passed through from what was
357
- * loaded, never recomputed.
358
- *
359
- * Which credentials a reader accepts now lives on the READER record and is
360
- * set from HID Readers > Identification Methods, because the device is what
361
- * holds them. The toggles that used to derive these two values are gone from
362
- * this screen, so deriving them again from a form that no longer has them
363
- * would erase the stored copy and flip `enabled` every time somebody saved
364
- * the printer settings.
365
- */
366
- const storedQrPass = siteData.value?.metadata?.hidQrCodePass || {};
367
460
  const metadata = {
368
461
  ...(siteData.value?.metadata || {}),
369
462
  hidQrCodePass: {
370
- enabled: Boolean(storedQrPass.enabled),
463
+ enabled: form.identificationMethods.qrCode,
371
464
  onlineMode: false,
372
465
  // Kept only for old background/mobile flows. Gate permissions, not this
373
466
  // compatibility value, decide where a new Visitor QR may be scanned.
@@ -378,9 +471,7 @@ function buildPayload() {
378
471
  },
379
472
  // Visitor QR passes always use Amico alphanumeric qrcodes.
380
473
  qrFormat: "0",
381
- ...(storedQrPass.identificationMethods
382
- ? { identificationMethods: storedQrPass.identificationMethods }
383
- : {}),
474
+ identificationMethods: { ...form.identificationMethods },
384
475
  printer: {
385
476
  vendorId: form.printer.vendorId.trim(),
386
477
  productId: form.printer.productId.trim(),
@@ -416,6 +507,43 @@ async function saveSection(section: string) {
416
507
  }
417
508
  }
418
509
 
510
+ async function saveIdentificationMethod(method: HidIdentificationMethod) {
511
+ if (!props.site) return;
512
+
513
+ form.enabled = form.identificationMethods.qrCode;
514
+
515
+ savingSection.value = `method-${method}`;
516
+ try {
517
+ if (method === "card") {
518
+ await setReaderConfiguration(form.readerId, {
519
+ identifier: {
520
+ card_identification_enabled: form.identificationMethods.card ? "1" : "0",
521
+ },
522
+ });
523
+ } else if (method === "pin" || method === "idPassword") {
524
+ if (method === "pin" && form.identificationMethods.pin) {
525
+ form.identificationMethods.idPassword = false;
526
+ } else if (method === "idPassword" && form.identificationMethods.idPassword) {
527
+ form.identificationMethods.pin = false;
528
+ }
529
+ await setReaderConfiguration(form.readerId, {
530
+ identifier: {
531
+ pin_identification_enabled: form.identificationMethods.pin ? "1" : "0",
532
+ // Mode 1: PIN is an alternative credential, never a second factor.
533
+ multi_factor_authentication: "0",
534
+ },
535
+ });
536
+ }
537
+ await updateSitebyId(props.site, buildPayload());
538
+ showMessage(`${identificationMethodOptions.find((item) => item.key === method)?.label || "Identification method"} updated.`, "success");
539
+ } catch (error: unknown) {
540
+ await loadConfig();
541
+ showMessage(errorConverter(error), "error");
542
+ } finally {
543
+ savingSection.value = "";
544
+ }
545
+ }
546
+
419
547
  async function scanPrinter() {
420
548
  if (!("usb" in navigator)) {
421
549
  showMessage("USB printer scan is not available in this browser.", "error");
@@ -523,6 +651,51 @@ function showMessage(text: string, color: string) {
523
651
  padding: 16px 0;
524
652
  }
525
653
 
654
+ .identification-section {
655
+ padding-bottom: 20px;
656
+ }
657
+
658
+ .methods-grid {
659
+ display: grid;
660
+ grid-template-columns: repeat(6, minmax(120px, 1fr));
661
+ gap: 18px;
662
+ margin-top: 16px;
663
+ }
664
+
665
+ .method-control {
666
+ display: flex;
667
+ align-items: center;
668
+ justify-content: space-between;
669
+ min-width: 0;
670
+ min-height: 42px;
671
+ padding: 0 10px 0 14px;
672
+ border: 1px solid var(--border);
673
+ border-radius: var(--r-inner);
674
+ color: var(--text2);
675
+ font-family: var(--font-sans);
676
+ font-size: 12px;
677
+ font-weight: 700;
678
+ }
679
+
680
+ .method-control span {
681
+ overflow: hidden;
682
+ text-overflow: ellipsis;
683
+ white-space: nowrap;
684
+ }
685
+
686
+ .method-control--disabled {
687
+ background-color: var(--hover);
688
+ color: var(--muted);
689
+ }
690
+
691
+ .method-control :deep(.v-switch) {
692
+ flex: 0 0 auto;
693
+ }
694
+
695
+ .method-control :deep(.v-selection-control) {
696
+ min-height: 32px;
697
+ }
698
+
526
699
  /* The three `.hid-config-switch` rules are gone: `.app-switch` in
527
700
  primitives.css is the handoff's toggle, and it is shared with the other two
528
701
  HID panels that have one. */
@@ -694,6 +867,10 @@ function showMessage(text: string, color: string) {
694
867
  }
695
868
 
696
869
  @media (max-width: 1100px) {
870
+ .methods-grid {
871
+ grid-template-columns: repeat(3, minmax(140px, 1fr));
872
+ }
873
+
697
874
  .printer-grid,
698
875
  .reader-grid,
699
876
  .template-grid,
@@ -714,6 +891,7 @@ function showMessage(text: string, color: string) {
714
891
  .reader-grid,
715
892
  .template-grid,
716
893
  .validity-grid,
894
+ .methods-grid,
717
895
  .permission-location,
718
896
  .permission-row,
719
897
  .permission-actions {