@7365admin1/layer-common 4.1.3-staging.252 → 4.1.3-staging.253
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.
|
@@ -89,15 +89,9 @@
|
|
|
89
89
|
|
|
90
90
|
<!-- Staging's hover-the-full-name tooltip, kept. -->
|
|
91
91
|
<template #[`item.name`]="{ item }">
|
|
92
|
+
<!-- Client requirement (2026-08-26): no face photo on an access log row.
|
|
93
|
+
The reader keeps the image; we neither fetch nor hold one. -->
|
|
92
94
|
<span class="hid-log-name app-cell--strong">
|
|
93
|
-
<v-avatar
|
|
94
|
-
v-if="item.faceImage"
|
|
95
|
-
size="34"
|
|
96
|
-
rounded="lg"
|
|
97
|
-
class="hid-face-avatar"
|
|
98
|
-
>
|
|
99
|
-
<v-img :src="item.faceImage" cover />
|
|
100
|
-
</v-avatar>
|
|
101
95
|
<span>{{ item.name }}</span>
|
|
102
96
|
<v-tooltip
|
|
103
97
|
v-if="item.name && item.name !== 'N/A'"
|
|
@@ -314,7 +308,6 @@ type AccessRow = {
|
|
|
314
308
|
name: string;
|
|
315
309
|
hidUserId: string;
|
|
316
310
|
facialData: string;
|
|
317
|
-
faceImage?: string;
|
|
318
311
|
unit: string;
|
|
319
312
|
method: string;
|
|
320
313
|
lastAccessTime: string;
|
|
@@ -371,7 +364,6 @@ type JsPdf = {
|
|
|
371
364
|
const {
|
|
372
365
|
getReaders,
|
|
373
366
|
getLogs,
|
|
374
|
-
getUserImage,
|
|
375
367
|
getReaderUsers,
|
|
376
368
|
getReaderAccessLogs,
|
|
377
369
|
} = useHidAmico();
|
|
@@ -384,7 +376,6 @@ const visitorRecords = ref<Record<string, HidAccessRecord>>({});
|
|
|
384
376
|
const logs = ref<HidAccessRecord[]>([]);
|
|
385
377
|
const liveAccessLogs = ref<HidAccessRecord[]>([]);
|
|
386
378
|
const administratorUserIds = ref<Set<number>>(new Set());
|
|
387
|
-
const userImages = ref<Record<string, string>>({});
|
|
388
379
|
const selectedReaderId = ref("");
|
|
389
380
|
const activeTab = ref<AccessTab>("all");
|
|
390
381
|
const search = ref("");
|
|
@@ -470,7 +461,6 @@ const selectedHistoryRows = computed<AccessRow[]>(() => {
|
|
|
470
461
|
name: selectedHistoryRow.value?.name || "N/A",
|
|
471
462
|
hidUserId: selectedHistoryRow.value?.hidUserId || "N/A",
|
|
472
463
|
facialData: selectedHistoryRow.value?.facialData || "N/A",
|
|
473
|
-
faceImage: selectedHistoryRow.value?.faceImage,
|
|
474
464
|
unit: selectedHistoryRow.value?.unit || "N/A",
|
|
475
465
|
method: getAccessMethod({}, log),
|
|
476
466
|
lastAccessTime: formatDate(getLogAccessDateValue(log)),
|
|
@@ -486,7 +476,6 @@ const selectedHistoryRows = computed<AccessRow[]>(() => {
|
|
|
486
476
|
name: selectedHistoryRow.value?.name || "N/A",
|
|
487
477
|
hidUserId: selectedHistoryRow.value?.hidUserId || "N/A",
|
|
488
478
|
facialData: selectedHistoryRow.value?.facialData || "N/A",
|
|
489
|
-
faceImage: selectedHistoryRow.value?.faceImage,
|
|
490
479
|
unit: selectedHistoryRow.value?.unit || "N/A",
|
|
491
480
|
method: getAccessMethod({}, { payload: { rawAccessLog: log } }),
|
|
492
481
|
lastAccessTime: formatDate(getAccessLogTime(log, {})),
|
|
@@ -700,14 +689,6 @@ async function loadDashboard() {
|
|
|
700
689
|
serverPageRange.value = String(
|
|
701
690
|
hidAccessLogResponse?.pageRange ?? hidAccessLogResponse?.data?.pageRange ?? "",
|
|
702
691
|
);
|
|
703
|
-
const accessLogUserIds = getAccessLogUserIds(liveItems);
|
|
704
|
-
const accessLogImageCandidates = [...accessLogUserIds].map((hidUserId) => ({
|
|
705
|
-
hidUserId: String(hidUserId),
|
|
706
|
-
}));
|
|
707
|
-
await loadUserImages(
|
|
708
|
-
[...readerUsers, ...accessLogImageCandidates],
|
|
709
|
-
accessLogUserIds,
|
|
710
|
-
);
|
|
711
692
|
} finally {
|
|
712
693
|
if (sequence === loadSequence.value) {
|
|
713
694
|
loading.value = false;
|
|
@@ -724,47 +705,8 @@ async function loadDashboardSource<T>(request: Promise<T>, fallback: T, label: s
|
|
|
724
705
|
}
|
|
725
706
|
}
|
|
726
707
|
|
|
727
|
-
async function loadUserImages(items: HidAccessRecord[], forceUserIds = new Set<number>()) {
|
|
728
|
-
if (!selectedReaderId.value) return;
|
|
729
|
-
|
|
730
|
-
await Promise.all(items.map(async (identity) => {
|
|
731
|
-
const hidUserId = toHidNumericId(identity.hidUserId);
|
|
732
|
-
const key = String(hidUserId || "");
|
|
733
|
-
if (
|
|
734
|
-
!hidUserId ||
|
|
735
|
-
key in userImages.value ||
|
|
736
|
-
(!forceUserIds.has(hidUserId) && !hasHidImageHint(identity))
|
|
737
|
-
) return;
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
const response = await getUserImage(selectedReaderId.value, hidUserId);
|
|
741
|
-
const image = response?.data || response;
|
|
742
|
-
userImages.value[key] = image?.base64
|
|
743
|
-
? `data:${image.contentType || "image/jpeg"};base64,${image.base64}`
|
|
744
|
-
: "";
|
|
745
|
-
} catch {
|
|
746
|
-
userImages.value[key] = "";
|
|
747
|
-
}
|
|
748
|
-
}));
|
|
749
|
-
}
|
|
750
708
|
|
|
751
|
-
function getAccessLogUserIds(items: HidAccessRecord[]) {
|
|
752
|
-
return new Set(
|
|
753
|
-
items
|
|
754
|
-
.map((item) => toHidNumericId(item.user_id ?? item.userId ?? item.hidUserId))
|
|
755
|
-
.filter((userId): userId is number => userId !== undefined),
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
709
|
|
|
759
|
-
function hasHidImageHint(identity: HidAccessRecord) {
|
|
760
|
-
return Boolean(
|
|
761
|
-
identity?.metadata?.imageTimestamp ||
|
|
762
|
-
identity?.metadata?.facialData ||
|
|
763
|
-
identity?.imageTimestamp ||
|
|
764
|
-
identity?.image_timestamp ||
|
|
765
|
-
identity?.facialData,
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
710
|
|
|
769
711
|
async function onTabChange(tab: AccessTab) {
|
|
770
712
|
if (tab) {
|
|
@@ -783,7 +725,6 @@ function resetDashboardData() {
|
|
|
783
725
|
visitorRecords.value = {};
|
|
784
726
|
logs.value = [];
|
|
785
727
|
liveAccessLogs.value = [];
|
|
786
|
-
userImages.value = {};
|
|
787
728
|
administratorUserIds.value = new Set();
|
|
788
729
|
serverPages.value = 1;
|
|
789
730
|
serverPageRange.value = "";
|
|
@@ -1128,7 +1069,6 @@ function mapAccessLogToRow(rawLog: HidAccessRecord, event: HidAccessRecord, inde
|
|
|
1128
1069
|
name: getRawLogName(rawLog, event, identity),
|
|
1129
1070
|
hidUserId: formatHidUid(rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? identity?.hidUserId),
|
|
1130
1071
|
facialData: identity ? getFacialData(identity) : "N/A",
|
|
1131
|
-
faceImage: logHidUserId ? userImages.value[String(logHidUserId)] || "" : "",
|
|
1132
1072
|
unit: identity ? getUnit(identity) : "N/A",
|
|
1133
1073
|
method: getAccessMethod(identity || {}, { ...event, payload: { ...event.payload, rawAccessLog: rawLog } }),
|
|
1134
1074
|
lastAccessTime: formatDate(rawAccessTime),
|
|
@@ -1773,10 +1713,6 @@ function escapeHtml(value: unknown) {
|
|
|
1773
1713
|
border-radius: 0;
|
|
1774
1714
|
}
|
|
1775
1715
|
|
|
1776
|
-
.hid-face-avatar {
|
|
1777
|
-
border: 1px solid var(--border);
|
|
1778
|
-
background: var(--hover);
|
|
1779
|
-
}
|
|
1780
1716
|
|
|
1781
1717
|
.hid-log-name {
|
|
1782
1718
|
align-items: center;
|
|
@@ -56,16 +56,10 @@
|
|
|
56
56
|
<span class="app-cell--num">{{ item.registration || "N/A" }}</span>
|
|
57
57
|
</template>
|
|
58
58
|
|
|
59
|
+
<!-- Client requirement (2026-08-26): the face photo is not shown once
|
|
60
|
+
registration is complete. Enrollment STATE only. -->
|
|
59
61
|
<template #[`item.facialData`]="{ item }">
|
|
60
|
-
<
|
|
61
|
-
v-if="getUserImageSrc(item)"
|
|
62
|
-
size="34"
|
|
63
|
-
rounded="lg"
|
|
64
|
-
class="hid-face-avatar"
|
|
65
|
-
>
|
|
66
|
-
<v-img :src="getUserImageSrc(item)" cover />
|
|
67
|
-
</v-avatar>
|
|
68
|
-
<span v-else :class="hasFacialData(item) ? 'facial-data--available' : 'app-cell--muted'">
|
|
62
|
+
<span :class="hasFacialData(item) ? 'facial-data--available' : 'app-cell--muted'">
|
|
69
63
|
<v-icon
|
|
70
64
|
:icon="hasFacialData(item) ? 'mdi-face-recognition' : 'mdi-face-man-shimmer-outline'"
|
|
71
65
|
size="19"
|
|
@@ -208,14 +202,12 @@ type HidReaderUser = Record<string, unknown> & {
|
|
|
208
202
|
const {
|
|
209
203
|
getReaders,
|
|
210
204
|
getReaderUsers,
|
|
211
|
-
getUserImage,
|
|
212
205
|
getUserPinStatus,
|
|
213
206
|
setUserPin,
|
|
214
207
|
deleteUserPin,
|
|
215
208
|
} = useHidAmico();
|
|
216
209
|
const readers = ref<HidReader[]>([]);
|
|
217
210
|
const users = ref<HidReaderUser[]>([]);
|
|
218
|
-
const userImages = ref<Record<string, string>>({});
|
|
219
211
|
const selectedReaderId = ref("");
|
|
220
212
|
const search = ref("");
|
|
221
213
|
const userType = ref<"all" | "user" | "visitor">("all");
|
|
@@ -328,7 +320,6 @@ async function loadUsers() {
|
|
|
328
320
|
? matchingItems.slice(pageStart, pageStart + 10)
|
|
329
321
|
: matchingItems;
|
|
330
322
|
users.value = pageItems.map(toReaderUser);
|
|
331
|
-
await loadUserImages(users.value);
|
|
332
323
|
total.value = clientFiltersUserType
|
|
333
324
|
? matchingItems.length
|
|
334
325
|
: toNumber(response.total ?? data.total, users.value.length);
|
|
@@ -356,7 +347,6 @@ function goToPage(nextPage: number) {
|
|
|
356
347
|
|
|
357
348
|
function reloadFromFirstPage() {
|
|
358
349
|
page.value = 1;
|
|
359
|
-
userImages.value = {};
|
|
360
350
|
loadUsers();
|
|
361
351
|
}
|
|
362
352
|
|
|
@@ -364,31 +354,6 @@ function hasFacialData(user: HidReaderUser) {
|
|
|
364
354
|
return user.metadata?.facialEnrolled === true || Boolean(toText(user.metadata?.facialData));
|
|
365
355
|
}
|
|
366
356
|
|
|
367
|
-
async function loadUserImages(items: HidReaderUser[]) {
|
|
368
|
-
if (!selectedReaderId.value) return;
|
|
369
|
-
|
|
370
|
-
await Promise.all(items.map(async (user) => {
|
|
371
|
-
const hidUserId = toHidNumericId(user.hidUserId);
|
|
372
|
-
const key = String(hidUserId || "");
|
|
373
|
-
if (!hidUserId || key in userImages.value) return;
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const response = toRecord(await getUserImage(selectedReaderId.value, hidUserId));
|
|
377
|
-
const image = toRecord(response.data ?? response);
|
|
378
|
-
const base64 = toText(image.base64);
|
|
379
|
-
userImages.value[key] = base64
|
|
380
|
-
? `data:${toText(image.contentType) || "image/jpeg"};base64,${base64}`
|
|
381
|
-
: "";
|
|
382
|
-
} catch {
|
|
383
|
-
userImages.value[key] = "";
|
|
384
|
-
}
|
|
385
|
-
}));
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function getUserImageSrc(user: HidReaderUser) {
|
|
389
|
-
const hidUserId = toHidNumericId(user.hidUserId);
|
|
390
|
-
return (hidUserId ? userImages.value[String(hidUserId)] : "") || "";
|
|
391
|
-
}
|
|
392
357
|
|
|
393
358
|
async function openPinDialog(user: HidReaderUser) {
|
|
394
359
|
const hidUserId = toHidNumericId(user.hidUserId);
|
|
@@ -583,9 +548,6 @@ function toNumber(value: unknown, fallback: number) {
|
|
|
583
548
|
color: var(--warning, #9a6b00);
|
|
584
549
|
}
|
|
585
550
|
|
|
586
|
-
.hid-face-avatar {
|
|
587
|
-
border: 1px solid var(--border-color, #e0e3e8);
|
|
588
|
-
}
|
|
589
551
|
|
|
590
552
|
.facial-data--available {
|
|
591
553
|
align-items: center;
|
|
@@ -85,18 +85,11 @@
|
|
|
85
85
|
<span class="app-cell--strong">{{ getName(item) }}</span>
|
|
86
86
|
</template>
|
|
87
87
|
|
|
88
|
-
<!--
|
|
89
|
-
|
|
88
|
+
<!-- Client requirement (2026-08-26): the face photo is not shown once
|
|
89
|
+
registration is complete. The column reports enrollment STATE only --
|
|
90
|
+
the reader holds the image and the biometric template, we hold neither. -->
|
|
90
91
|
<template #[`item.facialData`]="{ item }">
|
|
91
|
-
<
|
|
92
|
-
v-if="getUserImageSrc(item)"
|
|
93
|
-
size="34"
|
|
94
|
-
rounded="lg"
|
|
95
|
-
class="hid-face-avatar"
|
|
96
|
-
>
|
|
97
|
-
<v-img :src="getUserImageSrc(item)" cover />
|
|
98
|
-
</v-avatar>
|
|
99
|
-
<span v-else class="app-cell--muted">{{ getFacialData(item) }}</span>
|
|
92
|
+
<span class="app-cell--muted">{{ getFacialData(item) }}</span>
|
|
100
93
|
</template>
|
|
101
94
|
|
|
102
95
|
<template #[`item.hidUserId`]="{ item }">
|
|
@@ -192,6 +185,12 @@
|
|
|
192
185
|
<span v-if="form.photoPreview" class="photo-edit-icon">
|
|
193
186
|
<v-icon icon="mdi-pencil" size="16" />
|
|
194
187
|
</span>
|
|
188
|
+
<template v-else-if="facialEnrolled">
|
|
189
|
+
<!-- Enrolled, but the photo is deliberately not shown or held.
|
|
190
|
+
Re-enrolling needs a fresh capture. -->
|
|
191
|
+
<v-icon icon="mdi-face-recognition" size="40" />
|
|
192
|
+
<span>Face Enrolled</span>
|
|
193
|
+
</template>
|
|
195
194
|
<template v-else>
|
|
196
195
|
<!-- Was `color="#a4acb5"`, the same grey on both themes.
|
|
197
196
|
`.photo-button` already sets `--muted`, so it inherits. -->
|
|
@@ -207,7 +206,7 @@
|
|
|
207
206
|
@click="triggerCameraInput"
|
|
208
207
|
/>
|
|
209
208
|
<v-list-item
|
|
210
|
-
v-if="form.photoPreview"
|
|
209
|
+
v-if="form.photoPreview || facialEnrolled"
|
|
211
210
|
title="Remove Photo"
|
|
212
211
|
class="text-error"
|
|
213
212
|
@click="removePhoto"
|
|
@@ -538,7 +537,6 @@ const {
|
|
|
538
537
|
getReaders,
|
|
539
538
|
getIdentities,
|
|
540
539
|
getReaderUsers,
|
|
541
|
-
getUserImage,
|
|
542
540
|
setUserImage,
|
|
543
541
|
deleteUserImage,
|
|
544
542
|
getUserCards,
|
|
@@ -617,7 +615,6 @@ type HidIdentityPayload = Record<string, unknown> & {
|
|
|
617
615
|
|
|
618
616
|
const readers = ref<HidReader[]>([]);
|
|
619
617
|
const users = ref<HidUser[]>([]);
|
|
620
|
-
const userImages = ref<Record<string, string>>({});
|
|
621
618
|
const administratorUserIds = ref<Set<number>>(new Set());
|
|
622
619
|
const selectedReaderId = ref("");
|
|
623
620
|
const search = ref("");
|
|
@@ -650,6 +647,20 @@ const photoInput = ref<HTMLInputElement | null>(null);
|
|
|
650
647
|
const cameraDialog = ref(false);
|
|
651
648
|
const selectedPhotoFile = ref<File | null>(null);
|
|
652
649
|
const removePhotoRequested = ref(false);
|
|
650
|
+
/**
|
|
651
|
+
* Client requirement (2026-08-26): the photo is neither shown nor kept once registration
|
|
652
|
+
* completes, so the form tracks only WHETHER this person is enrolled. Re-enrolling needs a
|
|
653
|
+
* fresh capture -- there is no stored image to re-send.
|
|
654
|
+
*/
|
|
655
|
+
const facialEnrolled = ref(false);
|
|
656
|
+
|
|
657
|
+
function hasFacialEnrollment(user?: HidUser | null) {
|
|
658
|
+
return Boolean(
|
|
659
|
+
user?.metadata?.facialEnrolled ||
|
|
660
|
+
user?.metadata?.facialData ||
|
|
661
|
+
user?.metadata?.imageTimestamp
|
|
662
|
+
);
|
|
663
|
+
}
|
|
653
664
|
const removePinRequested = ref(false);
|
|
654
665
|
const snackbar = reactive({
|
|
655
666
|
show: false,
|
|
@@ -832,8 +843,6 @@ async function loadUsers() {
|
|
|
832
843
|
serverPageRange.value = String(responseRecord.pageRange ?? responseData.pageRange ?? "");
|
|
833
844
|
if (props.cardManagement) {
|
|
834
845
|
await loadVisibleUserCards(users.value);
|
|
835
|
-
} else {
|
|
836
|
-
await loadUserImages(users.value);
|
|
837
846
|
}
|
|
838
847
|
} catch (error) {
|
|
839
848
|
console.error("Unable to load HID reader users:", error);
|
|
@@ -850,27 +859,6 @@ async function loadUsers() {
|
|
|
850
859
|
}
|
|
851
860
|
}
|
|
852
861
|
|
|
853
|
-
async function loadUserImages(items: HidUser[]) {
|
|
854
|
-
if (!selectedReaderId.value) return;
|
|
855
|
-
|
|
856
|
-
await Promise.all(
|
|
857
|
-
items.map(async (user) => {
|
|
858
|
-
const hidUserId = toHidNumericId(user.hidUserId);
|
|
859
|
-
const key = String(hidUserId || "");
|
|
860
|
-
if (!hidUserId || userImages.value[key] || user?.metadata?.photo) return;
|
|
861
|
-
|
|
862
|
-
try {
|
|
863
|
-
const response = await getUserImage(selectedReaderId.value, hidUserId);
|
|
864
|
-
const image = response?.data || response;
|
|
865
|
-
userImages.value[key] = image?.base64
|
|
866
|
-
? `data:${image.contentType || "image/jpeg"};base64,${image.base64}`
|
|
867
|
-
: "";
|
|
868
|
-
} catch {
|
|
869
|
-
userImages.value[key] = "";
|
|
870
|
-
}
|
|
871
|
-
})
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
862
|
|
|
875
863
|
function resetForm() {
|
|
876
864
|
form.reader = selectedReaderId.value || readers.value[0]?._id || "";
|
|
@@ -888,6 +876,7 @@ function resetForm() {
|
|
|
888
876
|
form.photoPreview = "";
|
|
889
877
|
selectedPhotoFile.value = null;
|
|
890
878
|
removePhotoRequested.value = false;
|
|
879
|
+
facialEnrolled.value = false;
|
|
891
880
|
form.subjectCategory = "resident";
|
|
892
881
|
form.subjectId = "";
|
|
893
882
|
}
|
|
@@ -944,7 +933,8 @@ async function openEdit(user: HidUser) {
|
|
|
944
933
|
form.accessPin = "";
|
|
945
934
|
pinEnrolled.value = Boolean(user.metadata?.pinEnrolled);
|
|
946
935
|
removePinRequested.value = false;
|
|
947
|
-
form.photoPreview =
|
|
936
|
+
form.photoPreview = "";
|
|
937
|
+
facialEnrolled.value = hasFacialEnrollment(user);
|
|
948
938
|
form.subjectCategory = user.person
|
|
949
939
|
? "resident"
|
|
950
940
|
: user.serviceProvider
|
|
@@ -966,12 +956,6 @@ async function openEdit(user: HidUser) {
|
|
|
966
956
|
// Preserve the last known status if the reader is temporarily offline.
|
|
967
957
|
}
|
|
968
958
|
|
|
969
|
-
if (!form.photoPreview) {
|
|
970
|
-
await loadUserImages([user]);
|
|
971
|
-
if (selectedUser.value?._rowKey === user._rowKey) {
|
|
972
|
-
form.photoPreview = getUserImageSrc(user);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
959
|
}
|
|
976
960
|
|
|
977
961
|
function openView(user: HidUser) {
|
|
@@ -1017,6 +1001,7 @@ function removePhoto() {
|
|
|
1017
1001
|
form.photoPreview = "";
|
|
1018
1002
|
selectedPhotoFile.value = null;
|
|
1019
1003
|
removePhotoRequested.value = true;
|
|
1004
|
+
facialEnrolled.value = false;
|
|
1020
1005
|
}
|
|
1021
1006
|
|
|
1022
1007
|
function onPhotoChange(event: Event) {
|
|
@@ -1094,7 +1079,12 @@ async function saveUser() {
|
|
|
1094
1079
|
level: form.level,
|
|
1095
1080
|
unit: form.unit,
|
|
1096
1081
|
unitLabel: buildUnitLabel(),
|
|
1097
|
-
|
|
1082
|
+
// Enrollment STATE carries forward on its own. It used to be derived from the
|
|
1083
|
+
// photo loaded into the form, which now stays empty -- editing a name would have
|
|
1084
|
+
// silently un-enrolled the person.
|
|
1085
|
+
facialEnrolled: hasFacialEnrollment(selectedUser.value),
|
|
1086
|
+
facialEnrolledAt: selectedUser.value?.metadata?.facialEnrolledAt || "",
|
|
1087
|
+
facialData: selectedUser.value?.metadata?.facialData || "",
|
|
1098
1088
|
imageTimestamp: selectedUser.value?.metadata?.imageTimestamp || "",
|
|
1099
1089
|
facialScores: selectedUser.value?.metadata?.facialScores || {},
|
|
1100
1090
|
pinEnrolled: pinEnrolled.value,
|
|
@@ -1145,6 +1135,11 @@ async function saveUser() {
|
|
|
1145
1135
|
showToast(getHidErrorMessage(error), "error");
|
|
1146
1136
|
} finally {
|
|
1147
1137
|
saving.value = false;
|
|
1138
|
+
// Discard the captured image on every path. On a part-way failure this is what stops
|
|
1139
|
+
// the photo lingering in the open form after a retry or a cancel.
|
|
1140
|
+
form.photoPreview = "";
|
|
1141
|
+
selectedPhotoFile.value = null;
|
|
1142
|
+
removePhotoRequested.value = false;
|
|
1148
1143
|
}
|
|
1149
1144
|
}
|
|
1150
1145
|
|
|
@@ -1185,13 +1180,11 @@ async function syncFacialImage(
|
|
|
1185
1180
|
}
|
|
1186
1181
|
);
|
|
1187
1182
|
const result = response?.data;
|
|
1188
|
-
userImages.value[String(hidUserId)] = form.photoPreview;
|
|
1189
1183
|
return { action: "enrolled", timestamp, scores: result?.scores };
|
|
1190
1184
|
}
|
|
1191
1185
|
|
|
1192
1186
|
if (removePhotoRequested.value) {
|
|
1193
1187
|
await deleteUserImage(readerId, hidUserId);
|
|
1194
|
-
delete userImages.value[String(hidUserId)];
|
|
1195
1188
|
return { action: "removed" };
|
|
1196
1189
|
}
|
|
1197
1190
|
|
|
@@ -1203,12 +1196,15 @@ function applyFacialResult(
|
|
|
1203
1196
|
result: FacialSyncResult
|
|
1204
1197
|
) {
|
|
1205
1198
|
if (result.action === "enrolled") {
|
|
1199
|
+
metadata.facialEnrolled = true;
|
|
1200
|
+
metadata.facialEnrolledAt = new Date().toISOString();
|
|
1206
1201
|
metadata.facialData = form.hidUserId;
|
|
1207
1202
|
metadata.imageTimestamp = String(result.timestamp || "");
|
|
1208
1203
|
metadata.facialScores = result.scores || {};
|
|
1209
1204
|
} else if (result.action === "removed") {
|
|
1205
|
+
metadata.facialEnrolled = false;
|
|
1206
|
+
metadata.facialEnrolledAt = "";
|
|
1210
1207
|
metadata.facialData = "";
|
|
1211
|
-
metadata.photo = "";
|
|
1212
1208
|
metadata.imageTimestamp = "";
|
|
1213
1209
|
metadata.facialScores = {};
|
|
1214
1210
|
}
|
|
@@ -1675,16 +1671,7 @@ function getFacialData(user: HidUser) {
|
|
|
1675
1671
|
return (
|
|
1676
1672
|
user?.metadata?.facialData ||
|
|
1677
1673
|
user?.metadata?.imageTimestamp ||
|
|
1678
|
-
(user?.metadata?.
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
function getUserImageSrc(user: HidUser) {
|
|
1683
|
-
const hidUserId = toHidNumericId(user?.hidUserId);
|
|
1684
|
-
return (
|
|
1685
|
-
user?.metadata?.photo ||
|
|
1686
|
-
(hidUserId ? userImages.value[String(hidUserId)] : "") ||
|
|
1687
|
-
""
|
|
1674
|
+
(user?.metadata?.facialEnrolled ? user.hidUserId : "N/A")
|
|
1688
1675
|
);
|
|
1689
1676
|
}
|
|
1690
1677
|
|
|
@@ -2016,10 +2003,6 @@ function toHidCard(value: unknown): HidCard {
|
|
|
2016
2003
|
box-shadow: var(--shadow-menu);
|
|
2017
2004
|
}
|
|
2018
2005
|
|
|
2019
|
-
.hid-face-avatar {
|
|
2020
|
-
border: 1px solid var(--border);
|
|
2021
|
-
background: var(--hover);
|
|
2022
|
-
}
|
|
2023
2006
|
|
|
2024
2007
|
.admin-rule-indicator {
|
|
2025
2008
|
display: inline-flex;
|
|
@@ -299,11 +299,6 @@ export default function useHidAmico() {
|
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
function getUserImage(readerId: string, hidUserId: string | number) {
|
|
303
|
-
return useNuxtApp().$api<HidApiRecord>(`${basePath}/readers/${readerId}/users/${hidUserId}/image`, {
|
|
304
|
-
method: "GET",
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
302
|
|
|
308
303
|
function setUserImage(
|
|
309
304
|
readerId: string,
|
|
@@ -525,7 +520,6 @@ export default function useHidAmico() {
|
|
|
525
520
|
getReaderConfiguration,
|
|
526
521
|
setReaderConfiguration,
|
|
527
522
|
getLogs,
|
|
528
|
-
getUserImage,
|
|
529
523
|
setUserImage,
|
|
530
524
|
deleteUserImage,
|
|
531
525
|
getUserCards,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.1.3-staging.
|
|
5
|
+
"version": "4.1.3-staging.253",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|