@7365admin1/layer-common 1.10.9 → 1.11.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 +12 -0
- package/components/AccessCardAddForm.vue +1 -1
- package/components/AccessCardAssignToUnitForm.vue +10 -13
- package/components/AccessCardQrTagging.vue +2 -2
- package/components/BulletinBoardManagement.vue +18 -8
- package/components/Chat/SkeletonLoader.vue +71 -0
- package/components/DashboardMain.vue +176 -0
- package/components/DeliveryCompany.vue +240 -0
- package/components/EntryPassInformation.vue +38 -8
- package/components/FeedbackMain.vue +4 -19
- package/components/FileInputWithList.vue +304 -0
- package/components/IncidentReport/Authorities.vue +189 -151
- package/components/IncidentReport/IncidentInformation.vue +28 -12
- package/components/IncidentReport/IncidentInformationDownload.vue +225 -0
- package/components/IncidentReport/affectedEntities.vue +13 -57
- package/components/Signature.vue +133 -0
- package/components/SiteSettings.vue +285 -0
- package/components/SlideCardGroup.vue +194 -0
- package/components/Tooltip/Info.vue +33 -0
- package/components/VisitorForm.vue +65 -3
- package/components/VisitorManagement.vue +23 -6
- package/composables/useAccessManagement.ts +44 -6
- package/composables/useBulletin.ts +8 -3
- package/composables/useBulletinBoardPermission.ts +48 -0
- package/composables/useCleaningPermission.ts +2 -0
- package/composables/useComment.ts +147 -0
- package/composables/useCommonPermission.ts +29 -1
- package/composables/useFeedback.ts +79 -29
- package/composables/useFile.ts +6 -0
- package/composables/usePDFDownload.ts +1 -1
- package/composables/useSiteSettings.ts +1 -1
- package/composables/useVisitor.ts +6 -5
- package/composables/useWorkOrder.ts +61 -26
- package/constants/app.ts +12 -0
- package/nuxt.config.ts +2 -0
- package/package.json +3 -1
- package/plugins/vue-draggable-next.client.ts +5 -0
- package/public/default-image.svg +4 -0
- package/public/placeholder-image.svg +6 -0
- package/types/comment.d.ts +38 -0
- package/types/dashboard.d.ts +12 -0
- package/types/feedback.d.ts +56 -20
- package/types/site.d.ts +2 -1
- package/types/work-order.d.ts +54 -18
- package/utils/data.ts +31 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-sheet class="mx-auto">
|
|
3
|
+
<v-slide-group
|
|
4
|
+
class="pa-1"
|
|
5
|
+
show-arrows
|
|
6
|
+
:style="{ backgroundColor: '#FFFF' }"
|
|
7
|
+
>
|
|
8
|
+
<v-slide-group-item
|
|
9
|
+
v-for="(data, idx) in localDataFiles"
|
|
10
|
+
:key="idx"
|
|
11
|
+
v-slot="{ isSelected }"
|
|
12
|
+
>
|
|
13
|
+
<v-card
|
|
14
|
+
:class="['ma-1', isSelected && 'selected']"
|
|
15
|
+
width="150"
|
|
16
|
+
height="100"
|
|
17
|
+
@click="clickable && emit('onClickCarousel', data.path)"
|
|
18
|
+
class="rounded-lg border-thin"
|
|
19
|
+
border=" opacity-50 thin "
|
|
20
|
+
>
|
|
21
|
+
<v-btn
|
|
22
|
+
icon
|
|
23
|
+
variant="text"
|
|
24
|
+
size="x-small"
|
|
25
|
+
class="remove-btn pa-0 ma-0"
|
|
26
|
+
@click.stop="removeFile(data)"
|
|
27
|
+
>
|
|
28
|
+
<v-icon color="red" class="pa-0 ma-0" size="x-large"
|
|
29
|
+
>mdi-close-circle</v-icon
|
|
30
|
+
>
|
|
31
|
+
</v-btn>
|
|
32
|
+
<!-- File Icons -->
|
|
33
|
+
|
|
34
|
+
<v-img :src="data.url" cover height="200">
|
|
35
|
+
<template #placeholder>
|
|
36
|
+
<v-skeleton-loader height="100%" width="100%" />
|
|
37
|
+
</template>
|
|
38
|
+
</v-img>
|
|
39
|
+
<!-- <template v-if="url.type !== 'image'">
|
|
40
|
+
<div
|
|
41
|
+
class="d-flex fill-height align-center justify-center flex-column"
|
|
42
|
+
>
|
|
43
|
+
<v-icon
|
|
44
|
+
:icon="getFileIcon(url.type)"
|
|
45
|
+
size="64"
|
|
46
|
+
:color="getFileColor(url.type)"
|
|
47
|
+
></v-icon>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
<template v-else>
|
|
54
|
+
<v-img :src="url.path" cover height="200">
|
|
55
|
+
<template #placeholder>
|
|
56
|
+
<v-skeleton-loader height="100%" width="100%" />
|
|
57
|
+
</template>
|
|
58
|
+
</v-img>
|
|
59
|
+
</template> -->
|
|
60
|
+
|
|
61
|
+
<!-- Edit Button -->
|
|
62
|
+
<!-- <div v-if="imgEditable" class="edit-btn">
|
|
63
|
+
<v-btn
|
|
64
|
+
color="primary"
|
|
65
|
+
size="small"
|
|
66
|
+
@click.stop="onImageEdit(url.path, idx)"
|
|
67
|
+
>
|
|
68
|
+
<v-icon start>mdi-pencil</v-icon>
|
|
69
|
+
Edit
|
|
70
|
+
</v-btn>
|
|
71
|
+
</div> -->
|
|
72
|
+
</v-card>
|
|
73
|
+
</v-slide-group-item>
|
|
74
|
+
</v-slide-group>
|
|
75
|
+
</v-sheet>
|
|
76
|
+
|
|
77
|
+
<DrawImage
|
|
78
|
+
v-if="isShowImageEdit"
|
|
79
|
+
:is-show-dialog="isShowImageEdit"
|
|
80
|
+
:image-url="imageUrl"
|
|
81
|
+
:image-idx="imageIdx"
|
|
82
|
+
@on-submit="onImageSubmitEdit"
|
|
83
|
+
@on-close-dialog="isShowImageEdit = false"
|
|
84
|
+
/>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
const { getImage } = useUtils();
|
|
89
|
+
const { attachedFiles } = useUploadFiles();
|
|
90
|
+
|
|
91
|
+
const emit = defineEmits<{
|
|
92
|
+
(event: "onClickCarousel", url: string): void;
|
|
93
|
+
(event: "onImageEdit", url: string, idx: number): void;
|
|
94
|
+
(event: "onFileRemove"): void;
|
|
95
|
+
}>();
|
|
96
|
+
|
|
97
|
+
const props = defineProps<{
|
|
98
|
+
clickable?: boolean;
|
|
99
|
+
imgEditable?: boolean;
|
|
100
|
+
dataFiles?: File[];
|
|
101
|
+
}>();
|
|
102
|
+
const localDataFiles = ref([...props.dataFiles]);
|
|
103
|
+
const isShowImageEdit = ref(false);
|
|
104
|
+
const imageUrl = ref("");
|
|
105
|
+
const imageIdx = ref(0);
|
|
106
|
+
|
|
107
|
+
const getFileType = (url: string) => {
|
|
108
|
+
const extension = url.split(".").pop()?.toLowerCase() || "";
|
|
109
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) return "image";
|
|
110
|
+
if (["pdf"].includes(extension)) return "pdf";
|
|
111
|
+
if (["doc", "docx"].includes(extension)) return "word";
|
|
112
|
+
return "other";
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const getFileIcon = (type: string) => {
|
|
116
|
+
switch (type) {
|
|
117
|
+
case "pdf":
|
|
118
|
+
return "mdi-file-pdf-box";
|
|
119
|
+
case "word":
|
|
120
|
+
return "mdi-file-word-box";
|
|
121
|
+
default:
|
|
122
|
+
return "mdi-file-outline";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getFileColor = (type: string) => {
|
|
127
|
+
switch (type) {
|
|
128
|
+
case "pdf":
|
|
129
|
+
return "red";
|
|
130
|
+
case "word":
|
|
131
|
+
return "blue";
|
|
132
|
+
default:
|
|
133
|
+
return "grey";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const onImageEdit = (url: string, idx: number) => {
|
|
138
|
+
isShowImageEdit.value = true;
|
|
139
|
+
imageUrl.value = url;
|
|
140
|
+
imageIdx.value = idx;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const onImageSubmitEdit = async (url: string, idx: number) => {
|
|
144
|
+
const response = await getImage(url);
|
|
145
|
+
if (!response) return;
|
|
146
|
+
attachedFiles.value[idx].data = new File(
|
|
147
|
+
[response],
|
|
148
|
+
attachedFiles.value[idx].data?.name
|
|
149
|
+
);
|
|
150
|
+
attachedFiles.value[idx].url = url;
|
|
151
|
+
isShowImageEdit.value = false;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const isImageFile = (type: string) => {
|
|
155
|
+
return type?.startsWith("image/");
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
watch(
|
|
159
|
+
() => props.dataFiles,
|
|
160
|
+
(newDataFiles) => {
|
|
161
|
+
localDataFiles.value = [...newDataFiles];
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const removeFile = (file) => {
|
|
166
|
+
const index = localDataFiles.value.findIndex(
|
|
167
|
+
(dataFile) => dataFile.id === file.id
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (index !== -1) {
|
|
171
|
+
localDataFiles.value.splice(index, 1);
|
|
172
|
+
emit("onFileRemove", file);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<style scoped>
|
|
178
|
+
.edit-btn {
|
|
179
|
+
position: absolute;
|
|
180
|
+
bottom: 8px;
|
|
181
|
+
right: 8px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.selected {
|
|
185
|
+
border: 2px solid var(--v-theme-primary);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.remove-btn {
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 0px;
|
|
191
|
+
right: 0px;
|
|
192
|
+
z-index: 2;
|
|
193
|
+
}
|
|
194
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-tooltip v-bind="$attrs" :text="text" @click.stop>
|
|
3
|
+
<template v-slot:activator="{ props: activatorProps }">
|
|
4
|
+
<v-btn icon="mdi-information-symbol" v-bind="activatorProps" class="p-0 m-0 d-flex align-center"
|
|
5
|
+
:density="density" :size="size" color="primary-button" />
|
|
6
|
+
</template>
|
|
7
|
+
</v-tooltip>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
|
|
12
|
+
import type { VTooltip} from "vuetify/components"
|
|
13
|
+
|
|
14
|
+
type TTooltipDensity = VTooltip["$props"]["density"]
|
|
15
|
+
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
text: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: ""
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: ""
|
|
24
|
+
},
|
|
25
|
+
density: {
|
|
26
|
+
type: String as PropType<TTooltipDensity>,
|
|
27
|
+
default: "default"
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<style scoped></style>
|
|
@@ -110,6 +110,36 @@
|
|
|
110
110
|
</v-col>
|
|
111
111
|
</template>
|
|
112
112
|
|
|
113
|
+
<template v-if="shouldShowField('delivery-company')">
|
|
114
|
+
<v-col cols="12">
|
|
115
|
+
<InputLabel class="text-capitalize" title="Delivery Company" />
|
|
116
|
+
<v-combobox v-model="deliveryCompany" v-model:search="deliveryCompanyInput" ref="companyCombo"
|
|
117
|
+
autocomplete="off" :hide-no-data="false" :items="deliveryCompanyList" item-value="value"
|
|
118
|
+
:loading="siteDataPending" variant="outlined"
|
|
119
|
+
density="comfortable" persistent-hint small-chips>
|
|
120
|
+
<template v-slot:no-data>
|
|
121
|
+
<v-list-item>
|
|
122
|
+
<v-list-item-title v-if="fetchCompanyListPending">
|
|
123
|
+
<v-progress-circular indeterminate size="20" class="mr-3" />
|
|
124
|
+
Searching companies…
|
|
125
|
+
</v-list-item-title>
|
|
126
|
+
<v-list-item-title v-else-if="companyNameInput" @click.stop="handleAddNewCompany"
|
|
127
|
+
class="d-flex align-center ga-1">
|
|
128
|
+
<span><v-icon icon="mdi-plus" /></span>Add "<strong>{{ companyNameInput }}</strong>" as new
|
|
129
|
+
company.
|
|
130
|
+
</v-list-item-title>
|
|
131
|
+
<v-list-item-title v-else-if="!companyNameInput && companyNames.length === 0">
|
|
132
|
+
Start typing to search for companies.
|
|
133
|
+
</v-list-item-title>
|
|
134
|
+
<v-list-item-title v-else>
|
|
135
|
+
No companies available. Start typing to add a new one.
|
|
136
|
+
</v-list-item-title>
|
|
137
|
+
</v-list-item>
|
|
138
|
+
</template>
|
|
139
|
+
</v-combobox>
|
|
140
|
+
</v-col>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
113
143
|
|
|
114
144
|
<v-col v-if="shouldShowField('plateNumber')" cols="12">
|
|
115
145
|
<v-row>
|
|
@@ -292,6 +322,7 @@ const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
|
|
|
292
322
|
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
293
323
|
const { createVisitor, typeFieldMap, contractorTypes, getVisitors, updateVisitor } = useVisitor();
|
|
294
324
|
const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
|
|
325
|
+
const { createVisitorPass } = useAccessManagement();
|
|
295
326
|
const { findPersonByNRIC, findPersonByContact, searchCompanyList, findUsersByPlateNumber } = usePeople()
|
|
296
327
|
const { getById: getUnitDataById } = useBuildingUnit()
|
|
297
328
|
|
|
@@ -324,7 +355,7 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
324
355
|
|
|
325
356
|
const passType = ref("");
|
|
326
357
|
const passQuantity = ref<number | null>(1);
|
|
327
|
-
const passCards = ref<string[]>([]);
|
|
358
|
+
const passCards = ref<{ _id: string; cardNo: string }[]>([]);
|
|
328
359
|
|
|
329
360
|
const registeredUnitCompanyName = ref('N/A')
|
|
330
361
|
|
|
@@ -356,13 +387,17 @@ const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
|
356
387
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
357
388
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
358
389
|
|
|
390
|
+
const deliveryCompany = ref("");
|
|
391
|
+
const deliveryCompanyInput = ref("");
|
|
392
|
+
const deliveryCompanyList = ref<string[]>([]);
|
|
393
|
+
|
|
359
394
|
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
|
|
360
395
|
|
|
361
396
|
|
|
362
397
|
const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
363
398
|
|
|
364
399
|
|
|
365
|
-
const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
|
|
400
|
+
const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') => {
|
|
366
401
|
if (prop.type !== "contractor" || contractorStep.value === 1) {
|
|
367
402
|
const visibleFields = typeFieldMap[prop.type];
|
|
368
403
|
return visibleFields?.includes(fieldKey);
|
|
@@ -634,6 +669,7 @@ const {
|
|
|
634
669
|
data: siteData,
|
|
635
670
|
refresh: refreshSiteData,
|
|
636
671
|
status: blockStatus,
|
|
672
|
+
pending: siteDataPending,
|
|
637
673
|
} = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
|
|
638
674
|
getSiteById(prop.site)
|
|
639
675
|
);
|
|
@@ -665,7 +701,7 @@ const {
|
|
|
665
701
|
watch(
|
|
666
702
|
siteData,
|
|
667
703
|
(newVal) => {
|
|
668
|
-
const siteDataValue = newVal as
|
|
704
|
+
const siteDataValue = newVal as TSite;
|
|
669
705
|
if (siteDataValue) {
|
|
670
706
|
const numberOfBlocks = siteDataValue.metadata?.block || 0;
|
|
671
707
|
for (let i = 1; i <= numberOfBlocks; i++) {
|
|
@@ -674,6 +710,9 @@ watch(
|
|
|
674
710
|
value: i,
|
|
675
711
|
});
|
|
676
712
|
}
|
|
713
|
+
|
|
714
|
+
deliveryCompanyList.value = Array.isArray(siteDataValue?.deliveryCompanyList) ? siteDataValue.deliveryCompanyList : [];
|
|
715
|
+
|
|
677
716
|
} else {
|
|
678
717
|
blocksArray.value = [];
|
|
679
718
|
}
|
|
@@ -857,9 +896,32 @@ async function submit() {
|
|
|
857
896
|
members: visitor.members,
|
|
858
897
|
};
|
|
859
898
|
}
|
|
899
|
+
|
|
900
|
+
if(prop.type === "delivery"){
|
|
901
|
+
payload = {
|
|
902
|
+
...payload,
|
|
903
|
+
company: deliveryCompany.value
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
860
907
|
try {
|
|
861
908
|
const res = await createVisitor(payload);
|
|
862
909
|
if (res) {
|
|
910
|
+
if (prop.type === "contractor" && passType.value) {
|
|
911
|
+
const visitorId = res?._id;
|
|
912
|
+
const acmUrl = entryPassSettings.value?.data?.settings?.acm_url;
|
|
913
|
+
if (visitorId && acmUrl) {
|
|
914
|
+
await createVisitorPass({
|
|
915
|
+
site: prop.site,
|
|
916
|
+
unitId: visitor.unit!,
|
|
917
|
+
quantity: passQuantity.value ?? 1,
|
|
918
|
+
type: passType.value === "QR" ? "QRCODE" : passType.value,
|
|
919
|
+
nfcCards: passCards.value,
|
|
920
|
+
acm_url: acmUrl,
|
|
921
|
+
visitorId,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
863
925
|
if (createMore.value) {
|
|
864
926
|
emit("done:more");
|
|
865
927
|
} else emit("done");
|
|
@@ -89,13 +89,19 @@
|
|
|
89
89
|
<span class="text-capitalize">{{
|
|
90
90
|
UTCToLocalTIme(item.checkIn) || "-"
|
|
91
91
|
}}</span>
|
|
92
|
+
<span>
|
|
93
|
+
<v-icon v-if="item?.snapshotEntryImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotEntryImage)" />
|
|
94
|
+
</span>
|
|
92
95
|
</span>
|
|
93
96
|
<span class="d-flex align-center ga-2">
|
|
94
97
|
<v-icon icon="mdi-clock-time-eight-outline" color="red" size="20" />
|
|
95
98
|
<template v-if="item.checkOut">
|
|
96
99
|
<span class="text-capitalize">{{
|
|
97
|
-
UTCToLocalTIme(item.checkOut) || "
|
|
100
|
+
UTCToLocalTIme(item.checkOut) || "-"
|
|
98
101
|
}}</span>
|
|
102
|
+
<span>
|
|
103
|
+
<v-icon v-if="item?.snapshotExitImage" size="17" icon="mdi-image" @click.stop="handleViewImage(item.snapshotExitImage)" />
|
|
104
|
+
</span>
|
|
99
105
|
<span v-if="item?.manualCheckout">
|
|
100
106
|
<TooltipInfo text="Manual Checkout" density="compact" size="x-small" />
|
|
101
107
|
</span>
|
|
@@ -120,8 +126,9 @@
|
|
|
120
126
|
</v-dialog>
|
|
121
127
|
|
|
122
128
|
<v-dialog v-model="dialog.showForm" v-if="activeVisitorFormType" width="450" persistent>
|
|
123
|
-
<VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject"
|
|
124
|
-
|
|
129
|
+
<VisitorForm :mode="mode" :org="orgId" :site="siteId" :visitor-data="selectedVisitorDataObject"
|
|
130
|
+
:type="activeVisitorFormType" @back="handleClickBack" @done="handleVisitorFormDone"
|
|
131
|
+
@done:more="handleVisitorFormCreateMore" @close:all="handleCloseAll" />
|
|
125
132
|
</v-dialog>
|
|
126
133
|
|
|
127
134
|
<v-dialog v-model="dialog.viewVisitor" width="450" persistent>
|
|
@@ -200,6 +207,7 @@ const {
|
|
|
200
207
|
const { debounce, formatCamelCaseToWords, formatDate, UTCToLocalTIme } =
|
|
201
208
|
useUtils();
|
|
202
209
|
const { formatLocation } = useSecurityUtils();
|
|
210
|
+
const { getFileUrlAnpr } = useFile();
|
|
203
211
|
// const { status: visitorStatus, search } = useRoute().query as { status: string, search: string};
|
|
204
212
|
|
|
205
213
|
const route = useRoute()
|
|
@@ -276,8 +284,11 @@ const formattedFields = {
|
|
|
276
284
|
level: "Level",
|
|
277
285
|
unitName: "Unit",
|
|
278
286
|
checkIn: "Check In",
|
|
287
|
+
snapshotEntryImage: "Entry Image",
|
|
279
288
|
checkOut: "Check Out",
|
|
289
|
+
snapshotExitImage: "Exit Image",
|
|
280
290
|
remarks: "Remarks",
|
|
291
|
+
|
|
281
292
|
} as const;
|
|
282
293
|
|
|
283
294
|
function filterTypeSelectionLabel() {
|
|
@@ -326,7 +337,7 @@ const {
|
|
|
326
337
|
params.status = activeTab.value
|
|
327
338
|
} else if (activeTab.value === "guests") {
|
|
328
339
|
params.type = "guest"
|
|
329
|
-
params.status =
|
|
340
|
+
params.status = "pending"
|
|
330
341
|
}
|
|
331
342
|
|
|
332
343
|
return await getVisitors(params)
|
|
@@ -348,11 +359,11 @@ watch(getVisitorReq, (newData: any) => {
|
|
|
348
359
|
});
|
|
349
360
|
|
|
350
361
|
const selectedVisitorObject = computed(() => {
|
|
362
|
+
|
|
351
363
|
const obj = items.value.find((x: any) => x?._id === selectedVisitorId.value);
|
|
352
364
|
if (!obj) return {};
|
|
353
365
|
const type = obj?.type as TVisitorType | undefined;
|
|
354
|
-
|
|
355
|
-
let includedKeys: string[] = ["checkIn", "checkOut"];
|
|
366
|
+
let includedKeys: string[] = ["checkIn", "checkOut", "plateNumber", "snapshotEntryImage", "snapshotExitImage"];
|
|
356
367
|
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
357
368
|
return Object.fromEntries(
|
|
358
369
|
Object.entries(obj).filter(([key]) => includedKeys.includes(key))
|
|
@@ -434,6 +445,12 @@ function handleRegistrationUnregisteredVisitor(item: Partial<TVisitor>) {
|
|
|
434
445
|
mode.value = "register";
|
|
435
446
|
}
|
|
436
447
|
|
|
448
|
+
function handleViewImage(imageId: string) {
|
|
449
|
+
const imageEndpoint = `${siteId}/${imageId}`;
|
|
450
|
+
const imageUrl = getFileUrlAnpr(imageEndpoint);
|
|
451
|
+
window.open(imageUrl, "_blank");
|
|
452
|
+
}
|
|
453
|
+
|
|
437
454
|
|
|
438
455
|
async function handleProceedDeleteVisitor() {
|
|
439
456
|
try {
|
|
@@ -111,8 +111,8 @@ export default function useAccessManagement() {
|
|
|
111
111
|
site: string;
|
|
112
112
|
userType: string;
|
|
113
113
|
type: string;
|
|
114
|
-
accessLevel: string;
|
|
115
|
-
liftAccessLevel: string;
|
|
114
|
+
accessLevel: string | null;
|
|
115
|
+
liftAccessLevel: string | null;
|
|
116
116
|
}) {
|
|
117
117
|
return useNuxtApp().$api<Record<string, any>>(
|
|
118
118
|
`/api/access-management/access-and-lift-cards`,
|
|
@@ -122,8 +122,8 @@ export default function useAccessManagement() {
|
|
|
122
122
|
site: params.site,
|
|
123
123
|
userType: params.userType,
|
|
124
124
|
type: params.type,
|
|
125
|
-
accessLevel: params.accessLevel,
|
|
126
|
-
liftAccessLevel: params.liftAccessLevel,
|
|
125
|
+
...(params.accessLevel != null ? { accessLevel: params.accessLevel } : {}),
|
|
126
|
+
...(params.liftAccessLevel != null ? { liftAccessLevel: params.liftAccessLevel } : {}),
|
|
127
127
|
},
|
|
128
128
|
}
|
|
129
129
|
);
|
|
@@ -135,8 +135,8 @@ export default function useAccessManagement() {
|
|
|
135
135
|
type: string;
|
|
136
136
|
site: string;
|
|
137
137
|
userType: string;
|
|
138
|
-
accessLevel: string;
|
|
139
|
-
liftAccessLevel: string;
|
|
138
|
+
accessLevel: string | null;
|
|
139
|
+
liftAccessLevel: string | null;
|
|
140
140
|
}) {
|
|
141
141
|
return useNuxtApp().$api<Record<string, any>>(
|
|
142
142
|
`/api/access-management/assign-access-card`,
|
|
@@ -243,6 +243,42 @@ export default function useAccessManagement() {
|
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
function generateQrVms(params: {
|
|
247
|
+
site: string;
|
|
248
|
+
unitId: string;
|
|
249
|
+
quantity: number;
|
|
250
|
+
}) {
|
|
251
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
252
|
+
`/api/access-management/generate-qr-vms`,
|
|
253
|
+
{
|
|
254
|
+
method: "POST",
|
|
255
|
+
body: {
|
|
256
|
+
site: params.site,
|
|
257
|
+
unitId: params.unitId,
|
|
258
|
+
quantity: params.quantity,
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function createVisitorPass(payload: {
|
|
265
|
+
site: string;
|
|
266
|
+
unitId: string;
|
|
267
|
+
quantity: number;
|
|
268
|
+
type: string;
|
|
269
|
+
nfcCards: { _id: string; cardNo: string }[];
|
|
270
|
+
acm_url: string;
|
|
271
|
+
visitorId: string;
|
|
272
|
+
}) {
|
|
273
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
274
|
+
`/api/access-management/visitor`,
|
|
275
|
+
{
|
|
276
|
+
method: "POST",
|
|
277
|
+
body: payload,
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
246
282
|
return {
|
|
247
283
|
getDoorAccessLevels,
|
|
248
284
|
getLiftAccessLevels,
|
|
@@ -261,5 +297,7 @@ export default function useAccessManagement() {
|
|
|
261
297
|
saveVisitorAccessCardQrTag,
|
|
262
298
|
getAllVisitorAccessCardsQrTags,
|
|
263
299
|
getAvailableContractorCards,
|
|
300
|
+
generateQrVms,
|
|
301
|
+
createVisitorPass,
|
|
264
302
|
};
|
|
265
303
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import type { APP_CONSTANTS } from "../constants/app";
|
|
2
|
+
|
|
1
3
|
export default function(){
|
|
2
4
|
|
|
3
|
-
const recipientList: { title: string, value:
|
|
4
|
-
// resident | security_agency | cleaning_services | mechanical_electrical | property_management_agency
|
|
5
|
+
const recipientList: { title: string, value: typeof APP_CONSTANTS[keyof typeof APP_CONSTANTS] }[] = [
|
|
5
6
|
{ title: "Security Agency", value: "security_agency" },
|
|
6
7
|
{ title: "Cleaning Services", value: "cleaning_services" },
|
|
7
8
|
{ title: "Mechanical & Electrical", value: "mechanical_electrical" },
|
|
8
9
|
{ title: "Property Management Agency", value: "property_management_agency" },
|
|
9
|
-
{ title: "Resident", value: "resident" }
|
|
10
|
+
{ title: "Resident", value: "resident" },
|
|
11
|
+
{ title: "Pest Control", value: "pest_control_services" },
|
|
12
|
+
{ title: "Landscaping", value: "landscaping_services" },
|
|
13
|
+
{ title: "Pool Maintenance", value: "pool_maintenance_services" },
|
|
14
|
+
|
|
10
15
|
]
|
|
11
16
|
|
|
12
17
|
async function add(payload: Partial<TCreateAnnouncementPayload>) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useCommonPermissions } from "./useCommonPermission";
|
|
2
|
+
|
|
3
|
+
export function useBulletinBoardPermission() {
|
|
4
|
+
const { hasPermission } = usePermission();
|
|
5
|
+
const { bulletinBoardPermissions: permissions } = useCommonPermissions();
|
|
6
|
+
|
|
7
|
+
const { userAppRole } = useLocalSetup();
|
|
8
|
+
|
|
9
|
+
const canViewBulletinBoard = computed(() => {
|
|
10
|
+
if (!userAppRole.value) return true;
|
|
11
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
12
|
+
return hasPermission(userAppRole.value, permissions, "bulletin-board", "see-all-bulletin-boards");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const canCreateBulletinBoard = computed(() => {
|
|
16
|
+
if (!userAppRole.value) return true;
|
|
17
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
18
|
+
return hasPermission(userAppRole.value, permissions, "bulletin-board", "add-bulletin-board");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const canUpdateBulletinBoard = computed(() => {
|
|
22
|
+
if (!userAppRole.value) return true;
|
|
23
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
24
|
+
return hasPermission(userAppRole.value, permissions, "bulletin-board", "update-bulletin-board");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const canViewBulletinBoardDetails = computed(() => {
|
|
28
|
+
if (!userAppRole.value) return true;
|
|
29
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
30
|
+
return hasPermission(userAppRole.value, permissions, "bulletin-board", "see-bulletin-board-details");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const canDeleteBulletinBoard = computed(() => {
|
|
36
|
+
if (!userAppRole.value) return true;
|
|
37
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
38
|
+
return hasPermission(userAppRole.value, permissions, "bulletin-board", "delete-bulletin-board");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
canViewBulletinBoard,
|
|
43
|
+
canCreateBulletinBoard,
|
|
44
|
+
canUpdateBulletinBoard,
|
|
45
|
+
canViewBulletinBoardDetails,
|
|
46
|
+
canDeleteBulletinBoard,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -5,6 +5,7 @@ export default function useCleaningPermission() {
|
|
|
5
5
|
feedbackPermissions,
|
|
6
6
|
workOrderPermissions,
|
|
7
7
|
invitationPermissions,
|
|
8
|
+
bulletinBoardPermissions,
|
|
8
9
|
} = useCommonPermissions();
|
|
9
10
|
const permissions: TPermissions = {
|
|
10
11
|
members: memberPermissions,
|
|
@@ -12,6 +13,7 @@ export default function useCleaningPermission() {
|
|
|
12
13
|
work_order: workOrderPermissions,
|
|
13
14
|
roles: rolePermissions,
|
|
14
15
|
invitations: invitationPermissions,
|
|
16
|
+
"bulletin-board": bulletinBoardPermissions,
|
|
15
17
|
inventory: {
|
|
16
18
|
"view-inventory": {
|
|
17
19
|
check: true,
|