@7365admin1/layer-common 3.0.7 → 3.0.9
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 +13 -0
- package/components/CameraMain.vue +17 -1
- package/components/DashboardMain.vue +1854 -848
- package/components/EntryPassInformation.vue +60 -0
- package/components/HidAccessLogDashboard.vue +1691 -0
- package/components/HidEnabledGate.vue +51 -0
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidQrCodeConfiguration.vue +618 -0
- package/components/HidReaderForm.vue +246 -0
- package/components/HidReaderManagement.vue +1233 -0
- package/components/HidServiceSettingsPanel.vue +150 -0
- package/components/HidUserEnrollment.vue +1274 -0
- package/components/Layout/NavigationDrawer.vue +31 -1
- package/components/MemberInformation.vue +23 -15
- package/components/NavigationItem.vue +11 -4
- package/components/OnlineFormFill.vue +34 -0
- package/components/PassInformation.vue +73 -34
- package/components/SiteSettings.vue +60 -116
- package/components/VisitorForm.vue +815 -290
- package/components/VisitorFormSelection.vue +12 -2
- package/components/VisitorManagement.vue +652 -205
- package/composables/useCommonPermission.ts +59 -0
- package/composables/useDashboard.ts +46 -1
- package/composables/useHidAmico.ts +152 -0
- package/composables/useHidNavigation.ts +106 -0
- package/composables/useMember.ts +7 -1
- package/composables/useOptionalServices.ts +118 -0
- package/composables/useSettingsPermission.ts +138 -0
- package/composables/useSiteSettings.ts +1 -1
- package/composables/useUser.ts +1 -2
- package/nuxt.config.ts +1 -1
- package/package.json +3 -2
- package/pages/[org]/[site]/access-mgmt/access-logs/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +21 -0
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +21 -0
- package/types/local.d.ts +2 -1
- package/types/visitor.d.ts +4 -2
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<!-- NAVIGATION ITEMS -->
|
|
14
14
|
<v-tooltip
|
|
15
|
-
v-for="(item, index) in
|
|
15
|
+
v-for="(item, index) in navigationItems"
|
|
16
16
|
:key="`${item.title}-${index}`"
|
|
17
17
|
location="right"
|
|
18
18
|
:disabled="!item.disabled"
|
|
@@ -52,6 +52,36 @@ const emit = defineEmits(["navigate"]);
|
|
|
52
52
|
|
|
53
53
|
const { drawer } = useLocal();
|
|
54
54
|
const { APP_NAME } = useRuntimeConfig().public;
|
|
55
|
+
const route = useRoute();
|
|
56
|
+
const orgId = computed(() => String(route.params.org ?? ""));
|
|
57
|
+
const siteId = computed(() => String(route.params.site ?? ""));
|
|
58
|
+
const { useHidFaceReaderMenu } = useHidNavigation();
|
|
59
|
+
const { menuItem: hidFaceReaderMenu } = useHidFaceReaderMenu({
|
|
60
|
+
org: orgId,
|
|
61
|
+
site: siteId,
|
|
62
|
+
cacheKey: "layout-navigation-hid-site",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const navigationItems = computed(() => {
|
|
66
|
+
const items = [...(props.navigationItems as TNavigationItem[])];
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
hidFaceReaderMenu.value &&
|
|
70
|
+
!items.some((item) => item.title === hidFaceReaderMenu.value?.title)
|
|
71
|
+
) {
|
|
72
|
+
const insertAfterIndex = items.findIndex(
|
|
73
|
+
(item) => item.title === "Pass & Key Mgmt",
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (insertAfterIndex >= 0) {
|
|
77
|
+
items.splice(insertAfterIndex + 1, 0, hidFaceReaderMenu.value);
|
|
78
|
+
} else {
|
|
79
|
+
items.push(hidFaceReaderMenu.value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return items;
|
|
84
|
+
});
|
|
55
85
|
|
|
56
86
|
const handleClick = (item: any) => {
|
|
57
87
|
if (item.disabled) return;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<v-autocomplete v-model="selectedPass" v-model:search="passInput" :hide-no-data="false"
|
|
23
23
|
class="mt-3" :items="passItemsFilteredFinal" item-title="prefixAndName" item-value="_id"
|
|
24
24
|
label="Pass (optional)" variant="outlined" density="compact" :error-messages="errorScanPassMessage"
|
|
25
|
-
persistent-hint small-chips>
|
|
25
|
+
persistent-hint small-chips @click="fetchPasses">
|
|
26
26
|
<template v-slot:no-data>
|
|
27
27
|
<v-list-item density="compact">
|
|
28
28
|
<v-list-item-title v-if="passInput">
|
|
@@ -160,20 +160,28 @@ const passTypesComputed = computed(() => {
|
|
|
160
160
|
}
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
163
|
+
const isPassListLoading = ref(false);
|
|
164
|
+
|
|
165
|
+
async function fetchPasses() {
|
|
166
|
+
// load pass list
|
|
167
|
+
passItems.value = [];
|
|
168
|
+
isPassListLoading.value = true;
|
|
169
|
+
try {
|
|
170
|
+
const result = await getPassKeysByPageSearch({
|
|
171
|
+
page: 1,
|
|
172
|
+
sites: [props.site],
|
|
173
|
+
limit: 10000,
|
|
174
|
+
passTypes: passTypesComputed.value,
|
|
175
|
+
statuses: ["Available"],
|
|
176
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
177
|
+
});
|
|
178
|
+
passItems.value = result?.items;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
showMessage(errorConverter(error as string), "error");
|
|
181
|
+
} finally {
|
|
182
|
+
isPassListLoading.value = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
177
185
|
|
|
178
186
|
const passItemsFilteredFinal = computed(() => {
|
|
179
187
|
return passItems.value.filter((item: TPassKey) => {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
</v-list-group>
|
|
23
23
|
|
|
24
24
|
<v-list-item
|
|
25
|
-
v-else-if="
|
|
25
|
+
v-else-if="routeTo"
|
|
26
26
|
:prepend-icon="icon"
|
|
27
|
-
:to="
|
|
27
|
+
:to="routeTo"
|
|
28
28
|
class="text-subtitle-2"
|
|
29
29
|
>
|
|
30
30
|
{{ title }}
|
|
@@ -76,8 +76,15 @@ const props = defineProps({
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
function onParentClick() {
|
|
79
|
-
if (
|
|
80
|
-
navigateTo(
|
|
79
|
+
if (routeTo.value) {
|
|
80
|
+
navigateTo(routeTo.value);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
const routeTo = computed(() => {
|
|
85
|
+
if (!props.route) return null;
|
|
86
|
+
if (props.route.name) return props.route;
|
|
87
|
+
if (props.route.path) return props.route;
|
|
88
|
+
return null;
|
|
89
|
+
});
|
|
83
90
|
</script>
|
|
@@ -27,6 +27,24 @@
|
|
|
27
27
|
size="x-small"
|
|
28
28
|
class="text-capitalize"
|
|
29
29
|
>{{ submissionStatus }}</v-chip>
|
|
30
|
+
<template v-if="requiresPayment">
|
|
31
|
+
<v-chip
|
|
32
|
+
v-if="initialManagementValues?.paymentStatus"
|
|
33
|
+
color="green"
|
|
34
|
+
variant="flat"
|
|
35
|
+
size="x-small"
|
|
36
|
+
>Payment {{ initialManagementValues.paymentStatus }}</v-chip>
|
|
37
|
+
<v-chip
|
|
38
|
+
v-else
|
|
39
|
+
color="orange"
|
|
40
|
+
variant="flat"
|
|
41
|
+
size="x-small"
|
|
42
|
+
>Payment Not Paid</v-chip>
|
|
43
|
+
<span
|
|
44
|
+
v-if="initialManagementValues?.paymentOrderId"
|
|
45
|
+
class="text-caption text-medium-emphasis"
|
|
46
|
+
>Order: {{ initialManagementValues.paymentOrderId }}</span>
|
|
47
|
+
</template>
|
|
30
48
|
</div>
|
|
31
49
|
|
|
32
50
|
<!-- Scrollable body -->
|
|
@@ -404,6 +422,7 @@ const props = defineProps({
|
|
|
404
422
|
submissionUnit: { type: String, default: "" },
|
|
405
423
|
approvalMode: { type: Boolean, default: false },
|
|
406
424
|
initialManagementValues: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
|
|
425
|
+
formType: { type: String, default: "" },
|
|
407
426
|
/** Optional custom submit handler. Receives the collected form values.
|
|
408
427
|
* If omitted the component submits to /api/online-forms automatically. */
|
|
409
428
|
submitFn: {
|
|
@@ -505,6 +524,21 @@ const submissionStatusColor = computed(() => {
|
|
|
505
524
|
}
|
|
506
525
|
});
|
|
507
526
|
|
|
527
|
+
const PAYMENT_REQUIRED_FORM_TYPES = new Set([
|
|
528
|
+
"Application for Electronic Access Card",
|
|
529
|
+
"Application for Booking of BBQ Pit",
|
|
530
|
+
"Application for Moving In/Out & Delivery",
|
|
531
|
+
"Application for Main Entrance RFID Transponder",
|
|
532
|
+
]);
|
|
533
|
+
|
|
534
|
+
const requiresPayment = computed(() => {
|
|
535
|
+
if (!PAYMENT_REQUIRED_FORM_TYPES.has(props.formType)) return false;
|
|
536
|
+
if (props.formType === "Application for Main Entrance RFID Transponder") {
|
|
537
|
+
return !!(props.initialValues.reasonLost || props.initialValues.reasonDamaged);
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
540
|
+
});
|
|
541
|
+
|
|
508
542
|
// Today's date formatted DD MMM YYYY
|
|
509
543
|
const todayDate = computed(() => {
|
|
510
544
|
const d = new Date();
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
persistent-hint
|
|
28
28
|
small-chips
|
|
29
29
|
:clearable="props.clearable"
|
|
30
|
+
@click="fetchPasses"
|
|
31
|
+
:loading="isPassListLoading"
|
|
30
32
|
>
|
|
31
33
|
<template v-slot:chip="{ props, item }">
|
|
32
34
|
<v-chip
|
|
@@ -56,6 +58,8 @@
|
|
|
56
58
|
persistent-hint
|
|
57
59
|
small-chips
|
|
58
60
|
:clearable="props.clearable"
|
|
61
|
+
@click="fetchKeys"
|
|
62
|
+
:loading="isKeyListLoading"
|
|
59
63
|
>
|
|
60
64
|
<template v-slot:chip="{ props, item }">
|
|
61
65
|
<v-chip
|
|
@@ -83,6 +87,7 @@
|
|
|
83
87
|
</v-card-text>
|
|
84
88
|
</v-card>
|
|
85
89
|
</v-row>
|
|
90
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
86
91
|
</template>
|
|
87
92
|
|
|
88
93
|
<script setup lang="ts">
|
|
@@ -90,6 +95,7 @@ import type { PropType } from "vue";
|
|
|
90
95
|
import type { ValidationRule } from "vuetify/lib/types.mjs";
|
|
91
96
|
import usePassKey from "../composables/usePassKey";
|
|
92
97
|
import useKey from "../composables/useKey";
|
|
98
|
+
import { string } from "zod/v4";
|
|
93
99
|
|
|
94
100
|
const props = defineProps({
|
|
95
101
|
passRules: {
|
|
@@ -112,6 +118,10 @@ const props = defineProps({
|
|
|
112
118
|
type: String,
|
|
113
119
|
default: "",
|
|
114
120
|
},
|
|
121
|
+
passKeys: {
|
|
122
|
+
type: Array,
|
|
123
|
+
default: [],
|
|
124
|
+
},
|
|
115
125
|
hideKeys: {
|
|
116
126
|
type: Boolean,
|
|
117
127
|
default: false,
|
|
@@ -158,43 +168,72 @@ const passTypesComputed = computed(() => {
|
|
|
158
168
|
}
|
|
159
169
|
});
|
|
160
170
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
pending: fetchPassesPending,
|
|
165
|
-
} = await useLazyAsyncData("get-pass-keys", () => {
|
|
166
|
-
return getPassKeysByPageSearch({
|
|
167
|
-
search: passInput.value,
|
|
168
|
-
page: 1,
|
|
169
|
-
limit: 500,
|
|
170
|
-
passTypes: passTypesComputed.value,
|
|
171
|
-
sites: [props.site],
|
|
172
|
-
statuses: ["Available"],
|
|
173
|
-
});
|
|
174
|
-
});
|
|
171
|
+
const message = ref("");
|
|
172
|
+
const messageColor = ref("");
|
|
173
|
+
const messageSnackbar = ref(false);
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
function showMessage(msg: string, color: string) {
|
|
176
|
+
message.value = msg;
|
|
177
|
+
messageColor.value = color;
|
|
178
|
+
messageSnackbar.value = true;
|
|
179
|
+
}
|
|
179
180
|
|
|
180
|
-
const
|
|
181
|
-
data: keysData,
|
|
182
|
-
refresh: refreshKeysData,
|
|
183
|
-
pending: fetchKeysPending,
|
|
184
|
-
} = await useLazyAsyncData("get-keys", () => {
|
|
185
|
-
return getPassKeysByPageSearch({
|
|
186
|
-
search: keyInput.value,
|
|
187
|
-
statuses: ["Available"],
|
|
188
|
-
passTypes: ["pass-key"],
|
|
189
|
-
page: 1,
|
|
190
|
-
limit: 500,
|
|
191
|
-
sites: [props.site],
|
|
192
|
-
});
|
|
193
|
-
});
|
|
181
|
+
const isPassListLoading = ref(false);
|
|
194
182
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
async function fetchPasses() {
|
|
184
|
+
// load pass list
|
|
185
|
+
passItems.value = [];
|
|
186
|
+
isPassListLoading.value = true;
|
|
187
|
+
try {
|
|
188
|
+
const result = await getPassKeysByPageSearch({
|
|
189
|
+
page: 1,
|
|
190
|
+
sites: [props.site],
|
|
191
|
+
limit: 10000,
|
|
192
|
+
passTypes: passTypesComputed.value,
|
|
193
|
+
statuses: ["Available"],
|
|
194
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
195
|
+
});
|
|
196
|
+
passItems.value = result?.items;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
showMessage(errorConverter(error as string), "error");
|
|
199
|
+
} finally {
|
|
200
|
+
isPassListLoading.value = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const isKeyListLoading = ref(false);
|
|
205
|
+
|
|
206
|
+
async function fetchKeys() {
|
|
207
|
+
keyItems.value = [];
|
|
208
|
+
isKeyListLoading.value = true;
|
|
209
|
+
try {
|
|
210
|
+
const keys: any = await getPassKeysByPageSearch({
|
|
211
|
+
page: 1,
|
|
212
|
+
sites: [props.site],
|
|
213
|
+
limit: 10000,
|
|
214
|
+
passTypes: ["pass-key"],
|
|
215
|
+
statuses: ["Available"],
|
|
216
|
+
sortBy: { column: "prefixAndName", order: "1" },
|
|
217
|
+
});
|
|
218
|
+
const selectedKeys = keyItems.value.filter((item: any) => {
|
|
219
|
+
if (Array.isArray(props?.passKeys) && props.passKeys.length) {
|
|
220
|
+
return props.passKeys.includes(item._id);
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
});
|
|
224
|
+
const filteredArray = keys.items.filter((item: any) => {
|
|
225
|
+
if (Array.isArray(props?.passKeys) && props.passKeys.length) {
|
|
226
|
+
return !props.passKeys.includes(item._id);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
keyItems.value = [...selectedKeys, ...filteredArray];
|
|
231
|
+
} catch (error) {
|
|
232
|
+
showMessage(errorConverter(error as string), "error");
|
|
233
|
+
} finally {
|
|
234
|
+
isKeyListLoading.value = false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
198
237
|
|
|
199
238
|
watch(selectedPass, (newVal) => {
|
|
200
239
|
pass.value = [{ keyId: newVal }];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<v-row no-gutters>
|
|
3
3
|
<v-expansion-panels multiple v-model="openPanels">
|
|
4
4
|
<!-- SITE SETTINGS -->
|
|
5
|
-
<v-expansion-panel color="primary">
|
|
5
|
+
<v-expansion-panel v-if="canManageSiteInformation" color="primary">
|
|
6
6
|
<v-expansion-panel-title>
|
|
7
7
|
<v-icon class="mr-2">mdi-cog</v-icon>
|
|
8
8
|
Site Information
|
|
@@ -14,73 +14,36 @@
|
|
|
14
14
|
<v-row>
|
|
15
15
|
<!-- Cover Photo + Preview -->
|
|
16
16
|
<v-col cols="12" lg="5" class="pt-0">
|
|
17
|
-
<InputLabel
|
|
18
|
-
|
|
19
|
-
class="d-block mb-2"
|
|
20
|
-
/>
|
|
21
|
-
<InputFileV2
|
|
22
|
-
v-model="coverPhoto"
|
|
23
|
-
accept="image/*"
|
|
24
|
-
title="Upload cover photo"
|
|
25
|
-
:height="120"
|
|
26
|
-
/>
|
|
17
|
+
<InputLabel title="Cover Photo (displayed in resident app)" class="d-block mb-2" />
|
|
18
|
+
<InputFileV2 v-model="coverPhoto" accept="image/*" title="Upload cover photo" :height="120" />
|
|
27
19
|
</v-col>
|
|
28
20
|
<v-col cols="12" lg="7" class="pl-lg-4 pt-4 pt-lg-0">
|
|
29
21
|
<InputLabel title="Preview" class="d-block mb-2" />
|
|
30
|
-
<v-img
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
aspect-ratio="16/9"
|
|
34
|
-
cover
|
|
35
|
-
rounded="lg"
|
|
36
|
-
class="w-100 border"
|
|
37
|
-
/>
|
|
38
|
-
<div
|
|
39
|
-
v-else
|
|
22
|
+
<v-img v-if="coverPhotoUrl" :src="coverPhotoUrl" aspect-ratio="16/9" cover rounded="lg"
|
|
23
|
+
class="w-100 border" />
|
|
24
|
+
<div v-else
|
|
40
25
|
class="w-100 d-flex align-center justify-center rounded-lg bg-grey-lighten-4 border border-dashed"
|
|
41
|
-
style="aspect-ratio: 16/9"
|
|
42
|
-
|
|
43
|
-
<span class="text-caption text-medium-emphasis"
|
|
44
|
-
>No cover photo uploaded</span
|
|
45
|
-
>
|
|
26
|
+
style="aspect-ratio: 16/9">
|
|
27
|
+
<span class="text-caption text-medium-emphasis">No cover photo uploaded</span>
|
|
46
28
|
</div>
|
|
47
29
|
</v-col>
|
|
48
30
|
|
|
49
31
|
<!-- Description + Site Documents -->
|
|
50
32
|
<v-col cols="12" lg="6" class="mt-2">
|
|
51
33
|
<InputLabel title="Description" class="d-block mb-1" />
|
|
52
|
-
<v-textarea
|
|
53
|
-
|
|
54
|
-
variant="outlined"
|
|
55
|
-
density="compact"
|
|
56
|
-
rows="4"
|
|
57
|
-
hide-details
|
|
58
|
-
no-resize
|
|
59
|
-
style="height: 119px"
|
|
60
|
-
/>
|
|
34
|
+
<v-textarea v-model="description" variant="outlined" density="compact" rows="4" hide-details no-resize
|
|
35
|
+
style="height: 119px" />
|
|
61
36
|
</v-col>
|
|
62
37
|
<v-col cols="12" lg="6" class="mt-2 pl-lg-2">
|
|
63
38
|
<InputLabel title="Site Documents" class="d-block mb-1" />
|
|
64
|
-
<InputFileV2
|
|
65
|
-
|
|
66
|
-
:multiple="true"
|
|
67
|
-
accept="*/*"
|
|
68
|
-
title="Upload documents"
|
|
69
|
-
:height="104"
|
|
70
|
-
/>
|
|
39
|
+
<InputFileV2 v-model="siteDocuments" :multiple="true" accept="*/*" title="Upload documents"
|
|
40
|
+
:height="104" />
|
|
71
41
|
</v-col>
|
|
72
42
|
|
|
73
43
|
<!-- Submit -->
|
|
74
44
|
<v-col cols="12" class="pt-0 d-flex justify-end">
|
|
75
|
-
<v-btn
|
|
76
|
-
|
|
77
|
-
class="text-none"
|
|
78
|
-
size="large"
|
|
79
|
-
variant="flat"
|
|
80
|
-
text="Save"
|
|
81
|
-
:loading="savingSiteInfo"
|
|
82
|
-
@click="saveSiteInfo"
|
|
83
|
-
/>
|
|
45
|
+
<v-btn color="primary-button" class="text-none" size="large" variant="flat" text="Save"
|
|
46
|
+
:loading="savingSiteInfo" @click="saveSiteInfo" />
|
|
84
47
|
</v-col>
|
|
85
48
|
|
|
86
49
|
<!-- Divider -->
|
|
@@ -91,14 +54,8 @@
|
|
|
91
54
|
<v-col cols="12">
|
|
92
55
|
<v-row no-gutters>
|
|
93
56
|
<v-col cols="12" lg="4" class="mt-2">
|
|
94
|
-
<NumberSettingField
|
|
95
|
-
|
|
96
|
-
title="No. of blocks"
|
|
97
|
-
type="blocks"
|
|
98
|
-
:site-id="siteId"
|
|
99
|
-
:disabled="existingBlockNumber === blocks"
|
|
100
|
-
@success="refreshSiteData"
|
|
101
|
-
/>
|
|
57
|
+
<NumberSettingField v-model="blocks" title="No. of blocks" type="blocks" :site-id="siteId"
|
|
58
|
+
:disabled="existingBlockNumber === blocks" @success="refreshSiteData" />
|
|
102
59
|
</v-col>
|
|
103
60
|
</v-row>
|
|
104
61
|
</v-col>
|
|
@@ -106,14 +63,9 @@
|
|
|
106
63
|
<v-col cols="12">
|
|
107
64
|
<v-row no-gutters>
|
|
108
65
|
<v-col cols="12" lg="4" class="mt-2">
|
|
109
|
-
<NumberSettingField
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
type="guard_posts"
|
|
113
|
-
:site-id="siteId"
|
|
114
|
-
:disabled="existingGuardPostNumber === guardPosts"
|
|
115
|
-
@success="refreshSiteData"
|
|
116
|
-
/>
|
|
66
|
+
<NumberSettingField v-model="guardPosts" title="No. of guard posts" type="guard_posts"
|
|
67
|
+
:site-id="siteId" :disabled="existingGuardPostNumber === guardPosts"
|
|
68
|
+
@success="refreshSiteData" />
|
|
117
69
|
</v-col>
|
|
118
70
|
</v-row>
|
|
119
71
|
</v-col>
|
|
@@ -124,7 +76,7 @@
|
|
|
124
76
|
</v-expansion-panel>
|
|
125
77
|
|
|
126
78
|
<!-- ANPR CAMERA -->
|
|
127
|
-
<v-expansion-panel v-if="showAnprCamera" color="primary">
|
|
79
|
+
<v-expansion-panel v-if="showAnprCamera && canManageANPRCamera" color="primary">
|
|
128
80
|
<v-expansion-panel-title>
|
|
129
81
|
<v-icon class="mr-2">mdi-camera</v-icon>
|
|
130
82
|
ANPR Camera
|
|
@@ -134,45 +86,24 @@
|
|
|
134
86
|
<v-container fluid class="px-0">
|
|
135
87
|
<v-row dense>
|
|
136
88
|
<v-col cols="6">
|
|
137
|
-
<v-switch
|
|
138
|
-
|
|
139
|
-
:disabled="!canManageSiteSettings"
|
|
140
|
-
label="Enable Unregistered Vehicles"
|
|
141
|
-
color="primary"
|
|
142
|
-
hide-details
|
|
143
|
-
></v-switch>
|
|
89
|
+
<v-switch v-model="ANPRSwitches.enableUnregistered" :disabled="!canManageSiteSettings"
|
|
90
|
+
label="Enable Unregistered Vehicles" color="primary" hide-details></v-switch>
|
|
144
91
|
</v-col>
|
|
145
92
|
|
|
146
93
|
<v-col cols="6">
|
|
147
|
-
<v-switch
|
|
148
|
-
|
|
149
|
-
:disabled="!canManageSiteSettings"
|
|
150
|
-
label="Open Barrier for Pick-up and Drop-off"
|
|
151
|
-
color="primary"
|
|
152
|
-
hide-details
|
|
153
|
-
></v-switch>
|
|
94
|
+
<v-switch v-model="ANPRSwitches.openBarrierPickUpDropOff" :disabled="!canManageSiteSettings"
|
|
95
|
+
label="Open Barrier for Pick-up and Drop-off" color="primary" hide-details></v-switch>
|
|
154
96
|
</v-col>
|
|
155
97
|
|
|
156
98
|
<v-col cols="6">
|
|
157
|
-
<v-switch
|
|
158
|
-
|
|
159
|
-
:disabled="!canManageSiteSettings"
|
|
160
|
-
label="Capture Vehicle Snapshot"
|
|
161
|
-
color="primary"
|
|
162
|
-
hide-details
|
|
163
|
-
></v-switch>
|
|
99
|
+
<v-switch v-model="ANPRSwitches.vehicleSnapshot" :disabled="!canManageSiteSettings"
|
|
100
|
+
label="Capture Vehicle Snapshot" color="primary" hide-details></v-switch>
|
|
164
101
|
</v-col>
|
|
165
102
|
</v-row>
|
|
166
103
|
|
|
167
104
|
<v-row v-if="canManageSiteSettings">
|
|
168
105
|
<v-col cols="auto">
|
|
169
|
-
<v-btn
|
|
170
|
-
variant="flat"
|
|
171
|
-
color="primary"
|
|
172
|
-
class="text-none"
|
|
173
|
-
:loading="isSaving"
|
|
174
|
-
@click="saveANPRSettings"
|
|
175
|
-
>
|
|
106
|
+
<v-btn variant="flat" color="primary" class="text-none" :loading="isSaving" @click="saveANPRSettings">
|
|
176
107
|
Save
|
|
177
108
|
</v-btn>
|
|
178
109
|
</v-col>
|
|
@@ -181,47 +112,35 @@
|
|
|
181
112
|
|
|
182
113
|
<v-divider class="my-4"></v-divider>
|
|
183
114
|
|
|
184
|
-
<CameraMain
|
|
185
|
-
:
|
|
186
|
-
:read-only="!canManageSiteSettings"
|
|
187
|
-
:guard-posts="siteData?.metadata?.guardPosts"
|
|
188
|
-
/>
|
|
115
|
+
<CameraMain :site="siteId" :read-only="!canManageSiteSettings"
|
|
116
|
+
:guard-posts="siteData?.metadata?.guardPosts" />
|
|
189
117
|
</v-expansion-panel-text>
|
|
190
118
|
</v-expansion-panel>
|
|
191
119
|
|
|
192
120
|
<!-- CCTV CAMERA -->
|
|
193
|
-
<v-expansion-panel v-if="showCctvCamera" color="primary">
|
|
121
|
+
<v-expansion-panel v-if="showCctvCamera && canManageCCTVCamera" color="primary">
|
|
194
122
|
<v-expansion-panel-title>
|
|
195
123
|
<v-icon class="mr-2">mdi-cctv</v-icon>
|
|
196
124
|
CCTV Camera
|
|
197
125
|
</v-expansion-panel-title>
|
|
198
126
|
|
|
199
127
|
<v-expansion-panel-text>
|
|
200
|
-
<CameraMain
|
|
201
|
-
:site="siteId"
|
|
202
|
-
type="ip"
|
|
203
|
-
:read-only="!canManageSiteSettings"
|
|
204
|
-
/>
|
|
128
|
+
<CameraMain :site="siteId" type="ip" :read-only="!canManageSiteSettings" />
|
|
205
129
|
</v-expansion-panel-text>
|
|
206
130
|
</v-expansion-panel>
|
|
207
131
|
|
|
208
|
-
<v-expansion-panel v-if="showDeliveryCompanies" color="primary">
|
|
132
|
+
<v-expansion-panel v-if="showDeliveryCompanies && canManageDeliveryCompanies" color="primary">
|
|
209
133
|
<v-expansion-panel-title>
|
|
210
134
|
<v-icon class="mr-2">mdi-truck</v-icon>
|
|
211
135
|
Delivery Companies
|
|
212
136
|
</v-expansion-panel-title>
|
|
213
137
|
<v-expansion-panel-text class="">
|
|
214
|
-
<DeliveryCompany
|
|
215
|
-
:
|
|
216
|
-
v-model:initial="deliveryCompanies"
|
|
217
|
-
@refresh-site="refreshSiteData"
|
|
218
|
-
@update:companiesValue="handleUpdateCompanies"
|
|
219
|
-
:read-only="!canManageSiteSettings"
|
|
220
|
-
/>
|
|
138
|
+
<DeliveryCompany :site="siteId" v-model:initial="deliveryCompanies" @refresh-site="refreshSiteData"
|
|
139
|
+
@update:companiesValue="handleUpdateCompanies" :read-only="!canManageSiteSettings" />
|
|
221
140
|
</v-expansion-panel-text>
|
|
222
141
|
</v-expansion-panel>
|
|
223
142
|
|
|
224
|
-
<v-expansion-panel v-if="showResidentVisitors" color="primary">
|
|
143
|
+
<v-expansion-panel v-if="showResidentVisitors && canManageResidentVisitors" color="primary">
|
|
225
144
|
<v-expansion-panel-title>
|
|
226
145
|
<v-icon class="mr-2">mdi-car</v-icon>
|
|
227
146
|
Resident Visitors
|
|
@@ -231,6 +150,23 @@
|
|
|
231
150
|
</v-expansion-panel-text>
|
|
232
151
|
</v-expansion-panel>
|
|
233
152
|
|
|
153
|
+
<v-expansion-panel color="primary">
|
|
154
|
+
<v-expansion-panel-title>
|
|
155
|
+
<v-icon class="mr-2">mdi-face-recognition</v-icon>
|
|
156
|
+
HID Face Reader
|
|
157
|
+
</v-expansion-panel-title>
|
|
158
|
+
<v-expansion-panel-text>
|
|
159
|
+
<HidServiceSettingsPanel
|
|
160
|
+
:site="siteId"
|
|
161
|
+
:org="orgId"
|
|
162
|
+
:site-data="siteData"
|
|
163
|
+
@refresh="refreshSiteData"
|
|
164
|
+
/>
|
|
165
|
+
<v-divider class="my-4" />
|
|
166
|
+
<HidQrCodeConfiguration :site="siteId" />
|
|
167
|
+
</v-expansion-panel-text>
|
|
168
|
+
</v-expansion-panel>
|
|
169
|
+
|
|
234
170
|
<slot
|
|
235
171
|
name="panels"
|
|
236
172
|
:can-manage="canManageSiteSettings"
|
|
@@ -250,6 +186,7 @@ import useANPRSettings from "../composables/useANPRSettings";
|
|
|
250
186
|
|
|
251
187
|
const props = defineProps({
|
|
252
188
|
siteId: { type: String, required: true },
|
|
189
|
+
orgId: { type: String, default: "" },
|
|
253
190
|
createdBy: { type: String },
|
|
254
191
|
canManageSiteSettings: { type: Boolean, default: false },
|
|
255
192
|
showAnprCamera: { type: Boolean, default: false },
|
|
@@ -259,6 +196,12 @@ const props = defineProps({
|
|
|
259
196
|
});
|
|
260
197
|
|
|
261
198
|
const openPanels = ref([]); // default open first
|
|
199
|
+
const { canManageSiteInformation,
|
|
200
|
+
canManageANPRCamera,
|
|
201
|
+
canManageCCTVCamera,
|
|
202
|
+
canManageDeliveryCompanies,
|
|
203
|
+
canManageResidentVisitors,
|
|
204
|
+
} = useSettingsPermission();
|
|
262
205
|
|
|
263
206
|
const { getSiteById, updateSitebyId, updateSiteInformation } =
|
|
264
207
|
useSiteSettings();
|
|
@@ -306,6 +249,7 @@ const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
|
306
249
|
`site-${props.siteId}`,
|
|
307
250
|
() => getSiteById(props.siteId)
|
|
308
251
|
);
|
|
252
|
+
const orgId = computed(() => props.orgId || String((siteData.value as any)?.orgId ?? ""));
|
|
309
253
|
|
|
310
254
|
watch(
|
|
311
255
|
siteData,
|