@7365admin1/layer-common 3.2.8-staging.205 → 3.2.8-staging.207
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.
- package/components/HidAccessLogDashboard.vue +176 -246
- package/components/HidIntercomManagement.vue +51 -18
- package/components/HidQrCodeConfiguration.vue +2 -3
- package/components/HidReaderForm.vue +176 -10
- package/components/HidReaderManagement.vue +211 -96
- package/components/HidServiceSettingsPanel.vue +21 -6
- package/components/HidUserEnrollment.vue +371 -198
- package/components/Layout/NavigationDrawer.vue +19 -2
- package/composables/useHidAmico.ts +113 -28
- package/composables/useHidNavigation.ts +4 -2
- package/composables/useOptionalServices.ts +26 -16
- package/package.json +1 -1
|
@@ -62,13 +62,13 @@
|
|
|
62
62
|
v-model="selectedReaderId"
|
|
63
63
|
class="reader-select"
|
|
64
64
|
:items="readerOptions"
|
|
65
|
-
@update:model-value="
|
|
65
|
+
@update:model-value="reloadUsersFromFirstPage"
|
|
66
66
|
/>
|
|
67
67
|
<AppField
|
|
68
68
|
v-model="search"
|
|
69
69
|
search
|
|
70
70
|
placeholder="Search"
|
|
71
|
-
@keyup.enter="
|
|
71
|
+
@keyup.enter="reloadUsersFromFirstPage"
|
|
72
72
|
/>
|
|
73
73
|
<AppSelect
|
|
74
74
|
v-if="!props.cardManagement"
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
:items="statusOptions"
|
|
77
77
|
placeholder="Status"
|
|
78
78
|
clearable
|
|
79
|
-
@update:model-value="
|
|
79
|
+
@update:model-value="reloadUsersFromFirstPage"
|
|
80
80
|
/>
|
|
81
81
|
</div>
|
|
82
82
|
</template>
|
|
@@ -239,6 +239,24 @@
|
|
|
239
239
|
klass="mb-3"
|
|
240
240
|
/>
|
|
241
241
|
|
|
242
|
+
<div class="field-label">Link To <span>*</span></div>
|
|
243
|
+
<AppSelect
|
|
244
|
+
v-model="form.subjectCategory"
|
|
245
|
+
:items="subjectCategoryOptions"
|
|
246
|
+
placeholder="Select person type"
|
|
247
|
+
klass="mb-3"
|
|
248
|
+
@update:model-value="onSubjectCategoryChanged"
|
|
249
|
+
/>
|
|
250
|
+
|
|
251
|
+
<div class="field-label">Resident / Staff / Provider <span>*</span></div>
|
|
252
|
+
<AppSelect
|
|
253
|
+
v-model="form.subjectId"
|
|
254
|
+
:items="subjectOptions"
|
|
255
|
+
:loading="loadingSubjects"
|
|
256
|
+
placeholder="Select an existing site record"
|
|
257
|
+
klass="mb-3"
|
|
258
|
+
/>
|
|
259
|
+
|
|
242
260
|
<div class="field-label">ID</div>
|
|
243
261
|
<AppField v-model="form.hidUserId" aria-label="ID" readonly klass="mb-3" />
|
|
244
262
|
|
|
@@ -400,15 +418,30 @@
|
|
|
400
418
|
No physical HID card is assigned to this user.
|
|
401
419
|
</v-alert>
|
|
402
420
|
|
|
403
|
-
<div class="field-label">
|
|
404
|
-
<
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
<div class="field-label">Reader card data mode</div>
|
|
422
|
+
<v-alert
|
|
423
|
+
:type="cardProfileConfigured ? 'info' : 'warning'"
|
|
424
|
+
variant="tonal"
|
|
425
|
+
density="compact"
|
|
407
426
|
class="mb-3"
|
|
408
|
-
|
|
427
|
+
>
|
|
428
|
+
{{ cardProfileLabel }}
|
|
429
|
+
</v-alert>
|
|
409
430
|
<p class="field-hint hid-card-type-hint">
|
|
410
431
|
PACS uses Facility Code + Card Number. CSN uses the raw serial number read from the physical card.
|
|
411
432
|
</p>
|
|
433
|
+
<template v-if="hybridCardProfile">
|
|
434
|
+
<div class="field-label mt-3">Presented Card Type</div>
|
|
435
|
+
<AppSelect
|
|
436
|
+
v-model="cardForm.cardType"
|
|
437
|
+
:items="hybridCardTypeOptions"
|
|
438
|
+
aria-label="Presented Card Type"
|
|
439
|
+
class="mb-3"
|
|
440
|
+
/>
|
|
441
|
+
<p class="field-hint hid-card-type-hint">
|
|
442
|
+
Choose MIFARE CSN for a MIFARE card. Choose PACS for any other card in this reader mode.
|
|
443
|
+
</p>
|
|
444
|
+
</template>
|
|
412
445
|
<p class="field-hint hid-card-access-hint">
|
|
413
446
|
The card uses this HID user's existing portal and access rules; assigning a card does not grant new access.
|
|
414
447
|
</p>
|
|
@@ -453,14 +486,14 @@
|
|
|
453
486
|
<div class="hid-card-actions mt-4">
|
|
454
487
|
<AppButton
|
|
455
488
|
variant="secondary"
|
|
456
|
-
:disabled="savingCard || enrollingCard"
|
|
489
|
+
:disabled="savingCard || enrollingCard || !cardProfileConfigured"
|
|
457
490
|
@click="assignPhysicalCard"
|
|
458
491
|
>
|
|
459
492
|
Assign Manually
|
|
460
493
|
</AppButton>
|
|
461
494
|
<AppButton
|
|
462
495
|
icon="mdi-contactless-payment"
|
|
463
|
-
:disabled="savingCard || enrollingCard"
|
|
496
|
+
:disabled="savingCard || enrollingCard || !cardProfileConfigured"
|
|
464
497
|
@click="enrollPhysicalCard"
|
|
465
498
|
>
|
|
466
499
|
Read at Reader
|
|
@@ -534,6 +567,7 @@ const props = defineProps({
|
|
|
534
567
|
const {
|
|
535
568
|
getReaders,
|
|
536
569
|
getIdentities,
|
|
570
|
+
getReaderUsers,
|
|
537
571
|
getUserImage,
|
|
538
572
|
setUserImage,
|
|
539
573
|
deleteUserImage,
|
|
@@ -549,10 +583,70 @@ const {
|
|
|
549
583
|
createIdentity,
|
|
550
584
|
updateIdentity,
|
|
551
585
|
deleteIdentity,
|
|
586
|
+
getPermissionCandidates,
|
|
552
587
|
} = useHidAmico();
|
|
553
588
|
|
|
554
|
-
|
|
555
|
-
|
|
589
|
+
type HidMetadata = Record<string, unknown> & {
|
|
590
|
+
name?: string;
|
|
591
|
+
block?: string;
|
|
592
|
+
level?: string;
|
|
593
|
+
unit?: string;
|
|
594
|
+
unitLabel?: string;
|
|
595
|
+
photo?: string;
|
|
596
|
+
facialData?: string;
|
|
597
|
+
imageTimestamp?: string;
|
|
598
|
+
lastAccessAt?: string;
|
|
599
|
+
facialScores?: Record<string, unknown>;
|
|
600
|
+
pinEnrolled?: boolean;
|
|
601
|
+
hidPhysicalCards?: HidCard[];
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
type HidReader = Record<string, unknown> & {
|
|
605
|
+
_id: string;
|
|
606
|
+
name?: string;
|
|
607
|
+
deviceId?: string;
|
|
608
|
+
portalName?: string;
|
|
609
|
+
portalId?: string;
|
|
610
|
+
cardReadingMode?: string;
|
|
611
|
+
pacsFormat?: string;
|
|
612
|
+
csnByteOrder?: string;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
type HidUser = Record<string, unknown> & {
|
|
616
|
+
_id?: string;
|
|
617
|
+
_rowKey?: string;
|
|
618
|
+
reader?: string;
|
|
619
|
+
hidUserId?: string | number;
|
|
620
|
+
rawHidUserId?: string | number;
|
|
621
|
+
registration?: string;
|
|
622
|
+
cardNo?: string;
|
|
623
|
+
name?: string;
|
|
624
|
+
metadata?: HidMetadata;
|
|
625
|
+
isAdministrator?: boolean;
|
|
626
|
+
person?: string;
|
|
627
|
+
user?: string;
|
|
628
|
+
member?: string;
|
|
629
|
+
serviceProvider?: string;
|
|
630
|
+
visitor?: string;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
type HidCard = Record<string, unknown> & {
|
|
634
|
+
id?: string | number;
|
|
635
|
+
cardType?: "pacs" | "csn";
|
|
636
|
+
facilityCode?: string | number;
|
|
637
|
+
cardNumber?: string | number;
|
|
638
|
+
value?: string | number;
|
|
639
|
+
source?: "reader" | "manual" | "device";
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
type HidIdentityPayload = Record<string, unknown> & {
|
|
643
|
+
hidUserId: string;
|
|
644
|
+
registration: string;
|
|
645
|
+
metadata: HidMetadata;
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const readers = ref<HidReader[]>([]);
|
|
649
|
+
const users = ref<HidUser[]>([]);
|
|
556
650
|
const userImages = ref<Record<string, string>>({});
|
|
557
651
|
const administratorUserIds = ref<Set<number>>(new Set());
|
|
558
652
|
const selectedReaderId = ref("");
|
|
@@ -570,10 +664,10 @@ const formDialog = ref(false);
|
|
|
570
664
|
const viewDialog = ref(false);
|
|
571
665
|
const deleteDialog = ref(false);
|
|
572
666
|
const cardDialog = ref(false);
|
|
573
|
-
const selectedUser = ref<
|
|
574
|
-
const cardUser = ref<
|
|
575
|
-
const physicalCards = ref<
|
|
576
|
-
const physicalCardsByUserId = ref<Record<string,
|
|
667
|
+
const selectedUser = ref<HidUser | null>(null);
|
|
668
|
+
const cardUser = ref<HidUser | null>(null);
|
|
669
|
+
const physicalCards = ref<HidCard[]>([]);
|
|
670
|
+
const physicalCardsByUserId = ref<Record<string, HidCard[]>>({});
|
|
577
671
|
const loadingCards = ref(false);
|
|
578
672
|
const savingCard = ref(false);
|
|
579
673
|
const enrollingCard = ref(false);
|
|
@@ -603,8 +697,24 @@ const form = reactive({
|
|
|
603
697
|
isAdministrator: false,
|
|
604
698
|
accessPin: "",
|
|
605
699
|
photoPreview: "",
|
|
700
|
+
subjectCategory: "resident" as THidPermissionCategory,
|
|
701
|
+
subjectId: "",
|
|
606
702
|
});
|
|
607
703
|
|
|
704
|
+
const route = useRoute();
|
|
705
|
+
const orgId = computed(() => String(route.params.org || ""));
|
|
706
|
+
const permissionCandidates = ref<THidPermissionCandidate[]>([]);
|
|
707
|
+
const loadingSubjects = ref(false);
|
|
708
|
+
const subjectCategoryOptions = [
|
|
709
|
+
{ title: "Resident", value: "resident" },
|
|
710
|
+
{ title: "Property management", value: "property_management" },
|
|
711
|
+
{ title: "Service provider", value: "service_provider" },
|
|
712
|
+
];
|
|
713
|
+
const subjectOptions = computed(() => permissionCandidates.value.map((candidate) => ({
|
|
714
|
+
title: candidate.subtitle ? `${candidate.name} · ${candidate.subtitle}` : candidate.name,
|
|
715
|
+
value: candidate.subjectId,
|
|
716
|
+
})));
|
|
717
|
+
|
|
608
718
|
const cardForm = reactive({
|
|
609
719
|
cardType: "pacs" as "pacs" | "csn",
|
|
610
720
|
facilityCode: "",
|
|
@@ -612,10 +722,27 @@ const cardForm = reactive({
|
|
|
612
722
|
cardValue: "",
|
|
613
723
|
});
|
|
614
724
|
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
725
|
+
const cardReader = computed(() => {
|
|
726
|
+
const readerId = String(cardUser.value?.reader || selectedReaderId.value || "");
|
|
727
|
+
return readers.value.find((reader) => String(reader._id) === readerId) || null;
|
|
728
|
+
});
|
|
729
|
+
const cardProfileConfigured = computed(() => ["pacs", "csn", "csn_for_mifare"].includes(
|
|
730
|
+
String(cardReader.value?.cardReadingMode || ""),
|
|
731
|
+
));
|
|
732
|
+
const hybridCardProfile = computed(() => cardReader.value?.cardReadingMode === "csn_for_mifare");
|
|
733
|
+
const hybridCardTypeOptions = [
|
|
734
|
+
{ title: "MIFARE card (CSN)", value: "csn" },
|
|
735
|
+
{ title: "Other card (PACS)", value: "pacs" },
|
|
618
736
|
];
|
|
737
|
+
const cardProfileLabel = computed(() => {
|
|
738
|
+
const mode = String(cardReader.value?.cardReadingMode || "unconfigured");
|
|
739
|
+
if (mode === "pacs") return `PACS · ${(cardReader.value?.pacsFormat || "raw").toUpperCase()}`;
|
|
740
|
+
if (mode === "csn") return `CSN · ${(cardReader.value?.csnByteOrder || "msb").toUpperCase()} first`;
|
|
741
|
+
if (mode === "csn_for_mifare") {
|
|
742
|
+
return `PACS for non-MIFARE cards · CSN for MIFARE (${(cardReader.value?.csnByteOrder || "msb").toUpperCase()} first)`;
|
|
743
|
+
}
|
|
744
|
+
return "Configure the card data mode on the HID Reader screen before assigning a card.";
|
|
745
|
+
});
|
|
619
746
|
|
|
620
747
|
const statusOptions = ["Mapped", "Unmapped"];
|
|
621
748
|
|
|
@@ -655,6 +782,11 @@ function goToPage(next: number) {
|
|
|
655
782
|
loadUsers();
|
|
656
783
|
}
|
|
657
784
|
|
|
785
|
+
function reloadUsersFromFirstPage() {
|
|
786
|
+
page.value = 1;
|
|
787
|
+
loadUsers();
|
|
788
|
+
}
|
|
789
|
+
|
|
658
790
|
const readerOptions = computed(() =>
|
|
659
791
|
readers.value.map((reader) => ({
|
|
660
792
|
title: `${reader.name || reader.deviceId || reader._id} — ${reader.portalName || "Portal not configured"}`,
|
|
@@ -687,8 +819,12 @@ async function init() {
|
|
|
687
819
|
async function loadReaders() {
|
|
688
820
|
if (!props.site) return;
|
|
689
821
|
const response = await getReaders({ site: props.site, page: 1, limit: 100 });
|
|
690
|
-
|
|
691
|
-
|
|
822
|
+
const responseRecord = toRecord(response);
|
|
823
|
+
const responseData = toRecord(responseRecord.data);
|
|
824
|
+
const items = responseRecord.items ?? responseData.items ?? responseData.readers ?? [];
|
|
825
|
+
readers.value = (Array.isArray(items) ? items : [])
|
|
826
|
+
.map(toHidReader)
|
|
827
|
+
.filter((reader): reader is HidReader => reader !== null);
|
|
692
828
|
if (!selectedReaderId.value && readers.value.length) {
|
|
693
829
|
selectedReaderId.value = readers.value[0]._id;
|
|
694
830
|
}
|
|
@@ -704,44 +840,26 @@ async function loadUsers() {
|
|
|
704
840
|
|
|
705
841
|
loading.value = true;
|
|
706
842
|
try {
|
|
707
|
-
const
|
|
708
|
-
page:
|
|
709
|
-
limit:
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const identities =
|
|
724
|
-
identityResponse?.items ??
|
|
725
|
-
identityResponse?.data?.items ??
|
|
726
|
-
identityResponse?.data?.identities ??
|
|
727
|
-
[];
|
|
728
|
-
administratorUserIds.value = getAdministratorRoleUserIds(roleResponse);
|
|
729
|
-
const readerUsers = normalizeHidUsers(readerResponse).filter(
|
|
730
|
-
(user) => Number(user.userTypeId) !== 1,
|
|
731
|
-
);
|
|
732
|
-
const mergedUsers = mergeReaderUsersWithIdentities(readerUsers, identities).map(
|
|
733
|
-
(user) => ({
|
|
734
|
-
...user,
|
|
735
|
-
isAdministrator: hasAdministratorRule(user),
|
|
736
|
-
}),
|
|
843
|
+
const response = await getReaderUsers(selectedReaderId.value, {
|
|
844
|
+
page: page.value,
|
|
845
|
+
limit: limit.value,
|
|
846
|
+
search: search.value.trim(),
|
|
847
|
+
status: status.value.toLowerCase() as "mapped" | "unmapped" | "",
|
|
848
|
+
});
|
|
849
|
+
const responseRecord = toRecord(response);
|
|
850
|
+
const responseData = toRecord(responseRecord.data);
|
|
851
|
+
const responseItems = responseRecord.items ?? responseData.items ?? [];
|
|
852
|
+
const pageItems = (Array.isArray(responseItems) ? responseItems : []).map(toHidUser);
|
|
853
|
+
administratorUserIds.value = new Set(
|
|
854
|
+
pageItems
|
|
855
|
+
.filter((user) => user.isAdministrator === true)
|
|
856
|
+
.map((user) => toHidNumericId(user.hidUserId))
|
|
857
|
+
.filter((userId: number | undefined): userId is number => Boolean(userId)),
|
|
737
858
|
);
|
|
738
|
-
|
|
739
|
-
total.value =
|
|
740
|
-
pages.value =
|
|
741
|
-
|
|
742
|
-
const start = (page.value - 1) * limit.value;
|
|
743
|
-
users.value = filteredUsers.slice(start, start + limit.value);
|
|
744
|
-
serverPageRange.value = "";
|
|
859
|
+
users.value = pageItems;
|
|
860
|
+
total.value = Number(responseRecord.total ?? responseData.total ?? users.value.length);
|
|
861
|
+
pages.value = Number(responseRecord.pages ?? responseData.pages ?? 1);
|
|
862
|
+
serverPageRange.value = String(responseRecord.pageRange ?? responseData.pageRange ?? "");
|
|
745
863
|
if (props.cardManagement) {
|
|
746
864
|
await loadVisibleUserCards(users.value);
|
|
747
865
|
} else {
|
|
@@ -761,7 +879,7 @@ async function loadUsers() {
|
|
|
761
879
|
}
|
|
762
880
|
}
|
|
763
881
|
|
|
764
|
-
async function loadUserImages(items:
|
|
882
|
+
async function loadUserImages(items: HidUser[]) {
|
|
765
883
|
if (!selectedReaderId.value) return;
|
|
766
884
|
|
|
767
885
|
await Promise.all(
|
|
@@ -799,6 +917,34 @@ function resetForm() {
|
|
|
799
917
|
form.photoPreview = "";
|
|
800
918
|
selectedPhotoFile.value = null;
|
|
801
919
|
removePhotoRequested.value = false;
|
|
920
|
+
form.subjectCategory = "resident";
|
|
921
|
+
form.subjectId = "";
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async function loadSubjectCandidates() {
|
|
925
|
+
const readerId = form.reader || selectedReaderId.value;
|
|
926
|
+
if (!readerId || !orgId.value) {
|
|
927
|
+
permissionCandidates.value = [];
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
loadingSubjects.value = true;
|
|
931
|
+
try {
|
|
932
|
+
const response = await getPermissionCandidates(props.site, {
|
|
933
|
+
orgId: orgId.value,
|
|
934
|
+
readerId,
|
|
935
|
+
category: form.subjectCategory,
|
|
936
|
+
page: 1,
|
|
937
|
+
limit: 500,
|
|
938
|
+
});
|
|
939
|
+
permissionCandidates.value = response.items || [];
|
|
940
|
+
} finally {
|
|
941
|
+
loadingSubjects.value = false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async function onSubjectCategoryChanged() {
|
|
946
|
+
form.subjectId = "";
|
|
947
|
+
await loadSubjectCandidates();
|
|
802
948
|
}
|
|
803
949
|
|
|
804
950
|
async function openEnroll() {
|
|
@@ -810,10 +956,11 @@ async function openEnroll() {
|
|
|
810
956
|
]);
|
|
811
957
|
form.hidUserId = hidUserId;
|
|
812
958
|
form.registration = registration;
|
|
959
|
+
await loadSubjectCandidates();
|
|
813
960
|
formDialog.value = true;
|
|
814
961
|
}
|
|
815
962
|
|
|
816
|
-
async function openEdit(user:
|
|
963
|
+
async function openEdit(user: HidUser) {
|
|
817
964
|
selectedUser.value = user;
|
|
818
965
|
form.reader = String(user.reader ?? selectedReaderId.value);
|
|
819
966
|
form.block = user.metadata?.block ?? "";
|
|
@@ -827,6 +974,13 @@ async function openEdit(user: Record<string, any>) {
|
|
|
827
974
|
pinEnrolled.value = Boolean(user.metadata?.pinEnrolled);
|
|
828
975
|
removePinRequested.value = false;
|
|
829
976
|
form.photoPreview = getUserImageSrc(user);
|
|
977
|
+
form.subjectCategory = user.person
|
|
978
|
+
? "resident"
|
|
979
|
+
: user.serviceProvider
|
|
980
|
+
? "service_provider"
|
|
981
|
+
: "property_management";
|
|
982
|
+
form.subjectId = String(user.person || user.member || user.serviceProvider || "");
|
|
983
|
+
await loadSubjectCandidates();
|
|
830
984
|
selectedPhotoFile.value = null;
|
|
831
985
|
removePhotoRequested.value = false;
|
|
832
986
|
formDialog.value = true;
|
|
@@ -849,12 +1003,12 @@ async function openEdit(user: Record<string, any>) {
|
|
|
849
1003
|
}
|
|
850
1004
|
}
|
|
851
1005
|
|
|
852
|
-
function openView(user:
|
|
1006
|
+
function openView(user: HidUser) {
|
|
853
1007
|
selectedUser.value = user;
|
|
854
1008
|
viewDialog.value = true;
|
|
855
1009
|
}
|
|
856
1010
|
|
|
857
|
-
function openDelete(user:
|
|
1011
|
+
function openDelete(user: HidUser) {
|
|
858
1012
|
selectedUser.value = user;
|
|
859
1013
|
deleteDialog.value = true;
|
|
860
1014
|
}
|
|
@@ -903,8 +1057,8 @@ function onPhotoChange(event: Event) {
|
|
|
903
1057
|
showToast("Facial enrollment only accepts JPG or PNG images.", "error");
|
|
904
1058
|
return;
|
|
905
1059
|
}
|
|
906
|
-
if (file.size
|
|
907
|
-
showToast("Facial enrollment images must be smaller than
|
|
1060
|
+
if (file.size >= 1024 * 1024) {
|
|
1061
|
+
showToast("Facial enrollment images must be smaller than 1 MB.", "error");
|
|
908
1062
|
return;
|
|
909
1063
|
}
|
|
910
1064
|
|
|
@@ -920,7 +1074,7 @@ function onPhotoChange(event: Event) {
|
|
|
920
1074
|
async function saveUser() {
|
|
921
1075
|
const readerId =
|
|
922
1076
|
selectedReaderId.value || form.reader || readers.value[0]?._id;
|
|
923
|
-
if (!readerId || !form.name || !form.hidUserId || !form.registration) {
|
|
1077
|
+
if (!readerId || !form.name || !form.hidUserId || !form.registration || !form.subjectId) {
|
|
924
1078
|
showToast("Please complete the required HID user fields.", "error");
|
|
925
1079
|
return;
|
|
926
1080
|
}
|
|
@@ -934,6 +1088,7 @@ async function saveUser() {
|
|
|
934
1088
|
}
|
|
935
1089
|
|
|
936
1090
|
saving.value = true;
|
|
1091
|
+
let newlyCreatedHidUserId: number | undefined;
|
|
937
1092
|
try {
|
|
938
1093
|
const hidUser = toHidUserObject({
|
|
939
1094
|
hidUserId: form.hidUserId,
|
|
@@ -948,7 +1103,14 @@ async function saveUser() {
|
|
|
948
1103
|
const payload = {
|
|
949
1104
|
hidUserId: String(hidUser.id),
|
|
950
1105
|
registration: form.registration,
|
|
951
|
-
|
|
1106
|
+
person: form.subjectCategory === "resident" ? form.subjectId : "",
|
|
1107
|
+
member: form.subjectCategory === "property_management" ? form.subjectId : "",
|
|
1108
|
+
serviceProvider: form.subjectCategory === "service_provider" ? form.subjectId : "",
|
|
1109
|
+
type: (form.subjectCategory === "resident"
|
|
1110
|
+
? "resident"
|
|
1111
|
+
: form.subjectCategory === "service_provider"
|
|
1112
|
+
? "contractor"
|
|
1113
|
+
: form.isAdministrator ? "admin" : "staff") as
|
|
952
1114
|
| "resident"
|
|
953
1115
|
| "staff"
|
|
954
1116
|
| "contractor"
|
|
@@ -963,7 +1125,6 @@ async function saveUser() {
|
|
|
963
1125
|
unit: form.unit,
|
|
964
1126
|
unitLabel: buildUnitLabel(),
|
|
965
1127
|
facialData: form.photoPreview ? form.hidUserId : "",
|
|
966
|
-
photo: form.photoPreview,
|
|
967
1128
|
imageTimestamp: selectedUser.value?.metadata?.imageTimestamp || "",
|
|
968
1129
|
facialScores: selectedUser.value?.metadata?.facialScores || {},
|
|
969
1130
|
pinEnrolled: pinEnrolled.value,
|
|
@@ -983,7 +1144,8 @@ async function saveUser() {
|
|
|
983
1144
|
await saveIdentityOnReader(readerId, payload);
|
|
984
1145
|
}
|
|
985
1146
|
} else {
|
|
986
|
-
await createHidUserOnReader(readerId, hidUser);
|
|
1147
|
+
const created = await createHidUserOnReader(readerId, hidUser);
|
|
1148
|
+
if (created) newlyCreatedHidUserId = hidUser.id;
|
|
987
1149
|
await syncHidUserRole(readerId, hidUser.id, form.isAdministrator);
|
|
988
1150
|
const facialResult = await syncFacialImage(readerId, hidUser.id);
|
|
989
1151
|
applyFacialResult(payload.metadata, facialResult);
|
|
@@ -999,7 +1161,18 @@ async function saveUser() {
|
|
|
999
1161
|
? "HID user updated on reader."
|
|
1000
1162
|
: "HID user enrolled on reader."
|
|
1001
1163
|
);
|
|
1002
|
-
} catch (error:
|
|
1164
|
+
} catch (error: unknown) {
|
|
1165
|
+
if (newlyCreatedHidUserId) {
|
|
1166
|
+
try {
|
|
1167
|
+
await runObjectOperation(readerId, {
|
|
1168
|
+
operation: "destroy",
|
|
1169
|
+
object: "users",
|
|
1170
|
+
where: { users: { id: newlyCreatedHidUserId } },
|
|
1171
|
+
});
|
|
1172
|
+
} catch (rollbackError) {
|
|
1173
|
+
console.error("Unable to roll back the partially enrolled HID user:", rollbackError);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1003
1176
|
console.error("Unable to save HID user:", error);
|
|
1004
1177
|
showToast(getHidErrorMessage(error), "error");
|
|
1005
1178
|
} finally {
|
|
@@ -1094,7 +1267,7 @@ async function deleteUser() {
|
|
|
1094
1267
|
deleteDialog.value = false;
|
|
1095
1268
|
await loadUsers();
|
|
1096
1269
|
showToast("HID user deleted from reader.");
|
|
1097
|
-
} catch (error:
|
|
1270
|
+
} catch (error: unknown) {
|
|
1098
1271
|
console.error("Unable to delete HID user:", error);
|
|
1099
1272
|
showToast(getHidErrorMessage(error), "error");
|
|
1100
1273
|
} finally {
|
|
@@ -1104,7 +1277,7 @@ async function deleteUser() {
|
|
|
1104
1277
|
|
|
1105
1278
|
async function saveIdentityOnReader(
|
|
1106
1279
|
readerId: string,
|
|
1107
|
-
payload:
|
|
1280
|
+
payload: HidIdentityPayload,
|
|
1108
1281
|
) {
|
|
1109
1282
|
const existingIdentity = await findExistingIdentityOnReader(
|
|
1110
1283
|
readerId,
|
|
@@ -1117,7 +1290,7 @@ async function saveIdentityOnReader(
|
|
|
1117
1290
|
|
|
1118
1291
|
try {
|
|
1119
1292
|
await createIdentity(readerId, { ...payload, site: props.site });
|
|
1120
|
-
} catch (error:
|
|
1293
|
+
} catch (error: unknown) {
|
|
1121
1294
|
const message = getHidErrorMessage(error);
|
|
1122
1295
|
if (!message.toLowerCase().includes("already exists")) throw error;
|
|
1123
1296
|
|
|
@@ -1132,24 +1305,23 @@ async function saveIdentityOnReader(
|
|
|
1132
1305
|
|
|
1133
1306
|
async function findExistingIdentityOnReader(
|
|
1134
1307
|
readerId: string,
|
|
1135
|
-
payload:
|
|
1308
|
+
payload: HidIdentityPayload,
|
|
1136
1309
|
) {
|
|
1137
1310
|
const response = await getIdentities(readerId, {
|
|
1138
1311
|
page: 1,
|
|
1139
1312
|
limit: 500,
|
|
1140
1313
|
search: payload.hidUserId || payload.registration || payload.cardNo || "",
|
|
1141
1314
|
});
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
[];
|
|
1315
|
+
const responseRecord = toRecord(response);
|
|
1316
|
+
const responseData = toRecord(responseRecord.data);
|
|
1317
|
+
const responseItems = responseRecord.items ?? responseData.items ?? responseData.identities ?? [];
|
|
1318
|
+
const identities = (Array.isArray(responseItems) ? responseItems : []).map(toHidUser);
|
|
1147
1319
|
const hidUserId = String(payload.hidUserId || "");
|
|
1148
1320
|
const registration = String(payload.registration || "");
|
|
1149
1321
|
const cardNo = String(payload.cardNo || "");
|
|
1150
1322
|
|
|
1151
1323
|
return identities.find(
|
|
1152
|
-
(identity
|
|
1324
|
+
(identity) =>
|
|
1153
1325
|
String(identity.hidUserId || "") === hidUserId ||
|
|
1154
1326
|
Boolean(
|
|
1155
1327
|
registration && String(identity.registration || "") === registration
|
|
@@ -1160,12 +1332,12 @@ async function findExistingIdentityOnReader(
|
|
|
1160
1332
|
|
|
1161
1333
|
async function createHidUserOnReader(
|
|
1162
1334
|
readerId: string,
|
|
1163
|
-
user:
|
|
1335
|
+
user: { id?: number; name: string; registration: string },
|
|
1164
1336
|
) {
|
|
1165
1337
|
const existing = await getHidUserFromReader(readerId, user.id);
|
|
1166
1338
|
if (existing) {
|
|
1167
1339
|
await updateHidUserOnReader(readerId, user);
|
|
1168
|
-
return;
|
|
1340
|
+
return false;
|
|
1169
1341
|
}
|
|
1170
1342
|
|
|
1171
1343
|
await runObjectOperation(readerId, {
|
|
@@ -1173,6 +1345,7 @@ async function createHidUserOnReader(
|
|
|
1173
1345
|
object: "users",
|
|
1174
1346
|
values: [user],
|
|
1175
1347
|
});
|
|
1348
|
+
return true;
|
|
1176
1349
|
}
|
|
1177
1350
|
|
|
1178
1351
|
async function getHidUserFromReader(readerId: string, id: unknown) {
|
|
@@ -1193,7 +1366,7 @@ async function getHidUserFromReader(readerId: string, id: unknown) {
|
|
|
1193
1366
|
|
|
1194
1367
|
async function updateHidUserOnReader(
|
|
1195
1368
|
readerId: string,
|
|
1196
|
-
user:
|
|
1369
|
+
user: { id?: number; name: string; registration: string },
|
|
1197
1370
|
) {
|
|
1198
1371
|
const { id, ...values } = user;
|
|
1199
1372
|
await runObjectOperation(readerId, {
|
|
@@ -1210,7 +1383,7 @@ async function updateHidUserOnReader(
|
|
|
1210
1383
|
|
|
1211
1384
|
async function deleteHidUserFromReader(
|
|
1212
1385
|
readerId: string,
|
|
1213
|
-
user:
|
|
1386
|
+
user: HidUser,
|
|
1214
1387
|
) {
|
|
1215
1388
|
const id = toHidNumericId(user.hidUserId);
|
|
1216
1389
|
if (!readerId || !id) return;
|
|
@@ -1299,36 +1472,7 @@ async function getHidUserRole(readerId: string, userId: number) {
|
|
|
1299
1472
|
return Array.isArray(roles) ? roles[0] : undefined;
|
|
1300
1473
|
}
|
|
1301
1474
|
|
|
1302
|
-
function
|
|
1303
|
-
return runObjectOperation(readerId, {
|
|
1304
|
-
operation: "load",
|
|
1305
|
-
object: "user_roles",
|
|
1306
|
-
where: {
|
|
1307
|
-
user_roles: {
|
|
1308
|
-
role: 1,
|
|
1309
|
-
},
|
|
1310
|
-
},
|
|
1311
|
-
limit: 500,
|
|
1312
|
-
offset: 0,
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
function getAdministratorRoleUserIds(response: Record<string, any> | null) {
|
|
1317
|
-
const data = response?.data || response || {};
|
|
1318
|
-
const roles =
|
|
1319
|
-
data?.user_roles ||
|
|
1320
|
-
data?.data?.user_roles ||
|
|
1321
|
-
data?.result?.user_roles ||
|
|
1322
|
-
[];
|
|
1323
|
-
|
|
1324
|
-
return new Set(
|
|
1325
|
-
(Array.isArray(roles) ? roles : [])
|
|
1326
|
-
.map((role) => toHidNumericId(role.user_id))
|
|
1327
|
-
.filter((userId): userId is number => Boolean(userId))
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function toHidUserObject(identity: Record<string, any>) {
|
|
1475
|
+
function toHidUserObject(identity: Pick<HidUser, "hidUserId" | "name" | "registration">) {
|
|
1332
1476
|
return {
|
|
1333
1477
|
id: toHidNumericId(identity.hidUserId),
|
|
1334
1478
|
name:
|
|
@@ -1349,12 +1493,12 @@ function toHidNumericId(value: unknown) {
|
|
|
1349
1493
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
1350
1494
|
}
|
|
1351
1495
|
|
|
1352
|
-
function hasAdministratorRule(user?:
|
|
1496
|
+
function hasAdministratorRule(user?: HidUser | null) {
|
|
1353
1497
|
const hidUserId = toHidNumericId(user?.hidUserId);
|
|
1354
1498
|
return Boolean(hidUserId && administratorUserIds.value.has(hidUserId));
|
|
1355
1499
|
}
|
|
1356
1500
|
|
|
1357
|
-
async function loadVisibleUserCards(items:
|
|
1501
|
+
async function loadVisibleUserCards(items: HidUser[]) {
|
|
1358
1502
|
if (!selectedReaderId.value) return;
|
|
1359
1503
|
|
|
1360
1504
|
const entries = await Promise.all(
|
|
@@ -1364,7 +1508,9 @@ async function loadVisibleUserCards(items: Record<string, any>[]) {
|
|
|
1364
1508
|
|
|
1365
1509
|
try {
|
|
1366
1510
|
const response = await getUserCards(selectedReaderId.value, hidUserId);
|
|
1367
|
-
|
|
1511
|
+
const responseData = toRecord(toRecord(response).data);
|
|
1512
|
+
const cards = responseData.cards;
|
|
1513
|
+
return [String(hidUserId), (Array.isArray(cards) ? cards : []).map(toHidCard)] as const;
|
|
1368
1514
|
} catch {
|
|
1369
1515
|
return [String(hidUserId), []] as const;
|
|
1370
1516
|
}
|
|
@@ -1376,14 +1522,15 @@ async function loadVisibleUserCards(items: Record<string, any>[]) {
|
|
|
1376
1522
|
);
|
|
1377
1523
|
}
|
|
1378
1524
|
|
|
1379
|
-
function getUserPhysicalCards(user:
|
|
1525
|
+
function getUserPhysicalCards(user: HidUser) {
|
|
1380
1526
|
const hidUserId = toHidNumericId(user.hidUserId);
|
|
1381
1527
|
return hidUserId ? physicalCardsByUserId.value[String(hidUserId)] || [] : [];
|
|
1382
1528
|
}
|
|
1383
1529
|
|
|
1384
|
-
async function openCardManager(user:
|
|
1530
|
+
async function openCardManager(user: HidUser) {
|
|
1385
1531
|
cardUser.value = user;
|
|
1386
|
-
|
|
1532
|
+
const reader = readers.value.find((item) => String(item._id) === String(user.reader || selectedReaderId.value));
|
|
1533
|
+
cardForm.cardType = reader?.cardReadingMode === "pacs" ? "pacs" : "csn";
|
|
1387
1534
|
cardForm.facilityCode = "";
|
|
1388
1535
|
cardForm.cardNumber = "";
|
|
1389
1536
|
cardForm.cardValue = "";
|
|
@@ -1405,7 +1552,9 @@ async function loadPhysicalCards() {
|
|
|
1405
1552
|
loadingCards.value = true;
|
|
1406
1553
|
try {
|
|
1407
1554
|
const response = await getUserCards(readerId, hidUserId);
|
|
1408
|
-
|
|
1555
|
+
const responseData = toRecord(toRecord(response).data);
|
|
1556
|
+
const cards = responseData.cards;
|
|
1557
|
+
physicalCards.value = (Array.isArray(cards) ? cards : []).map(toHidCard);
|
|
1409
1558
|
} catch (error: unknown) {
|
|
1410
1559
|
physicalCards.value = [];
|
|
1411
1560
|
showToast(getHidErrorMessage(error), "error");
|
|
@@ -1414,17 +1563,17 @@ async function loadPhysicalCards() {
|
|
|
1414
1563
|
}
|
|
1415
1564
|
}
|
|
1416
1565
|
|
|
1417
|
-
function parsePhysicalCardInteger(value: string, label: string) {
|
|
1566
|
+
function parsePhysicalCardInteger(value: string, label: string, maximum: bigint) {
|
|
1418
1567
|
if (!/^\d+$/.test(value.trim())) {
|
|
1419
1568
|
showToast(`${label} must contain numbers only.`, "error");
|
|
1420
1569
|
return null;
|
|
1421
1570
|
}
|
|
1422
|
-
const
|
|
1423
|
-
if (
|
|
1571
|
+
const normalized = value.trim();
|
|
1572
|
+
if (BigInt(normalized) > maximum) {
|
|
1424
1573
|
showToast(`${label} is outside the supported range.`, "error");
|
|
1425
1574
|
return null;
|
|
1426
1575
|
}
|
|
1427
|
-
return
|
|
1576
|
+
return normalized;
|
|
1428
1577
|
}
|
|
1429
1578
|
|
|
1430
1579
|
async function assignPhysicalCard() {
|
|
@@ -1432,20 +1581,20 @@ async function assignPhysicalCard() {
|
|
|
1432
1581
|
if (!readerId || !hidUserId) return;
|
|
1433
1582
|
|
|
1434
1583
|
let payload:
|
|
1435
|
-
| { cardType: "pacs"; facilityCode:
|
|
1584
|
+
| { cardType: "pacs"; facilityCode: string; cardNumber: string }
|
|
1436
1585
|
| { cardType: "csn"; cardValue: string };
|
|
1437
1586
|
if (cardForm.cardType === "pacs") {
|
|
1438
|
-
const facilityCode = parsePhysicalCardInteger(cardForm.facilityCode, "Facility Code");
|
|
1439
|
-
const cardNumber = parsePhysicalCardInteger(cardForm.cardNumber, "Card Number");
|
|
1587
|
+
const facilityCode = parsePhysicalCardInteger(cardForm.facilityCode, "Facility Code", 4294967295n);
|
|
1588
|
+
const cardNumber = parsePhysicalCardInteger(cardForm.cardNumber, "Card Number", 4294967295n);
|
|
1440
1589
|
if (facilityCode === null || cardNumber === null) return;
|
|
1441
1590
|
payload = { cardType: "pacs", facilityCode, cardNumber };
|
|
1442
1591
|
} else {
|
|
1443
|
-
const cardValue = parsePhysicalCardInteger(cardForm.cardValue, "CSN");
|
|
1444
|
-
if (cardValue === null || cardValue <=
|
|
1445
|
-
if (cardValue === 0) showToast("CSN must be greater than zero.", "error");
|
|
1592
|
+
const cardValue = parsePhysicalCardInteger(cardForm.cardValue, "CSN", 18446744073709551615n);
|
|
1593
|
+
if (cardValue === null || BigInt(cardValue) <= 0n) {
|
|
1594
|
+
if (cardValue === "0") showToast("CSN must be greater than zero.", "error");
|
|
1446
1595
|
return;
|
|
1447
1596
|
}
|
|
1448
|
-
payload = { cardType: "csn", cardValue
|
|
1597
|
+
payload = { cardType: "csn", cardValue };
|
|
1449
1598
|
}
|
|
1450
1599
|
|
|
1451
1600
|
savingCard.value = true;
|
|
@@ -1496,7 +1645,7 @@ async function cancelPhysicalCardEnrollment() {
|
|
|
1496
1645
|
}
|
|
1497
1646
|
}
|
|
1498
1647
|
|
|
1499
|
-
async function removePhysicalCard(card:
|
|
1648
|
+
async function removePhysicalCard(card: HidCard) {
|
|
1500
1649
|
if (!window.confirm(`Remove physical HID card ${formatPhysicalCard(card)}?`)) return;
|
|
1501
1650
|
const { readerId, hidUserId } = getCardReaderAndUser();
|
|
1502
1651
|
if (!readerId || !hidUserId || !card?.id) return;
|
|
@@ -1514,7 +1663,7 @@ async function removePhysicalCard(card: Record<string, any>) {
|
|
|
1514
1663
|
}
|
|
1515
1664
|
}
|
|
1516
1665
|
|
|
1517
|
-
function formatPhysicalCard(card:
|
|
1666
|
+
function formatPhysicalCard(card: HidCard) {
|
|
1518
1667
|
if (card?.cardType === "pacs") {
|
|
1519
1668
|
return `PACS ${card.facilityCode}.${card.cardNumber}`;
|
|
1520
1669
|
}
|
|
@@ -1522,7 +1671,7 @@ function formatPhysicalCard(card: Record<string, any>) {
|
|
|
1522
1671
|
return `Card ${card?.value || "N/A"}`;
|
|
1523
1672
|
}
|
|
1524
1673
|
|
|
1525
|
-
function getPhysicalCardSubtitle(card:
|
|
1674
|
+
function getPhysicalCardSubtitle(card: HidCard) {
|
|
1526
1675
|
const source = card?.source === "reader" ? "Read at reader" : card?.source === "manual" ? "Manual" : "On device";
|
|
1527
1676
|
return `${source} - Raw value ${card?.value || "N/A"}`;
|
|
1528
1677
|
}
|
|
@@ -1532,11 +1681,11 @@ function buildUnitLabel() {
|
|
|
1532
1681
|
return parts.length ? `BLK ${parts.join("/")}` : "";
|
|
1533
1682
|
}
|
|
1534
1683
|
|
|
1535
|
-
function getName(user?:
|
|
1684
|
+
function getName(user?: HidUser | null) {
|
|
1536
1685
|
return user?.metadata?.name || user?.name || "N/A";
|
|
1537
1686
|
}
|
|
1538
1687
|
|
|
1539
|
-
function getUnit(user?:
|
|
1688
|
+
function getUnit(user?: HidUser | null) {
|
|
1540
1689
|
return (
|
|
1541
1690
|
user?.metadata?.unitLabel ||
|
|
1542
1691
|
[user?.metadata?.block, user?.metadata?.level, user?.metadata?.unit]
|
|
@@ -1546,7 +1695,7 @@ function getUnit(user?: Record<string, any> | null) {
|
|
|
1546
1695
|
);
|
|
1547
1696
|
}
|
|
1548
1697
|
|
|
1549
|
-
function getFacialData(user:
|
|
1698
|
+
function getFacialData(user: HidUser) {
|
|
1550
1699
|
return (
|
|
1551
1700
|
user?.metadata?.facialData ||
|
|
1552
1701
|
user?.metadata?.imageTimestamp ||
|
|
@@ -1554,7 +1703,7 @@ function getFacialData(user: Record<string, any>) {
|
|
|
1554
1703
|
);
|
|
1555
1704
|
}
|
|
1556
1705
|
|
|
1557
|
-
function getUserImageSrc(user:
|
|
1706
|
+
function getUserImageSrc(user: HidUser) {
|
|
1558
1707
|
const hidUserId = toHidNumericId(user?.hidUserId);
|
|
1559
1708
|
return (
|
|
1560
1709
|
user?.metadata?.photo ||
|
|
@@ -1621,39 +1770,41 @@ async function getExistingHidReaderUsers(fields?: string[]) {
|
|
|
1621
1770
|
}
|
|
1622
1771
|
}
|
|
1623
1772
|
|
|
1624
|
-
function normalizeHidUsers(response:
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
[];
|
|
1773
|
+
function normalizeHidUsers(response: unknown): HidUser[] {
|
|
1774
|
+
const responseRecord = toRecord(response);
|
|
1775
|
+
const responseData = toRecord(responseRecord.data);
|
|
1776
|
+
const nestedData = toRecord(responseData.data);
|
|
1777
|
+
const items = responseData.users ?? nestedData.users ?? responseRecord.users ?? [];
|
|
1630
1778
|
|
|
1631
1779
|
return Array.isArray(items)
|
|
1632
|
-
? items.map((
|
|
1780
|
+
? items.map((value) => {
|
|
1781
|
+
const user = toRecord(value);
|
|
1782
|
+
return {
|
|
1633
1783
|
_rowKey: `reader-${user.id}`,
|
|
1634
1784
|
hidUserId: String(user.id || ""),
|
|
1635
|
-
rawHidUserId: user.id,
|
|
1636
|
-
registration: user.registration,
|
|
1637
|
-
userTypeId: user.user_type_id,
|
|
1638
|
-
cardNo: user.card_value || user.cardNo
|
|
1639
|
-
name: user.name,
|
|
1785
|
+
rawHidUserId: toText(user.id),
|
|
1786
|
+
registration: toText(user.registration),
|
|
1787
|
+
userTypeId: toText(user.user_type_id),
|
|
1788
|
+
cardNo: toText(user.card_value || user.cardNo),
|
|
1789
|
+
name: toText(user.name),
|
|
1640
1790
|
metadata: {
|
|
1641
|
-
name: user.name,
|
|
1791
|
+
name: toText(user.name),
|
|
1642
1792
|
imageTimestamp: user.image_timestamp
|
|
1643
1793
|
? String(user.image_timestamp)
|
|
1644
1794
|
: "",
|
|
1645
1795
|
lastAccessAt: fromUnixSeconds(user.last_access),
|
|
1646
1796
|
hidReaderSource: true,
|
|
1647
1797
|
},
|
|
1648
|
-
|
|
1798
|
+
};
|
|
1799
|
+
})
|
|
1649
1800
|
: [];
|
|
1650
1801
|
}
|
|
1651
1802
|
|
|
1652
1803
|
function mergeReaderUsersWithIdentities(
|
|
1653
|
-
readerUsers:
|
|
1654
|
-
identities:
|
|
1804
|
+
readerUsers: HidUser[],
|
|
1805
|
+
identities: HidUser[],
|
|
1655
1806
|
) {
|
|
1656
|
-
const identityById = new Map<string,
|
|
1807
|
+
const identityById = new Map<string, HidUser>();
|
|
1657
1808
|
identities.forEach((identity) => {
|
|
1658
1809
|
const id = toHidNumericId(identity.hidUserId);
|
|
1659
1810
|
if (id) identityById.set(String(id), identity);
|
|
@@ -1671,9 +1822,9 @@ function mergeReaderUsersWithIdentities(
|
|
|
1671
1822
|
_rowKey: `mapped-${identity._id || readerUser.rawHidUserId}`,
|
|
1672
1823
|
hidUserId: identity.hidUserId || readerUser.hidUserId,
|
|
1673
1824
|
rawHidUserId: readerUser.rawHidUserId,
|
|
1674
|
-
name: identity.metadata?.name || identity.name || readerUser.name,
|
|
1675
|
-
registration: identity.registration || readerUser.registration,
|
|
1676
|
-
cardNo: identity.cardNo || readerUser.cardNo,
|
|
1825
|
+
name: identity.metadata?.name || identity.name || readerUser.name || "",
|
|
1826
|
+
registration: identity.registration || readerUser.registration || "",
|
|
1827
|
+
cardNo: identity.cardNo || readerUser.cardNo || "",
|
|
1677
1828
|
metadata: {
|
|
1678
1829
|
...readerUser.metadata,
|
|
1679
1830
|
...identity.metadata,
|
|
@@ -1689,33 +1840,12 @@ function mergeReaderUsersWithIdentities(
|
|
|
1689
1840
|
return merged;
|
|
1690
1841
|
}
|
|
1691
1842
|
|
|
1692
|
-
function
|
|
1693
|
-
const searchText = search.value.trim().toLowerCase();
|
|
1694
|
-
return items.filter((item) => {
|
|
1695
|
-
const matchesSearch =
|
|
1696
|
-
!searchText ||
|
|
1697
|
-
[getName(item), item.hidUserId, item.registration, item.cardNo].some(
|
|
1698
|
-
(value) =>
|
|
1699
|
-
String(value || "")
|
|
1700
|
-
.toLowerCase()
|
|
1701
|
-
.includes(searchText)
|
|
1702
|
-
);
|
|
1703
|
-
const matchesStatus = !status.value || getMappingStatus(item) === status.value;
|
|
1704
|
-
return matchesSearch && matchesStatus;
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
function getTotalFromPageRange(value: string) {
|
|
1709
|
-
const match = value.match(/\bof\s+(\d+)$/i);
|
|
1710
|
-
return match ? Number(match[1]) : undefined;
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
function getMappingStatus(user: Record<string, any>) {
|
|
1843
|
+
function getMappingStatus(user: HidUser) {
|
|
1714
1844
|
return hasMappedRecord(user) ? "Mapped" : "Unmapped";
|
|
1715
1845
|
}
|
|
1716
1846
|
|
|
1717
|
-
function hasMappedRecord(user:
|
|
1718
|
-
return Boolean(user?.person || user?.user || user?.member || user?.visitor);
|
|
1847
|
+
function hasMappedRecord(user: HidUser) {
|
|
1848
|
+
return Boolean(user?.person || user?.user || user?.member || user?.serviceProvider || user?.visitor);
|
|
1719
1849
|
}
|
|
1720
1850
|
|
|
1721
1851
|
function formatDate(value?: string) {
|
|
@@ -1741,23 +1871,66 @@ function showToast(message: string, color = "success") {
|
|
|
1741
1871
|
snackbar.show = true;
|
|
1742
1872
|
}
|
|
1743
1873
|
|
|
1744
|
-
function getHidErrorMessage(error:
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
{};
|
|
1752
|
-
|
|
1753
|
-
return String(
|
|
1754
|
-
data?.message ||
|
|
1755
|
-
data?.error ||
|
|
1756
|
-
data?.statusMessage ||
|
|
1757
|
-
error?.statusMessage ||
|
|
1758
|
-
error?.message ||
|
|
1759
|
-
"Unable to save HID user."
|
|
1874
|
+
function getHidErrorMessage(error: unknown) {
|
|
1875
|
+
const errorRecord = toRecord(error);
|
|
1876
|
+
const response = toRecord(errorRecord.response);
|
|
1877
|
+
const cause = toRecord(errorRecord.cause);
|
|
1878
|
+
const causeResponse = toRecord(cause.response);
|
|
1879
|
+
const data = toRecord(
|
|
1880
|
+
errorRecord.data || response._data || response.data || cause.data || causeResponse._data,
|
|
1760
1881
|
);
|
|
1882
|
+
|
|
1883
|
+
return toText(
|
|
1884
|
+
data.message || data.error || data.statusMessage || errorRecord.statusMessage || errorRecord.message,
|
|
1885
|
+
"Unable to save HID user.",
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function toRecord(value: unknown): Record<string, unknown> {
|
|
1890
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1891
|
+
? value as Record<string, unknown>
|
|
1892
|
+
: {};
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
function toHidReader(value: unknown): HidReader | null {
|
|
1896
|
+
const reader = toRecord(value);
|
|
1897
|
+
const id = toText(reader._id);
|
|
1898
|
+
return id ? { ...reader, _id: id } : null;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function toHidUser(value: unknown): HidUser {
|
|
1902
|
+
const user = toRecord(value);
|
|
1903
|
+
return {
|
|
1904
|
+
...user,
|
|
1905
|
+
_id: toText(user._id) || undefined,
|
|
1906
|
+
_rowKey: toText(user._rowKey) || undefined,
|
|
1907
|
+
reader: toText(user.reader) || undefined,
|
|
1908
|
+
hidUserId: toText(user.hidUserId) || undefined,
|
|
1909
|
+
registration: toText(user.registration) || undefined,
|
|
1910
|
+
cardNo: toText(user.cardNo) || undefined,
|
|
1911
|
+
name: toText(user.name) || undefined,
|
|
1912
|
+
metadata: toHidMetadata(user.metadata),
|
|
1913
|
+
isAdministrator: user.isAdministrator === true,
|
|
1914
|
+
person: toText(user.person) || undefined,
|
|
1915
|
+
user: toText(user.user) || undefined,
|
|
1916
|
+
member: toText(user.member) || undefined,
|
|
1917
|
+
serviceProvider: toText(user.serviceProvider) || undefined,
|
|
1918
|
+
visitor: toText(user.visitor) || undefined,
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function toHidMetadata(value: unknown): HidMetadata {
|
|
1923
|
+
return toRecord(value) as HidMetadata;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function toHidCard(value: unknown): HidCard {
|
|
1927
|
+
const card = toRecord(value);
|
|
1928
|
+
return {
|
|
1929
|
+
...card,
|
|
1930
|
+
id: toText(card.id) || undefined,
|
|
1931
|
+
cardType: card.cardType === "pacs" || card.cardType === "csn" ? card.cardType : undefined,
|
|
1932
|
+
source: card.source === "reader" || card.source === "manual" || card.source === "device" ? card.source : undefined,
|
|
1933
|
+
};
|
|
1761
1934
|
}
|
|
1762
1935
|
</script>
|
|
1763
1936
|
|