@7365admin1/layer-common 3.2.1-staging.72 → 3.2.2

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 (45) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/components/EntryPassInformation.vue +4 -4
  3. package/components/FeedbackMain.vue +7 -7
  4. package/components/HidAccessLogDashboard.vue +44 -119
  5. package/components/HidIdentityMapping.vue +975 -0
  6. package/components/HidIntercomManagement.vue +201 -808
  7. package/components/HidQrCodeConfiguration.vue +95 -907
  8. package/components/HidServiceSettingsPanel.vue +1 -20
  9. package/components/HidUserEnrollment.vue +81 -392
  10. package/components/Layout/NavigationDrawer.vue +2 -43
  11. package/components/NavigationItem.vue +0 -9
  12. package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
  13. package/components/OnlineFormConfigurationForm.vue +224 -620
  14. package/components/OnlineFormFill.vue +8 -36
  15. package/components/OnlineFormsConfiguration.vue +2 -6
  16. package/components/QrTemplate/CreditCardLandscape.vue +12 -19
  17. package/components/QrTemplate/PrintDialog.vue +196 -209
  18. package/components/SiteSettings.vue +2 -4
  19. package/components/VisitorForm.vue +78 -201
  20. package/components/VisitorManagement.vue +2 -1
  21. package/composables/useComment.ts +3 -3
  22. package/composables/useHidAmico.ts +0 -127
  23. package/composables/useHidNavigation.ts +7 -0
  24. package/composables/useOnlineFormPages.ts +0 -2
  25. package/composables/useSiteSettings.ts +0 -50
  26. package/composables/useWebUsb.ts +37 -127
  27. package/middleware/member.ts +4 -0
  28. package/package.json +1 -2
  29. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
  30. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  31. package/plugins/secure-member.client.ts +56 -21
  32. package/plugins/vuetify.ts +9 -37
  33. package/types/site.d.ts +0 -71
  34. package/.changeset/camera-wall-gated-endpoint.md +0 -47
  35. package/.changeset/camera-wall-plain-english.md +0 -60
  36. package/.changeset/camera-wall-selection-and-guidance.md +0 -65
  37. package/.changeset/dark-primary-contrast.md +0 -39
  38. package/components/CameraWall.vue +0 -1034
  39. package/components/CameraWallTile.vue +0 -818
  40. package/components/HidCameraCaptureDialog.vue +0 -199
  41. package/composables/useSipWebPhone.ts +0 -311
  42. package/utils/camera-wall.test.ts +0 -571
  43. package/utils/camera-wall.ts +0 -926
  44. package/utils/theme.test.ts +0 -201
  45. package/utils/theme.ts +0 -136
@@ -50,13 +50,7 @@
50
50
 
51
51
  <v-card flat border class="table-card">
52
52
  <div class="table-refresh">
53
- <v-btn
54
- icon="mdi-refresh"
55
- variant="text"
56
- density="comfortable"
57
- :loading="loading"
58
- @click="loadUsers"
59
- />
53
+ <v-btn icon="mdi-refresh" variant="text" density="comfortable" :loading="loading" @click="loadUsers" />
60
54
  <div class="page-count">{{ pageRange }}</div>
61
55
  </div>
62
56
 
@@ -72,18 +66,10 @@
72
66
  </tr>
73
67
  </thead>
74
68
  <tbody>
75
- <tr
76
- v-for="user in users"
77
- :key="user._rowKey || user._id || user.hidUserId"
78
- >
69
+ <tr v-for="user in users" :key="user._rowKey || user._id || user.hidUserId">
79
70
  <td>{{ getName(user) }}</td>
80
71
  <td>
81
- <v-avatar
82
- v-if="getUserImageSrc(user)"
83
- size="34"
84
- rounded="lg"
85
- class="hid-face-avatar"
86
- >
72
+ <v-avatar v-if="getUserImageSrc(user)" size="34" rounded="lg" class="hid-face-avatar">
87
73
  <v-img :src="getUserImageSrc(user)" cover />
88
74
  </v-avatar>
89
75
  <span v-else>{{ getFacialData(user) }}</span>
@@ -102,22 +88,13 @@
102
88
  <td class="text-right">
103
89
  <v-menu>
104
90
  <template #activator="{ props: menuProps }">
105
- <v-btn
106
- v-bind="menuProps"
107
- icon="mdi-dots-vertical"
108
- variant="text"
109
- density="comfortable"
110
- />
91
+ <v-btn v-bind="menuProps" icon="mdi-dots-vertical" variant="text" density="comfortable" />
111
92
  </template>
112
93
 
113
94
  <v-list density="compact" min-width="150">
114
95
  <v-list-item title="View" @click="openView(user)" />
115
96
  <v-list-item title="Edit" @click="openEdit(user)" />
