@7365admin1/layer-common 3.0.25 → 3.0.27

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.
@@ -1542,72 +1542,108 @@ const {
1542
1542
  } = useDashboard();
1543
1543
 
1544
1544
  const { userAppRole } = useLocalSetup();
1545
- const { hasPermission } = usePermission();
1546
- const { dashboardPermissions } = useCommonPermissions();
1547
1545
  const { downloadPDF } = usePDFDownload();
1548
1546
  const route = useRoute();
1549
1547
 
1550
- function canViewWidget(key: string) {
1551
- if (!userAppRole.value) return true;
1552
- if (userAppRole.value.permissions?.includes("*")) return true;
1553
-
1554
- const widgetToModulePermissionMap: Record<string, string[]> = {
1555
- workOrders: ["work-orders:see-all-work-orders"],
1556
- incidents: [
1557
- "incident-reports:see-incident-reports",
1558
- "incident-reports:see-all-incident-reports",
1559
- ],
1560
- visitors: ["visitor:see-all-visitor"],
1561
- facilityBookings: ["facility-booking:see-all-facility-booking"],
1562
- patrolCompliance: [
1563
- "virtual-patrol:see-all-virtual-patrol-logs",
1564
- "virtual-patrol:see-all-virtual-patrol-route",
1565
- ],
1566
- staffStatus: ["attendance:see-all-attendance"],
1567
- attendance: ["attendance:see-all-attendance"],
1568
- feedbacks: ["feedbacks:see-all-feedbacks"],
1569
- upcomingEvents: [
1570
- "bulletin-boards:see-all-bulletin-boards",
1571
- "bulletin-board:see-all-bulletin-board",
1572
- ],
1573
- workOrderStatus: ["work-orders:see-all-work-orders"],
1574
- activePatrol: [
1575
- "virtual-patrol:see-all-virtual-patrol-logs",
1576
- "virtual-patrol:see-all-virtual-patrol-route",
1577
- ],
1578
- taskSchedule: ["cleaning-schedule-mgmt:see-all-schedules"],
1579
- };
1548
+ const dashboardWidgetPermissions: Record<string, string> = {
1549
+ activity: "dashboard:view-activity",
1550
+ recentActivity: "dashboard:view-activity",
1551
+ taskSchedule: "dashboard:view-task-schedule",
1552
+ staffStatus: "dashboard:view-attendance-widget",
1553
+ attendance: "dashboard:view-attendance-widget",
1554
+ feedbacks: "dashboard:view-feedbacks-widget",
1555
+ workOrderStatus: "dashboard:view-work-order-status",
1556
+ activePatrol: "dashboard:view-active-patrol",
1557
+ upcomingEvents: "dashboard:view-upcoming-events",
1558
+ todayReminders: "dashboard:view-today-reminders",
1559
+ todayAttentions: "dashboard:view-today-attentions",
1560
+ };
1580
1561
 
1581
- const modulePerms = widgetToModulePermissionMap[key];
1582
- if (
1583
- modulePerms &&
1584
- modulePerms.some((p) => userAppRole.value.permissions?.includes(p))
1585
- ) {
1586
- return true;
1587
- }
1562
+ const propertyWidgetPermissions: Record<string, string[]> = {
1563
+ workOrders: ["workOrder:see-all-work-orders"],
1564
+ incidents: ["incident-reports:see-incident-reports"],
1565
+ visitors: ["visitorManagement:see-all-visitor"],
1566
+ facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
1567
+ activity: ["workOrder:see-all-work-orders"],
1568
+ upcomingEvents: ["event-mgmt:see-all-event"],
1569
+ todayReminders: ["event-mgmt:see-all-event"],
1570
+ todayAttentions: [
1571
+ "incident-reports:see-incident-reports",
1572
+ "facility-booking-mgmt:see-all-facility-booking",
1573
+ ],
1574
+ workOrderStatus: ["workOrder:see-all-work-orders"],
1575
+ feedbacks: ["feedbacks:see-all-feedback"],
1576
+ activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1577
+ };
1588
1578
 
1589
- const actionMap: Record<string, string> = {
1590
- activity: "view-activity",
1591
- taskSchedule: "view-task-schedule",
1592
- staffStatus: "view-attendance-widget",
1593
- attendance: "view-attendance-widget",
1594
- feedbacks: "view-feedbacks-widget",
1595
- upcomingEvents: "view-upcoming-events",
1596
- todayReminders: "view-today-reminders",
1597
- todayAttentions: "view-today-attentions",
1598
- workOrderStatus: "view-work-order-status",
1599
- activePatrol: "view-active-patrol",
1600
- };
1579
+ const moduleWidgetPermissions: Record<string, string[]> = {
1580
+ openWorkOrder: [
1581
+ "work_order:see-all-work-orders",
1582
+ "work-order:see-all-work-orders",
1583
+ "work-orders:see-all-work-orders",
1584
+ ],
1585
+ workOrders: [
1586
+ "work_order:see-all-work-orders",
1587
+ "work-order:see-all-work-orders",
1588
+ "work-orders:see-all-work-orders",
1589
+ ],
1590
+ taskSchedule: [
1591
+ "cleaning-schedule-mgmt:see-all-schedules",
1592
+ "schedule-task-mgmt:see-all-schedule-tasks",
1593
+ ],
1594
+ taskCompleted: [
1595
+ "cleaning-schedule-mgmt:see-all-schedules",
1596
+ "schedule-task-mgmt:see-all-schedule-tasks",
1597
+ ],
1598
+ staffStatus: ["attendance-mgmt:see-all-attendance"],
1599
+ attendance: ["attendance-mgmt:see-all-attendance"],
1600
+ supplyAlert: ["supply-mgmt:see-all-supplies"],
1601
+ feedbacks: [
1602
+ "feedbacks:see-all-feedback",
1603
+ "feedback:see-all-feedback",
1604
+ "feedbacks:see-all-feedbacks",
1605
+ ],
1606
+ incidents: [
1607
+ "incident-reports:see-incident-reports",
1608
+ "incident-reports:see-all-incident-reports",
1609
+ ],
1610
+ visitors: ["visitor:see-all-visitor"],
1611
+ facilityBookings: ["facility-booking:see-all-facility-booking"],
1612
+ patrolCompliance: [
1613
+ "virtual-patrol:see-all-virtual-patrol-logs",
1614
+ "virtual-patrol:see-all-virtual-patrol-route",
1615
+ ],
1616
+ activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1617
+ };
1618
+
1619
+ function canViewWidget(key: string) {
1620
+ const permissions = userAppRole.value?.permissions;
1621
+ const dashboardPermission = dashboardWidgetPermissions[key];
1622
+ const modulePermissions = isPropertyMode.value
1623
+ ? propertyWidgetPermissions[key]
1624
+ : moduleWidgetPermissions[key];
1625
+
1626
+ if (!permissions) return true;
1627
+ if (permissions.includes("*")) return true;
1628
+
1629
+ if (isPropertyMode.value && modulePermissions) {
1630
+ return modulePermissions.some((permission) =>
1631
+ permissions.includes(permission)
1632
+ );
1633
+ }
1601
1634
 
1602
- const action = actionMap[key];
1603
- if (!action) return true;
1635
+ if (dashboardPermission || modulePermissions) {
1636
+ return (
1637
+ (dashboardPermission && permissions.includes(dashboardPermission)) ||
1638
+ Boolean(
1639
+ modulePermissions?.some((permission) =>
1640
+ permissions.includes(permission)
1641
+ )
1642
+ )
1643
+ );
1644
+ }
1604
1645
 
1605
- return hasPermission(
1606
- userAppRole.value,
1607
- { dashboard: dashboardPermissions },
1608
- "dashboard",
1609
- action
1610
- );
1646
+ return true;
1611
1647
  }
1612
1648
 
1613
1649
  // ─── State ────────────────────────────────────────────────────────────────────
@@ -332,6 +332,7 @@
332
332
  class="text-none"
333
333
  size="large"
334
334
  @click="editFolderDialog = false"
335
+ :disabled="folderUpdateLoading"
335
336
  height="48"
336
337
  >
337
338
  Cancel
@@ -345,6 +346,7 @@
345
346
  class="text-none"
346
347
  size="large"
347
348
  @click="handleApplyFolderEdit"
349
+ :loading="folderUpdateLoading"
348
350
  height="48"
349
351
  >
350
352
  Apply
@@ -382,6 +384,7 @@
382
384
  variant="text"
383
385
  class="text-none"
384
386
  @click="deleteFolderDialog = false"
387
+ :disabled="folderDeleteLoading"
385
388
  >
386
389
  Cancel
387
390
  </v-btn>
@@ -395,6 +398,7 @@
395
398
  variant="flat"
396
399
  class="text-none"
397
400
  @click="handleDeleteFolderUiConfirm"
401
+ :loading="folderDeleteLoading"
398
402
  >
399
403
  Confirm
400
404
  </v-btn>
@@ -684,6 +688,8 @@ const documentService = useDocument();
684
688
  const {
685
689
  getAll: _getAllDocuments,
686
690
  deleteById,
691
+ deleteFolderById,
692
+ updateFolderById,
687
693
  createFolder,
688
694
  create,
689
695
  } = documentService;
@@ -848,6 +854,8 @@ const selectedFolder = ref<(Partial<TDocument> & Record<string, any>) | null>(
848
854
  );
849
855
  const editFolderDialog = ref(false);
850
856
  const deleteFolderDialog = ref(false);
857
+ const folderUpdateLoading = ref(false);
858
+ const folderDeleteLoading = ref(false);
851
859
  const editFolderTitle = ref("");
852
860
  const editFolderRemarks = ref("");
853
861
  const validEditFolderForm = ref(false);
@@ -1022,7 +1030,10 @@ function closeFolderActionMenu(folderId?: string) {
1022
1030
  function openEditFolderDialog(
1023
1031
  folder: Partial<TDocument> & Record<string, any>
1024
1032
  ) {
1025
- selectedFolder.value = folder;
1033
+ selectedFolder.value = {
1034
+ ...folder,
1035
+ parentId: folder.parentId || activeParentId.value,
1036
+ };
1026
1037
  editFolderTitle.value = String(folder.name || "");
1027
1038
  editFolderRemarks.value = String(folder.remarks || "");
1028
1039
  editFolderDialog.value = true;
@@ -1037,12 +1048,67 @@ function openDeleteFolderDialog(
1037
1048
  closeFolderActionMenu(String(folder._id || ""));
1038
1049
  }
1039
1050
 
1040
- function handleApplyFolderEdit() {
1041
- editFolderDialog.value = false;
1051
+ async function handleApplyFolderEdit() {
1052
+ const folderId = String(selectedFolder.value?._id || "");
1053
+
1054
+ if (!folderId) {
1055
+ showMessage("Failed to update folder", "error");
1056
+ return;
1057
+ }
1058
+
1059
+ if (!editFolderTitle.value) {
1060
+ showMessage("Folder title is required", "error");
1061
+ return;
1062
+ }
1063
+
1064
+ folderUpdateLoading.value = true;
1065
+ try {
1066
+ const response: any = await updateFolderById(folderId, {
1067
+ name: editFolderTitle.value,
1068
+ remarks: editFolderRemarks.value,
1069
+ parentId: selectedFolder.value?.parentId || activeParentId.value,
1070
+ });
1071
+
1072
+ showMessage(
1073
+ response?.message || "Folder updated successfully!",
1074
+ "success"
1075
+ );
1076
+ await getDocuments();
1077
+ selectedFolder.value = null;
1078
+ editFolderDialog.value = false;
1079
+ } catch (error: any) {
1080
+ showMessage(
1081
+ error?.response?._data?.message || "Failed to update folder",
1082
+ "error"
1083
+ );
1084
+ } finally {
1085
+ folderUpdateLoading.value = false;
1086
+ }
1042
1087
  }
1043
1088
 
1044
- function handleDeleteFolderUiConfirm() {
1045
- deleteFolderDialog.value = false;
1089
+ async function handleDeleteFolderUiConfirm() {
1090
+ const folderId = String(selectedFolder.value?._id || "");
1091
+
1092
+ if (!folderId) {
1093
+ showMessage("Failed to delete folder", "error");
1094
+ return;
1095
+ }
1096
+
1097
+ folderDeleteLoading.value = true;
1098
+ try {
1099
+ const response = await deleteFolderById(folderId);
1100
+ showMessage(response.message || "Folder deleted successfully!", "success");
1101
+ await getDocuments();
1102
+ selectedFolder.value = null;
1103
+ deleteFolderDialog.value = false;
1104
+ } catch (error: any) {
1105
+ showMessage(
1106
+ error?.response?._data?.message || "Failed to delete folder",
1107
+ "error"
1108
+ );
1109
+ } finally {
1110
+ folderDeleteLoading.value = false;
1111
+ }
1046
1112
  }
1047
1113
 
1048
1114
  async function handleCreateFolder() {
@@ -59,21 +59,39 @@
59
59
  No printer is configured in the settings. QR Pass may not print.
60
60
  </v-alert>
61
61
  <template v-else>
62
- <div class="d-flex align-center justify-space-between mt-3">
63
- <InputLabel title="Quantity" />
64
- <span v-if="availableCount !== null" class="text-caption text-grey">
65
- {{ availableCount }} available
66
- </span>
67
- <v-progress-circular v-else-if="cardsLoading" size="14" width="2" indeterminate color="grey" />
62
+ <!-- Generating / loading state -->
63
+ <div v-if="generating || cardsLoading" class="d-flex align-center ga-2 mt-3 text-caption text-grey-darken-1">
64
+ <v-progress-circular size="14" width="2" indeterminate color="grey" />
65
+ <span>{{ generating ? 'Generating QR passes...' : 'Checking availability...' }}</span>
68
66
  </div>
69
- <v-text-field
70
- v-model.number="quantity"
71
- type="number"
72
- density="comfortable"
73
- :min="1"
74
- :max="availableCount ?? undefined"
75
- hide-details
76
- />
67
+ <!-- 0 available after all loading -->
68
+ <v-alert
69
+ v-else-if="availableCount === 0"
70
+ type="warning"
71
+ variant="tonal"
72
+ density="compact"
73
+ class="mt-3"
74
+ icon="mdi-information-outline"
75
+ >
76
+ No QR passes available. Add at least 1 non-physical card to this unit in Access Management to enable auto-generation.
77
+ </v-alert>
78
+ <!-- Normal: quantity input -->
79
+ <template v-else>
80
+ <div class="d-flex align-center justify-space-between mt-3">
81
+ <InputLabel title="Quantity" />
82
+ <span v-if="availableCount !== null" class="text-caption text-grey">
83
+ {{ availableCount }} available
84
+ </span>
85
+ </div>
86
+ <v-text-field
87
+ v-model.number="quantity"
88
+ type="number"
89
+ density="comfortable"
90
+ :min="1"
91
+ :max="availableCount ?? undefined"
92
+ hide-details
93
+ />
94
+ </template>
77
95
  </template>
78
96
  </template>
79
97
 
@@ -83,7 +101,7 @@
83
101
  <InputLabel title="Select Cards" />
84
102
  <v-select
85
103
  v-model="selectedCards"
86
- :items="cardItems"
104
+ :items="filteredCardItems"
87
105
  :loading="cardsLoading"
88
106
  item-title="cardNo"
89
107
  item-value="_id"
@@ -265,9 +283,13 @@ const props = defineProps({
265
283
  type: Boolean,
266
284
  default: false,
267
285
  },
286
+ excludedCardIds: {
287
+ type: Array as PropType<string[]>,
288
+ default: () => [],
289
+ },
268
290
  });
269
291
 
270
- const emit = defineEmits(["update:modelValue", "update:quantity", "update:cards"]);
292
+ const emit = defineEmits(["update:modelValue", "update:quantity", "update:cards", "update:availableQrCount"]);
271
293
 
272
294
  const { getAvailableContractorCards, generateQrVms } = useAccessManagement();
273
295
 
@@ -309,12 +331,19 @@ function showSnackbar(text: string, color = "error") {
309
331
 
310
332
  // ─── Card fetching ────────────────────────────────────────────────
311
333
  const cardItems = ref<any[]>([]);
334
+ const filteredCardItems = computed(() =>
335
+ props.excludedCardIds.length
336
+ ? cardItems.value.filter((c) => !props.excludedCardIds.includes(c._id))
337
+ : cardItems.value
338
+ );
312
339
  const cardsLoading = ref(false);
340
+ const generating = ref(false);
313
341
  const availableCount = ref<number | null>(null);
314
342
 
315
343
  async function fetchCards(type: "NFC" | "QRCODE" = "NFC") {
316
344
  if (!props.siteId || !props.unitId) return;
317
345
  cardsLoading.value = true;
346
+ if (type === "QRCODE") emit("update:availableQrCount", null);
318
347
  try {
319
348
  const res = await getAvailableContractorCards({
320
349
  type,
@@ -324,7 +353,9 @@ async function fetchCards(type: "NFC" | "QRCODE" = "NFC") {
324
353
  cardItems.value = res?.data?.[0]?.items ?? [];
325
354
  availableCount.value = (res?.data?.[2] as any)?.count ?? cardItems.value.length;
326
355
 
327
- if (type === "QRCODE" && props.settings?.data?._id && availableCount.value !== null && availableCount.value < 50) {
356
+ if (type === "QRCODE" && props.settings?.data?._id && availableCount.value !== null && availableCount.value > 0 && availableCount.value < 50) {
357
+ cardsLoading.value = false;
358
+ generating.value = true;
328
359
  await generateQrVms({
329
360
  site: props.settings.data.site,
330
361
  unitId: props.unitId,
@@ -338,14 +369,20 @@ async function fetchCards(type: "NFC" | "QRCODE" = "NFC") {
338
369
  });
339
370
  cardItems.value = refreshed?.data?.[0]?.items ?? [];
340
371
  availableCount.value = (refreshed?.data?.[2] as any)?.count ?? cardItems.value.length;
372
+ generating.value = false;
341
373
  }
374
+
375
+ if (type === "QRCODE") emit("update:availableQrCount", availableCount.value);
342
376
  } catch (err: any) {
343
377
  const msg = err?.data?.message ?? err?.message ?? String(err);
344
378
  showSnackbar(`Entry pass service error: ${msg}`);
345
379
  availableCount.value = null;
346
380
  cardItems.value = [];
381
+ generating.value = false;
382
+ if (type === "QRCODE") emit("update:availableQrCount", null);
347
383
  } finally {
348
384
  cardsLoading.value = false;
385
+ generating.value = false;
349
386
  }
350
387
  }
351
388
 
@@ -500,6 +537,7 @@ watch(
500
537
  fetchCards("QRCODE");
501
538
  } else {
502
539
  availableCount.value = null;
540
+ emit("update:availableQrCount", null);
503
541
  isBarcodeScanningActive.value = false;
504
542
  barcodeBuffer.value = "";
505
543
  if (barcodeTimeout.value) clearTimeout(barcodeTimeout.value);
@@ -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
  }