@7365admin1/layer-common 3.0.24 → 3.0.26

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 (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/components/AccessCardAddForm.vue +2 -2
  3. package/components/AccessCardAssignToUnitForm.vue +2 -2
  4. package/components/AccessCardDetailsDialog.vue +31 -1
  5. package/components/AccessManagement.vue +16 -6
  6. package/components/AddPassKeyToVisitor.vue +346 -225
  7. package/components/BuildingUnitFormEdit.vue +1 -1
  8. package/components/DashboardMain.vue +124 -60
  9. package/components/DocumentManagement.vue +294 -1
  10. package/components/HidIntercomManagement.vue +186 -0
  11. package/components/HidReaderManagement.vue +21 -1
  12. package/components/Nfc/NFCPatrolReportMain.vue +1 -0
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +2 -1
  14. package/components/Nfc/PatrolReport/ReportFilters.vue +3 -1
  15. package/components/RolePermissionMain.vue +34 -5
  16. package/components/SiteSettings.vue +3 -1
  17. package/components/VisitorForm.vue +174 -398
  18. package/components/VisitorManagement.vue +302 -679
  19. package/components/VisitorSocketPopUp.vue +29 -1
  20. package/composables/useANPRSettings.ts +57 -8
  21. package/composables/useAccessManagement.ts +9 -1
  22. package/composables/useCleaningPermission.ts +2 -2
  23. package/composables/useCommonPermission.ts +2 -39
  24. package/composables/useDocument.ts +21 -0
  25. package/composables/useLocalAuth.ts +3 -1
  26. package/composables/useNFCPatrolLog.ts +35 -0
  27. package/composables/useNFCPatrolReport.ts +37 -4
  28. package/composables/useNFCPatrolReportFilters.ts +28 -5
  29. package/package.json +1 -1
  30. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
  31. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
  32. package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
  33. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
  34. package/services/nfcPatrolReport.ts +5 -1
  35. package/types/nfc-patrol-report.ts +7 -1
  36. package/types/visitor.d.ts +1 -0
@@ -104,6 +104,7 @@
104
104
  <v-list-item title="SIP Settings" @click="openSipDialog(row)" />
105
105
  <v-list-item title="Call Settings" @click="openCallDialog(row)" />
106
106
  <v-list-item title="Dial Code" @click="openDialCodeDialog(row)" />
107
+ <v-list-item title="Make a call" @click="openMakeCallDialog(row)" />
107
108
  </v-list>
108
109
  </v-menu>
109
110
  </td>
@@ -344,6 +345,55 @@
344
345
  </v-card>
345
346
  </v-dialog>
346
347
 
348
+ <v-dialog v-model="makeCallDialog" max-width="520" persistent>
349
+ <v-card class="form-dialog make-call-dialog" rounded="lg">
350
+ <v-card-title class="dialog-title">Make a Call</v-card-title>
351
+ <v-card-text class="dialog-body make-call-body">
352
+ <div class="make-call-field">
353
+ <div class="call-label">Address to dial</div>
354
+ <v-text-field
355
+ v-model="makeCallForm.target"
356
+ placeholder="Enter extension, address, or contact"
357
+ variant="outlined"
358
+ density="compact"
359
+ hide-details="auto"
360
+ class="call-input"
361
+ @keyup.enter="runMakeCall"
362
+ />
363
+ </div>
364
+ <div class="make-call-actions">
365
+ <v-btn
366
+ class="text-none make-call-primary"
367
+ variant="flat"
368
+ :loading="calling"
369
+ :disabled="!makeCallForm.target.trim()"
370
+ @click="runMakeCall"
371
+ >
372
+ Make a call
373
+ </v-btn>
374
+ <v-btn
375
+ class="text-none make-call-danger"
376
+ variant="flat"
377
+ :loading="hangingUp"
378
+ @click="runHangupCall"
379
+ >
380
+ Hang up call
381
+ </v-btn>
382
+ </div>
383
+ <div v-if="makeCallForm.status" class="make-call-status" :class="`is-${makeCallForm.statusType}`">
384
+ {{ makeCallForm.status }}
385
+ </div>
386
+ <div class="make-call-note">
387
+ The call will only be made if the equipment is on the idle or identification screen.
388
+ </div>
389
+ </v-card-text>
390
+ <v-card-actions class="dialog-actions pa-0">
391
+ <v-btn class="text-none action-cancel" variant="flat" height="44" @click="makeCallDialog = false">Cancel</v-btn>
392
+ <v-btn class="text-none action-submit" variant="flat" height="44" @click="makeCallDialog = false">Close</v-btn>
393
+ </v-card-actions>
394
+ </v-card>
395
+ </v-dialog>
396
+
347
397
  <v-dialog v-model="contactDialog" max-width="360" persistent>
348
398
  <v-card class="form-dialog" rounded="lg">
349
399
  <v-card-title class="dialog-title">{{ contactForm.id ? 'Edit Contact' : 'Add Contact' }}</v-card-title>
@@ -461,6 +511,8 @@ const {
461
511
  setReaderConfiguration,
462
512
  runObjectOperation,
463
513
  getIntercomStatus,
514
+ makeIntercomCall,
515
+ finalizeIntercomCall,
464
516
  } = useHidAmico();
465
517
 
466
518
  const activeTab = ref<IntercomTab>("intercom");
@@ -470,6 +522,8 @@ const contacts = ref<ContactRow[]>([]);
470
522
  const loading = ref(false);
471
523
  const saving = ref(false);
472
524
  const contactsSaving = ref(false);
525
+ const calling = ref(false);
526
+ const hangingUp = ref(false);
473
527
  const search = ref("");
474
528
  const statusFilter = ref("Status");
475
529
  const page = ref(1);
@@ -484,6 +538,7 @@ const rebootConfirmDialog = ref(false);
484
538
  const rebootingDialog = ref(false);
485
539
  const callDialog = ref(false);
486
540
  const dialCodeDialog = ref(false);
541
+ const makeCallDialog = ref(false);
487
542
  const contactDialog = ref(false);
488
543
  const contactViewDialog = ref(false);
489
544
  const deleteContactDialog = ref(false);
@@ -556,6 +611,12 @@ const dialForm = reactive({
556
611
  code: "",
557
612
  });
558
613
 
614
+ const makeCallForm = reactive({
615
+ target: "",
616
+ status: "",
617
+ statusType: "info" as MessageType,
618
+ });
619
+
559
620
  const contactForm = reactive({
560
621
  id: undefined as number | undefined,
561
622
  readerId: "",
@@ -882,6 +943,14 @@ function openDialCodeDialog(row: IntercomRow) {
882
943
  dialCodeDialog.value = true;
883
944
  }
884
945
 
946
+ function openMakeCallDialog(row: IntercomRow) {
947
+ selectedRow.value = row;
948
+ makeCallForm.target = row.extension !== "N/A" ? row.extension : "";
949
+ makeCallForm.status = "";
950
+ makeCallForm.statusType = "info";
951
+ makeCallDialog.value = true;
952
+ }
953
+
885
954
  function openContactDialog(contact?: ContactRow) {
886
955
  selectedContact.value = contact || null;
887
956
  contactForm.id = contact?.id;
@@ -947,6 +1016,51 @@ async function saveDialCode() {
947
1016
  dialCodeDialog.value = false;
948
1017
  }
949
1018
 
1019
+ async function runMakeCall() {
1020
+ if (!selectedRow.value) return;
1021
+ const target = makeCallForm.target.trim();
1022
+ if (!target) {
1023
+ makeCallForm.status = "Please enter an address to dial.";
1024
+ makeCallForm.statusType = "warning";
1025
+ return;
1026
+ }
1027
+
1028
+ calling.value = true;
1029
+ makeCallForm.status = "";
1030
+ try {
1031
+ await makeIntercomCall(selectedRow.value.reader._id, target);
1032
+ makeCallForm.status = `Calling ${target}.`;
1033
+ makeCallForm.statusType = "success";
1034
+ showMessage(`Calling ${target}.`, "success");
1035
+ } catch (error: any) {
1036
+ const message = getErrorMessage(error);
1037
+ makeCallForm.status = message;
1038
+ makeCallForm.statusType = "error";
1039
+ showMessage(message, "error");
1040
+ } finally {
1041
+ calling.value = false;
1042
+ }
1043
+ }
1044
+
1045
+ async function runHangupCall() {
1046
+ if (!selectedRow.value) return;
1047
+ hangingUp.value = true;
1048
+ makeCallForm.status = "";
1049
+ try {
1050
+ await finalizeIntercomCall(selectedRow.value.reader._id);
1051
+ makeCallForm.status = "Call ended.";
1052
+ makeCallForm.statusType = "success";
1053
+ showMessage("Intercom call ended.", "success");
1054
+ } catch (error: any) {
1055
+ const message = getErrorMessage(error);
1056
+ makeCallForm.status = message;
1057
+ makeCallForm.statusType = "error";
1058
+ showMessage(message, "error");
1059
+ } finally {
1060
+ hangingUp.value = false;
1061
+ }
1062
+ }
1063
+
950
1064
  async function savePjsip(readerId: string, payload: Record<string, string>, options: { showSuccessMessage?: boolean } = {}) {
951
1065
  saving.value = true;
952
1066
  try {
@@ -1745,6 +1859,78 @@ export default {
1745
1859
  margin-bottom: 0;
1746
1860
  }
1747
1861
 
1862
+ .make-call-dialog {
1863
+ width: 520px;
1864
+ max-width: 94vw;
1865
+ }
1866
+
1867
+ .make-call-body {
1868
+ display: grid;
1869
+ gap: 18px;
1870
+ padding: 20px 24px 26px;
1871
+ }
1872
+
1873
+ .make-call-field {
1874
+ max-width: 320px;
1875
+ }
1876
+
1877
+ .make-call-actions {
1878
+ display: grid;
1879
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
1880
+ gap: 18px;
1881
+ align-items: center;
1882
+ }
1883
+
1884
+ .make-call-primary,
1885
+ .make-call-danger {
1886
+ min-height: 46px;
1887
+ border-radius: 0;
1888
+ color: #fff;
1889
+ font-size: 14px;
1890
+ letter-spacing: 0;
1891
+ }
1892
+
1893
+ .make-call-primary {
1894
+ background: #06324f;
1895
+ }
1896
+
1897
+ .make-call-danger {
1898
+ background: #d91345;
1899
+ }
1900
+
1901
+ .make-call-status {
1902
+ padding: 10px 12px;
1903
+ border-radius: 4px;
1904
+ font-size: 12px;
1905
+ line-height: 1.4;
1906
+ }
1907
+
1908
+ .make-call-status.is-success {
1909
+ background: #e7f7ec;
1910
+ color: #17833b;
1911
+ }
1912
+
1913
+ .make-call-status.is-warning {
1914
+ background: #fff7db;
1915
+ color: #8a5b00;
1916
+ }
1917
+
1918
+ .make-call-status.is-error {
1919
+ background: #fde8e8;
1920
+ color: #b42318;
1921
+ }
1922
+
1923
+ .make-call-status.is-info {
1924
+ background: #edf4ff;
1925
+ color: #075fb7;
1926
+ }
1927
+
1928
+ .make-call-note {
1929
+ color: #5d6673;
1930
+ font-size: 13px;
1931
+ line-height: 1.45;
1932
+ }
1933
+
1748
1934
  .call-two-col {
1749
1935
  gap: 12px;
1750
1936
  }
@@ -859,12 +859,32 @@ function getFriendlyHidError(error: any) {
859
859
  return "Unable to verify the HID reader session. Please check the reader username and password, then try again.";
860
860
  }
861
861
 
862
+ if (
863
+ message.includes("status 401") ||
864
+ message.includes("401 Authorization Required") ||
865
+ lowerMessage.includes("authorization required")
866
+ ) {
867
+ return "Unable to authorize the HID gateway connection. Please check the relay credentials on the server, then try again.";
868
+ }
869
+
870
+ if (message.includes("status 403") || lowerMessage.includes("forbidden")) {
871
+ return "The HID gateway rejected this request. Please check the relay access permissions and try again.";
872
+ }
873
+
874
+ if (message.includes("status 404") || lowerMessage.includes("not found")) {
875
+ return "The HID gateway endpoint could not be found. Please check the reader Base URL and try again.";
876
+ }
877
+
878
+ if (lowerMessage.includes("<html") || lowerMessage.includes("<!doctype")) {
879
+ return "The HID gateway returned an unexpected response. Please check the relay configuration and try again.";
880
+ }
881
+
862
882
  if (message.includes("reader is not active")) {
863
883
  return "This HID reader is not active. Please enable the reader before syncing.";
864
884
  }
865
885
 
866
886
  if (message.includes("HID Amico request failed:")) {
867
- return message;
887
+ return "The HID device rejected the request. Please check the reader connection and try again.";
868
888
  }
869
889
 
870
890
  return message || "The HID device rejected the request. Please check the reader connection and try again.";
@@ -24,6 +24,7 @@
24
24
  <v-window-item value="patrol">
25
25
  <PatrolReportTab
26
26
  :active="activeTab === 'patrol'"
27
+ :site="site"
27
28
  :filters="filters"
28
29
  :options="options"
29
30
  @update:filters="updateFilters"
@@ -43,6 +43,7 @@ import ReportLocationHeader from "./ReportLocationHeader.vue";
43
43
 
44
44
  const props = defineProps<{
45
45
  active: boolean;
46
+ site: string;
46
47
  filters: NFCPatrolReportFilters;
47
48
  options: NFCPatrolReportFilterOptions;
48
49
  }>();
@@ -59,7 +60,7 @@ const {
59
60
  dialogRemarks,
60
61
  fetchReport,
61
62
  openRemarksDialog,
62
- } = useNFCPatrolReport(props.filters);
63
+ } = useNFCPatrolReport(props.filters, props.site);
63
64
 
64
65
  watch(
65
66
  () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
@@ -4,6 +4,8 @@
4
4
  <v-select
5
5
  :model-value="modelValue.route"
6
6
  :items="options.routes"
7
+ item-title="title"
8
+ item-value="value"
7
9
  label="Select Route"
8
10
  variant="outlined"
9
11
  density="comfortable"
@@ -18,7 +20,7 @@
18
20
  <v-select
19
21
  :model-value="modelValue.timeRange"
20
22
  :items="options.timeRanges"
21
- label="Time Range"
23
+ label="Start Time"
22
24
  variant="outlined"
23
25
  density="comfortable"
24
26
  rounded="lg"
@@ -58,7 +58,7 @@
58
58
  <span class="text-caption font-weight-bold text-capitalize">
59
59
  permissions
60
60
  </span>
61
- <v-chip>{{ value.length }}</v-chip>
61
+ <v-chip>{{ getPermissionCount(value) }}</v-chip>
62
62
  </template>
63
63
 
64
64
  <template #item.action-table="{ item }" v-if="canDeleteRole">
@@ -265,10 +265,22 @@ const selectedPermissions = ref<Array<string>>([]);
265
265
  const originalPermissions = ref<Array<string>>([]);
266
266
  const edit = ref(false);
267
267
 
268
- const { convertPermissionsToArray } = useUtils();
269
-
270
268
  const roleId = ref("");
271
269
 
270
+ function getPermissionCount(permissions: unknown): number {
271
+ return Array.isArray(permissions) ? permissions.length : 0;
272
+ }
273
+
274
+ function updateRolePermissionsInList(
275
+ roles: Array<Record<string, any>>,
276
+ roleId: string,
277
+ permissions: string[]
278
+ ) {
279
+ return roles.map((role) =>
280
+ role._id === roleId ? { ...role, permissions } : role
281
+ );
282
+ }
283
+
272
284
  const { getRoleById: _getRoleById, deleteRole } = useRole();
273
285
 
274
286
  const { data: role, refresh: getRoleById } = useLazyAsyncData(
@@ -324,9 +336,26 @@ const Permissions = computed(() => {
324
336
  });
325
337
 
326
338
  function successfulUpdate() {
339
+ const savedPermissions = [...selectedPermissions.value];
340
+
327
341
  previewDialog.value = false;
328
- getRoles();
329
- useLocalSetup(props.type);
342
+ edit.value = false;
343
+ originalPermissions.value = savedPermissions;
344
+ items.value = updateRolePermissionsInList(
345
+ items.value,
346
+ roleId.value,
347
+ savedPermissions
348
+ );
349
+ void refreshRolesAfterUpdate(savedPermissions);
350
+ }
351
+
352
+ async function refreshRolesAfterUpdate(savedPermissions: string[]) {
353
+ await getRoles();
354
+ items.value = updateRolePermissionsInList(
355
+ items.value,
356
+ roleId.value,
357
+ savedPermissions
358
+ );
330
359
  }
331
360
 
332
361
  const confirmDialog = ref(false);
@@ -371,7 +371,9 @@ function show(message: string, color: string) {
371
371
  }
372
372
 
373
373
  // ANPR Settings
374
- const { getANPRSettingBySite, createUpdateANPRSettings } = useANPRSettings();
374
+ const { getANPRSettingBySite, createUpdateANPRSettings } = useANPRSettings(
375
+ props.siteId
376
+ );
375
377
  const isSaving = ref(false);
376
378
 
377
379
  // Reactive object to hold the state of the 3 switches