116
- <v-list-item
117
- title="Delete"
118
- class="text-error"
119
- @click="openDelete(user)"
120
- />
97
+ <v-list-item title="Delete" class="text-error" @click="openDelete(user)" />
121
98
  </v-list>
122
99
  </v-menu>
123
100
  </td>
@@ -132,26 +109,8 @@
132
109
 
133
110
  <div class="table-footer">
134
111
  <span>{{ pageRange }}</span>
135
- <v-btn
136
- icon="mdi-chevron-left"
137
- variant="text"
138
- density="comfortable"
139
- :disabled="page <= 1"
140
- @click="
141
- page--;
142
- loadUsers();
143
- "
144
- />
145
- <v-btn
146
- icon="mdi-chevron-right"
147
- variant="text"
148
- density="comfortable"
149
- :disabled="page >= pages"
150
- @click="
151
- page++;
152
- loadUsers();
153
- "
154
- />
112
+ <v-btn icon="mdi-chevron-left" variant="text" density="comfortable" :disabled="page <= 1" @click="page--; loadUsers()" />
113
+ <v-btn icon="mdi-chevron-right" variant="text" density="comfortable" :disabled="page >= pages" @click="page++; loadUsers()" />
155
114
  </div>
156
115
  </v-card>
157
116
 
@@ -166,12 +125,7 @@
166
125
  <v-menu>
167
126
  <template #activator="{ props: menuProps }">
168
127
  <button v-bind="menuProps" class="photo-button" type="button">
169
- <v-img
170
- v-if="form.photoPreview"
171
- :src="form.photoPreview"
172
- cover
173
- class="photo-preview"
174
- />
128
+ <v-img v-if="form.photoPreview" :src="form.photoPreview" cover class="photo-preview" />
175
129
  <span v-if="form.photoPreview" class="photo-edit-icon">
176
130
  <v-icon icon="mdi-pencil" size="16" />
177
131
  </span>
@@ -183,25 +137,10 @@
183
137
  </template>
184
138
  <v-list density="compact" min-width="190">
185
139
  <v-list-item title="Browse" @click="triggerPhotoInput" />
186
- <v-list-item
187
- title="Take Photo using Camera"
188
- @click="triggerCameraInput"
189
- />
190
- <v-list-item
191
- v-if="form.photoPreview"
192
- title="Remove Photo"
193
- class="text-error"
194
- @click="removePhoto"
195
- />
140
+ <v-list-item title="Take Photo using Camera" @click="triggerPhotoInput" />
196
141
  </v-list>
197
142
  </v-menu>
198
- <input
199
- ref="photoInput"
200
- type="file"
201
- accept="image/*"
202
- class="hidden-input"
203
- @change="onPhotoChange"
204
- />
143
+ <input ref="photoInput" type="file" accept="image/*" class="hidden-input" @change="onPhotoChange" />
205
144
  </div>
206
145
 
207
146
  <div class="field-label">Name <span>*</span></div>
@@ -264,61 +203,31 @@
264
203
  </v-card-text>
265
204
 
266
205
  <v-card-actions class="pa-0">
267
- <v-btn
268
- class="text-none action-cancel"
269
- variant="flat"
270
- height="44"
271
- @click="formDialog = false"
272
- >Cancel</v-btn
273
- >
274
- <v-btn
275
- class="text-none action-submit"
276
- variant="flat"
277
- height="44"
278
- :loading="saving"
279
- @click="saveUser"
280
- >
206
+ <v-btn class="text-none action-cancel" variant="flat" height="44" @click="formDialog = false">Cancel</v-btn>
207
+ <v-btn class="text-none action-submit" variant="flat" height="44" :loading="saving" @click="saveUser">
281
208
  Save
282
209
  </v-btn>
283
210
  </v-card-actions>
284
211
  </v-card>
285
212
  </v-dialog>
286
213
 
287
- <HidCameraCaptureDialog v-model="cameraDialog" @capture="onCameraCapture" />
288
-
289
214
  <v-dialog v-model="viewDialog" max-width="430">
290
215
  <v-card rounded="lg">
291
216
  <v-card-title class="small-title">User Information</v-card-title>
292
217
  <v-card-text>
293
218
  <div class="detail-grid">
294
219
  <span>Name</span><strong>{{ getName(selectedUser) }}</strong>
