@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.
- package/CHANGELOG.md +14 -0
- package/components/AccessCardDetailsDialog.vue +129 -3
- package/components/AccessCardPreviewDialog.vue +31 -3
- package/components/AccessManagement.vue +10 -1
- package/components/AddPassKeyToVisitor.vue +346 -225
- package/components/DashboardMain.vue +95 -59
- package/components/DocumentManagement.vue +71 -5
- package/components/EntryPassInformation.vue +55 -17
- package/components/HidIntercomManagement.vue +186 -0
- package/components/HidReaderForm.vue +10 -16
- package/components/HidReaderManagement.vue +21 -1
- package/components/MemberInformation.vue +59 -1
- package/components/RolePermissionMain.vue +14 -8
- package/components/SiteSettings.vue +3 -1
- package/components/VisitorForm.vue +35 -7
- package/components/VisitorManagement.vue +233 -39
- package/components/VisitorSocketPopUp.vue +29 -1
- package/composables/useANPRSettings.ts +57 -8
- package/composables/useAccessManagement.ts +17 -1
- package/composables/useCleaningPermission.ts +2 -2
- package/composables/useCommonPermission.ts +2 -39
- package/composables/useDocument.ts +21 -0
- package/composables/useLocalAuth.ts +3 -1
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +2 -0
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +2 -0
|
@@ -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
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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
|
-
|
|
1603
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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="
|
|
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
|
}
|