@7365admin1/layer-common 1.10.9 → 1.10.10
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/BulletinBoardManagement.vue +18 -8
- package/components/DeliveryCompany.vue +240 -0
- package/components/EntryPassInformation.vue +35 -1
- package/components/FeedbackMain.vue +4 -19
- package/components/IncidentReport/Authorities.vue +189 -151
- package/components/IncidentReport/IncidentInformation.vue +14 -10
- package/components/IncidentReport/IncidentInformationDownload.vue +212 -0
- package/components/IncidentReport/affectedEntities.vue +8 -57
- package/components/SiteSettings.vue +285 -0
- package/components/Tooltip/Info.vue +33 -0
- package/components/VisitorForm.vue +48 -2
- package/components/VisitorManagement.vue +23 -6
- package/composables/useAccessManagement.ts +19 -0
- package/composables/useBulletin.ts +8 -3
- package/composables/useBulletinBoardPermission.ts +48 -0
- package/composables/useCleaningPermission.ts +2 -0
- package/composables/useCommonPermission.ts +29 -1
- package/composables/useFile.ts +6 -0
- package/composables/useSiteSettings.ts +1 -1
- package/composables/useVisitor.ts +6 -5
- package/constants/app.ts +12 -0
- package/nuxt.config.ts +2 -0
- package/package.json +2 -1
- package/plugins/vue-draggable-next.client.ts +5 -0
- package/types/site.d.ts +2 -1
|
@@ -1,58 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters class="px-5 pt-4">
|
|
3
|
-
<!-- Any unit affected -->
|
|
4
|
-
<v-col cols="12" class="border-b pb-5 mb-5 mt-1">
|
|
5
|
-
<p class="mb-2" style="font-size: 17px; font-weight: 600">
|
|
6
|
-
Any unit affected?
|
|
7
|
-
</p>
|
|
8
|
-
<p
|
|
9
|
-
v-if="affectedEntities?.anyUnitAffectedValue == 'no'"
|
|
10
|
-
class="mt-2 text-h6 text-capitalize"
|
|
11
|
-
>
|
|
12
|
-
{{ affectedEntities?.anyUnitAffectedValue }}
|
|
13
|
-
</p>
|
|
14
|
-
<!-- Block Level Unit Section -->
|
|
15
|
-
<v-row v-else no-gutters class="d-flex align-center mt-5">
|
|
16
|
-
<v-row
|
|
17
|
-
v-if="affectedEntities?.affectedUnit.hasOwnProperty('block')"
|
|
18
|
-
no-gutters
|
|
19
|
-
>
|
|
20
|
-
<v-col cols="12" sm="4">
|
|
21
|
-
<InputLabel class="text-capitalize" title="Block" />
|
|
22
|
-
<p class="my-1 text-h6">
|
|
23
|
-
{{ affectedEntities?.affectedUnit?.block }}
|
|
24
|
-
</p>
|
|
25
|
-
</v-col>
|
|
26
|
-
<v-col cols="12" sm="4">
|
|
27
|
-
<InputLabel class="text-capitalize" title="Level" />
|
|
28
|
-
<p class="my-1 text-h6">
|
|
29
|
-
{{ affectedEntities?.affectedUnit?.level }}
|
|
30
|
-
</p>
|
|
31
|
-
</v-col>
|
|
32
|
-
<v-col cols="12" sm="4">
|
|
33
|
-
<InputLabel class="text-capitalize" title="Unit" />
|
|
34
|
-
<p class="my-1 text-h6">
|
|
35
|
-
{{ affectedEntities?.affectedUnit?.unit }}
|
|
36
|
-
</p>
|
|
37
|
-
</v-col>
|
|
38
|
-
</v-row>
|
|
39
|
-
|
|
40
|
-
<v-col v-else cols="12" class="px-1 mb-0">
|
|
41
|
-
<InputLabel class="text-capitalize" title="Location" />
|
|
42
|
-
<p class="my-1 text-h6 text-capitalize">
|
|
43
|
-
{{ affectedEntities?.affectedUnit?.other }}
|
|
44
|
-
</p>
|
|
45
|
-
</v-col>
|
|
46
|
-
|
|
47
|
-
<v-col cols="12" class="px-1 mb-0 mt-5">
|
|
48
|
-
<InputLabel class="text-capitalize" title="Remarks" />
|
|
49
|
-
<p class="my-1 text-h6 text-capitalize">
|
|
50
|
-
{{ affectedEntities?.affectedUnit?.remarks }}
|
|
51
|
-
</p>
|
|
52
|
-
</v-col>
|
|
53
|
-
</v-row>
|
|
54
|
-
</v-col>
|
|
55
|
-
|
|
56
3
|
<!-- Anyone affected/injured -->
|
|
57
4
|
<v-col cols="12" class="border-b pb-5 mb-5 mt-1">
|
|
58
5
|
<p class="mb-2" style="font-size: 17px; font-weight: 600">
|
|
@@ -161,6 +108,14 @@ const injuredTableHeader = [
|
|
|
161
108
|
title: "Contact Number",
|
|
162
109
|
value: "contact",
|
|
163
110
|
},
|
|
111
|
+
{
|
|
112
|
+
title: "Block / Level / Unit Location",
|
|
113
|
+
value: "incidentLocation",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
title: "Remarks",
|
|
117
|
+
value: "remarks",
|
|
118
|
+
},
|
|
164
119
|
];
|
|
165
120
|
|
|
166
121
|
const damagePropertyTableHeader = [
|
|
@@ -180,10 +135,6 @@ const damagePropertyTableHeader = [
|
|
|
180
135
|
title: "Contact Number",
|
|
181
136
|
value: "contact",
|
|
182
137
|
},
|
|
183
|
-
{
|
|
184
|
-
title: "Action",
|
|
185
|
-
value: "action",
|
|
186
|
-
},
|
|
187
138
|
];
|
|
188
139
|
|
|
189
140
|
const isShowNRIC = ref(false);
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-expansion-panels multiple v-model="openPanels" >
|
|
4
|
+
|
|
5
|
+
<!-- SITE SETTINGS -->
|
|
6
|
+
<v-expansion-panel color="primary">
|
|
7
|
+
<v-expansion-panel-title>
|
|
8
|
+
<v-icon class="mr-2">mdi-cog</v-icon>
|
|
9
|
+
Site Settings
|
|
10
|
+
</v-expansion-panel-title>
|
|
11
|
+
|
|
12
|
+
<v-expansion-panel-text>
|
|
13
|
+
<v-row no-gutters class="d-flex justify-center">
|
|
14
|
+
<v-col cols="12">
|
|
15
|
+
<v-row no-gutters>
|
|
16
|
+
<v-col cols="12">
|
|
17
|
+
<span class="text-h4 font-weight-bold"> Site Settings </span>
|
|
18
|
+
</v-col>
|
|
19
|
+
|
|
20
|
+
<v-col cols="12">
|
|
21
|
+
<v-row no-gutters>
|
|
22
|
+
<v-col cols="12" lg="4" class="mt-2">
|
|
23
|
+
<NumberSettingField v-model="blocks" title="No. of blocks" type="blocks"
|
|
24
|
+
:read-only="!canManageSiteSettings"
|
|
25
|
+
:existing-block-number="existingBlockNumber" :site-id="siteId"
|
|
26
|
+
:disabled="existingBlockNumber === blocks" @success="refreshSiteData" />
|
|
27
|
+
</v-col>
|
|
28
|
+
</v-row>
|
|
29
|
+
</v-col>
|
|
30
|
+
|
|
31
|
+
<v-col cols="12">
|
|
32
|
+
<v-row no-gutters>
|
|
33
|
+
<v-col cols="12" lg="4" class="mt-2">
|
|
34
|
+
<NumberSettingField v-model="guardPosts" title="No. of guard posts"
|
|
35
|
+
type="guard_posts" :read-only="!canManageSiteSettings"
|
|
36
|
+
:existing-guard-posts-number="existingGuardPostNumber" :site-id="siteId"
|
|
37
|
+
:disabled="existingGuardPostNumber === guardPosts"
|
|
38
|
+
@success="refreshSiteData" />
|
|
39
|
+
</v-col>
|
|
40
|
+
</v-row>
|
|
41
|
+
</v-col>
|
|
42
|
+
|
|
43
|
+
<v-col cols="12">
|
|
44
|
+
<v-row no-gutters>
|
|
45
|
+
<v-col cols="12" lg="4" class="mt-2">
|
|
46
|
+
<v-row no-gutters>
|
|
47
|
+
<v-form v-model="gracePeriodValid">
|
|
48
|
+
<v-row>
|
|
49
|
+
<v-col cols="6">
|
|
50
|
+
<InputLabel class="text-capitalize font-weight-bold"
|
|
51
|
+
title="Grace Period" required />
|
|
52
|
+
<v-text-field v-model="gracePeriod" type="number"
|
|
53
|
+
density="comfortable" :readonly="!canManageSiteSettings"
|
|
54
|
+
:rules="[requiredRule]" />
|
|
55
|
+
</v-col>
|
|
56
|
+
|
|
57
|
+
<v-col cols="6">
|
|
58
|
+
<v-btn v-if="canManageSiteSettings" color="primary"
|
|
59
|
+
class="text-none mt-6" size="large" variant="flat"
|
|
60
|
+
:disabled="!gracePeriodValid ||
|
|
61
|
+
existingGracePeriodNumber === gracePeriod
|
|
62
|
+
" :loading="gracePeriodLoading" text="Save"
|
|
63
|
+
@click="handleSaveGracePeriod" />
|
|
64
|
+
</v-col>
|
|
65
|
+
</v-row>
|
|
66
|
+
</v-form>
|
|
67
|
+
</v-row>
|
|
68
|
+
</v-col>
|
|
69
|
+
</v-row>
|
|
70
|
+
</v-col>
|
|
71
|
+
|
|
72
|
+
</v-row>
|
|
73
|
+
</v-col>
|
|
74
|
+
</v-row>
|
|
75
|
+
</v-expansion-panel-text>
|
|
76
|
+
</v-expansion-panel>
|
|
77
|
+
|
|
78
|
+
<!-- ANPR CAMERA -->
|
|
79
|
+
<v-expansion-panel color="primary">
|
|
80
|
+
<v-expansion-panel-title>
|
|
81
|
+
<v-icon class="mr-2">mdi-camera</v-icon>
|
|
82
|
+
ANPR Camera
|
|
83
|
+
</v-expansion-panel-title>
|
|
84
|
+
|
|
85
|
+
<v-expansion-panel-text>
|
|
86
|
+
<CameraMain :site="siteId" :read-only="!canManageSiteSettings"
|
|
87
|
+
:guard-posts="siteData?.metadata?.guardPosts" />
|
|
88
|
+
</v-expansion-panel-text>
|
|
89
|
+
</v-expansion-panel>
|
|
90
|
+
|
|
91
|
+
<!-- CCTV CAMERA -->
|
|
92
|
+
<v-expansion-panel color="primary">
|
|
93
|
+
<v-expansion-panel-title>
|
|
94
|
+
<v-icon class="mr-2">mdi-cctv</v-icon>
|
|
95
|
+
CCTV Camera
|
|
96
|
+
</v-expansion-panel-title>
|
|
97
|
+
|
|
98
|
+
<v-expansion-panel-text>
|
|
99
|
+
<CameraMain :site="siteId" type="ip" :read-only="!canManageSiteSettings" />
|
|
100
|
+
</v-expansion-panel-text>
|
|
101
|
+
</v-expansion-panel>
|
|
102
|
+
|
|
103
|
+
<v-expansion-panel color="primary">
|
|
104
|
+
<v-expansion-panel-title>
|
|
105
|
+
<v-icon class="mr-2">mdi-format-list-numbered</v-icon>
|
|
106
|
+
Work Order Settings
|
|
107
|
+
</v-expansion-panel-title>
|
|
108
|
+
|
|
109
|
+
<v-expansion-panel-text>
|
|
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" />
|
|
115
|
+
|
|
116
|
+
<v-text-field v-model="orderNumberPreview" label="Preview" readonly />
|
|
117
|
+
|
|
118
|
+
<v-btn v-if="canManageSiteSettings" class="mt-2" :loading="isLoading" @click="save">
|
|
119
|
+
Save
|
|
120
|
+
</v-btn>
|
|
121
|
+
</v-expansion-panel-text>
|
|
122
|
+
</v-expansion-panel>
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
<v-expansion-panel color="primary">
|
|
126
|
+
<v-expansion-panel-title>
|
|
127
|
+
<v-icon class="mr-2">mdi-truck</v-icon>
|
|
128
|
+
Delivery Companies
|
|
129
|
+
</v-expansion-panel-title>
|
|
130
|
+
<v-expansion-panel-text class="">
|
|
131
|
+
<DeliveryCompany :site="siteId" v-model:initial="deliveryCompanies" @refresh-site="refreshSiteData" @update:companiesValue="handleUpdateCompanies"
|
|
132
|
+
:read-only="!canManageSiteSettings" />
|
|
133
|
+
</v-expansion-panel-text>
|
|
134
|
+
|
|
135
|
+
</v-expansion-panel>
|
|
136
|
+
|
|
137
|
+
</v-expansion-panels>
|
|
138
|
+
|
|
139
|
+
<Snackbar v-model="toast.show" :text="toast.message" :color="toast.color" />
|
|
140
|
+
</v-row>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<script setup lang="ts">
|
|
144
|
+
const props = defineProps({
|
|
145
|
+
siteId: { type: String, required: true },
|
|
146
|
+
canManageSiteSettings: { type: Boolean, default: false }
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const openPanels = ref([0]) // default open first
|
|
150
|
+
|
|
151
|
+
const { getSiteById, updateSitebyId } = useSiteSettings()
|
|
152
|
+
const { requiredRule } = useUtils()
|
|
153
|
+
const { getFileUrl } = useFile()
|
|
154
|
+
const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder()
|
|
155
|
+
|
|
156
|
+
const blocks = ref(0)
|
|
157
|
+
const guardPosts = ref(0)
|
|
158
|
+
const gracePeriod = ref(0)
|
|
159
|
+
|
|
160
|
+
const existingBlockNumber = ref(0)
|
|
161
|
+
const existingGuardPostNumber = ref(0)
|
|
162
|
+
const existingGracePeriodNumber = ref(0)
|
|
163
|
+
|
|
164
|
+
const gracePeriodValid = ref(false)
|
|
165
|
+
const gracePeriodLoading = ref(false)
|
|
166
|
+
const deliveryCompanies = ref<string[]>([])
|
|
167
|
+
|
|
168
|
+
const siteLogo = ref<any[]>([])
|
|
169
|
+
const uploadedSiteLogo = ref("")
|
|
170
|
+
|
|
171
|
+
const prefix = ref("")
|
|
172
|
+
const noOfDigits = ref(1)
|
|
173
|
+
const isLoading = ref(false)
|
|
174
|
+
|
|
175
|
+
const toast = reactive({
|
|
176
|
+
show: false,
|
|
177
|
+
message: "",
|
|
178
|
+
color: ""
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
|
|
182
|
+
`site-${props.siteId}`,
|
|
183
|
+
() => getSiteById(props.siteId)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
watch(siteData, (val: TSite) => {
|
|
187
|
+
if (!val) return
|
|
188
|
+
|
|
189
|
+
blocks.value = val.metadata?.block || 0
|
|
190
|
+
guardPosts.value = val.metadata?.guardPosts || 0
|
|
191
|
+
gracePeriod.value = val.metadata?.gracePeriod || 0
|
|
192
|
+
|
|
193
|
+
existingBlockNumber.value = blocks.value
|
|
194
|
+
existingGuardPostNumber.value = guardPosts.value
|
|
195
|
+
existingGracePeriodNumber.value = gracePeriod.value
|
|
196
|
+
deliveryCompanies.value = Array.isArray(val.deliveryCompanyList) ? [...val.deliveryCompanyList] : []
|
|
197
|
+
|
|
198
|
+
uploadedSiteLogo.value = val.metadata?.incidentLogo || ""
|
|
199
|
+
}, { immediate: true })
|
|
200
|
+
|
|
201
|
+
async function handleSaveGracePeriod() {
|
|
202
|
+
gracePeriodLoading.value = true
|
|
203
|
+
try {
|
|
204
|
+
await updateSitebyId(props.siteId, {
|
|
205
|
+
field: "metadata.gracePeriod",
|
|
206
|
+
value: gracePeriod.value
|
|
207
|
+
})
|
|
208
|
+
show("Grace period updated", "success")
|
|
209
|
+
refreshSiteData()
|
|
210
|
+
} catch (e: any) {
|
|
211
|
+
show(e?.data?.message || "Error updating grace period", "error")
|
|
212
|
+
} finally {
|
|
213
|
+
gracePeriodLoading.value = false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function handleLogo(action: string) {
|
|
218
|
+
try {
|
|
219
|
+
await updateSitebyId(props.siteId, {
|
|
220
|
+
field: "metadata.incidentLogo",
|
|
221
|
+
value: action === "add" ? siteLogo.value[0] : 0
|
|
222
|
+
})
|
|
223
|
+
show("Logo updated", "success")
|
|
224
|
+
refreshSiteData()
|
|
225
|
+
} catch {
|
|
226
|
+
show("Error updating logo", "error")
|
|
227
|
+
} finally {
|
|
228
|
+
siteLogo.value = []
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const onUploadedLogoPreview = () => {
|
|
233
|
+
if (!uploadedSiteLogo.value) return ""
|
|
234
|
+
return getFileUrl(uploadedSiteLogo.value)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const { data: workOrderSetting } = await useLazyAsyncData(
|
|
238
|
+
`work-${props.siteId}`,
|
|
239
|
+
() => getWorkOrderSettings({ site: props.siteId, service: "Security" })
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
watchEffect(() => {
|
|
243
|
+
if (workOrderSetting.value) {
|
|
244
|
+
prefix.value = workOrderSetting.value.prefix
|
|
245
|
+
noOfDigits.value = workOrderSetting.value.noOfDigits
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
function handleInput(e: any) {
|
|
250
|
+
prefix.value = e.target.value
|
|
251
|
+
.replace(/[^A-Z]/g, "")
|
|
252
|
+
.toUpperCase()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function handleUpdateCompanies(value: string[]) {
|
|
256
|
+
deliveryCompanies.value = value
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const orderNumberPreview = computed(() => {
|
|
260
|
+
return `${prefix.value}${"0".repeat(noOfDigits.value - 1)}1`
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
async function save() {
|
|
264
|
+
try {
|
|
265
|
+
isLoading.value = true
|
|
266
|
+
await createWorkOrderSettings({
|
|
267
|
+
site: props.siteId,
|
|
268
|
+
service: "Security",
|
|
269
|
+
prefix: prefix.value,
|
|
270
|
+
noOfDigits: noOfDigits.value
|
|
271
|
+
})
|
|
272
|
+
show("Saved successfully", "success")
|
|
273
|
+
} catch {
|
|
274
|
+
show("Error saving", "error")
|
|
275
|
+
} finally {
|
|
276
|
+
isLoading.value = false
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function show(message: string, color: string) {
|
|
281
|
+
toast.show = true
|
|
282
|
+
toast.message = message
|
|
283
|
+
toast.color = color
|
|
284
|
+
}
|
|
285
|
+
</script>
|
|
@@ -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>
|
|
@@ -356,13 +386,17 @@ const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
|
356
386
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
357
387
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
358
388
|
|
|
389
|
+
const deliveryCompany = ref("");
|
|
390
|
+
const deliveryCompanyInput = ref("");
|
|
391
|
+
const deliveryCompanyList = ref<string[]>([]);
|
|
392
|
+
|
|
359
393
|
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
|
|
360
394
|
|
|
361
395
|
|
|
362
396
|
const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
363
397
|
|
|
364
398
|
|
|
365
|
-
const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
|
|
399
|
+
const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') => {
|
|
366
400
|
if (prop.type !== "contractor" || contractorStep.value === 1) {
|
|
367
401
|
const visibleFields = typeFieldMap[prop.type];
|
|
368
402
|
return visibleFields?.includes(fieldKey);
|
|
@@ -634,6 +668,7 @@ const {
|
|
|
634
668
|
data: siteData,
|
|
635
669
|
refresh: refreshSiteData,
|
|
636
670
|
status: blockStatus,
|
|
671
|
+
pending: siteDataPending,
|
|
637
672
|
} = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
|
|
638
673
|
getSiteById(prop.site)
|
|
639
674
|
);
|
|
@@ -665,7 +700,7 @@ const {
|
|
|
665
700
|
watch(
|
|
666
701
|
siteData,
|
|
667
702
|
(newVal) => {
|
|
668
|
-
const siteDataValue = newVal as
|
|
703
|
+
const siteDataValue = newVal as TSite;
|
|
669
704
|
if (siteDataValue) {
|
|
670
705
|
const numberOfBlocks = siteDataValue.metadata?.block || 0;
|
|
671
706
|
for (let i = 1; i <= numberOfBlocks; i++) {
|
|
@@ -674,6 +709,9 @@ watch(
|
|
|
674
709
|
value: i,
|
|
675
710
|
});
|
|
676
711
|
}
|
|
712
|
+
|
|
713
|
+
deliveryCompanyList.value = Array.isArray(siteDataValue?.deliveryCompanyList) ? siteDataValue.deliveryCompanyList : [];
|
|
714
|
+
|
|
677
715
|
} else {
|
|
678
716
|
blocksArray.value = [];
|
|
679
717
|
}
|
|
@@ -857,6 +895,14 @@ async function submit() {
|
|
|
857
895
|
members: visitor.members,
|
|
858
896
|
};
|
|
859
897
|
}
|
|
898
|
+
|
|
899
|
+
if(prop.type === "delivery"){
|
|
900
|
+
payload = {
|
|
901
|
+
...payload,
|
|
902
|
+
company: deliveryCompany.value
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
860
906
|
try {
|
|
861
907
|
const res = await createVisitor(payload);
|
|
862
908
|
if (res) {
|
|
@@ -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 {
|
|
@@ -243,6 +243,24 @@ 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
|
+
|
|
246
264
|
return {
|
|
247
265
|
getDoorAccessLevels,
|
|
248
266
|
getLiftAccessLevels,
|
|
@@ -261,5 +279,6 @@ export default function useAccessManagement() {
|
|
|
261
279
|
saveVisitorAccessCardQrTag,
|
|
262
280
|
getAllVisitorAccessCardsQrTags,
|
|
263
281
|
getAvailableContractorCards,
|
|
282
|
+
generateQrVms,
|
|
264
283
|
};
|
|
265
284
|
}
|
|
@@ -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>) {
|