295
- <span>User ID</span
296
- ><strong>{{
297
- selectedUser ? formatHidUid(selectedUser.hidUserId) : "N/A"
298
- }}</strong>
299
- <span>Registration No.</span
300
- ><strong>{{ selectedUser?.registration || "N/A" }}</strong>
301
- <span>Last access time</span
302
- ><strong>{{
303
- formatDate(selectedUser?.metadata?.lastAccessAt)
304
- }}</strong>
305
- <span>Status</span
306
- ><strong>{{
307
- selectedUser ? getMappingStatus(selectedUser) : "N/A"
308
- }}</strong>
309
- <span>Panic Pin</span
310
- ><strong>{{ selectedUser?.cardNo || "N/A" }}</strong>
311
- <span>Private Password</span
312
- ><strong>{{
313
- selectedUser?.metadata?.pinPassword ? "********" : "N/A"
314
- }}</strong>
220
+ <span>User ID</span><strong>{{ selectedUser ? formatHidUid(selectedUser.hidUserId) : "N/A" }}</strong>
221
+ <span>Registration No.</span><strong>{{ selectedUser?.registration || "N/A" }}</strong>
222
+ <span>Last access time</span><strong>{{ formatDate(selectedUser?.metadata?.lastAccessAt) }}</strong>
223
+ <span>Status</span><strong>{{ selectedUser ? getMappingStatus(selectedUser) : "N/A" }}</strong>
224
+ <span>Panic Pin</span><strong>{{ selectedUser?.cardNo || "N/A" }}</strong>
225
+ <span>Private Password</span><strong>{{ selectedUser?.metadata?.pinPassword ? "********" : "N/A" }}</strong>
315
226
  </div>
316
227
  </v-card-text>
317
228
  <v-card-actions>
318
229
  <v-spacer />
319
- <v-btn class="text-none" variant="text" @click="viewDialog = false"
320
- >Close</v-btn
321
- >
230
+ <v-btn class="text-none" variant="text" @click="viewDialog = false">Close</v-btn>
322
231
  </v-card-actions>
323
232
  </v-card>
324
233
  </v-dialog>
@@ -329,26 +238,12 @@
329
238
  <v-card-text class="text-body-2">
330
239
  Are you sure you want to permanently delete this HID user?
331
240
  <div class="text-caption text-medium-emphasis mt-2">
332
- This action will remove the user from the HID reader and mark the
333
- identity as deleted.
241
+ This action will remove the user from the HID reader and mark the identity as deleted.
334
242
  </div>
335
243
  </v-card-text>
336
244
  <v-card-actions class="pa-0">
337
- <v-btn
338
- class="text-none action-cancel"
339
- variant="flat"
340
- height="44"
341
- @click="deleteDialog = false"
342
- >Cancel</v-btn
343
- >
344
- <v-btn
345
- class="text-none action-submit"
346
- variant="flat"
347
- height="44"
348
- :loading="deleting"
349
- @click="deleteUser"
350
- >Delete</v-btn
351
- >
245
+ <v-btn class="text-none action-cancel" variant="flat" height="44" @click="deleteDialog = false">Cancel</v-btn>
246
+ <v-btn class="text-none action-submit" variant="flat" height="44" :loading="deleting" @click="deleteUser">Delete</v-btn>
352
247
  </v-card-actions>
353
248
  </v-card>
354
249
  </v-dialog>
@@ -375,8 +270,6 @@ const {
375
270
  getReaders,
376
271
  getIdentities,
377
272
  getUserImage,
378
- setUserImage,
379
- deleteUserImage,
380
273
  runObjectOperation,
381
274
  createIdentity,
382
275
  updateIdentity,
@@ -403,9 +296,6 @@ const deleteDialog = ref(false);
403
296
  const selectedUser = ref<Record<string, any> | null>(null);
404
297
  const showPassword = ref(false);
405
298
  const photoInput = ref<HTMLInputElement | null>(null);
406
- const cameraDialog = ref(false);
407
- const selectedPhotoFile = ref<File | null>(null);
408
- const removePhotoRequested = ref(false);
409
299
  const snackbar = reactive({
410
300
  show: false,
411
301
  color: "success",
@@ -431,7 +321,7 @@ const readerOptions = computed(() =>
431
321
  readers.value.map((reader) => ({
432
322
  title: reader.name || reader.deviceId || reader._id,
433
323
  value: reader._id,
434
- }))
324
+ })),
435
325
  );
436
326
 
