@7365admin1/layer-common 4.0.3-staging.232 → 4.0.3-staging.233

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.
@@ -138,10 +138,6 @@
138
138
  </StatusChip>
139
139
  </template>
140
140
 
141
- <template #[`item.mappingStatus`]="{ item }">
142
- <StatusChip :status="item.mappingStatus" />
143
- </template>
144
-
145
141
  <template #[`item.action-table`]="{ item }">
146
142
  <AppButton
147
143
  variant="row"
@@ -307,7 +303,7 @@ const props = defineProps({
307
303
  },
308
304
  });
309
305
 
310
- type AccessTab = "all" | "unmapped" | "mapped" | "visitor" | "administrator";
306
+ type AccessTab = "all" | "visitor" | "administrator";
311
307
  type IdentityCategory = "resident" | "visitor" | "administrator";
312
308
  type AuthorizationStatus = "Authorized" | "Not Authorized" | "Unknown";
313
309
  type AccessRow = {
@@ -374,7 +370,6 @@ type JsPdf = {
374
370
 
375
371
  const {
376
372
  getReaders,
377
- getIdentities,
378
373
  getLogs,
379
374
  getUserImage,
380
375
  getReaderUsers,
@@ -519,7 +514,6 @@ const filteredRows = computed(() => {
519
514
  row.method,
520
515
  row.lastAccessTime,
521
516
  row.status,
522
- row.mappingStatus,
523
517
  ].some((value) => String(value ?? "").toLowerCase().includes(query));
524
518
 
525
519
  const matchesStatus = !status.value || row.status === status.value;
@@ -531,12 +525,10 @@ const filteredRows = computed(() => {
531
525
 
532
526
  /**
533
527
  * The source of truth is the Amico access log, so All is the initial view.
534
- * The other tabs are audit filters; unmapped events never fall into Resident.
528
+ * The other tabs are Amico audit filters.
535
529
  */
536
530
  const tabOptions: Array<{ label: string; value: AccessTab }> = [
537
531
  { label: "All", value: "all" },
538
- { label: "Unmapped", value: "unmapped" },
539
- { label: "Mapped", value: "mapped" },
540
532
  { label: "Visitor", value: "visitor" },
541
533
  { label: "Administrator", value: "administrator" },
542
534
  ];
@@ -553,7 +545,6 @@ const logHeaders = [
553
545
  { title: "Access Method", value: "method", sortable: false },
554
546
  { title: "Access Date and Time", value: "lastAccessTime", sortable: false },
555
547
  { title: "Status", value: "status", sortable: false },
556
- { title: "iService Mapping", value: "mappingStatus", sortable: false },
557
548
  { title: "", value: "action-table", sortable: false },
558
549
  ];
559
550
 
@@ -626,15 +617,7 @@ async function loadDashboard() {
626
617
  resetDashboardData();
627
618
  try {
628
619
  const isVisitorTab = activeTab.value === "visitor";
629
- const [identityResponse, logResponse, hidAccessLogResponse, visitorResponse, readerUserResponse] = await Promise.all([
630
- loadDashboardSource(
631
- getIdentities(selectedReaderId.value, {
632
- page: page.value,
633
- limit: 100,
634
- }),
635
- null,
636
- "identities",
637
- ),
620
+ const [logResponse, hidAccessLogResponse, visitorResponse, readerUserResponse] = await Promise.all([
638
621
  loadDashboardSource(
639
622
  getLogs(selectedReaderId.value, {
640
623
  page: page.value,
@@ -693,16 +676,8 @@ async function loadDashboard() {
693
676
 
694
677
  if (sequence !== loadSequence.value) return;
695
678
 
696
- const loadedIdentities = identityResponse?.items ?? identityResponse?.data?.items ?? identityResponse?.data?.identities ?? [];
697
679
  const liveItems = hidAccessLogResponse?.items ?? hidAccessLogResponse?.data?.items ?? [];
698
680
  const readerUsers = readerUserResponse?.items ?? readerUserResponse?.data?.items ?? [];
699
- const embeddedIdentities = liveItems
700
- .map((item: Record<string, unknown>) => item.identity)
701
- .filter(isUnknownRecord);
702
- const mergedIdentities = mergeReaderUsersWithIdentities(
703
- readerUsers,
704
- [...loadedIdentities, ...embeddedIdentities],
705
- );
706
681
  const loadedVisitors = visitorResponse?.items ?? visitorResponse?.data?.items ?? visitorResponse?.data?.visitors ?? [];
707
682
  visitorRecords.value = Object.fromEntries(
708
683
  loadedVisitors.map((visitor: HidAccessRecord) => [String(visitor._id), visitor]),
@@ -713,8 +688,10 @@ async function loadDashboard() {
713
688
  .map((item: Record<string, unknown>) => toHidNumericId(item.hidUserId ?? item.user_id))
714
689
  .filter((userId: number | undefined): userId is number => Boolean(userId)),
715
690
  );
716
- allIdentities.value = mergedIdentities;
717
- identities.value = mergedIdentities.filter(isMappedIdentity);
691
+ // The reader is the source of HID identities. Do not merge old iService
692
+ // identity metadata here: it can contain a stale photo from another user.
693
+ allIdentities.value = readerUsers;
694
+ identities.value = readerUsers;
718
695
  logs.value = liveItems.length
719
696
  ? []
720
697
  : logResponse?.items ?? logResponse?.data?.items ?? logResponse?.data?.logs ?? [];
@@ -728,7 +705,7 @@ async function loadDashboard() {
728
705
  hidUserId: String(hidUserId),
729
706
  }));
730
707
  await loadUserImages(
731
- [...mergedIdentities, ...accessLogImageCandidates],
708
+ [...readerUsers, ...accessLogImageCandidates],
732
709
  accessLogUserIds,
733
710
  );
734
711
  } finally {
@@ -756,7 +733,6 @@ async function loadUserImages(items: HidAccessRecord[], forceUserIds = new Set<n
756
733
  if (
757
734
  !hidUserId ||
758
735
  key in userImages.value ||
759
- identity?.metadata?.photo ||
760
736
  (!forceUserIds.has(hidUserId) && !hasHidImageHint(identity))
761
737
  ) return;
762
738
 
@@ -784,11 +760,9 @@ function hasHidImageHint(identity: HidAccessRecord) {
784
760
  return Boolean(
785
761
  identity?.metadata?.imageTimestamp ||
786
762
  identity?.metadata?.facialData ||
787
- identity?.metadata?.photo ||
788
763
  identity?.imageTimestamp ||
789
764
  identity?.image_timestamp ||
790
- identity?.facialData ||
791
- identity?.photo,
765
+ identity?.facialData,
792
766
  );
793
767
  }
794
768
 
@@ -802,38 +776,6 @@ async function onTabChange(tab: AccessTab) {
802
776
  loadDashboard();
803
777
  }
804
778
 
805
- function mergeReaderUsersWithIdentities(readerUsers: HidAccessRecord[], identities: HidAccessRecord[]) {
806
- const identityById = new Map<string, HidAccessRecord>();
807
- identities.forEach((identity) => {
808
- const id = toHidNumericId(identity.hidUserId);
809
- if (id) identityById.set(String(id), identity);
810
- });
811
-
812
- const mergedReaderUsers = readerUsers.map((readerUser) => {
813
- const identity = identityById.get(String(toHidNumericId(readerUser.hidUserId)));
814
- if (!identity) return readerUser;
815
-
816
- return {
817
- ...readerUser,
818
- ...identity,
819
- hidUserId: identity.hidUserId || readerUser.hidUserId,
820
- rawHidUserId: readerUser.rawHidUserId,
821
- name: identity.metadata?.name || identity.name || readerUser.name,
822
- registration: identity.registration || readerUser.registration,
823
- cardNo: identity.cardNo || readerUser.cardNo,
824
- metadata: {
825
- ...readerUser.metadata,
826
- ...identity.metadata,
827
- imageTimestamp: identity.metadata?.imageTimestamp || readerUser.metadata?.imageTimestamp,
828
- },
829
- };
830
- });
831
-
832
- const readerUserIds = new Set(readerUsers.map((user) => String(toHidNumericId(user.hidUserId))));
833
- const identitiesOnly = identities.filter((identity) => !readerUserIds.has(String(toHidNumericId(identity.hidUserId))));
834
- return [...mergedReaderUsers, ...identitiesOnly];
835
- }
836
-
837
779
  function resetDashboardData() {
838
780
  selectedHistoryRow.value = undefined;
839
781
  identities.value = [];
@@ -841,6 +783,7 @@ function resetDashboardData() {
841
783
  visitorRecords.value = {};
842
784
  logs.value = [];
843
785
  liveAccessLogs.value = [];
786
+ userImages.value = {};
844
787
  administratorUserIds.value = new Set();
845
788
  serverPages.value = 1;
846
789
  serverPageRange.value = "";
@@ -849,8 +792,6 @@ function resetDashboardData() {
849
792
  function isVisibleForActiveTab(row: AccessRow) {
850
793
  const identity = findIdentityByKey(row.identityKey);
851
794
  if (activeTab.value === "all") return true;
852
- if (activeTab.value === "unmapped") return !isMappedIdentity(identity);
853
- if (activeTab.value === "mapped") return isMappedIdentity(identity);
854
795
  return classifyIdentity(identity, row) === activeTab.value;
855
796
  }
856
797
 
@@ -912,12 +853,7 @@ function getName(identity: HidAccessRecord) {
912
853
  }
913
854
 
914
855
  function getFacialData(identity: HidAccessRecord) {
915
- return identity.metadata?.facialData || identity.metadata?.imageTimestamp || (identity.metadata?.photo ? identity.hidUserId : "N/A");
916
- }
917
-
918
- function getUserImageSrc(identity: HidAccessRecord) {
919
- const hidUserId = toHidNumericId(identity?.hidUserId);
920
- return identity?.metadata?.photo || (hidUserId ? userImages.value[String(hidUserId)] : "") || "";
856
+ return identity.metadata?.facialData || identity.metadata?.imageTimestamp || "N/A";
921
857
  }
922
858
 
923
859
  function getUnit(identity: HidAccessRecord) {
@@ -953,7 +889,7 @@ function getAccessMethod(identity: HidAccessRecord, log?: HidAccessRecord) {
953
889
  if (payload.confidence || payload.user_id || payload.userId || payload.hidUserId) return "Facial";
954
890
  if (payload.password_value || payload.password || isValidIdentifierId(payload.identifier_id)) return "ID and Password";
955
891
  if (identity.metadata?.pinPassword) return "ID and Password";
956
- return identity.metadata?.photo || identity.metadata?.facialData ? "Facial" : "N/A";
892
+ return identity.metadata?.facialData || identity.metadata?.imageTimestamp ? "Facial" : "N/A";
957
893
  }
958
894
 
959
895
  function formatDate(value?: string | number) {
@@ -1177,6 +1113,9 @@ function mapAccessLogToRow(rawLog: HidAccessRecord, event: HidAccessRecord, inde
1177
1113
  const mapped = isMappedIdentity(identity);
1178
1114
  const eventIdentity = event.payload?.identity || event.payload?.identityLookup || {};
1179
1115
  const rawAccessTime = getAccessLogTime(rawLog, event);
1116
+ const logHidUserId = toHidNumericId(
1117
+ rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? identity?.hidUserId,
1118
+ );
1180
1119
  const identityKey = identity ? getIdentityKey(identity) : getLogIdentityKey({ ...event, payload: { ...event.payload, rawAccessLog: rawLog } });
1181
1120
 
1182
1121
  return {
@@ -1189,7 +1128,7 @@ function mapAccessLogToRow(rawLog: HidAccessRecord, event: HidAccessRecord, inde
1189
1128
  name: getRawLogName(rawLog, event, identity),
1190
1129
  hidUserId: formatHidUid(rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? identity?.hidUserId),
1191
1130
  facialData: identity ? getFacialData(identity) : "N/A",
1192
- faceImage: identity ? getUserImageSrc(identity) : "",
1131
+ faceImage: logHidUserId ? userImages.value[String(logHidUserId)] || "" : "",
1193
1132
  unit: identity ? getUnit(identity) : "N/A",
1194
1133
  method: getAccessMethod(identity || {}, { ...event, payload: { ...event.payload, rawAccessLog: rawLog } }),
1195
1134
  lastAccessTime: formatDate(rawAccessTime),
@@ -21,19 +21,26 @@
21
21
  <div class="config-body">
22
22
  <div class="section-label config-body__title">HID Configuration</div>
23
23
 
24
- <div class="online-mode-row">
25
- <div>
26
- <h3>Activate Online Mode</h3>
27
- <p>Enables real-time server communication, synchronizing live credential verification and access data across the network.</p>
24
+ <div class="form-section">
25
+ <div class="section-label">Visitor QR Reader</div>
26
+ <p class="section-description">
27
+ Select the HID reader and portal used when creating a Visitor QR pass.
28
+ </p>
29
+ <div class="reader-grid">
30
+ <AppSelect
31
+ v-model="form.readerId"
32
+ :items="visitorQrReaderOptions"
33
+ placeholder="Select a HID reader"
34
+ :disabled="Boolean(savingSection) || visitorQrReaderOptions.length === 0"
35
+ />
36
+ <AppButton
37
+ icon="mdi-content-save-outline"
38
+ :disabled="Boolean(savingSection) || !form.readerId"
39
+ @click="saveSection('reader')"
40
+ >
41
+ Save Reader
42
+ </AppButton>
28
43
  </div>
29
- <v-switch
30
- v-model="form.onlineMode"
31
- class="app-switch"
32
- hide-details
33
- :loading="savingSection === 'online-mode'"
34
- :disabled="Boolean(savingSection) || !form.readerId"
35
- @update:model-value="saveOnlineMode"
36
- />
37
44
  </div>
38
45
 
39
46
  <v-divider />
@@ -48,7 +55,6 @@
48
55
  v-for="method in identificationMethodOptions"
49
56
  :key="method.key"
50
57
  class="method-control"
51
- :class="{ 'method-control--disabled': !form.onlineMode }"
52
58
  >
53
59
  <span>{{ method.label }}</span>
54
60
  <v-switch
@@ -57,7 +63,7 @@
57
63
  hide-details
58
64
  density="compact"
59
65
  :loading="savingSection === `method-${method.key}`"
60
- :disabled="Boolean(savingSection) || !form.onlineMode"
66
+ :disabled="Boolean(savingSection) || !form.readerId"
61
67
  @update:model-value="saveIdentificationMethod(method.key)"
62
68
  />
63
69
  </div>
@@ -368,7 +374,6 @@ const {
368
374
  getReaders,
369
375
  getReaderConfiguration,
370
376
  setReaderConfiguration,
371
- setReaderOperatingMode,
372
377
  getSitePermissions,
373
378
  getPermissionCandidates,
374
379
  updateSitePermissions,
@@ -436,6 +441,10 @@ const savingPermissions = ref(false);
436
441
  const hasPrinterConfig = computed(() =>
437
442
  Boolean(form.printer.vendorId && form.printer.productId),
438
443
  );
444
+ const visitorQrReaderOptions = computed(() => readers.value.map((reader) => ({
445
+ title: String(reader.name || reader.deviceId || reader._id) + " - " + (reader.portalName || "Portal not configured"),
446
+ value: reader._id,
447
+ })));
439
448
  const identificationMethodOptions: Array<{
440
449
  key: HidIdentificationMethod;
441
450
  label: string;
@@ -537,7 +546,9 @@ async function loadConfig() {
537
546
  const configuredMethods = config.identificationMethods;
538
547
  Object.assign(form, {
539
548
  enabled: Boolean(config.enabled),
540
- onlineMode: Boolean(config.onlineMode),
549
+ // Visitor QR is reader-local. Do not restore a legacy Online Mode value
550
+ // from the site record and accidentally make a scan depend on Monitor.
551
+ onlineMode: false,
541
552
  readerId: String(config.readerId || readers.value[0]?._id || ""),
542
553
  qrFormat: "0",
543
554
  identificationMethods: {
@@ -705,7 +716,6 @@ async function loadReaderConfiguration() {
705
716
 
706
717
  try {
707
718
  const response = await getReaderConfiguration(form.readerId, {
708
- general: ["online"],
709
719
  identifier: [
710
720
  "card_identification_enabled",
711
721
  "pin_identification_enabled",
@@ -713,12 +723,8 @@ async function loadReaderConfiguration() {
713
723
  ],
714
724
  });
715
725
  const configuration = getConfigurationData(response);
716
- const general = isUnknownRecord(configuration.general) ? configuration.general : {};
717
726
  const identifier = isUnknownRecord(configuration.identifier) ? configuration.identifier : {};
718
727
 
719
- if (general.online !== undefined) {
720
- form.onlineMode = isEnabledConfiguration(general.online);
721
- }
722
728
  if (identifier.pin_identification_enabled !== undefined) {
723
729
  const pinEnabled = isEnabledConfiguration(identifier.pin_identification_enabled);
724
730
  form.identificationMethods.pin = pinEnabled;
@@ -739,7 +745,7 @@ function buildPayload() {
739
745
  ...(siteData.value?.metadata || {}),
740
746
  hidQrCodePass: {
741
747
  enabled: form.identificationMethods.qrCode,
742
- onlineMode: form.onlineMode,
748
+ onlineMode: false,
743
749
  readerId: form.readerId,
744
750
  // Visitor QR passes always use Amico alphanumeric qrcodes.
745
751
  qrFormat: "0",
@@ -775,22 +781,6 @@ async function saveSection(section: string) {
775
781
  }
776
782
  }
777
783
 
778
- async function saveOnlineMode() {
779
- if (!props.site || !form.readerId) return;
780
-
781
- savingSection.value = "online-mode";
782
- try {
783
- await setReaderOperatingMode(form.readerId, form.onlineMode ? "online" : "standalone");
784
- await updateSitebyId(props.site, buildPayload());
785
- showMessage("Online mode updated.", "success");
786
- } catch (error: unknown) {
787
- await loadConfig();
788
- showMessage(errorConverter(error), "error");
789
- } finally {
790
- savingSection.value = "";
791
- }
792
- }
793
-
794
784
  async function saveIdentificationMethod(method: HidIdentificationMethod) {
795
785
  if (!props.site) return;
796
786
 
@@ -991,6 +981,13 @@ function showMessage(text: string, color: string) {
991
981
  align-items: end;
992
982
  }
993
983
 
984
+ .reader-grid {
985
+ display: grid;
986
+ grid-template-columns: minmax(260px, 1fr) minmax(150px, 220px);
987
+ gap: 14px;
988
+ align-items: end;
989
+ }
990
+
994
991
  .template-grid {
995
992
  display: grid;
996
993
  grid-template-columns: minmax(220px, 1fr) minmax(220px, 1fr);
@@ -1337,6 +1334,7 @@ function showMessage(text: string, color: string) {
1337
1334
  }
1338
1335
 
1339
1336
  .printer-grid,
1337
+ .reader-grid,
1340
1338
  .template-grid,
1341
1339
  .validity-grid,
1342
1340
  .permission-location,
@@ -1352,6 +1350,7 @@ function showMessage(text: string, color: string) {
1352
1350
  @media (max-width: 700px) {
1353
1351
  .online-mode-row,
1354
1352
  .printer-grid,
1353
+ .reader-grid,
1355
1354
  .template-grid,
1356
1355
  .validity-grid,
1357
1356
  .methods-grid,