@7365admin1/layer-common 1.11.27 → 1.11.28
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/AccessCardAddForm.vue +30 -51
- package/components/AccessCardAssignToUnitForm.vue +19 -35
- package/components/AccessManagement.vue +1 -1
- package/components/InvitationForm.vue +18 -5
- package/components/RolePermissionFormCreate.vue +3 -0
- package/components/RolePermissionFormPreviewUpdate.vue +3 -0
- package/components/ServiceProviderMain.vue +12 -7
- package/components/SiteSettings.vue +144 -128
- package/composables/useAccessManagement.ts +11 -0
- package/composables/useOrg.ts +2 -2
- package/composables/useRole.ts +2 -0
- package/composables/useServiceProvider.ts +12 -4
- package/package.json +1 -1
- package/types/customer.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -435,8 +435,6 @@ const prop = defineProps({
|
|
|
435
435
|
});
|
|
436
436
|
|
|
437
437
|
const { add: _addCard, updateById: _updateCardById } = useCard();
|
|
438
|
-
const { getAll: _getBuildings } = useBuilding();
|
|
439
|
-
const { getAllUnits: _getUnits } = useBuildingUnit();
|
|
440
438
|
const {
|
|
441
439
|
getDoorAccessLevels: _getDoorAccessLevels,
|
|
442
440
|
getLiftAccessLevels: _getLiftAccessLevels,
|
|
@@ -444,6 +442,7 @@ const {
|
|
|
444
442
|
addPhysicalCard: _addPhysicalCard,
|
|
445
443
|
addNonPhysicalCard: _addNonPhysicalCard,
|
|
446
444
|
bulkPhysicalAccessCard: _bulkPhysicalAccessCard,
|
|
445
|
+
getBlockLevelUnitList: _getBlockLevelUnitList,
|
|
447
446
|
} = useAccessManagement();
|
|
448
447
|
const { getBySiteId: _getEntryPassSettings } = useSiteEntryPassSettings();
|
|
449
448
|
const { getFileUrl } = useFile();
|
|
@@ -570,7 +569,7 @@ const liftAccessLevelItems = ref<{ name: string; no: string }[]>([]);
|
|
|
570
569
|
const buildingItems = ref<{ name: string; value: string }[]>([]);
|
|
571
570
|
const levelItems = ref<{ name: string; value: string }[]>([]);
|
|
572
571
|
const unitItems = ref<{ name: string; value: string }[]>([]);
|
|
573
|
-
const
|
|
572
|
+
const blockLevelUnitData = ref<any[]>([]);
|
|
574
573
|
const encryptedAcmUrl = ref("");
|
|
575
574
|
|
|
576
575
|
const route = useRoute();
|
|
@@ -644,28 +643,24 @@ function updateEndDate(startDate: string | null) {
|
|
|
644
643
|
}
|
|
645
644
|
}
|
|
646
645
|
|
|
647
|
-
// Watch for Assign toggle to fetch
|
|
646
|
+
// Watch for Assign toggle to fetch block/level/unit data
|
|
648
647
|
watch(() => card.value.showAssign, async (newVal) => {
|
|
649
648
|
if (newVal) {
|
|
650
649
|
try {
|
|
651
|
-
const response = await
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
buildingsData.value = response.items || [];
|
|
657
|
-
buildingItems.value = buildingsData.value.map((building: any) => ({
|
|
658
|
-
name: building.block,
|
|
659
|
-
value: building._id,
|
|
650
|
+
const response = await _getBlockLevelUnitList(siteId);
|
|
651
|
+
blockLevelUnitData.value = (response as any)?.data || [];
|
|
652
|
+
buildingItems.value = blockLevelUnitData.value.map((b: any) => ({
|
|
653
|
+
name: b.block,
|
|
654
|
+
value: b._id,
|
|
660
655
|
}));
|
|
661
656
|
} catch (error) {
|
|
662
|
-
console.error("Failed to fetch
|
|
657
|
+
console.error("Failed to fetch block/level/unit list:", error);
|
|
663
658
|
}
|
|
664
659
|
} else {
|
|
665
|
-
// Reset selections when toggled off
|
|
666
660
|
card.value.assignBuilding = "";
|
|
667
661
|
card.value.assignLevel = "";
|
|
668
662
|
card.value.assignUnit = "";
|
|
663
|
+
blockLevelUnitData.value = [];
|
|
669
664
|
buildingItems.value = [];
|
|
670
665
|
levelItems.value = [];
|
|
671
666
|
unitItems.value = [];
|
|
@@ -673,51 +668,35 @@ watch(() => card.value.showAssign, async (newVal) => {
|
|
|
673
668
|
});
|
|
674
669
|
|
|
675
670
|
// Watch for building selection to populate levels
|
|
676
|
-
watch(() => card.value.assignBuilding,
|
|
671
|
+
watch(() => card.value.assignBuilding, (newVal) => {
|
|
672
|
+
card.value.assignLevel = "";
|
|
673
|
+
card.value.assignUnit = "";
|
|
674
|
+
unitItems.value = [];
|
|
675
|
+
|
|
677
676
|
if (newVal) {
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
// Reset level and unit when building changes
|
|
686
|
-
card.value.assignLevel = "";
|
|
687
|
-
card.value.assignUnit = "";
|
|
688
|
-
unitItems.value = [];
|
|
677
|
+
const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === newVal);
|
|
678
|
+
levelItems.value = selectedBlock?.levels?.map((l: any) => ({
|
|
679
|
+
name: l.level,
|
|
680
|
+
value: l._id,
|
|
681
|
+
})) || [];
|
|
689
682
|
} else {
|
|
690
683
|
levelItems.value = [];
|
|
691
|
-
card.value.assignLevel = "";
|
|
692
|
-
card.value.assignUnit = "";
|
|
693
|
-
unitItems.value = [];
|
|
694
684
|
}
|
|
695
685
|
});
|
|
696
686
|
|
|
697
|
-
// Watch for level selection to
|
|
698
|
-
watch(() => card.value.assignLevel,
|
|
687
|
+
// Watch for level selection to populate units
|
|
688
|
+
watch(() => card.value.assignLevel, (newVal) => {
|
|
689
|
+
card.value.assignUnit = "";
|
|
690
|
+
|
|
699
691
|
if (newVal && card.value.assignBuilding) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
});
|
|
707
|
-
const units = response.items || [];
|
|
708
|
-
// Filter units by selected level
|
|
709
|
-
const filteredUnits = units.filter((unit: any) => unit.level === newVal);
|
|
710
|
-
unitItems.value = filteredUnits.map((unit: any) => ({
|
|
711
|
-
name: unit.name,
|
|
712
|
-
value: unit._id,
|
|
713
|
-
}));
|
|
714
|
-
} catch (error) {
|
|
715
|
-
console.error("Failed to fetch units:", error);
|
|
716
|
-
}
|
|
717
|
-
card.value.assignUnit = "";
|
|
692
|
+
const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === card.value.assignBuilding);
|
|
693
|
+
const selectedLevel = selectedBlock?.levels?.find((l: any) => l._id === newVal);
|
|
694
|
+
unitItems.value = selectedLevel?.units?.map((u: any) => ({
|
|
695
|
+
name: u.name,
|
|
696
|
+
value: u._id,
|
|
697
|
+
})) || [];
|
|
718
698
|
} else {
|
|
719
699
|
unitItems.value = [];
|
|
720
|
-
card.value.assignUnit = "";
|
|
721
700
|
}
|
|
722
701
|
});
|
|
723
702
|
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
persistent-placeholder
|
|
69
69
|
:rules="[requiredArrayRule]"
|
|
70
70
|
:disabled="!form.level"
|
|
71
|
-
:loading="unitLoading"
|
|
72
71
|
multiple
|
|
73
72
|
chips
|
|
74
73
|
closable-chips
|
|
@@ -209,9 +208,8 @@ const {
|
|
|
209
208
|
getLiftAccessLevels: _getLiftAccessLevels,
|
|
210
209
|
assignAccessCard: _assignAccessCard,
|
|
211
210
|
getAvailableAccessCards: _getAvailableAccessCards,
|
|
211
|
+
getBlockLevelUnitList: _getBlockLevelUnitList,
|
|
212
212
|
} = useAccessManagement();
|
|
213
|
-
const { getAll: _getBuildings } = useBuilding();
|
|
214
|
-
const { getAllUnits: _getUnits } = useBuildingUnit();
|
|
215
213
|
const { requiredRule } = useUtils();
|
|
216
214
|
|
|
217
215
|
const route = useRoute();
|
|
@@ -221,7 +219,6 @@ const validForm = ref(false);
|
|
|
221
219
|
const loading = ref(false);
|
|
222
220
|
const levelsLoading = ref(false);
|
|
223
221
|
const buildingLoading = ref(false);
|
|
224
|
-
const unitLoading = ref(false);
|
|
225
222
|
const availableLoading = ref(false);
|
|
226
223
|
const availableCount = ref<number | null>(null);
|
|
227
224
|
|
|
@@ -245,7 +242,7 @@ const NO_SELECTION = { name: "No Selection", no: null };
|
|
|
245
242
|
const accessLevelItems = ref<{ name: string; no: string | null }[]>([]);
|
|
246
243
|
const liftAccessLevelItems = ref<{ name: string; no: string | null }[]>([]);
|
|
247
244
|
const buildingItems = ref<{ name: string; value: string }[]>([]);
|
|
248
|
-
const
|
|
245
|
+
const blockLevelUnitData = ref<any[]>([]);
|
|
249
246
|
const levelItems = ref<{ name: string; value: string }[]>([]);
|
|
250
247
|
const unitItems = ref<{ name: string; value: string }[]>([]);
|
|
251
248
|
|
|
@@ -303,15 +300,15 @@ onMounted(async () => {
|
|
|
303
300
|
buildingLoading.value = true;
|
|
304
301
|
levelsLoading.value = true;
|
|
305
302
|
|
|
306
|
-
const [
|
|
307
|
-
|
|
303
|
+
const [blockLevelResult, acmResult] = await Promise.allSettled([
|
|
304
|
+
_getBlockLevelUnitList(siteId.value),
|
|
308
305
|
$fetch<{ encrypted: string }>("/api/encrypt-acm-url"),
|
|
309
306
|
]);
|
|
310
307
|
|
|
311
|
-
//
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
buildingItems.value =
|
|
308
|
+
// Blocks / Levels / Units
|
|
309
|
+
if (blockLevelResult.status === "fulfilled") {
|
|
310
|
+
blockLevelUnitData.value = (blockLevelResult.value as any)?.data || [];
|
|
311
|
+
buildingItems.value = blockLevelUnitData.value.map((b: any) => ({
|
|
315
312
|
name: b.block,
|
|
316
313
|
value: b._id,
|
|
317
314
|
}));
|
|
@@ -345,20 +342,18 @@ onMounted(async () => {
|
|
|
345
342
|
|
|
346
343
|
watch(
|
|
347
344
|
() => form.value.building,
|
|
348
|
-
|
|
345
|
+
(newVal) => {
|
|
349
346
|
form.value.level = null;
|
|
350
347
|
form.value.units = [];
|
|
351
348
|
levelItems.value = [];
|
|
352
349
|
unitItems.value = [];
|
|
353
350
|
|
|
354
351
|
if (newVal) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
name: level,
|
|
361
|
-
value: level,
|
|
352
|
+
const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === newVal);
|
|
353
|
+
if (selectedBlock?.levels) {
|
|
354
|
+
levelItems.value = selectedBlock.levels.map((l: any) => ({
|
|
355
|
+
name: l.level,
|
|
356
|
+
value: l._id,
|
|
362
357
|
}));
|
|
363
358
|
}
|
|
364
359
|
}
|
|
@@ -367,29 +362,18 @@ watch(
|
|
|
367
362
|
|
|
368
363
|
watch(
|
|
369
364
|
() => form.value.level,
|
|
370
|
-
|
|
365
|
+
(newVal) => {
|
|
371
366
|
form.value.units = [];
|
|
372
367
|
unitItems.value = [];
|
|
373
368
|
|
|
374
369
|
if (newVal && form.value.building) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
status: "active",
|
|
380
|
-
site: siteId.value,
|
|
381
|
-
building: form.value.building,
|
|
382
|
-
});
|
|
383
|
-
const units = response.items || [];
|
|
384
|
-
const filtered = units.filter((u: any) => u.level === newVal);
|
|
385
|
-
unitItems.value = filtered.map((u: any) => ({
|
|
370
|
+
const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === form.value.building);
|
|
371
|
+
const selectedLevel = selectedBlock?.levels?.find((l: any) => l._id === newVal);
|
|
372
|
+
if (selectedLevel?.units) {
|
|
373
|
+
unitItems.value = selectedLevel.units.map((u: any) => ({
|
|
386
374
|
name: u.name,
|
|
387
375
|
value: u._id,
|
|
388
376
|
}));
|
|
389
|
-
} catch {
|
|
390
|
-
console.error("Failed to fetch units");
|
|
391
|
-
} finally {
|
|
392
|
-
unitLoading.value = false;
|
|
393
377
|
}
|
|
394
378
|
}
|
|
395
379
|
}
|
|
@@ -131,6 +131,13 @@
|
|
|
131
131
|
</template>
|
|
132
132
|
|
|
133
133
|
<script setup lang="ts">
|
|
134
|
+
import useCustomerSite from '../composables/useCustomerSite';
|
|
135
|
+
import useLocal from '../composables/useLocal';
|
|
136
|
+
import { useLocalSetup } from '../composables/useLocalSetup';
|
|
137
|
+
import useRole from '../composables/useRole';
|
|
138
|
+
import useUser from '../composables/useUser';
|
|
139
|
+
import useUtils from '../composables/useUtils';
|
|
140
|
+
|
|
134
141
|
const APP = useRuntimeConfig().public.APP;
|
|
135
142
|
const props = defineProps({
|
|
136
143
|
title: {
|
|
@@ -190,6 +197,7 @@ const invite = ref<Record<string, any>>({
|
|
|
190
197
|
org: "",
|
|
191
198
|
site: "",
|
|
192
199
|
siteName: "",
|
|
200
|
+
isMemberInvite: false,
|
|
193
201
|
});
|
|
194
202
|
|
|
195
203
|
invite.value.app = props.app;
|
|
@@ -298,9 +306,14 @@ const roles = ref<Array<Record<string, any>>>([]);
|
|
|
298
306
|
const { getRoles } = useRole();
|
|
299
307
|
|
|
300
308
|
const { data: RolesData, refresh: refreshRoles } = await useLazyAsyncData(
|
|
301
|
-
"get-roles-by-type",
|
|
302
|
-
() =>
|
|
303
|
-
{
|
|
309
|
+
"get-roles-by-type-" + props.org + "-" + props.app,
|
|
310
|
+
() =>
|
|
311
|
+
getRoles({
|
|
312
|
+
org: props.org,
|
|
313
|
+
type: props.app,
|
|
314
|
+
limit: 50,
|
|
315
|
+
}),
|
|
316
|
+
{ watch: [() => props.app] }
|
|
304
317
|
);
|
|
305
318
|
|
|
306
319
|
watchEffect(() => {
|
|
@@ -309,10 +322,10 @@ watchEffect(() => {
|
|
|
309
322
|
}
|
|
310
323
|
});
|
|
311
324
|
|
|
312
|
-
function handleUpdateApp(value: string)
|
|
325
|
+
async function handleUpdateApp(value: string) {
|
|
313
326
|
invite.value.role = "";
|
|
314
327
|
invite.value.site = "";
|
|
315
|
-
refreshRoles()
|
|
328
|
+
await refreshRoles()
|
|
316
329
|
}
|
|
317
330
|
|
|
318
331
|
const createMore = ref(false);
|
|
@@ -327,6 +327,8 @@
|
|
|
327
327
|
</template>
|
|
328
328
|
|
|
329
329
|
<script lang="ts" setup>
|
|
330
|
+
import useServiceProvider from '../composables/useServiceProvider';
|
|
331
|
+
|
|
330
332
|
const props = defineProps({
|
|
331
333
|
orgId: {
|
|
332
334
|
type: String,
|
|
@@ -418,7 +420,7 @@ const serviceProvider = ref({
|
|
|
418
420
|
const {
|
|
419
421
|
getAll: getAllServiceProvider,
|
|
420
422
|
add: addServiceProvider,
|
|
421
|
-
|
|
423
|
+
createServiceProviderInvite
|
|
422
424
|
} = useServiceProvider();
|
|
423
425
|
|
|
424
426
|
const { getSiteById } = useSite();
|
|
@@ -536,12 +538,15 @@ async function submitServiceProviderInvite() {
|
|
|
536
538
|
disableServiceProvider.value = true;
|
|
537
539
|
messageServiceProvider.value = "";
|
|
538
540
|
try {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
541
|
+
const payload = {
|
|
542
|
+
email: serviceProvider.value.email?.trim(),
|
|
543
|
+
orgId: props.orgId,
|
|
544
|
+
siteId: props.siteId,
|
|
545
|
+
siteName: site.value?.name || props.siteName,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
await createServiceProviderInvite(payload);
|
|
545
550
|
if (createMoreServiceProvider.value) {
|
|
546
551
|
serviceProvider.value.email = "";
|
|
547
552
|
} else {
|
|
@@ -5,47 +5,57 @@
|
|
|
5
5
|
<v-expansion-panel color="primary">
|
|
6
6
|
<v-expansion-panel-title>
|
|
7
7
|
<v-icon class="mr-2">mdi-cog</v-icon>
|
|
8
|
-
Site
|
|
8
|
+
Site Information
|
|
9
9
|
</v-expansion-panel-title>
|
|
10
10
|
|
|
11
11
|
<v-expansion-panel-text>
|
|
12
|
-
<v-row no-gutters class="d-flex justify-center">
|
|
13
|
-
<v-
|
|
14
|
-
<v-row
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
<v-row no-gutters class="d-flex justify-center py-10">
|
|
13
|
+
<v-form class="w-100">
|
|
14
|
+
<v-row>
|
|
15
|
+
<!-- Cover Photo + Preview -->
|
|
16
|
+
<v-col cols="12" lg="5" class="pt-0">
|
|
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" />
|
|
19
|
+
</v-col>
|
|
20
|
+
<v-col cols="12" lg="7" class="pl-lg-4 pt-4 pt-lg-0">
|
|
21
|
+
<InputLabel title="Preview" class="d-block mb-2" />
|
|
22
|
+
<v-img v-if="coverPhotoUrl" :src="coverPhotoUrl" aspect-ratio="16/9" cover rounded="lg"
|
|
23
|
+
class="w-100 border" />
|
|
24
|
+
<div v-else
|
|
25
|
+
class="w-100 d-flex align-center justify-center rounded-lg bg-grey-lighten-4 border border-dashed"
|
|
26
|
+
style="aspect-ratio: 16/9">
|
|
27
|
+
<span class="text-caption text-medium-emphasis">No cover photo uploaded</span>
|
|
28
|
+
</div>
|
|
17
29
|
</v-col>
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
<!-- Description + Site Documents -->
|
|
32
|
+
<v-col cols="12" lg="6" class="mt-2">
|
|
33
|
+
<InputLabel title="Description" class="d-block mb-1" />
|
|
34
|
+
<v-textarea v-model="description" variant="outlined" density="compact" rows="4" hide-details no-resize
|
|
35
|
+
style="height: 119px" />
|
|
36
|
+
</v-col>
|
|
37
|
+
<v-col cols="12" lg="6" class="mt-2 pl-lg-2">
|
|
38
|
+
<InputLabel title="Site Documents" class="d-block mb-1" />
|
|
39
|
+
<InputFileV2 v-model="siteDocuments" :multiple="true" accept="*/*" title="Upload documents"
|
|
40
|
+
:height="104" />
|
|
41
|
+
</v-col>
|
|
42
|
+
|
|
43
|
+
<!-- Submit -->
|
|
44
|
+
<v-col cols="12" class="pt-0 d-flex justify-end">
|
|
45
|
+
<v-btn color="primary-button" class="text-none" size="large" variant="flat" text="Save"
|
|
46
|
+
:loading="savingSiteInfo" @click="saveSiteInfo" />
|
|
47
|
+
</v-col>
|
|
48
|
+
|
|
49
|
+
<!-- Divider -->
|
|
50
|
+
<v-col cols="12" class="my-2">
|
|
51
|
+
<v-divider />
|
|
34
52
|
</v-col>
|
|
35
53
|
|
|
36
54
|
<v-col cols="12">
|
|
37
55
|
<v-row no-gutters>
|
|
38
56
|
<v-col cols="12" lg="4" class="mt-2">
|
|
39
|
-
<NumberSettingField
|
|
40
|
-
|
|
41
|
-
title="No. of guard posts"
|
|
42
|
-
type="guard_posts"
|
|
43
|
-
:read-only="!canManageSiteSettings"
|
|
44
|
-
:existing-guard-posts-number="existingGuardPostNumber"
|
|
45
|
-
:site-id="siteId"
|
|
46
|
-
:disabled="existingGuardPostNumber === guardPosts"
|
|
47
|
-
@success="refreshSiteData"
|
|
48
|
-
/>
|
|
57
|
+
<NumberSettingField v-model="blocks" title="No. of blocks" type="blocks" :site-id="siteId"
|
|
58
|
+
:disabled="existingBlockNumber === blocks" @success="refreshSiteData" />
|
|
49
59
|
</v-col>
|
|
50
60
|
</v-row>
|
|
51
61
|
</v-col>
|
|
@@ -53,135 +63,76 @@
|
|
|
53
63
|
<v-col cols="12">
|
|
54
64
|
<v-row no-gutters>
|
|
55
65
|
<v-col cols="12" lg="4" class="mt-2">
|
|
56
|
-
<v-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<v-col cols="6">
|
|
60
|
-
<InputLabel
|
|
61
|
-
class="text-capitalize font-weight-bold"
|
|
62
|
-
title="Grace Period"
|
|
63
|
-
required
|
|
64
|
-
/>
|
|
65
|
-
<v-text-field
|
|
66
|
-
v-model="gracePeriod"
|
|
67
|
-
type="number"
|
|
68
|
-
density="comfortable"
|
|
69
|
-
:readonly="!canManageSiteSettings"
|
|
70
|
-
:rules="[requiredRule]"
|
|
71
|
-
/>
|
|
72
|
-
</v-col>
|
|
73
|
-
|
|
74
|
-
<v-col cols="6">
|
|
75
|
-
<v-btn
|
|
76
|
-
v-if="canManageSiteSettings"
|
|
77
|
-
color="primary"
|
|
78
|
-
class="text-none mt-6"
|
|
79
|
-
size="large"
|
|
80
|
-
variant="flat"
|
|
81
|
-
:disabled="
|
|
82
|
-
!gracePeriodValid ||
|
|
83
|
-
existingGracePeriodNumber === gracePeriod
|
|
84
|
-
"
|
|
85
|
-
:loading="gracePeriodLoading"
|
|
86
|
-
text="Save"
|
|
87
|
-
@click="handleSaveGracePeriod"
|
|
88
|
-
/>
|
|
89
|
-
</v-col>
|
|
90
|
-
</v-row>
|
|
91
|
-
</v-form>
|
|
92
|
-
</v-row>
|
|
66
|
+
<NumberSettingField v-model="guardPosts" title="No. of guard posts" type="guard_posts"
|
|
67
|
+
:site-id="siteId" :disabled="existingGuardPostNumber === guardPosts"
|
|
68
|
+
@success="refreshSiteData" />
|
|
93
69
|
</v-col>
|
|
94
70
|
</v-row>
|
|
95
71
|
</v-col>
|
|
96
72
|
</v-row>
|
|
97
|
-
</v-
|
|
73
|
+
</v-form>
|
|
98
74
|
</v-row>
|
|
99
75
|
</v-expansion-panel-text>
|
|
100
76
|
</v-expansion-panel>
|
|
101
77
|
|
|
102
78
|
<!-- ANPR CAMERA -->
|
|
103
|
-
<v-expansion-panel color="primary">
|
|
79
|
+
<v-expansion-panel v-if="showAnprCamera" color="primary">
|
|
104
80
|
<v-expansion-panel-title>
|
|
105
81
|
<v-icon class="mr-2">mdi-camera</v-icon>
|
|
106
82
|
ANPR Camera
|
|
107
83
|
</v-expansion-panel-title>
|
|
108
84
|
|
|
109
85
|
<v-expansion-panel-text>
|
|
110
|
-
<CameraMain
|
|
111
|
-
:
|
|
112
|
-
:read-only="!canManageSiteSettings"
|
|
113
|
-
:guard-posts="siteData?.metadata?.guardPosts"
|
|
114
|
-
/>
|
|
86
|
+
<CameraMain :site="siteId" :read-only="!canManageSiteSettings"
|
|
87
|
+
:guard-posts="siteData?.metadata?.guardPosts" />
|
|
115
88
|
</v-expansion-panel-text>
|
|
116
89
|
</v-expansion-panel>
|
|
117
90
|
|
|
118
91
|
<!-- CCTV CAMERA -->
|
|
119
|
-
<v-expansion-panel color="primary">
|
|
92
|
+
<v-expansion-panel v-if="showCctvCamera" color="primary">
|
|
120
93
|
<v-expansion-panel-title>
|
|
121
94
|
<v-icon class="mr-2">mdi-cctv</v-icon>
|
|
122
95
|
CCTV Camera
|
|
123
96
|
</v-expansion-panel-title>
|
|
124
97
|
|
|
125
98
|
<v-expansion-panel-text>
|
|
126
|
-
<CameraMain
|
|
127
|
-
:site="siteId"
|
|
128
|
-
type="ip"
|
|
129
|
-
:read-only="!canManageSiteSettings"
|
|
130
|
-
/>
|
|
99
|
+
<CameraMain :site="siteId" type="ip" :read-only="!canManageSiteSettings" />
|
|
131
100
|
</v-expansion-panel-text>
|
|
132
101
|
</v-expansion-panel>
|
|
133
102
|
|
|
134
|
-
<v-expansion-panel color="primary">
|
|
103
|
+
<v-expansion-panel v-if="showWorkOrderSettings" color="primary" class="">
|
|
135
104
|
<v-expansion-panel-title>
|
|
136
105
|
<v-icon class="mr-2">mdi-format-list-numbered</v-icon>
|
|
137
106
|
Work Order Settings
|
|
138
107
|
</v-expansion-panel-title>
|
|
139
108
|
|
|
140
109
|
<v-expansion-panel-text>
|
|
141
|
-
<v-text-field
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
/>
|
|
147
|
-
|
|
148
|
-
<v-select
|
|
149
|
-
v-model="noOfDigits"
|
|
150
|
-
:items="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
|
151
|
-
label="No. of Digits"
|
|
152
|
-
:disabled="isLoading || !canManageSiteSettings"
|
|
153
|
-
/>
|
|
110
|
+
<v-text-field v-model="prefix" label="Prefix" :disabled="isLoading || !canManageSiteSettings"
|
|
111
|
+
@input="handleInput" />
|
|
112
|
+
|
|
113
|
+
<v-select v-model="noOfDigits" :items="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" label="No. of Digits"
|
|
114
|
+
:disabled="isLoading || !canManageSiteSettings" />
|
|
154
115
|
|
|
155
116
|
<v-text-field v-model="orderNumberPreview" label="Preview" readonly />
|
|
156
117
|
|
|
157
|
-
<v-btn
|
|
158
|
-
v-if="canManageSiteSettings"
|
|
159
|
-
class="mt-2"
|
|
160
|
-
:loading="isLoading"
|
|
161
|
-
@click="save"
|
|
162
|
-
>
|
|
118
|
+
<v-btn v-if="canManageSiteSettings" class="mt-2" :loading="isLoading" @click="save">
|
|
163
119
|
Save
|
|
164
120
|
</v-btn>
|
|
165
121
|
</v-expansion-panel-text>
|
|
166
122
|
</v-expansion-panel>
|
|
167
123
|
|
|
168
|
-
<v-expansion-panel color="primary">
|
|
124
|
+
<v-expansion-panel v-if="showDeliveryCompanies" color="primary">
|
|
169
125
|
<v-expansion-panel-title>
|
|
170
126
|
<v-icon class="mr-2">mdi-truck</v-icon>
|
|
171
127
|
Delivery Companies
|
|
172
128
|
</v-expansion-panel-title>
|
|
173
129
|
<v-expansion-panel-text class="">
|
|
174
|
-
<DeliveryCompany
|
|
175
|
-
:
|
|
176
|
-
v-model:initial="deliveryCompanies"
|
|
177
|
-
@refresh-site="refreshSiteData"
|
|
178
|
-
@update:companiesValue="handleUpdateCompanies"
|
|
179
|
-
:read-only="!canManageSiteSettings"
|
|
180
|
-
/>
|
|
130
|
+
<DeliveryCompany :site="siteId" v-model:initial="deliveryCompanies" @refresh-site="refreshSiteData"
|
|
131
|
+
@update:companiesValue="handleUpdateCompanies" :read-only="!canManageSiteSettings" />
|
|
181
132
|
</v-expansion-panel-text>
|
|
182
133
|
</v-expansion-panel>
|
|
183
134
|
|
|
184
|
-
<v-expansion-panel color="primary">
|
|
135
|
+
<v-expansion-panel v-if="showResidentVisitors" color="primary">
|
|
185
136
|
<v-expansion-panel-title>
|
|
186
137
|
<v-icon class="mr-2">mdi-car</v-icon>
|
|
187
138
|
Resident Visitors
|
|
@@ -190,6 +141,8 @@
|
|
|
190
141
|
<OvernightParkingAvailability :site="siteId" />
|
|
191
142
|
</v-expansion-panel-text>
|
|
192
143
|
</v-expansion-panel>
|
|
144
|
+
|
|
145
|
+
<slot name="panels" :can-manage="canManageSiteSettings" :site-id="siteId" />
|
|
193
146
|
</v-expansion-panels>
|
|
194
147
|
|
|
195
148
|
<Snackbar v-model="toast.show" :text="toast.message" :color="toast.color" />
|
|
@@ -197,14 +150,25 @@
|
|
|
197
150
|
</template>
|
|
198
151
|
|
|
199
152
|
<script setup lang="ts">
|
|
153
|
+
import useFile from '../composables/useFile';
|
|
154
|
+
import useSiteSettings from '../composables/useSiteSettings';
|
|
155
|
+
import useUtils from '../composables/useUtils';
|
|
156
|
+
import useWorkOrder from '../composables/useWorkOrder';
|
|
157
|
+
|
|
200
158
|
const props = defineProps({
|
|
201
159
|
siteId: { type: String, required: true },
|
|
202
160
|
canManageSiteSettings: { type: Boolean, default: false },
|
|
161
|
+
showAnprCamera: { type: Boolean, default: false },
|
|
162
|
+
showCctvCamera: { type: Boolean, default: false },
|
|
163
|
+
showWorkOrderSettings: { type: Boolean, default: false },
|
|
164
|
+
showDeliveryCompanies: { type: Boolean, default: false },
|
|
165
|
+
showResidentVisitors: { type: Boolean, default: false },
|
|
203
166
|
});
|
|
204
167
|
|
|
205
|
-
const openPanels = ref([0]); // default open first
|
|
206
168
|
|
|
207
|
-
const
|
|
169
|
+
const openPanels = ref([]); // default open first
|
|
170
|
+
|
|
171
|
+
const { getSiteById, updateSitebyId, updateSiteInformation } = useSiteSettings();
|
|
208
172
|
const { requiredRule } = useUtils();
|
|
209
173
|
const { getFileUrl } = useFile();
|
|
210
174
|
const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder();
|
|
@@ -228,12 +192,30 @@ const prefix = ref("");
|
|
|
228
192
|
const noOfDigits = ref(1);
|
|
229
193
|
const isLoading = ref(false);
|
|
230
194
|
|
|
195
|
+
const coverPhoto = ref<string[]>([]);
|
|
196
|
+
const description = ref<string>("");
|
|
197
|
+
const siteDocuments = ref<string[]>([]);
|
|
198
|
+
const siteDocumentsNames = ref<Record<string, string>>({});
|
|
199
|
+
const savingSiteInfo = ref<boolean>(false);
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
const coverPhotoUrl = computed(() =>
|
|
203
|
+
coverPhoto.value.length > 0 ? getFileUrl(coverPhoto.value[0]) : "",
|
|
204
|
+
);
|
|
205
|
+
|
|
231
206
|
const toast = reactive({
|
|
232
207
|
show: false,
|
|
233
208
|
message: "",
|
|
234
209
|
color: "",
|
|
235
210
|
});
|
|
236
211
|
|
|
212
|
+
function showSnackbar(text: string, color = "success") {
|
|
213
|
+
toast.message = text;
|
|
214
|
+
toast.color = color;
|
|
215
|
+
toast.show = true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
237
219
|
const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
238
220
|
`site-${props.siteId}`,
|
|
239
221
|
() => getSiteById(props.siteId)
|
|
@@ -242,24 +224,35 @@ const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
|
242
224
|
watch(
|
|
243
225
|
siteData,
|
|
244
226
|
(val: TSite) => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
227
|
+
const siteDataValue = val as any;
|
|
228
|
+
if (siteDataValue) {
|
|
229
|
+
blocks.value = siteDataValue.metadata?.block || 0;
|
|
230
|
+
existingBlockNumber.value = blocks.value;
|
|
231
|
+
|
|
232
|
+
guardPosts.value = siteDataValue.metadata?.guardPosts || 0;
|
|
233
|
+
existingGuardPostNumber.value = guardPosts.value;
|
|
234
|
+
deliveryCompanies.value = Array.isArray(
|
|
235
|
+
siteDataValue?.deliveryCompanyList,
|
|
236
|
+
)
|
|
237
|
+
? [...siteDataValue.deliveryCompanyList]
|
|
238
|
+
: [];
|
|
239
|
+
|
|
240
|
+
const info = siteDataValue.siteInformation;
|
|
241
|
+
if (info) {
|
|
242
|
+
coverPhoto.value = info.bgImage ? [info.bgImage] : [];
|
|
243
|
+
description.value = info.description ?? "";
|
|
244
|
+
const docs: { id: string; name: string }[] = info.docs ?? [];
|
|
245
|
+
siteDocuments.value = docs.map((d) => d.id);
|
|
246
|
+
docs.forEach((d) => {
|
|
247
|
+
siteDocumentsNames.value[d.id] = d.name;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
259
251
|
},
|
|
260
252
|
{ immediate: true }
|
|
261
253
|
);
|
|
262
254
|
|
|
255
|
+
|
|
263
256
|
async function handleSaveGracePeriod() {
|
|
264
257
|
gracePeriodLoading.value = true;
|
|
265
258
|
try {
|
|
@@ -276,6 +269,29 @@ async function handleSaveGracePeriod() {
|
|
|
276
269
|
}
|
|
277
270
|
}
|
|
278
271
|
|
|
272
|
+
async function saveSiteInfo() {
|
|
273
|
+
savingSiteInfo.value = true;
|
|
274
|
+
try {
|
|
275
|
+
await updateSiteInformation(props.siteId, {
|
|
276
|
+
bgImage: coverPhoto.value[0] ?? "",
|
|
277
|
+
description: description.value,
|
|
278
|
+
docs: siteDocuments.value.map((id) => ({
|
|
279
|
+
id,
|
|
280
|
+
name: siteDocumentsNames.value[id] ?? "",
|
|
281
|
+
})),
|
|
282
|
+
});
|
|
283
|
+
showSnackbar("Site information updated successfully.");
|
|
284
|
+
} catch (err: any) {
|
|
285
|
+
const msg =
|
|
286
|
+
err?.response?._data?.message ??
|
|
287
|
+
err?.message ??
|
|
288
|
+
"Failed to update site information.";
|
|
289
|
+
showSnackbar(msg, "error");
|
|
290
|
+
} finally {
|
|
291
|
+
savingSiteInfo.value = false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
279
295
|
async function handleLogo(action: string) {
|
|
280
296
|
try {
|
|
281
297
|
await updateSitebyId(props.siteId, {
|
|
@@ -303,8 +319,8 @@ const { data: workOrderSetting } = await useLazyAsyncData(
|
|
|
303
319
|
|
|
304
320
|
watchEffect(() => {
|
|
305
321
|
if (workOrderSetting.value) {
|
|
306
|
-
prefix.value = workOrderSetting.value
|
|
307
|
-
noOfDigits.value = workOrderSetting.value
|
|
322
|
+
prefix.value = workOrderSetting.value?.prefix;
|
|
323
|
+
noOfDigits.value = workOrderSetting.value?.noOfDigits;
|
|
308
324
|
}
|
|
309
325
|
});
|
|
310
326
|
|
|
@@ -279,6 +279,16 @@ export default function useAccessManagement() {
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
function getBlockLevelUnitList(siteId: string) {
|
|
283
|
+
return useNuxtApp().$api<{ message: string; data: any[] }>(
|
|
284
|
+
`/api/access-management/block-level-unit-list`,
|
|
285
|
+
{
|
|
286
|
+
method: "GET",
|
|
287
|
+
query: { site: siteId },
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
282
292
|
function signQr(payload: { cardId: string; purpose: string }) {
|
|
283
293
|
return useNuxtApp().$api<{ message: string; data: string }>(
|
|
284
294
|
`/api/access-management/sign-qr`,
|
|
@@ -310,5 +320,6 @@ export default function useAccessManagement() {
|
|
|
310
320
|
generateQrVms,
|
|
311
321
|
createVisitorPass,
|
|
312
322
|
signQr,
|
|
323
|
+
getBlockLevelUnitList,
|
|
313
324
|
};
|
|
314
325
|
}
|
package/composables/useOrg.ts
CHANGED
|
@@ -116,7 +116,7 @@ export default function useOrg() {
|
|
|
116
116
|
value: Partial<Pick<TOrg, "name" | "email" | "nature" | "contact">>
|
|
117
117
|
) {
|
|
118
118
|
return useNuxtApp()
|
|
119
|
-
.$api<Record<string, any>>("/api/organizations
|
|
119
|
+
.$api<Record<string, any>>("/api/organizations", {
|
|
120
120
|
method: "POST",
|
|
121
121
|
body: value,
|
|
122
122
|
})
|
|
@@ -126,7 +126,7 @@ export default function useOrg() {
|
|
|
126
126
|
) as TOrg;
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
return {
|
|
131
131
|
org,
|
|
132
132
|
getOrgs,
|
package/composables/useRole.ts
CHANGED
|
@@ -128,21 +128,29 @@ export default function useServiceProvider() {
|
|
|
128
128
|
|
|
129
129
|
async function createServiceProviderInvite({
|
|
130
130
|
email,
|
|
131
|
+
orgId,
|
|
131
132
|
siteId,
|
|
132
|
-
|
|
133
|
+
siteName,
|
|
133
134
|
}: {
|
|
134
135
|
email: string;
|
|
136
|
+
orgId: string;
|
|
135
137
|
siteId: string;
|
|
136
|
-
|
|
138
|
+
siteName: string;
|
|
137
139
|
}) {
|
|
138
140
|
try {
|
|
139
|
-
|
|
141
|
+
const res = await useNuxtApp().$api<Record<string, any>>(
|
|
140
142
|
"/api/auth/invite/service-provider",
|
|
141
143
|
{
|
|
142
144
|
method: "POST",
|
|
143
|
-
|
|
145
|
+
body: {
|
|
146
|
+
email,
|
|
147
|
+
orgId,
|
|
148
|
+
siteId,
|
|
149
|
+
siteName,
|
|
150
|
+
},
|
|
144
151
|
}
|
|
145
152
|
);
|
|
153
|
+
console.log(">>> response:", res);
|
|
146
154
|
} catch (err) {
|
|
147
155
|
console.error("Error creating service provider invite:", err);
|
|
148
156
|
}
|
package/package.json
CHANGED
package/types/customer.d.ts
CHANGED
|
@@ -35,4 +35,4 @@ declare type TCustomerSite = {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
declare type TSiteCategory = "residential" | "commercial" | "industrial" | "institutional" | "mixed_development" | "infrastructure" | "hospitality"
|
|
38
|
+
declare type TSiteCategory = "residential" | "commercial" | "industrial" | "institutional" | "mixed_development" | "infrastructure" | "hospitality"
|