437
327
  const pageRange = computed(() => {
@@ -445,7 +335,7 @@ const pageRange = computed(() => {
445
335
  watch(
446
336
  () => props.site,
447
337
  () => init(),
448
- { immediate: true }
338
+ { immediate: true },
449
339
  );
450
340
 
451
341
  async function init() {
@@ -456,8 +346,7 @@ async function init() {
456
346
  async function loadReaders() {
457
347
  if (!props.site) return;
458
348
  const response = await getReaders({ site: props.site, page: 1, limit: 100 });
459
- readers.value =
460
- response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
349
+ readers.value = response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
461
350
  if (!selectedReaderId.value && readers.value.length) {
462
351
  selectedReaderId.value = readers.value[0]._id;
463
352
  }
@@ -486,19 +375,12 @@ async function loadUsers() {
486
375
  limit: 500,
487
376
  offset: 0,
488
377
  }),
489
- isAdministratorIdentity()
490
- ? loadAdministratorRoles(selectedReaderId.value)
491
- : Promise.resolve(null),
378
+ isAdministratorIdentity() ? loadAdministratorRoles(selectedReaderId.value) : Promise.resolve(null),
492
379
  ]);
493
380
 
494
- const identities =
495
- identityResponse?.items ??
496
- identityResponse?.data?.items ??
497
- identityResponse?.data?.identities ??
498
- [];
381
+ const identities = identityResponse?.items ?? identityResponse?.data?.items ?? identityResponse?.data?.identities ?? [];
499
382
  const adminRoleIds = getAdministratorRoleUserIds(roleResponse);
500
383
  const readerUsers = normalizeHidUsers(readerResponse).filter((user) => {
501
- if (Number(user.userTypeId) === 1) return false;
502
384
  if (!isAdministratorIdentity()) return true;
503
385
  const hidUserId = toHidNumericId(user.hidUserId);
504
386
  return Boolean(hidUserId && adminRoleIds.has(hidUserId));
@@ -527,23 +409,21 @@ async function loadUsers() {
527
409
  async function loadUserImages(items: Record<string, any>[]) {
528
410
  if (!selectedReaderId.value) return;
529
411
 
530
- await Promise.all(
531
- items.map(async (user) => {
532
- const hidUserId = toHidNumericId(user.hidUserId);
533
- const key = String(hidUserId || "");
534
- if (!hidUserId || userImages.value[key] || user?.metadata?.photo) return;
535
-
536
- try {
537
- const response = await getUserImage(selectedReaderId.value, hidUserId);
538
- const image = response?.data || response;
539
- userImages.value[key] = image?.base64
540
- ? `data:${image.contentType || "image/jpeg"};base64,${image.base64}`
541
- : "";
542
- } catch {
543
- userImages.value[key] = "";
544
- }
545
- })
546
- );
412
+ await Promise.all(items.map(async (user) => {
413
+ const hidUserId = toHidNumericId(user.hidUserId);
414
+ const key = String(hidUserId || "");
415
+ if (!hidUserId || userImages.value[key] || user?.metadata?.photo) return;
416
+
417
+ try {
418
+ const response = await getUserImage(selectedReaderId.value, hidUserId);
419
+ const image = response?.data || response;
420
+ userImages.value[key] = image?.base64
421
+ ? `data:${image.contentType || "image/jpeg"};base64,${image.base64}`
422
+ : "";
423
+ } catch {
424
+ userImages.value[key] = "";
425
+ }
426
+ }));
547
427
  }
548
428
 
549
429
  function resetForm() {
@@ -557,8 +437,6 @@ function resetForm() {
557
437
  form.cardNo = "";
558
438
  form.pinPassword = "";
559
439
  form.photoPreview = "";
560
- selectedPhotoFile.value = null;
561
- removePhotoRequested.value = false;
562
440
  }
563
441
 
564
442
  async function openEnroll() {
@@ -585,8 +463,6 @@ async function openEdit(user: Record<string, any>) {
585
463
  form.cardNo = user.cardNo ?? "";
586
464
  form.pinPassword = user.metadata?.pinPassword ?? "";
587
465
  form.photoPreview = getUserImageSrc(user);
588
- selectedPhotoFile.value = null;
589
- removePhotoRequested.value = false;
590
466
  formDialog.value = true;
591
467
 
592
468
  if (!form.photoPreview) {
@@ -611,53 +487,9 @@ function triggerPhotoInput() {
611
487
  photoInput.value?.click();
612
488
  }
613
489
 
614
- function triggerCameraInput() {
615
- cameraDialog.value = true;
616
- }
617
-
618
- function onCameraCapture(dataUrl: string) {
619
- const [header, encodedData] = dataUrl.split(",", 2);
620
- if (!header || !encodedData) {
621
- showToast("Unable to capture the facial image.", "error");
622
- return;
623
- }
624
-
625
- const mimeType = header.match(/^data:([^;]+);base64$/)?.[1] || "image/jpeg";
626
- const decodedData = atob(encodedData);
627
- const bytes = new Uint8Array(decodedData.length);
628
- for (let index = 0; index < decodedData.length; index += 1) {
629
- bytes[index] = decodedData.charCodeAt(index);
630
- }
631
-
632
- selectedPhotoFile.value = new File([bytes], `hid-facial-${Date.now()}.jpg`, {
633
- type: mimeType,
634
- });
635
- form.photoPreview = dataUrl;
636
- removePhotoRequested.value = false;
637
- }
638
-
639
- function removePhoto() {
640
- form.photoPreview = "";
641
- selectedPhotoFile.value = null;
642
- removePhotoRequested.value = true;
643
- }
644
-
645
490
  function onPhotoChange(event: Event) {
646
- const input = event.target as HTMLInputElement;
647
- const file = input.files?.[0];
648
- input.value = "";
491
+ const file = (event.target as HTMLInputElement).files?.[0];
649
492
  if (!file) return;
650
- if (!["image/jpeg", "image/png"].includes(file.type)) {
651
- showToast("Facial enrollment only accepts JPG or PNG images.", "error");
652
- return;
653
- }
654
- if (file.size > 2 * 1024 * 1024) {
655
- showToast("Facial enrollment images must be smaller than 2 MB.", "error");
656
- return;
657
- }
658
-
659
- selectedPhotoFile.value = file;
660
- removePhotoRequested.value = false;
661
493
  const reader = new FileReader();
662
494
  reader.onload = () => {
663
495
  form.photoPreview = String(reader.result || "");
@@ -666,8 +498,7 @@ function onPhotoChange(event: Event) {
666
498
  }
667
499
 
668
500
  async function saveUser() {
669
- const readerId =
670
- selectedReaderId.value || form.reader || readers.value[0]?._id;
501
+ const readerId = selectedReaderId.value || form.reader || readers.value[0]?._id;
671
502
  if (!readerId || !form.name || !form.hidUserId || !form.registration) {
672
503
  showToast("Please complete the required HID user fields.", "error");
673
504
  return;
@@ -689,13 +520,7 @@ async function saveUser() {
689
520
  hidUserId: String(hidUser.id),
690
521
  registration: form.registration,
691
522
  cardNo: form.cardNo,
692
- type: props.identityType as
693
- | "resident"
694
- | "staff"
695
- | "contractor"
696
- | "visitor"
697
- | "admin"
698
- | "unknown",
523
+ type: props.identityType as "resident" | "staff" | "contractor" | "visitor" | "admin" | "unknown",
699
524
  status: "active" as const,
700
525
  metadata: {
701
526
  name: form.name,
@@ -705,8 +530,6 @@ async function saveUser() {
705
530
  unitLabel: buildUnitLabel(),
706
531
  facialData: form.photoPreview ? form.hidUserId : "",
707
532
  photo: form.photoPreview,
708
- imageTimestamp: selectedUser.value?.metadata?.imageTimestamp || "",
709
- facialScores: selectedUser.value?.metadata?.facialScores || {},
710
533
  pinPassword: form.pinPassword,
711
534
  },
712
535
  };
@@ -714,8 +537,6 @@ async function saveUser() {
714
537
  if (selectedUser.value) {
715
538
  await updateHidUserOnReader(readerId, hidUser);
716
539
  await syncHidUserRole(readerId, hidUser.id, isAdministratorIdentity());
717
- const facialResult = await syncFacialImage(readerId, hidUser.id);
718
- applyFacialResult(payload.metadata, facialResult);
719
540
  if (selectedUser.value._id) {
720
541
  await updateIdentity(selectedUser.value._id, payload);
721
542
  } else {
@@ -724,19 +545,13 @@ async function saveUser() {
724
545
  } else {
725
546
  await createHidUserOnReader(readerId, hidUser);
726
547
  await syncHidUserRole(readerId, hidUser.id, isAdministratorIdentity());
727
- const facialResult = await syncFacialImage(readerId, hidUser.id);
728
- applyFacialResult(payload.metadata, facialResult);
729
548
  await saveIdentityOnReader(readerId, payload);
730
549
  selectedReaderId.value = readerId;
731
550
  }
732
551
 
733
552
  formDialog.value = false;
734
553
  await loadUsers();
735
- showToast(
736
- selectedUser.value?._id
737
- ? "HID user updated on reader."
738
- : "HID user enrolled on reader."
739
- );
554
+ showToast(selectedUser.value?._id ? "HID user updated on reader." : "HID user enrolled on reader.");
740
555
  } catch (error: any) {
741
556
  console.error("Unable to save HID user:", error);
742
557
  showToast(getHidErrorMessage(error), "error");
@@ -745,67 +560,12 @@ async function saveUser() {
745
560
  }
746
561
  }
747
562
 
748
- type FacialSyncResult = {
749
- action: "none" | "enrolled" | "removed";
750
- timestamp?: number;
751
- scores?: Record<string, unknown>;
752
- };
753
-
754
- async function syncFacialImage(
755
- readerId: string,
756
- hidUserId: number
757
- ): Promise<FacialSyncResult> {
758
- if (selectedPhotoFile.value) {
759
- const timestamp = Math.floor(Date.now() / 1000);
760
- const response = await setUserImage(
761
- readerId,
762
- hidUserId,
763
- selectedPhotoFile.value,
764
- {
765
- timestamp,
766
- match: true,
767
- }
768
- );
769
- const result = response?.data;
770
- userImages.value[String(hidUserId)] = form.photoPreview;
771
- return { action: "enrolled", timestamp, scores: result?.scores };
772
- }
773
-
774
- if (removePhotoRequested.value) {
775
- await deleteUserImage(readerId, hidUserId);
776
- delete userImages.value[String(hidUserId)];
777
- return { action: "removed" };
778
- }
779
-
780
- return { action: "none" };
781
- }
782
-
783
- function applyFacialResult(
784
- metadata: Record<string, unknown>,
785
- result: FacialSyncResult
786
- ) {
787
- if (result.action === "enrolled") {
788
- metadata.facialData = form.hidUserId;
789
- metadata.imageTimestamp = String(result.timestamp || "");
790
- metadata.facialScores = result.scores || {};
791
- } else if (result.action === "removed") {
792
- metadata.facialData = "";
793
- metadata.photo = "";
794
- metadata.imageTimestamp = "";
795
- metadata.facialScores = {};
796
- }
797
- }
798
-
799
563
  async function deleteUser() {
800
564
  if (!selectedUser.value) return;
801
565
 
802
566
  deleting.value = true;
803
567
  try {
804
- await syncHidUserRole(
805
- selectedReaderId.value,
806
- toHidNumericId(selectedUser.value.hidUserId),
807
- false
808
- );
568
+ await syncHidUserRole(selectedReaderId.value, toHidNumericId(selectedUser.value.hidUserId), false);
809
569
  await deleteHidUserFromReader(selectedReaderId.value, selectedUser.value);
810
570
  if (selectedUser.value._id) {
811
571
  await deleteIdentity(selectedUser.value._id);
@@ -821,14 +581,8 @@ async function deleteUser() {
821
581
  }
822
582
  }
823
583
 
824
- async function saveIdentityOnReader(
825
- readerId: string,
826
- payload: Record<string, any>
827
- ) {
828
- const existingIdentity = await findExistingIdentityOnReader(
829
- readerId,
830
- payload
831
- );
584
+ async function saveIdentityOnReader(readerId: string, payload: Record<string, any>) {
585
+ const existingIdentity = await findExistingIdentityOnReader(readerId, payload);
832
586
  if (existingIdentity?._id) {
833
587
  await updateIdentity(existingIdentity._id, payload);
834
588
  return;
@@ -840,47 +594,31 @@ async function saveIdentityOnReader(
840
594
  const message = getHidErrorMessage(error);
841
595
  if (!message.toLowerCase().includes("already exists")) throw error;
842
596
 
843
- const duplicatedIdentity = await findExistingIdentityOnReader(
844
- readerId,
845
- payload
846
- );
597
+ const duplicatedIdentity = await findExistingIdentityOnReader(readerId, payload);
847
598
  if (!duplicatedIdentity?._id) throw error;
848
599
  await updateIdentity(duplicatedIdentity._id, payload);
849
600
  }
850
601
  }
851
602
 
852
- async function findExistingIdentityOnReader(
853
- readerId: string,
854
- payload: Record<string, any>
855
- ) {
603
+ async function findExistingIdentityOnReader(readerId: string, payload: Record<string, any>) {
856
604
  const response = await getIdentities(readerId, {
857
605
  page: 1,
858
606
  limit: 500,
859
607
  search: payload.hidUserId || payload.registration || payload.cardNo || "",
860
608
  });
861
- const identities =
862
- response?.items ??
863
- response?.data?.items ??
864
- response?.data?.identities ??
865
- [];
609
+ const identities = response?.items ?? response?.data?.items ?? response?.data?.identities ?? [];
866
610
  const hidUserId = String(payload.hidUserId || "");
867
611
  const registration = String(payload.registration || "");
868
612
  const cardNo = String(payload.cardNo || "");
869
613
 
870
- return identities.find(
871
- (identity: Record<string, any>) =>
872
- String(identity.hidUserId || "") === hidUserId ||
873
- Boolean(
874
- registration && String(identity.registration || "") === registration
875
- ) ||
876
- Boolean(cardNo && String(identity.cardNo || "") === cardNo)
614
+ return identities.find((identity: Record<string, any>) =>
615
+ String(identity.hidUserId || "") === hidUserId ||
616
+ Boolean(registration && String(identity.registration || "") === registration) ||
617
+ Boolean(cardNo && String(identity.cardNo || "") === cardNo),
877
618
  );
878
619
  }
879
620
 
880
- async function createHidUserOnReader(
881
- readerId: string,
882
- user: Record<string, any>
883
- ) {
621
+ async function createHidUserOnReader(readerId: string, user: Record<string, any>) {
884
622
  const existing = await getHidUserFromReader(readerId, user.id);
885
623
  if (existing) {
886
624
  await updateHidUserOnReader(readerId, user);
@@ -910,10 +648,7 @@ async function getHidUserFromReader(readerId: string, id: unknown) {
910
648
  return normalizeHidUsers(response)[0];
911
649
  }
912
650
 
913
- async function updateHidUserOnReader(
914
- readerId: string,
915
- user: Record<string, any>
916
- ) {
651
+ async function updateHidUserOnReader(readerId: string, user: Record<string, any>) {
917
652
  const { id, ...values } = user;
918
653
  await runObjectOperation(readerId, {
919
654
  operation: "modify",
@@ -927,10 +662,7 @@ async function updateHidUserOnReader(
927
662
  });
928
663
  }
929
664
 
930
- async function deleteHidUserFromReader(
931
- readerId: string,
932
- user: Record<string, any>
933
- ) {
665
+ async function deleteHidUserFromReader(readerId: string, user: Record<string, any>) {
934
666
  const id = toHidNumericId(user.hidUserId);
935
667
  if (!readerId || !id) return;
936
668
 
@@ -945,11 +677,7 @@ async function deleteHidUserFromReader(
945
677
  });
946
678
  }
947
679
 
948
- async function syncHidUserRole(
949
- readerId: string,
950
- hidUserId: unknown,
951
- isAdministrator: boolean
952
- ) {
680
+ async function syncHidUserRole(readerId: string, hidUserId: unknown, isAdministrator: boolean) {
953
681
  const userId = toHidNumericId(hidUserId);
954
682
  if (!readerId || !userId) return;
955
683
 
@@ -1043,17 +771,14 @@ function getAdministratorRoleUserIds(response: Record<string, any> | null) {
1043
771
  return new Set(
1044
772
  (Array.isArray(roles) ? roles : [])
1045
773
  .map((role) => toHidNumericId(role.user_id))
1046
- .filter((userId): userId is number => Boolean(userId))
774
+ .filter((userId): userId is number => Boolean(userId)),
1047
775
  );
1048
776
  }
1049
777
 
1050
778
  function toHidUserObject(identity: Record<string, any>) {
1051
779
  return {
1052
780
  id: toHidNumericId(identity.hidUserId),
1053
- name:
1054
- identity.metadata?.name ||
1055
- identity.name ||
1056
- `User ${identity.hidUserId || ""}`.trim(),
781
+ name: identity.metadata?.name || identity.name || `User ${identity.hidUserId || ""}`.trim(),
1057
782
  registration: identity.registration || "",
1058
783
  };
1059
784
  }
@@ -1082,40 +807,23 @@ function getName(user?: Record<string, any> | null) {
1082
807
  }
1083
808
 
1084
809
  function getUnit(user?: Record<string, any> | null) {
1085
- return (
1086
- user?.metadata?.unitLabel ||
1087
- [user?.metadata?.block, user?.metadata?.level, user?.metadata?.unit]
1088
- .filter(Boolean)
1089
- .join("/") ||
1090
- "N/A"
1091
- );
810
+ return user?.metadata?.unitLabel || [user?.metadata?.block, user?.metadata?.level, user?.metadata?.unit].filter(Boolean).join("/") || "N/A";
1092
811
  }
1093
812
 
1094
813
  function getFacialData(user: Record<string, any>) {
1095
- return (
1096
- user?.metadata?.facialData ||
1097
- user?.metadata?.imageTimestamp ||
1098
- (user?.metadata?.photo ? user.hidUserId : "N/A")
1099
- );
814
+ return user?.metadata?.facialData || user?.metadata?.imageTimestamp || (user?.metadata?.photo ? user.hidUserId : "N/A");
1100
815
  }
1101
816
 
1102
817
  function getUserImageSrc(user: Record<string, any>) {
1103
818
  const hidUserId = toHidNumericId(user?.hidUserId);
1104
- return (
1105
- user?.metadata?.photo ||
1106
- (hidUserId ? userImages.value[String(hidUserId)] : "") ||
1107
- ""
1108
- );
819
+ return user?.metadata?.photo || (hidUserId ? userImages.value[String(hidUserId)] : "") || "";
1109
820
  }
1110
821
 
1111
822
  async function getNextUid() {
1112
823
  const readerIds = await getExistingHidReaderIds();
1113
824
  const maxNumber = users.value.reduce((max, user) => {
1114
825
  const match = String(user.hidUserId ?? "").match(/^UID(\d+)$/i);
1115
- return Math.max(
1116
- max,
1117
- match ? Number(match[1]) : toHidNumericId(user.hidUserId) || 0
1118
- );
826
+ return Math.max(max, match ? Number(match[1]) : toHidNumericId(user.hidUserId) || 0);
1119
827
  }, 0);
1120
828
  const nextNumber = Math.max(maxNumber, ...readerIds, 0) + 1;
1121
829
  return `UID${String(nextNumber).padStart(6, "0")}`;
@@ -1129,15 +837,10 @@ async function getNextRegistration() {
1129
837
  const numericRegistrations = registrations
1130
838
  .map((registration) => {
1131
839
  const match = registration.match(/^(\d+)$/);
1132
- return match
1133
- ? { value: Number(match[1]), width: match[1].length }
1134
- : undefined;
840
+ return match ? { value: Number(match[1]), width: match[1].length } : undefined;
1135
841
  })
1136
842
  .filter((item): item is { value: number; width: number } => Boolean(item));
1137
- const maxRegistration = numericRegistrations.reduce(
1138
- (max, item) => Math.max(max, item.value),
1139
- 0
1140
- );
843
+ const maxRegistration = numericRegistrations.reduce((max, item) => Math.max(max, item.value), 0);
1141
844
  const width = Math.max(6, ...numericRegistrations.map((item) => item.width));
1142
845
 
1143
846
  return String(maxRegistration + 1).padStart(width, "0");
@@ -1179,14 +882,11 @@ function normalizeHidUsers(response: Record<string, any>) {
1179
882
  hidUserId: String(user.id || ""),
1180
883
  rawHidUserId: user.id,
1181
884
  registration: user.registration,
1182
- userTypeId: user.user_type_id,
1183
885
  cardNo: user.card_value || user.cardNo || "",
1184
886
  name: user.name,
1185
887
  metadata: {
1186
888
  name: user.name,
1187
- imageTimestamp: user.image_timestamp
1188
- ? String(user.image_timestamp)
1189
- : "",
889
+ imageTimestamp: user.image_timestamp ? String(user.image_timestamp) : "",
1190
890
  lastAccessAt: fromUnixSeconds(user.last_access),
1191
891
  hidReaderSource: true,
1192
892
  },
@@ -1194,10 +894,7 @@ function normalizeHidUsers(response: Record<string, any>) {
1194
894
  : [];
1195
895
  }
1196
896
 
1197
- function mergeReaderUsersWithIdentities(
1198
- readerUsers: Record<string, any>[],
1199
- identities: Record<string, any>[]
1200
- ) {
897
+ function mergeReaderUsersWithIdentities(readerUsers: Record<string, any>[], identities: Record<string, any>[]) {
1201
898
  const identityById = new Map<string, Record<string, any>>();
1202
899
  identities.forEach((identity) => {
1203
900
  const id = toHidNumericId(identity.hidUserId);
@@ -1205,9 +902,7 @@ function mergeReaderUsersWithIdentities(
1205
902
  });
1206
903
 
1207
904
  const merged = readerUsers.map((readerUser) => {
1208
- const identity = identityById.get(
1209
- String(toHidNumericId(readerUser.hidUserId))
1210
- );
905
+ const identity = identityById.get(String(toHidNumericId(readerUser.hidUserId)));
1211
906
  if (!identity) return readerUser;
1212
907
 
1213
908
  return {
@@ -1222,11 +917,8 @@ function mergeReaderUsersWithIdentities(
1222
917
  metadata: {
1223
918
  ...readerUser.metadata,
1224
919
  ...identity.metadata,
1225
- imageTimestamp:
1226
- identity.metadata?.imageTimestamp ||
1227
- readerUser.metadata?.imageTimestamp,
1228
- lastAccessAt:
1229
- identity.metadata?.lastAccessAt || readerUser.metadata?.lastAccessAt,
920
+ imageTimestamp: identity.metadata?.imageTimestamp || readerUser.metadata?.imageTimestamp,
921
+ lastAccessAt: identity.metadata?.lastAccessAt || readerUser.metadata?.lastAccessAt,
1230
922
  },
1231
923
  };
1232
924
  });
@@ -1237,16 +929,13 @@ function mergeReaderUsersWithIdentities(
1237
929
  function filterUsers(items: Record<string, any>[]) {
1238
930
  const searchText = search.value.trim().toLowerCase();
1239
931
  return items.filter((item) => {
1240
- const matchesSearch =
1241
- !searchText ||
1242
- [getName(item), item.hidUserId, item.registration, item.cardNo].some(
1243
- (value) =>
1244
- String(value || "")
1245
- .toLowerCase()
1246
- .includes(searchText)
1247
- );
1248
- const matchesStatus =
1249
- status.value === "Status" || getMappingStatus(item) === status.value;
932
+ const matchesSearch = !searchText || [
933
+ getName(item),
934
+ item.hidUserId,
935
+ item.registration,
936
+ item.cardNo,
937
+ ].some((value) => String(value || "").toLowerCase().includes(searchText));
938
+ const matchesStatus = status.value === "Status" || getMappingStatus(item) === status.value;
1250
939
  return matchesSearch && matchesStatus;
1251
940
  });
1252
941
  }
@@ -1306,7 +995,7 @@ function getHidErrorMessage(error: any) {
1306
995
  data?.statusMessage ||
1307
996
  error?.statusMessage ||
1308
997
  error?.message ||
1309
- "Unable to save HID user."
998
+ "Unable to save HID user.",
1310
999
  );
1311
1000
  }
1312
1001
  </script>