@7365admin1/layer-common 3.2.2-staging.106 → 3.2.2-staging.108
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/components/CreateSubsciptionPlan.vue +82 -57
- package/components/EntryPassInformation.vue +29 -1
- package/components/HidAccessLogDashboard.vue +42 -29
- package/components/HidIntercomManagement.vue +43 -9
- package/components/HidQrCodeConfiguration.vue +1 -2
- package/components/HidReaderForm.vue +102 -11
- package/components/HidReaderManagement.vue +1 -0
- package/components/HidUserEnrollment.vue +21 -2
- package/components/MemberInformation.vue +9 -0
- package/components/SubscriptionPlanTable.vue +168 -104
- package/components/VisitorForm.vue +30 -1
- package/composables/useHidAmico.ts +13 -0
- package/composables/useSubscriptionPlan.ts +62 -0
- package/package.json +1 -1
- package/types/subscription.ts +18 -11
|
@@ -260,6 +260,8 @@
|
|
|
260
260
|
:loading="entryPassSettingsPending || siteDataPending" :site-id="prop.site"
|
|
261
261
|
:unit-id="visitor.unit || null" :visitor-type="prop.type" :hid-qr-code-enabled="hidQrCodePassAllowed"
|
|
262
262
|
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
263
|
+
:hid-reader-name="hidQrReader?.name || ''"
|
|
264
|
+
:hid-portal-name="hidQrReader?.portalName || ''"
|
|
263
265
|
@update:available-qr-count="(val) => availableQrCount = val" />
|
|
264
266
|
</v-col>
|
|
265
267
|
|
|
@@ -269,6 +271,7 @@
|
|
|
269
271
|
:settings="entryPassSettings" :settings-loading="entryPassSettingsPending || siteDataPending"
|
|
270
272
|
:unit-id="visitor.unit || null" :hid-qr-code-enabled="hidQrCodePassAllowed"
|
|
271
273
|
:hid-qr-printer-configured="hasHidQrPrinterConfig"
|
|
274
|
+
:hid-reader-name="hidQrReader?.name || ''" :hid-portal-name="hidQrReader?.portalName || ''"
|
|
272
275
|
:selected-nfc-cards="passType === 'NFC' ? passCards : []" />
|
|
273
276
|
</v-col>
|
|
274
277
|
|
|
@@ -533,6 +536,9 @@
|
|
|
533
536
|
</v-btn>
|
|
534
537
|
|
|
535
538
|
<div class="text-h6 font-weight-bold">{{ visitor.name }}</div>
|
|
539
|
+
<div v-if="hidQrReader?.portalName" class="text-caption text-medium-emphasis mt-1">
|
|
540
|
+
{{ hidQrReader.name || "HID Reader" }} · {{ hidQrReader.portalName }}
|
|
541
|
+
</div>
|
|
536
542
|
<div v-if="hidQrPreview.expiresAt" class="text-caption text-medium-emphasis mt-1">
|
|
537
543
|
Valid until {{ formatHidQrExpiry(hidQrPreview.expiresAt) }}
|
|
538
544
|
</div>
|
|
@@ -668,7 +674,7 @@ const {
|
|
|
668
674
|
const { getBySiteId: getEntryPassSettingsBySiteId } =
|
|
669
675
|
useSiteEntryPassSettings();
|
|
670
676
|
const { createVisitorPass, signQr } = useAccessManagement();
|
|
671
|
-
const { issueVisitorQr } = useHidAmico();
|
|
677
|
+
const { getReaders, issueVisitorQr } = useHidAmico();
|
|
672
678
|
const { testConnection } = useWebUsb();
|
|
673
679
|
const {
|
|
674
680
|
findPersonByNRIC,
|
|
@@ -877,6 +883,23 @@ const hidQrValidityMinutes = computed(() => {
|
|
|
877
883
|
});
|
|
878
884
|
|
|
879
885
|
const hidQrReaderId = computed(() => String(hidQrCodePassConfig.value?.readerId || ""));
|
|
886
|
+
const hidQrReader = ref<Record<string, any> | null>(null);
|
|
887
|
+
|
|
888
|
+
watch(
|
|
889
|
+
[hidQrReaderId, () => prop.site],
|
|
890
|
+
async ([readerId, site]) => {
|
|
891
|
+
hidQrReader.value = null;
|
|
892
|
+
if (!readerId || !site) return;
|
|
893
|
+
try {
|
|
894
|
+
const response = await getReaders({ site, page: 1, limit: 100 });
|
|
895
|
+
const readers = response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
|
|
896
|
+
hidQrReader.value = readers.find((reader: Record<string, any>) => String(reader._id) === readerId) || null;
|
|
897
|
+
} catch {
|
|
898
|
+
hidQrReader.value = null;
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
{ immediate: true },
|
|
902
|
+
);
|
|
880
903
|
|
|
881
904
|
const hidQrCodePassAllowed = computed(() => {
|
|
882
905
|
return (
|
|
@@ -2100,6 +2123,12 @@ function validateHidQrReaderConfig() {
|
|
|
2100
2123
|
return false;
|
|
2101
2124
|
}
|
|
2102
2125
|
|
|
2126
|
+
if (!hidQrReader.value?.portalId || !hidQrReader.value?.portalName) {
|
|
2127
|
+
errorMessage.value =
|
|
2128
|
+
"The selected HID reader has no portal configured. Reconnect the reader and select a portal before generating a QR code.";
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2103
2132
|
return true;
|
|
2104
2133
|
}
|
|
2105
2134
|
|
|
@@ -6,6 +6,8 @@ type HidReaderPayload = {
|
|
|
6
6
|
username?: string;
|
|
7
7
|
password?: string;
|
|
8
8
|
deviceId?: string;
|
|
9
|
+
portalId?: number;
|
|
10
|
+
portalName?: string;
|
|
9
11
|
monitorPath?: string;
|
|
10
12
|
enabled?: boolean;
|
|
11
13
|
status?: string;
|
|
@@ -34,6 +36,9 @@ type HidVisitorQrData = {
|
|
|
34
36
|
hidUserId: number;
|
|
35
37
|
qrValue: string;
|
|
36
38
|
qrFormat: "0";
|
|
39
|
+
portalId: number;
|
|
40
|
+
portalName: string;
|
|
41
|
+
readerName: string;
|
|
37
42
|
issuedAt: string;
|
|
38
43
|
expiresAt: string;
|
|
39
44
|
};
|
|
@@ -189,6 +194,13 @@ export default function useHidAmico() {
|
|
|
189
194
|
);
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
function discoverReader(payload: Pick<HidReaderPayload, "baseUrl" | "username" | "password">) {
|
|
198
|
+
return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/discover`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: payload,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
function getUserPinStatus(readerId: string, hidUserId: string | number) {
|
|
193
205
|
return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
|
|
194
206
|
`${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
|
|
@@ -294,6 +306,7 @@ export default function useHidAmico() {
|
|
|
294
306
|
|
|
295
307
|
return {
|
|
296
308
|
getReaders,
|
|
309
|
+
discoverReader,
|
|
297
310
|
createReader,
|
|
298
311
|
updateReader,
|
|
299
312
|
deleteReader,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export default function useSubscriptionPlan() {
|
|
2
|
+
function getAll({
|
|
3
|
+
page = 1,
|
|
4
|
+
search = "",
|
|
5
|
+
limit = 10,
|
|
6
|
+
status = "active",
|
|
7
|
+
planType = "",
|
|
8
|
+
billingCycle = "",
|
|
9
|
+
} = {}) {
|
|
10
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/subscription-plans`, {
|
|
11
|
+
method: "GET",
|
|
12
|
+
query: {
|
|
13
|
+
page,
|
|
14
|
+
search,
|
|
15
|
+
limit,
|
|
16
|
+
status,
|
|
17
|
+
planType,
|
|
18
|
+
billingCycle,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function add(value: any) {
|
|
23
|
+
return useNuxtApp().$api<Record<string, any>>("/api/subscription-plans", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: value,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function getById(id: string) {
|
|
29
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
30
|
+
`/api/subscription-plans/id/${id}`,
|
|
31
|
+
{
|
|
32
|
+
method: "GET",
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function updateById(id: string, value: any) {
|
|
37
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
38
|
+
`/api/subscription-plans/${id}`,
|
|
39
|
+
{
|
|
40
|
+
method: "PUT",
|
|
41
|
+
body: value,
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function updateStatusById(id: string, status: "active" | "deactive") {
|
|
46
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
47
|
+
`/api/subscription-plans/${id}/status`,
|
|
48
|
+
{
|
|
49
|
+
method: "PATCH",
|
|
50
|
+
body: { status },
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
getAll,
|
|
57
|
+
add,
|
|
58
|
+
getById,
|
|
59
|
+
updateById,
|
|
60
|
+
updateStatusById,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/package.json
CHANGED
package/types/subscription.ts
CHANGED
|
@@ -5,10 +5,8 @@ export type PlanType = "free_bundle" | "paid_bundle" | "custom_apps";
|
|
|
5
5
|
export type BillingCycle = "monthly" | "annually";
|
|
6
6
|
|
|
7
7
|
export interface AppOption {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
monthlyPrice: number;
|
|
11
|
-
yearlyPrice: number;
|
|
8
|
+
title: string;
|
|
9
|
+
value: string;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export interface SubscriptionPlan {
|
|
@@ -32,6 +30,7 @@ export interface PlanFormState {
|
|
|
32
30
|
bundleApps: string[];
|
|
33
31
|
bundlePrice: number | null;
|
|
34
32
|
customApps: string[];
|
|
33
|
+
customAppPrices: Record<string, number>;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
export const PLAN_TYPE_LABEL: Record<PlanType, string> = {
|
|
@@ -40,12 +39,20 @@ export const PLAN_TYPE_LABEL: Record<PlanType, string> = {
|
|
|
40
39
|
custom_apps: "Paid (Custom Apps)",
|
|
41
40
|
};
|
|
42
41
|
|
|
43
|
-
//
|
|
42
|
+
// Catalogue of applications that can be bundled into a plan.
|
|
44
43
|
// Replace with data fetched from your backend.
|
|
45
|
-
export const
|
|
46
|
-
{
|
|
47
|
-
{
|
|
48
|
-
{
|
|
49
|
-
{
|
|
50
|
-
{
|
|
44
|
+
export const APP_LIST: AppOption[] = [
|
|
45
|
+
{ title: "Organization", value: "organization" },
|
|
46
|
+
{ title: "Security Agency", value: "security_agency" },
|
|
47
|
+
{ title: "Cleaning Services", value: "cleaning_services" },
|
|
48
|
+
{ title: "Property Management Agency", value: "property_management_agency" },
|
|
49
|
+
{ title: "Mechanical & Electrical Services", value: "mechanical_electrical_services" },
|
|
50
|
+
{ title: "Pest Control Services", value: "pest_control_services" },
|
|
51
|
+
{ title: "Landscaping Services", value: "landscaping_services" },
|
|
52
|
+
{ title: "Pool Maintenance Services", value: "pool_maintenance_services" },
|
|
51
53
|
];
|
|
54
|
+
|
|
55
|
+
// Static placeholder price applied to every app, for now.
|
|
56
|
+
// Replace with real per-app pricing once that's available.
|
|
57
|
+
export const STATIC_APP_MONTHLY_PRICE = 49;
|
|
58
|
+
export const STATIC_APP_YEARLY_PRICE = 490;
|