@7365admin1/layer-common 3.2.2-staging.180 → 3.2.2-staging.182
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/assets/css/primitives.css +33 -14
- package/assets/css/screens.css +12 -0
- package/components/AppSelect.vue +80 -36
- package/components/FileInputWithList.vue +14 -0
- package/components/HidAccessLogDashboard.vue +24 -9
- package/components/HidEnabledGate.vue +5 -1
- package/components/HidIntercomManagement.vue +172 -26
- package/components/HidQrCodeConfiguration.vue +17 -2
- package/components/HidReaderManagement.vue +13 -4
- package/components/HidUserEnrollment.vue +549 -33
- package/components/TableMain.vue +7 -1
- package/composables/useHidAmico.ts +71 -0
- package/composables/useHidNavigation.ts +2 -2
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +23 -0
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
class="app-tab"
|
|
77
77
|
:class="{ 'app-tab--active': activeTab === tab.value }"
|
|
78
78
|
:aria-selected="activeTab === tab.value"
|
|
79
|
-
@click="
|
|
79
|
+
@click="selectTab(tab.value)"
|
|
80
80
|
>
|
|
81
81
|
{{ tab.label }}
|
|
82
82
|
</button>
|
|
@@ -94,7 +94,12 @@
|
|
|
94
94
|
@update:model-value="page = 1"
|
|
95
95
|
/>
|
|
96
96
|
<AppField v-model="search" search placeholder="Search" />
|
|
97
|
-
<AppSelect
|
|
97
|
+
<AppSelect
|
|
98
|
+
v-model="statusFilter"
|
|
99
|
+
:items="statusOptions"
|
|
100
|
+
placeholder="Status"
|
|
101
|
+
clearable
|
|
102
|
+
/>
|
|
98
103
|
</div>
|
|
99
104
|
</template>
|
|
100
105
|
|
|
@@ -126,6 +131,18 @@
|
|
|
126
131
|
<StatusChip :status="item.status" />
|
|
127
132
|
</template>
|
|
128
133
|
|
|
134
|
+
<template #[`item.contactStatus`]="{ item }">
|
|
135
|
+
<v-btn
|
|
136
|
+
size="small"
|
|
137
|
+
variant="outlined"
|
|
138
|
+
class="text-none"
|
|
139
|
+
:loading="testingContactReaderId === item.readerId"
|
|
140
|
+
@click="testContactSipStatus(item)"
|
|
141
|
+
>
|
|
142
|
+
{{ getContactReaderStatus(item) }}
|
|
143
|
+
</v-btn>
|
|
144
|
+
</template>
|
|
145
|
+
|
|
129
146
|
<template #[`item.action-table`]="{ item }">
|
|
130
147
|
<v-menu location="bottom end">
|
|
131
148
|
<template #activator="{ props: menuProps }">
|
|
@@ -133,13 +150,19 @@
|
|
|
133
150
|
</template>
|
|
134
151
|
<v-list v-if="activeTab === 'intercom'" density="compact" class="row-menu">
|
|
135
152
|
<v-list-item title="View" @click="openViewDialog(item)" />
|
|
136
|
-
<v-list-item
|
|
153
|
+
<v-list-item
|
|
154
|
+
title="Test Connection with SIP"
|
|
155
|
+
:disabled="testingSipReaderId === item.reader._id"
|
|
156
|
+
@click="testSipConnection(item)"
|
|
157
|
+
/>
|
|
137
158
|
<v-list-item title="Call Settings" @click="openCallDialog(item)" />
|
|
138
159
|
<v-list-item title="Dial Code" @click="openDialCodeDialog(item)" />
|
|
139
160
|
<v-list-item title="Make a call" @click="openMakeCallDialog(item)" />
|
|
140
161
|
<v-list-item title="Web call" @click="openWebPhoneDialog(item)" />
|
|
141
162
|
</v-list>
|
|
142
163
|
<v-list v-else density="compact" class="row-menu">
|
|
164
|
+
<v-list-item title="Call" prepend-icon="mdi-phone" @click="openContactCall(item)" />
|
|
165
|
+
<v-list-item title="Check reader SIP status" prepend-icon="mdi-lan-check" @click="testContactSipStatus(item)" />
|
|
143
166
|
<v-list-item title="View" @click="openContactView(item)" />
|
|
144
167
|
<v-list-item title="Edit" @click="openContactDialog(item)" />
|
|
145
168
|
<v-list-item title="Delete" @click="openDeleteContact(item)" />
|
|
@@ -554,12 +577,16 @@
|
|
|
554
577
|
<v-card class="form-dialog" rounded="lg">
|
|
555
578
|
<v-card-title class="dialog-title">{{ contactForm.id ? 'Edit Contact' : 'Add Contact' }}</v-card-title>
|
|
556
579
|
<v-card-text class="dialog-body">
|
|
580
|
+
<FieldLabel text="HID Reader" required />
|
|
581
|
+
<AppSelect
|
|
582
|
+
v-model="contactForm.readerId"
|
|
583
|
+
:items="readerOptions"
|
|
584
|
+
:disabled="Boolean(contactForm.id)"
|
|
585
|
+
/>
|
|
557
586
|
<FieldLabel text="Name" required />
|
|
558
587
|
<AppField v-model="contactForm.name" placeholder="Enter name" />
|
|
559
|
-
<FieldLabel text="
|
|
560
|
-
<AppField v-model="contactForm.
|
|
561
|
-
<FieldLabel text="Server Address" required />
|
|
562
|
-
<AppField v-model="contactForm.serverAddress" placeholder="192.168.1.1" />
|
|
588
|
+
<FieldLabel text="SIP Number / Address" required />
|
|
589
|
+
<AppField v-model="contactForm.serverAddress" placeholder="7100 or user@example.com" />
|
|
563
590
|
</v-card-text>
|
|
564
591
|
<v-card-actions class="dialog-actions pa-0">
|
|
565
592
|
<v-btn class="text-none action-cancel" variant="flat" height="44" @click="contactDialog = false">Cancel</v-btn>
|
|
@@ -693,15 +720,19 @@ const {
|
|
|
693
720
|
const activeTab = ref<IntercomTab>("intercom");
|
|
694
721
|
const readers = ref<Record<string, any>[]>([]);
|
|
695
722
|
const selectedReaderId = ref("");
|
|
723
|
+
const testingSipReaderId = ref("");
|
|
724
|
+
const testingContactReaderId = ref("");
|
|
696
725
|
const intercomRows = ref<IntercomRow[]>([]);
|
|
697
726
|
const contacts = ref<ContactRow[]>([]);
|
|
727
|
+
const intercomLoaded = ref(false);
|
|
728
|
+
const contactsLoaded = ref(false);
|
|
698
729
|
const loading = ref(false);
|
|
699
730
|
const saving = ref(false);
|
|
700
731
|
const contactsSaving = ref(false);
|
|
701
732
|
const calling = ref(false);
|
|
702
733
|
const hangingUp = ref(false);
|
|
703
734
|
const search = ref("");
|
|
704
|
-
const statusFilter = ref("
|
|
735
|
+
const statusFilter = ref("");
|
|
705
736
|
const page = ref(1);
|
|
706
737
|
const limit = 20;
|
|
707
738
|
const selectedRow = ref<IntercomRow | null>(null);
|
|
@@ -728,7 +759,7 @@ const deleteContactDialog = ref(false);
|
|
|
728
759
|
|
|
729
760
|
const message = reactive<{ type: MessageType; text: string }>({ type: "info", text: "" });
|
|
730
761
|
|
|
731
|
-
const statusOptions = ["
|
|
762
|
+
const statusOptions = ["Connected", "Not connected", "Connecting", "Disabled", "Failed"];
|
|
732
763
|
|
|
733
764
|
const pjsipKeys = [
|
|
734
765
|
"enabled",
|
|
@@ -839,7 +870,7 @@ const filteredIntercomRows = computed(() => {
|
|
|
839
870
|
row.dialCode,
|
|
840
871
|
row.status,
|
|
841
872
|
].some((value) => String(value || "").toLowerCase().includes(query));
|
|
842
|
-
const matchesStatus = statusFilter.value
|
|
873
|
+
const matchesStatus = !statusFilter.value || row.status === statusFilter.value;
|
|
843
874
|
return matchesReader && matchesSearch && matchesStatus;
|
|
844
875
|
});
|
|
845
876
|
});
|
|
@@ -848,13 +879,16 @@ const filteredContacts = computed(() => {
|
|
|
848
879
|
const query = search.value.trim().toLowerCase();
|
|
849
880
|
return contacts.value.filter((contact) => {
|
|
850
881
|
const matchesReader = !selectedReaderId.value || contact.readerId === selectedReaderId.value;
|
|
882
|
+
const readerStatus = getContactReaderStatus(contact);
|
|
851
883
|
const matchesSearch = !query || [
|
|
852
884
|
contact.name,
|
|
853
885
|
contact.location,
|
|
854
886
|
contact.serverAddress,
|
|
855
887
|
contact.dialCode,
|
|
888
|
+
readerStatus,
|
|
856
889
|
].some((value) => String(value || "").toLowerCase().includes(query));
|
|
857
|
-
|
|
890
|
+
const matchesStatus = !statusFilter.value || readerStatus === statusFilter.value;
|
|
891
|
+
return matchesReader && matchesSearch && matchesStatus;
|
|
858
892
|
});
|
|
859
893
|
});
|
|
860
894
|
|
|
@@ -890,8 +924,8 @@ const tableHeaders = computed(() =>
|
|
|
890
924
|
: [
|
|
891
925
|
{ title: "Name", value: "name", sortable: false },
|
|
892
926
|
{ title: "Location", value: "location", sortable: false },
|
|
893
|
-
{ title: "SIP
|
|
894
|
-
{ title: "
|
|
927
|
+
{ title: "SIP Number / Address", value: "serverAddress", sortable: false },
|
|
928
|
+
{ title: "Reader SIP Status", value: "contactStatus", sortable: false },
|
|
895
929
|
{ title: "", value: "action-table", sortable: false },
|
|
896
930
|
]
|
|
897
931
|
);
|
|
@@ -1034,8 +1068,9 @@ async function reload() {
|
|
|
1034
1068
|
if (!readers.value.some((reader) => reader._id === selectedReaderId.value)) {
|
|
1035
1069
|
selectedReaderId.value = readers.value[0]?._id || "";
|
|
1036
1070
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1071
|
+
intercomLoaded.value = false;
|
|
1072
|
+
contactsLoaded.value = false;
|
|
1073
|
+
await loadActiveTab();
|
|
1039
1074
|
} catch (error: any) {
|
|
1040
1075
|
showMessage(getErrorMessage(error), "error");
|
|
1041
1076
|
} finally {
|
|
@@ -1043,10 +1078,36 @@ async function reload() {
|
|
|
1043
1078
|
}
|
|
1044
1079
|
}
|
|
1045
1080
|
|
|
1046
|
-
async function
|
|
1047
|
-
|
|
1081
|
+
async function loadActiveTab() {
|
|
1082
|
+
if (activeTab.value === "intercom") {
|
|
1083
|
+
await loadIntercomRows();
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
await Promise.all([
|
|
1088
|
+
intercomLoaded.value ? Promise.resolve() : loadIntercomRows(),
|
|
1089
|
+
loadContacts(),
|
|
1090
|
+
]);
|
|
1091
|
+
}
|
|
1048
1092
|
|
|
1049
|
-
|
|
1093
|
+
async function selectTab(tab: IntercomTab) {
|
|
1094
|
+
activeTab.value = tab;
|
|
1095
|
+
page.value = 1;
|
|
1096
|
+
|
|
1097
|
+
if ((tab === "intercom" && intercomLoaded.value) || (tab === "contacts" && contactsLoaded.value)) return;
|
|
1098
|
+
|
|
1099
|
+
loading.value = true;
|
|
1100
|
+
try {
|
|
1101
|
+
await loadActiveTab();
|
|
1102
|
+
} catch (error: unknown) {
|
|
1103
|
+
showMessage(getErrorMessage(error), "error");
|
|
1104
|
+
} finally {
|
|
1105
|
+
loading.value = false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async function loadIntercomRows() {
|
|
1110
|
+
const rows = await Promise.all(readers.value.map(async (reader): Promise<IntercomRow> => {
|
|
1050
1111
|
const [configurationResult, statusResult] = await Promise.allSettled([
|
|
1051
1112
|
getReaderConfiguration(reader._id, { pjsip: pjsipKeys }),
|
|
1052
1113
|
getIntercomStatus(reader._id),
|
|
@@ -1058,16 +1119,15 @@ async function loadIntercomRows() {
|
|
|
1058
1119
|
const statusPayload = statusResponse?.data ?? statusResponse;
|
|
1059
1120
|
const pjsip = normalizePjsip(configurationResponse, statusPayload);
|
|
1060
1121
|
const status = normalizeStatus(statusPayload, pjsip);
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1122
|
+
return toIntercomRow(reader, pjsip, status);
|
|
1123
|
+
}));
|
|
1063
1124
|
|
|
1064
1125
|
intercomRows.value = rows;
|
|
1126
|
+
intercomLoaded.value = true;
|
|
1065
1127
|
}
|
|
1066
1128
|
|
|
1067
1129
|
async function loadContacts() {
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
for (const reader of readers.value) {
|
|
1130
|
+
const readerContacts = await Promise.all(readers.value.map(async (reader): Promise<ContactRow[]> => {
|
|
1071
1131
|
try {
|
|
1072
1132
|
const response = await runObjectOperation(reader._id, {
|
|
1073
1133
|
operation: "load",
|
|
@@ -1075,13 +1135,15 @@ async function loadContacts() {
|
|
|
1075
1135
|
fields: ["id", "name", "number"],
|
|
1076
1136
|
limit: 100,
|
|
1077
1137
|
});
|
|
1078
|
-
|
|
1138
|
+
return normalizeContacts(reader, response?.data ?? response);
|
|
1079
1139
|
} catch {
|
|
1080
1140
|
// Some firmware builds may not expose contacts until intercom is configured.
|
|
1141
|
+
return [];
|
|
1081
1142
|
}
|
|
1082
|
-
}
|
|
1143
|
+
}));
|
|
1083
1144
|
|
|
1084
|
-
contacts.value =
|
|
1145
|
+
contacts.value = readerContacts.flat();
|
|
1146
|
+
contactsLoaded.value = true;
|
|
1085
1147
|
}
|
|
1086
1148
|
|
|
1087
1149
|
function paginate<T>(items: T[]) {
|
|
@@ -1398,6 +1460,39 @@ function openContactView(contact: ContactRow) {
|
|
|
1398
1460
|
contactViewDialog.value = true;
|
|
1399
1461
|
}
|
|
1400
1462
|
|
|
1463
|
+
function getContactReaderStatus(contact: ContactRow) {
|
|
1464
|
+
return intercomRows.value.find((row) => row.reader._id === contact.readerId)?.status || "Check status";
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
async function testContactSipStatus(contact: ContactRow) {
|
|
1468
|
+
const reader = intercomRows.value.find((row) => row.reader._id === contact.readerId);
|
|
1469
|
+
if (!reader) {
|
|
1470
|
+
showMessage("The HID reader for this contact is not available.", "error");
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
testingContactReaderId.value = contact.readerId;
|
|
1475
|
+
try {
|
|
1476
|
+
await testSipConnection(reader);
|
|
1477
|
+
} finally {
|
|
1478
|
+
testingContactReaderId.value = "";
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function openContactCall(contact: ContactRow) {
|
|
1483
|
+
const reader = intercomRows.value.find((row) => row.reader._id === contact.readerId);
|
|
1484
|
+
if (!reader) {
|
|
1485
|
+
showMessage("The HID reader for this contact is not available.", "error");
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
selectedRow.value = reader;
|
|
1490
|
+
makeCallForm.target = contact.serverAddress === "N/A" ? "" : contact.serverAddress;
|
|
1491
|
+
makeCallForm.status = "";
|
|
1492
|
+
makeCallForm.statusType = "info";
|
|
1493
|
+
makeCallDialog.value = true;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1401
1496
|
function openDeleteContact(contact: ContactRow) {
|
|
1402
1497
|
selectedContact.value = contact;
|
|
1403
1498
|
deleteContactDialog.value = true;
|
|
@@ -1517,6 +1612,52 @@ async function runHangupCall() {
|
|
|
1517
1612
|
}
|
|
1518
1613
|
}
|
|
1519
1614
|
|
|
1615
|
+
async function testSipConnection(row: IntercomRow) {
|
|
1616
|
+
const readerId = toText(row.reader._id);
|
|
1617
|
+
if (!readerId || testingSipReaderId.value) return;
|
|
1618
|
+
|
|
1619
|
+
testingSipReaderId.value = readerId;
|
|
1620
|
+
try {
|
|
1621
|
+
const response = await getIntercomStatus(readerId);
|
|
1622
|
+
const responseRecord = toRecord(response);
|
|
1623
|
+
const statusPayload = toRecord(responseRecord.data || responseRecord);
|
|
1624
|
+
const status = Number(statusPayload.status);
|
|
1625
|
+
|
|
1626
|
+
await reload();
|
|
1627
|
+
|
|
1628
|
+
if (status === 200) {
|
|
1629
|
+
showMessage(`${row.name}: SIP is connected.`, "success");
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (status === -1) {
|
|
1633
|
+
showMessage(`${row.name}: SIP is disabled on the HID reader.`, "warning");
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (status === 0 || status === 100) {
|
|
1637
|
+
showMessage(`${row.name}: SIP is still connecting. Check the SIP settings on the HID reader.`, "warning");
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
if (status === 401 || status === 403) {
|
|
1641
|
+
showMessage(`${row.name}: SIP authentication failed. Check the account configured on the HID reader.`, "error");
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (status === 408) {
|
|
1645
|
+
showMessage(`${row.name}: The HID reader cannot reach the SIP server.`, "error");
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
if (status === 503) {
|
|
1649
|
+
showMessage(`${row.name}: The HID reader reported a SIP network failure.`, "error");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
showMessage(`${row.name}: SIP is not connected. Configure SIP on the HID reader and test again.`, "warning");
|
|
1654
|
+
} catch (error: unknown) {
|
|
1655
|
+
showMessage(getErrorMessage(error), "error");
|
|
1656
|
+
} finally {
|
|
1657
|
+
testingSipReaderId.value = "";
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1520
1661
|
async function savePjsip(readerId: string, payload: Record<string, string>, options: { showSuccessMessage?: boolean } = {}) {
|
|
1521
1662
|
saving.value = true;
|
|
1522
1663
|
try {
|
|
@@ -1540,6 +1681,11 @@ async function saveContact() {
|
|
|
1540
1681
|
return;
|
|
1541
1682
|
}
|
|
1542
1683
|
|
|
1684
|
+
if (!isSipCallTarget(contactForm.serverAddress)) {
|
|
1685
|
+
showMessage("Enter a valid extension, SIP address, or IPv4 address.", "warning");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1543
1689
|
contactsSaving.value = true;
|
|
1544
1690
|
try {
|
|
1545
1691
|
if (contactForm.id) {
|
|
@@ -705,7 +705,11 @@ async function loadReaderConfiguration() {
|
|
|
705
705
|
try {
|
|
706
706
|
const response = await getReaderConfiguration(form.readerId, {
|
|
707
707
|
general: ["online"],
|
|
708
|
-
identifier: [
|
|
708
|
+
identifier: [
|
|
709
|
+
"card_identification_enabled",
|
|
710
|
+
"pin_identification_enabled",
|
|
711
|
+
"multi_factor_authentication",
|
|
712
|
+
],
|
|
709
713
|
});
|
|
710
714
|
const configuration = getConfigurationData(response);
|
|
711
715
|
const general = isUnknownRecord(configuration.general) ? configuration.general : {};
|
|
@@ -719,6 +723,11 @@ async function loadReaderConfiguration() {
|
|
|
719
723
|
form.identificationMethods.pin = pinEnabled;
|
|
720
724
|
form.identificationMethods.idPassword = !pinEnabled;
|
|
721
725
|
}
|
|
726
|
+
if (identifier.card_identification_enabled !== undefined) {
|
|
727
|
+
form.identificationMethods.card = isEnabledConfiguration(
|
|
728
|
+
identifier.card_identification_enabled,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
722
731
|
} catch (error: unknown) {
|
|
723
732
|
console.warn("Unable to load HID reader configuration:", errorConverter(error));
|
|
724
733
|
}
|
|
@@ -790,7 +799,13 @@ async function saveIdentificationMethod(method: HidIdentificationMethod) {
|
|
|
790
799
|
|
|
791
800
|
savingSection.value = `method-${method}`;
|
|
792
801
|
try {
|
|
793
|
-
if (method === "
|
|
802
|
+
if (method === "card") {
|
|
803
|
+
await setReaderConfiguration(form.readerId, {
|
|
804
|
+
identifier: {
|
|
805
|
+
card_identification_enabled: form.identificationMethods.card ? "1" : "0",
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
} else if (method === "pin" || method === "idPassword") {
|
|
794
809
|
if (method === "pin" && form.identificationMethods.pin) {
|
|
795
810
|
form.identificationMethods.idPassword = false;
|
|
796
811
|
} else if (method === "idPassword" && form.identificationMethods.idPassword) {
|
|
@@ -38,7 +38,12 @@
|
|
|
38
38
|
<template #extension>
|
|
39
39
|
<div class="app-filter-row hid-reader-filters">
|
|
40
40
|
<AppField v-model="search" search placeholder="Search" />
|
|
41
|
-
<AppSelect
|
|
41
|
+
<AppSelect
|
|
42
|
+
v-model="status"
|
|
43
|
+
:items="statusOptions"
|
|
44
|
+
placeholder="Status"
|
|
45
|
+
clearable
|
|
46
|
+
/>
|
|
42
47
|
</div>
|
|
43
48
|
</template>
|
|
44
49
|
|
|
@@ -256,7 +261,7 @@ const {
|
|
|
256
261
|
|
|
257
262
|
const readers = ref<Record<string, any>[]>([]);
|
|
258
263
|
const search = ref("");
|
|
259
|
-
const status = ref("
|
|
264
|
+
const status = ref("");
|
|
260
265
|
const loading = ref(false);
|
|
261
266
|
const saving = ref(false);
|
|
262
267
|
const actionLoading = ref(false);
|
|
@@ -280,7 +285,11 @@ const syncPreviewStats = ref({
|
|
|
280
285
|
});
|
|
281
286
|
const resultStats = ref<typeof syncPreviewStats.value | null>(null);
|
|
282
287
|
|
|
283
|
-
const statusOptions = [
|
|
288
|
+
const statusOptions = [
|
|
289
|
+
{ title: "Active", value: "active" },
|
|
290
|
+
{ title: "Inactive", value: "inactive" },
|
|
291
|
+
{ title: "Offline", value: "offline" },
|
|
292
|
+
];
|
|
284
293
|
|
|
285
294
|
/**
|
|
286
295
|
* The columns. NO `weight`, on purpose.
|
|
@@ -324,7 +333,7 @@ const filteredReaders = computed(() => {
|
|
|
324
333
|
resolveReaderStatus(reader),
|
|
325
334
|
].some((value) => String(value ?? "").toLowerCase().includes(query));
|
|
326
335
|
|
|
327
|
-
const matchesStatus = status.value
|
|
336
|
+
const matchesStatus = !status.value || resolveReaderStatus(reader) === status.value;
|
|
328
337
|
|
|
329
338
|
return matchesSearch && matchesStatus;
|
|
330
339
|
});
|