@7365admin1/layer-common 3.1.4-staging.53 → 3.2.0
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 +6 -0
- package/components/CreateSubsciptionPlan.vue +376 -0
- package/components/DashboardMain.vue +16 -38
- package/components/EntryPassInformation.vue +4 -4
- package/components/HidAccessLogDashboard.vue +8 -53
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidIntercomManagement.vue +174 -621
- package/components/HidQrCodeConfiguration.vue +94 -742
- package/components/HidServiceSettingsPanel.vue +1 -20
- package/components/HidUserEnrollment.vue +0 -2
- package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
- package/components/PlanActionsMenu.vue +33 -0
- package/components/PlanStatusBadge.vue +26 -0
- package/components/QrTemplate/CreditCardLandscape.vue +12 -19
- package/components/QrTemplate/PrintDialog.vue +196 -209
- package/components/SubscriptionPlanTable.vue +362 -0
- package/components/VisitorForm.vue +78 -201
- package/components/VisitorManagement.vue +1 -0
- package/composables/useEquipmentManagementPermission.ts +1 -1
- package/composables/useEquipmentPermission.ts +1 -1
- package/composables/useHidAmico.ts +0 -66
- package/composables/useHidNavigation.ts +7 -0
- package/composables/useWebUsb.ts +37 -127
- package/package.json +1 -2
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
- package/plugins/secure-member.client.ts +56 -21
- package/types/site.d.ts +0 -65
- package/types/subscription.ts +51 -0
- package/composables/useSipWebPhone.ts +0 -236
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card class="pa-2">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<v-card-title class="d-flex align-center justify-space-between pb-2">
|
|
5
|
+
<span class="text-h6 font-weight-bold">
|
|
6
|
+
{{ isEditing ? "Edit Plan" : "Create New Plan" }}
|
|
7
|
+
</span>
|
|
8
|
+
|
|
9
|
+
<v-btn
|
|
10
|
+
icon="mdi-close"
|
|
11
|
+
variant="text"
|
|
12
|
+
density="comfortable"
|
|
13
|
+
aria-label="Close"
|
|
14
|
+
@click="$emit('cancel')"
|
|
15
|
+
/>
|
|
16
|
+
</v-card-title>
|
|
17
|
+
|
|
18
|
+
<v-divider />
|
|
19
|
+
|
|
20
|
+
<!-- Form Content -->
|
|
21
|
+
<v-form ref="formRef" class="my-4" @submit.prevent="submit">
|
|
22
|
+
<v-card-text class="pa-2 max-h-70vh overflow-y-auto">
|
|
23
|
+
<!-- Plan Details Section -->
|
|
24
|
+
<div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
|
|
25
|
+
Plan Details
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Plan Name -->
|
|
29
|
+
<v-text-field
|
|
30
|
+
v-model="form.name"
|
|
31
|
+
label="Plan Name"
|
|
32
|
+
placeholder="e.g., Business"
|
|
33
|
+
variant="outlined"
|
|
34
|
+
density="compact"
|
|
35
|
+
:error-messages="errors.name"
|
|
36
|
+
class="mb-3"
|
|
37
|
+
required
|
|
38
|
+
>
|
|
39
|
+
<template #label>
|
|
40
|
+
Plan Name <span class="text-error">*</span>
|
|
41
|
+
</template>
|
|
42
|
+
</v-text-field>
|
|
43
|
+
|
|
44
|
+
<!-- Description -->
|
|
45
|
+
<v-textarea
|
|
46
|
+
v-model="form.description"
|
|
47
|
+
label="Description (optional)"
|
|
48
|
+
placeholder="Enter description"
|
|
49
|
+
variant="outlined"
|
|
50
|
+
density="compact"
|
|
51
|
+
rows="2"
|
|
52
|
+
auto-grow
|
|
53
|
+
class="mb-3"
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<!-- Max Seats -->
|
|
57
|
+
<v-text-field
|
|
58
|
+
v-model.number="form.maxSeats"
|
|
59
|
+
type="number"
|
|
60
|
+
min="0"
|
|
61
|
+
variant="outlined"
|
|
62
|
+
density="compact"
|
|
63
|
+
class="mb-4"
|
|
64
|
+
required
|
|
65
|
+
>
|
|
66
|
+
<template #label>
|
|
67
|
+
Max seats <span class="text-error">*</span>
|
|
68
|
+
</template>
|
|
69
|
+
</v-text-field>
|
|
70
|
+
|
|
71
|
+
<v-divider class="my-4" />
|
|
72
|
+
|
|
73
|
+
<!-- Pricing Section -->
|
|
74
|
+
<div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
|
|
75
|
+
Pricing
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Plan Type -->
|
|
79
|
+
<v-select
|
|
80
|
+
v-model="form.planType"
|
|
81
|
+
:items="planTypeOptions"
|
|
82
|
+
variant="outlined"
|
|
83
|
+
density="compact"
|
|
84
|
+
class="mb-3"
|
|
85
|
+
>
|
|
86
|
+
<template #label>
|
|
87
|
+
Subscription plan type <span class="text-error">*</span>
|
|
88
|
+
</template>
|
|
89
|
+
</v-select>
|
|
90
|
+
|
|
91
|
+
<!-- Billing Cycle -->
|
|
92
|
+
<v-select
|
|
93
|
+
v-if="form.planType !== 'free_bundle'"
|
|
94
|
+
v-model="form.billingCycle"
|
|
95
|
+
:items="billingCycleOptions"
|
|
96
|
+
placeholder="Select billing cycle"
|
|
97
|
+
variant="outlined"
|
|
98
|
+
density="compact"
|
|
99
|
+
class="mb-3"
|
|
100
|
+
>
|
|
101
|
+
<template #label>
|
|
102
|
+
Billing cycle <span class="text-error">*</span>
|
|
103
|
+
</template>
|
|
104
|
+
</v-select>
|
|
105
|
+
|
|
106
|
+
<!-- Bundle Applications (Chip Toggle) -->
|
|
107
|
+
<div v-if="form.planType !== 'custom_apps'" class="mb-4">
|
|
108
|
+
<div class="text-caption font-weight-medium text-grey-darken-2 mb-2">
|
|
109
|
+
Included Applications
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<v-chip-group
|
|
113
|
+
v-model="form.bundleApps"
|
|
114
|
+
column
|
|
115
|
+
multiple
|
|
116
|
+
selected-class="bg-grey-darken-3 text-white"
|
|
117
|
+
>
|
|
118
|
+
<v-chip
|
|
119
|
+
v-for="app in AVAILABLE_APPLICATIONS"
|
|
120
|
+
:key="app.id"
|
|
121
|
+
:value="app.id"
|
|
122
|
+
filter
|
|
123
|
+
variant="outlined"
|
|
124
|
+
size="small"
|
|
125
|
+
>
|
|
126
|
+
{{ app.name }}
|
|
127
|
+
</v-chip>
|
|
128
|
+
</v-chip-group>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Bundle Price -->
|
|
132
|
+
<v-text-field
|
|
133
|
+
v-if="form.planType === 'paid_bundle'"
|
|
134
|
+
v-model.number="form.bundlePrice"
|
|
135
|
+
type="number"
|
|
136
|
+
min="0"
|
|
137
|
+
placeholder="0"
|
|
138
|
+
prefix="$"
|
|
139
|
+
:suffix="form.billingCycle === 'annually' ? '/yr' : '/mo'"
|
|
140
|
+
variant="outlined"
|
|
141
|
+
density="compact"
|
|
142
|
+
class="mb-3"
|
|
143
|
+
>
|
|
144
|
+
<template #label>
|
|
145
|
+
Price <span class="text-error">*</span>
|
|
146
|
+
</template>
|
|
147
|
+
</v-text-field>
|
|
148
|
+
|
|
149
|
+
<!-- Custom Applications (List Checkbox) -->
|
|
150
|
+
<div v-if="form.planType === 'custom_apps'" class="mb-4">
|
|
151
|
+
<div class="text-caption font-weight-medium text-grey-darken-2 mb-1">
|
|
152
|
+
Included Applications <span class="text-error">*</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<v-card variant="outlined" class="pa-1 mb-3">
|
|
156
|
+
<v-list density="compact" class="py-0">
|
|
157
|
+
<v-list-item
|
|
158
|
+
v-for="app in AVAILABLE_APPLICATIONS"
|
|
159
|
+
:key="app.id"
|
|
160
|
+
class="px-2"
|
|
161
|
+
>
|
|
162
|
+
<template #prepend>
|
|
163
|
+
<v-checkbox-btn
|
|
164
|
+
:model-value="form.customApps.includes(app.id)"
|
|
165
|
+
density="compact"
|
|
166
|
+
@update:model-value="toggleApp(form.customApps, app.id)"
|
|
167
|
+
/>
|
|
168
|
+
</template>
|
|
169
|
+
|
|
170
|
+
<v-list-item-title class="text-body-2">
|
|
171
|
+
{{ app.name }}
|
|
172
|
+
</v-list-item-title>
|
|
173
|
+
|
|
174
|
+
<template #append>
|
|
175
|
+
<span class="text-caption text-grey">
|
|
176
|
+
${{ form.billingCycle === 'annually' ? app.yearlyPrice : app.monthlyPrice }}/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}
|
|
177
|
+
</span>
|
|
178
|
+
</template>
|
|
179
|
+
</v-list-item>
|
|
180
|
+
</v-list>
|
|
181
|
+
</v-card>
|
|
182
|
+
|
|
183
|
+
<!-- Custom Total Price Display -->
|
|
184
|
+
<v-sheet
|
|
185
|
+
color="grey-lighten-4"
|
|
186
|
+
rounded
|
|
187
|
+
class="pa-3 d-flex align-center justify-space-between mb-1"
|
|
188
|
+
>
|
|
189
|
+
<span class="text-caption text-grey-darken-2">
|
|
190
|
+
Total price {{ form.billingCycle === 'annually' ? '/ year' : '/ month' }}
|
|
191
|
+
</span>
|
|
192
|
+
<span class="text-subtitle-1 font-weight-bold">
|
|
193
|
+
${{ customTotalPrice.toLocaleString() }}
|
|
194
|
+
</span>
|
|
195
|
+
</v-sheet>
|
|
196
|
+
|
|
197
|
+
<p class="text-caption text-grey">
|
|
198
|
+
Calculated automatically from the selected applications.
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<!-- Global Pricing Error Message -->
|
|
203
|
+
<v-alert
|
|
204
|
+
v-if="errors.pricing"
|
|
205
|
+
type="error"
|
|
206
|
+
variant="tonal"
|
|
207
|
+
density="compact"
|
|
208
|
+
class="mt-2 text-caption"
|
|
209
|
+
>
|
|
210
|
+
{{ errors.pricing }}
|
|
211
|
+
</v-alert>
|
|
212
|
+
</v-card-text>
|
|
213
|
+
|
|
214
|
+
<v-divider />
|
|
215
|
+
|
|
216
|
+
<!-- Footer Buttons -->
|
|
217
|
+
<v-card-actions class="pt-3 px-2">
|
|
218
|
+
<v-btn
|
|
219
|
+
variant="outlined"
|
|
220
|
+
color="grey-darken-1"
|
|
221
|
+
class="flex-1 text-none"
|
|
222
|
+
@click="$emit('cancel')"
|
|
223
|
+
>
|
|
224
|
+
Cancel
|
|
225
|
+
</v-btn>
|
|
226
|
+
|
|
227
|
+
<v-btn
|
|
228
|
+
type="submit"
|
|
229
|
+
color="grey-darken-4"
|
|
230
|
+
variant="flat"
|
|
231
|
+
class="flex-1 text-none"
|
|
232
|
+
>
|
|
233
|
+
{{ isEditing ? "Save Changes" : "Create Plan" }}
|
|
234
|
+
</v-btn>
|
|
235
|
+
</v-card-actions>
|
|
236
|
+
</v-form>
|
|
237
|
+
</v-card>
|
|
238
|
+
</template>
|
|
239
|
+
|
|
240
|
+
<script setup lang="ts">
|
|
241
|
+
import { reactive, computed, watch } from "vue";
|
|
242
|
+
|
|
243
|
+
import {
|
|
244
|
+
AVAILABLE_APPLICATIONS,
|
|
245
|
+
type PlanFormState,
|
|
246
|
+
type SubscriptionPlan,
|
|
247
|
+
} from "../types/subscription";
|
|
248
|
+
|
|
249
|
+
const props = defineProps<{
|
|
250
|
+
initialPlan?: SubscriptionPlan | null;
|
|
251
|
+
}>();
|
|
252
|
+
|
|
253
|
+
const emit = defineEmits<{
|
|
254
|
+
(e: "cancel"): void;
|
|
255
|
+
(e: "submit", payload: PlanFormState): void;
|
|
256
|
+
}>();
|
|
257
|
+
|
|
258
|
+
// Select options
|
|
259
|
+
const planTypeOptions = [
|
|
260
|
+
{ title: "Free (Bundle)", value: "free_bundle" },
|
|
261
|
+
{ title: "Paid (Bundle)", value: "paid_bundle" },
|
|
262
|
+
{ title: "Paid (Custom Apps)", value: "custom_apps" },
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const billingCycleOptions = [
|
|
266
|
+
{ title: "Monthly", value: "monthly" },
|
|
267
|
+
{ title: "Annually", value: "annually" },
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
const isEditing = computed(() => !!props.initialPlan);
|
|
271
|
+
|
|
272
|
+
const form = reactive<PlanFormState>({
|
|
273
|
+
name: props.initialPlan?.name ?? "",
|
|
274
|
+
description: props.initialPlan?.description ?? "",
|
|
275
|
+
maxSeats: props.initialPlan?.maxSeats ?? 0,
|
|
276
|
+
planType: props.initialPlan?.planType ?? "free_bundle",
|
|
277
|
+
billingCycle: props.initialPlan?.billingCycle ?? null,
|
|
278
|
+
bundleApps:
|
|
279
|
+
props.initialPlan?.planType !== "custom_apps"
|
|
280
|
+
? [...(props.initialPlan?.applicationIds ?? [])]
|
|
281
|
+
: [],
|
|
282
|
+
bundlePrice: props.initialPlan?.price ?? null,
|
|
283
|
+
customApps:
|
|
284
|
+
props.initialPlan?.planType === "custom_apps"
|
|
285
|
+
? [...(props.initialPlan?.applicationIds ?? [])]
|
|
286
|
+
: [],
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const errors = reactive({
|
|
290
|
+
name: "",
|
|
291
|
+
pricing: "",
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
/*
|
|
295
|
+
* Reset pricing fields when plan type changes
|
|
296
|
+
*/
|
|
297
|
+
watch(
|
|
298
|
+
() => form.planType,
|
|
299
|
+
(type) => {
|
|
300
|
+
if (type === "free_bundle") {
|
|
301
|
+
form.billingCycle = null;
|
|
302
|
+
form.bundlePrice = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
/*
|
|
308
|
+
* Toggle application selection helper
|
|
309
|
+
*/
|
|
310
|
+
function toggleApp(list: string[], id: string) {
|
|
311
|
+
const index = list.indexOf(id);
|
|
312
|
+
if (index === -1) {
|
|
313
|
+
list.push(id);
|
|
314
|
+
} else {
|
|
315
|
+
list.splice(index, 1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
* Custom app total price calculation
|
|
321
|
+
*/
|
|
322
|
+
const customTotalPrice = computed(() => {
|
|
323
|
+
return form.customApps.reduce((total, id) => {
|
|
324
|
+
const app = AVAILABLE_APPLICATIONS.find((item) => item.id === id);
|
|
325
|
+
if (!app) return total;
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
total +
|
|
329
|
+
(form.billingCycle === "annually" ? app.yearlyPrice : app.monthlyPrice)
|
|
330
|
+
);
|
|
331
|
+
}, 0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
* Validate Form
|
|
336
|
+
*/
|
|
337
|
+
function validate(): boolean {
|
|
338
|
+
errors.name = form.name.trim() ? "" : "Plan name is required.";
|
|
339
|
+
errors.pricing = "";
|
|
340
|
+
|
|
341
|
+
if (form.planType !== "free_bundle" && !form.billingCycle) {
|
|
342
|
+
errors.pricing = "Please select a billing cycle.";
|
|
343
|
+
} else if (
|
|
344
|
+
form.planType === "paid_bundle" &&
|
|
345
|
+
(!form.bundlePrice || form.bundlePrice <= 0)
|
|
346
|
+
) {
|
|
347
|
+
errors.pricing = "Please enter a price.";
|
|
348
|
+
} else if (
|
|
349
|
+
form.planType === "custom_apps" &&
|
|
350
|
+
form.customApps.length === 0
|
|
351
|
+
) {
|
|
352
|
+
errors.pricing = "Select at least one application.";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return !errors.name && !errors.pricing;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* Submit Form
|
|
360
|
+
*/
|
|
361
|
+
function submit() {
|
|
362
|
+
if (!validate()) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
emit("submit", {
|
|
367
|
+
...form,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
</script>
|
|
371
|
+
|
|
372
|
+
<style scoped>
|
|
373
|
+
.max-h-70vh {
|
|
374
|
+
max-height: 70vh;
|
|
375
|
+
}
|
|
376
|
+
</style>
|
|
@@ -1483,6 +1483,7 @@ const props = defineProps({
|
|
|
1483
1483
|
},
|
|
1484
1484
|
title: { type: String, default: "" },
|
|
1485
1485
|
extraWidgetKeys: { type: Array as () => string[], default: () => [] },
|
|
1486
|
+
hiddenWidgetKeys: { type: Array as () => string[], default: () => [] },
|
|
1486
1487
|
showHeaderActions: { type: Boolean, default: true },
|
|
1487
1488
|
appKey: {
|
|
1488
1489
|
type: String as () =>
|
|
@@ -1530,57 +1531,33 @@ const { downloadPDF } = usePDFDownload();
|
|
|
1530
1531
|
const route = useRoute();
|
|
1531
1532
|
|
|
1532
1533
|
const propertyWidgetPermissions: Record<string, string[]> = {
|
|
1533
|
-
workOrders: [
|
|
1534
|
-
"work_orders:see-all-work-orders",
|
|
1535
|
-
"work-order:see-all-work-orders",
|
|
1536
|
-
],
|
|
1534
|
+
workOrders: ["work_orders:see-all-work-orders"],
|
|
1537
1535
|
incidents: ["incident-reports:see-incident-reports"],
|
|
1538
|
-
visitors: ["
|
|
1536
|
+
visitors: ["visitor-mgmt:see-all-visitor"],
|
|
1539
1537
|
facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
|
|
1540
|
-
activity: [
|
|
1541
|
-
"work_orders:see-all-work-orders",
|
|
1542
|
-
"work-order:see-all-work-orders",
|
|
1543
|
-
],
|
|
1538
|
+
activity: ["work_orders:see-all-work-orders"],
|
|
1544
1539
|
upcomingEvents: ["event-mgmt:see-all-event"],
|
|
1545
1540
|
todayReminders: ["event-mgmt:see-all-event"],
|
|
1546
1541
|
todayAttentions: [
|
|
1547
1542
|
"incident-reports:see-incident-reports",
|
|
1548
1543
|
"facility-booking-mgmt:see-all-facility-booking",
|
|
1549
1544
|
],
|
|
1550
|
-
workOrderStatus: [
|
|
1551
|
-
"work_orders:see-all-work-orders",
|
|
1552
|
-
"work-order:see-all-work-orders",
|
|
1553
|
-
],
|
|
1545
|
+
workOrderStatus: ["work_orders:see-all-work-orders"],
|
|
1554
1546
|
feedbacks: ["feedbacks:see-all-feedback"],
|
|
1555
1547
|
activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
|
|
1556
1548
|
};
|
|
1557
1549
|
|
|
1558
1550
|
const moduleWidgetPermissions: Record<string, string[]> = {
|
|
1559
|
-
openWorkOrder: [
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
"
|
|
1564
|
-
|
|
1565
|
-
workOrders: [
|
|
1566
|
-
"work_orders:see-all-work-orders",
|
|
1567
|
-
"work_order:see-all-work-orders",
|
|
1568
|
-
"work-order:see-all-work-orders",
|
|
1569
|
-
"work-orders:see-all-work-orders",
|
|
1570
|
-
],
|
|
1571
|
-
activity: [
|
|
1572
|
-
"work_orders:see-all-work-orders",
|
|
1573
|
-
"work_order:see-all-work-orders",
|
|
1574
|
-
"work-order:see-all-work-orders",
|
|
1575
|
-
"work-orders:see-all-work-orders",
|
|
1576
|
-
"work_orders:see-all-work-orders",
|
|
1551
|
+
openWorkOrder: ["work_orders:see-all-work-orders"],
|
|
1552
|
+
workOrders: ["work_orders:see-all-work-orders"],
|
|
1553
|
+
activity: ["work_orders:see-all-work-orders"],
|
|
1554
|
+
taskSchedule: [
|
|
1555
|
+
"cleaning-schedule-mgmt:see-all-schedules",
|
|
1556
|
+
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
1577
1557
|
],
|
|
1578
|
-
|
|
1579
|
-
"
|
|
1580
|
-
"
|
|
1581
|
-
"work-order:see-all-work-orders",
|
|
1582
|
-
"work-orders:see-all-work-orders",
|
|
1583
|
-
"work_orders:see-all-work-orders",
|
|
1558
|
+
taskCompleted: [
|
|
1559
|
+
"cleaning-schedule-mgmt:see-all-schedules",
|
|
1560
|
+
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
1584
1561
|
],
|
|
1585
1562
|
staffStatus: ["attendance-mgmt:see-all-attendance"],
|
|
1586
1563
|
attendance: ["attendance-mgmt:see-all-attendance"],
|
|
@@ -1594,7 +1571,7 @@ const moduleWidgetPermissions: Record<string, string[]> = {
|
|
|
1594
1571
|
"incident-reports:see-incident-reports",
|
|
1595
1572
|
"incident-reports:see-all-incident-reports",
|
|
1596
1573
|
],
|
|
1597
|
-
visitors: ["visitor:see-all-visitor"],
|
|
1574
|
+
visitors: ["visitor-mgmt:see-all-visitor"],
|
|
1598
1575
|
facilityBookings: ["facility-booking:see-all-facility-booking"],
|
|
1599
1576
|
patrolCompliance: [
|
|
1600
1577
|
"virtual-patrol:see-all-virtual-patrol-logs",
|
|
@@ -1610,6 +1587,7 @@ function hasSchedulePermission(permissions: string[], action: string) {
|
|
|
1610
1587
|
}
|
|
1611
1588
|
|
|
1612
1589
|
function canViewWidget(key: string) {
|
|
1590
|
+
if (props.hiddenWidgetKeys.includes(key)) return false;
|
|
1613
1591
|
const permissions = userAppRole.value?.permissions;
|
|
1614
1592
|
const modulePermissions = isPropertyMode.value
|
|
1615
1593
|
? propertyWidgetPermissions[key]
|
|
@@ -168,13 +168,13 @@
|
|
|
168
168
|
<template v-if="passType === 'HID_QR'">
|
|
169
169
|
<v-alert
|
|
170
170
|
v-if="!hidQrPrinterConfigured"
|
|
171
|
-
type="
|
|
171
|
+
type="warning"
|
|
172
172
|
variant="tonal"
|
|
173
173
|
density="compact"
|
|
174
174
|
class="mt-3"
|
|
175
|
-
icon="mdi-
|
|
175
|
+
icon="mdi-printer-alert"
|
|
176
176
|
>
|
|
177
|
-
|
|
177
|
+
No printer is configured on HID settings. QR Pass may not print.
|
|
178
178
|
</v-alert>
|
|
179
179
|
<v-alert
|
|
180
180
|
v-else
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
class="mt-3"
|
|
185
185
|
icon="mdi-qrcode-scan"
|
|
186
186
|
>
|
|
187
|
-
HID QR Code will be generated and
|
|
187
|
+
HID QR Code will be generated and printed when you continue.
|
|
188
188
|
</v-alert>
|
|
189
189
|
</template>
|
|
190
190
|
</template>
|
|
@@ -418,17 +418,8 @@ const selectedHistoryRows = computed<AccessRow[]>(() => {
|
|
|
418
418
|
if (!selectedHistoryRow.value) return [];
|
|
419
419
|
|
|
420
420
|
const selectedKey = selectedHistoryRow.value.identityKey;
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
selectedIdentity
|
|
424
|
-
? accessLogMatchesIdentity(log, selectedIdentity)
|
|
425
|
-
: getLogIdentityKey(log) === selectedKey,
|
|
426
|
-
);
|
|
427
|
-
const matchedLiveLogs = liveAccessLogs.value.filter((log) =>
|
|
428
|
-
selectedIdentity
|
|
429
|
-
? accessLogMatchesIdentity(log, selectedIdentity)
|
|
430
|
-
: getAccessLogIdentityKey(log) === selectedKey,
|
|
431
|
-
);
|
|
421
|
+
const matchedLogs = logs.value.filter((log) => getLogIdentityKey(log) === selectedKey);
|
|
422
|
+
const matchedLiveLogs = liveAccessLogs.value.filter((log) => getAccessLogIdentityKey(log) === selectedKey);
|
|
432
423
|
|
|
433
424
|
if (!matchedLogs.length && !matchedLiveLogs.length) {
|
|
434
425
|
return rows.value.filter((row) => row.identityKey === selectedKey);
|
|
@@ -844,7 +835,7 @@ function getIdentityKey(identity: Record<string, any>) {
|
|
|
844
835
|
return [
|
|
845
836
|
identity.hidUserId,
|
|
846
837
|
identity.registration,
|
|
847
|
-
identity.cardNo
|
|
838
|
+
identity.cardNo,
|
|
848
839
|
].filter(Boolean).join("|");
|
|
849
840
|
}
|
|
850
841
|
|
|
@@ -860,7 +851,7 @@ function getLogIdentityKey(log: Record<string, any>) {
|
|
|
860
851
|
return [
|
|
861
852
|
identity.hidUserId || lookup.hidUserId || rawLog.user_id || rawLog.userId || rawLog.hidUserId,
|
|
862
853
|
identity.registration || lookup.registration,
|
|
863
|
-
identity.cardNo || lookup.cardNo || rawLog.card_value || rawLog.cardNo
|
|
854
|
+
identity.cardNo || lookup.cardNo || rawLog.card_value || rawLog.cardNo,
|
|
864
855
|
].filter(Boolean).join("|");
|
|
865
856
|
}
|
|
866
857
|
|
|
@@ -1182,26 +1173,25 @@ function findIdentityForAccessLog(rawLog: Record<string, any>, event: Record<str
|
|
|
1182
1173
|
const hidUserId = rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? event.payload?.identity?.hidUserId ?? event.payload?.identityLookup?.hidUserId;
|
|
1183
1174
|
const registration = rawLog.registration ?? event.payload?.identity?.registration ?? event.payload?.identityLookup?.registration;
|
|
1184
1175
|
const cardNo = rawLog.card_value || rawLog.cardNo || event.payload?.identity?.cardNo || event.payload?.identityLookup?.cardNo;
|
|
1185
|
-
const qrCodeValue = rawLog.qrcode_value || rawLog.qrCode || rawLog.qrcode;
|
|
1186
1176
|
|
|
1187
1177
|
return identities.value.find((identity) => {
|
|
1188
1178
|
return (
|
|
1189
1179
|
(hidUserId !== undefined && hidUserId !== null && String(identity.hidUserId) === String(hidUserId)) ||
|
|
1190
1180
|
(registration && identity.registration === registration) ||
|
|
1191
|
-
(cardNo && String(identity.cardNo) === String(cardNo))
|
|
1192
|
-
(qrCodeValue && String(identity.metadata?.qrCodeValue || "") === String(qrCodeValue))
|
|
1181
|
+
(cardNo && String(identity.cardNo) === String(cardNo))
|
|
1193
1182
|
);
|
|
1194
1183
|
});
|
|
1195
1184
|
}
|
|
1196
1185
|
|
|
1197
1186
|
function getLatestAccessLogForIdentity(identity: Record<string, any>) {
|
|
1187
|
+
const identityKey = getIdentityKey(identity);
|
|
1198
1188
|
const accessLogs = [
|
|
1199
1189
|
...liveAccessLogs.value,
|
|
1200
1190
|
...logs.value.flatMap((event) => getRawAccessLogs(event)),
|
|
1201
1191
|
];
|
|
1202
1192
|
|
|
1203
1193
|
return accessLogs
|
|
1204
|
-
.filter((log) =>
|
|
1194
|
+
.filter((log) => getAccessLogIdentityKey(log) === identityKey)
|
|
1205
1195
|
.sort((a, b) => {
|
|
1206
1196
|
const aTime = parseDate(getAccessLogTime(a, {}))?.getTime() || 0;
|
|
1207
1197
|
const bTime = parseDate(getAccessLogTime(b, {}))?.getTime() || 0;
|
|
@@ -1209,46 +1199,11 @@ function getLatestAccessLogForIdentity(identity: Record<string, any>) {
|
|
|
1209
1199
|
})[0];
|
|
1210
1200
|
}
|
|
1211
1201
|
|
|
1212
|
-
function toUnknownRecord(value: unknown): Record<string, unknown> {
|
|
1213
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1214
|
-
? value as Record<string, unknown>
|
|
1215
|
-
: {};
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
function accessLogMatchesIdentity(log: unknown, identity: unknown) {
|
|
1219
|
-
const logRecord = toUnknownRecord(log);
|
|
1220
|
-
const identityRecord = toUnknownRecord(identity);
|
|
1221
|
-
const payload = toUnknownRecord(logRecord.payload);
|
|
1222
|
-
const rawLog = Object.keys(toUnknownRecord(payload.rawAccessLog)).length
|
|
1223
|
-
? toUnknownRecord(payload.rawAccessLog)
|
|
1224
|
-
: logRecord;
|
|
1225
|
-
const eventIdentity = Object.keys(toUnknownRecord(payload.identity)).length
|
|
1226
|
-
? toUnknownRecord(payload.identity)
|
|
1227
|
-
: toUnknownRecord(payload.identityLookup);
|
|
1228
|
-
const metadata = toUnknownRecord(identityRecord.metadata);
|
|
1229
|
-
const hidUserId = rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? eventIdentity.hidUserId;
|
|
1230
|
-
const registration = rawLog.registration || eventIdentity.registration;
|
|
1231
|
-
const credential =
|
|
1232
|
-
rawLog.card_value ||
|
|
1233
|
-
rawLog.cardNo ||
|
|
1234
|
-
rawLog.qrcode_value ||
|
|
1235
|
-
rawLog.qrCode ||
|
|
1236
|
-
rawLog.qrcode ||
|
|
1237
|
-
eventIdentity.cardNo;
|
|
1238
|
-
const identityCredential = identityRecord.cardNo || metadata.qrCodeValue;
|
|
1239
|
-
|
|
1240
|
-
return Boolean(
|
|
1241
|
-
(hidUserId !== undefined && hidUserId !== null && String(identityRecord.hidUserId) === String(hidUserId)) ||
|
|
1242
|
-
(registration && identityRecord.registration === registration) ||
|
|
1243
|
-
(credential && identityCredential && String(identityCredential) === String(credential)),
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
1202
|
function getAccessLogIdentityKey(rawLog: Record<string, any>) {
|
|
1248
1203
|
return [
|
|
1249
1204
|
rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId,
|
|
1250
1205
|
rawLog.registration,
|
|
1251
|
-
rawLog.card_value || rawLog.cardNo
|
|
1206
|
+
rawLog.card_value || rawLog.cardNo,
|
|
1252
1207
|
].filter(Boolean).join("|");
|
|
1253
1208
|
}
|
|
1254
1209
|
|