@7365admin1/layer-common 1.10.8 → 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 +12 -0
- package/components/AccessCardAddForm.vue +1 -1
- package/components/AccessCardAssignToUnitForm.vue +1 -1
- package/components/AccessManagement.vue +1 -1
- package/components/BulletinBoardManagement.vue +18 -8
- package/components/Carousel.vue +474 -0
- package/components/DeliveryCompany.vue +240 -0
- package/components/DrawImage.vue +172 -0
- package/components/EntryPassInformation.vue +70 -10
- package/components/EquipmentItemMain.vue +9 -4
- package/components/Feedback/Form.vue +4 -4
- package/components/FeedbackMain.vue +734 -146
- package/components/FileInput.vue +289 -0
- 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/StockCard.vue +11 -7
- package/components/Tooltip/Info.vue +33 -0
- package/components/VisitorForm.vue +176 -45
- package/components/VisitorManagement.vue +23 -6
- package/composables/useAccessManagement.ts +60 -18
- 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/useEquipmentManagement.ts +63 -0
- package/composables/useFeedback.ts +53 -21
- package/composables/useFile.ts +6 -0
- package/composables/useLocalAuth.ts +29 -1
- package/composables/useSiteSettings.ts +1 -1
- package/composables/useUploadFiles.ts +94 -0
- package/composables/useUtils.ts +152 -53
- package/composables/useVisitor.ts +9 -6
- 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/types/feedback.d.ts +5 -2
- package/types/site.d.ts +2 -1
- package/types/user.d.ts +1 -0
|
@@ -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>
|
|
@@ -144,7 +174,8 @@
|
|
|
144
174
|
|
|
145
175
|
<v-col v-if="shouldShowField('unit')" cols="12">
|
|
146
176
|
<InputLabel class="text-capitalize" title="Registered Unit Company Name" required />
|
|
147
|
-
<v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable"
|
|
177
|
+
<v-text-field v-model.trim="registeredUnitCompanyName" density="comfortable"
|
|
178
|
+
:loading="buildingUnitDataPending" readonly class="no-pointer" />
|
|
148
179
|
</v-col>
|
|
149
180
|
|
|
150
181
|
<v-col v-if="shouldShowField('remarks')" cols="12">
|
|
@@ -154,10 +185,16 @@
|
|
|
154
185
|
|
|
155
186
|
<v-col v-if="prop.type === 'contractor' && contractorStep === 2" cols="12">
|
|
156
187
|
<PassInformation />
|
|
157
|
-
<EntryPassInformation
|
|
158
|
-
v-
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
<EntryPassInformation
|
|
189
|
+
v-if="entryPassSettings?.data?.settings?.nfcPass"
|
|
190
|
+
v-model="passType"
|
|
191
|
+
v-model:quantity="passQuantity"
|
|
192
|
+
v-model:cards="passCards"
|
|
193
|
+
:settings="entryPassSettings"
|
|
194
|
+
:loading="entryPassSettingsPending"
|
|
195
|
+
:site-id="prop.site"
|
|
196
|
+
:unit-id="visitor.unit || null"
|
|
197
|
+
/>
|
|
161
198
|
</v-col>
|
|
162
199
|
|
|
163
200
|
<v-col v-if="prop.type === 'contractor' && contractorStep === 3" cols="12">
|
|
@@ -190,8 +227,8 @@
|
|
|
190
227
|
prop.type === 'contractor' &&
|
|
191
228
|
contractorStep > 1
|
|
192
229
|
" tile block variant="text" class="text-none" size="48" @click="handleGoToPreviousPage" text="Back" />
|
|
193
|
-
<v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none"
|
|
194
|
-
@click="backToSelection" text="Back to Selection" />
|
|
230
|
+
<v-btn v-else-if="prop.mode === 'add' || prop.mode === 'register'" tile block variant="text" class="text-none"
|
|
231
|
+
size="48" @click="backToSelection" text="Back to Selection" />
|
|
195
232
|
<v-btn v-else tile block variant="text" class="text-none" size="48" @click="emit('close:all')" text="Close" />
|
|
196
233
|
</v-col>
|
|
197
234
|
<v-col cols="6">
|
|
@@ -209,6 +246,44 @@
|
|
|
209
246
|
:vehicle-number-users-list="vehicleNumberUserItems" @close="dialog.vehicleNumberUsersList = false"
|
|
210
247
|
@update:people="handleAutofillDataViaVehicleNumber" />
|
|
211
248
|
</v-dialog>
|
|
249
|
+
|
|
250
|
+
<v-dialog v-model="dialog.showNonCheckedOutDialog" max-width="700" persistent>
|
|
251
|
+
<v-card :loading="loading.checkingOut">
|
|
252
|
+
<v-toolbar>
|
|
253
|
+
<v-toolbar-title>
|
|
254
|
+
<v-row no-gutters class="d-flex align-center justify-space-between">
|
|
255
|
+
<span class="font-weight-bold">
|
|
256
|
+
You have an unchecked-out vehicle for: {{ visitor.plateNumber }}
|
|
257
|
+
</span>
|
|
258
|
+
</v-row>
|
|
259
|
+
</v-toolbar-title>
|
|
260
|
+
</v-toolbar>
|
|
261
|
+
|
|
262
|
+
<v-card-text>
|
|
263
|
+
|
|
264
|
+
<v-list lines="three">
|
|
265
|
+
<v-list-item v-if="matchingPlateNumberNonCheckedOutArr.length > 0"
|
|
266
|
+
v-for="v in matchingPlateNumberNonCheckedOutArr" :key="v._id" class="cursor-pointer">
|
|
267
|
+
<v-list-item-title>
|
|
268
|
+
{{ v.plateNumber }} - {{ v.name }}
|
|
269
|
+
</v-list-item-title>
|
|
270
|
+
|
|
271
|
+
<v-list-item-subtitle>
|
|
272
|
+
Checked In at : {{ UTCToLocalTIme(v.checkIn) }}
|
|
273
|
+
</v-list-item-subtitle>
|
|
274
|
+
|
|
275
|
+
<template #append>
|
|
276
|
+
<v-btn size="x-small" class="text-capitalize" color="red" text="Checkout"
|
|
277
|
+
:loading="loading.checkingOut && v?._id === prop.visitorData?._id"
|
|
278
|
+
@click.stop="handleCheckout(v._id)" />
|
|
279
|
+
</template>
|
|
280
|
+
|
|
281
|
+
</v-list-item>
|
|
282
|
+
</v-list>
|
|
283
|
+
|
|
284
|
+
</v-card-text>
|
|
285
|
+
</v-card>
|
|
286
|
+
</v-dialog>
|
|
212
287
|
</v-card>
|
|
213
288
|
</template>
|
|
214
289
|
|
|
@@ -243,12 +318,12 @@ type AutofillSource = "nric" | "contact" | "vehicleNumber" | null;
|
|
|
243
318
|
const currentAutofillSource = ref<AutofillSource>(null);
|
|
244
319
|
|
|
245
320
|
|
|
246
|
-
const { requiredRule, debounce } = useUtils();
|
|
321
|
+
const { requiredRule, debounce, UTCToLocalTIme } = useUtils();
|
|
247
322
|
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
248
|
-
const { createVisitor, typeFieldMap, contractorTypes } = useVisitor();
|
|
323
|
+
const { createVisitor, typeFieldMap, contractorTypes, getVisitors, updateVisitor } = useVisitor();
|
|
249
324
|
const { getBySiteId: getEntryPassSettingsBySiteId } = useSiteEntryPassSettings();
|
|
250
325
|
const { findPersonByNRIC, findPersonByContact, searchCompanyList, findUsersByPlateNumber } = usePeople()
|
|
251
|
-
const { getById: getUnitDataById} = useBuildingUnit()
|
|
326
|
+
const { getById: getUnitDataById } = useBuildingUnit()
|
|
252
327
|
|
|
253
328
|
const emit = defineEmits([
|
|
254
329
|
"back",
|
|
@@ -278,15 +353,20 @@ const visitor = reactive<Partial<TVisitorPayload>>({
|
|
|
278
353
|
});
|
|
279
354
|
|
|
280
355
|
const passType = ref("");
|
|
281
|
-
const passQuantity = ref<number | null>(
|
|
356
|
+
const passQuantity = ref<number | null>(1);
|
|
282
357
|
const passCards = ref<string[]>([]);
|
|
283
358
|
|
|
284
359
|
const registeredUnitCompanyName = ref('N/A')
|
|
285
360
|
|
|
286
361
|
const dialog = reactive({
|
|
287
362
|
vehicleNumberUsersList: false,
|
|
363
|
+
showNonCheckedOutDialog: false,
|
|
288
364
|
});
|
|
289
365
|
|
|
366
|
+
const loading = reactive({
|
|
367
|
+
checkingOut: false,
|
|
368
|
+
})
|
|
369
|
+
|
|
290
370
|
const validForm = ref(false);
|
|
291
371
|
const formRef = ref<HTMLFormElement | null>(null);
|
|
292
372
|
const processing = ref(false);
|
|
@@ -306,11 +386,17 @@ const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
|
306
386
|
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
307
387
|
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
308
388
|
|
|
389
|
+
const deliveryCompany = ref("");
|
|
390
|
+
const deliveryCompanyInput = ref("");
|
|
391
|
+
const deliveryCompanyList = ref<string[]>([]);
|
|
392
|
+
|
|
393
|
+
const matchingPlateNumberNonCheckedOutArr = ref<TVisitor[]>([])
|
|
394
|
+
|
|
309
395
|
|
|
310
396
|
const vehicleNumberUserItems = ref<TPeople[]>([])
|
|
311
397
|
|
|
312
398
|
|
|
313
|
-
const shouldShowField = (fieldKey: keyof TVisitorPayload) => {
|
|
399
|
+
const shouldShowField = (fieldKey: keyof TVisitorPayload | 'delivery-company') => {
|
|
314
400
|
if (prop.type !== "contractor" || contractorStep.value === 1) {
|
|
315
401
|
const visibleFields = typeFieldMap[prop.type];
|
|
316
402
|
return visibleFields?.includes(fieldKey);
|
|
@@ -466,25 +552,70 @@ watch(fetchCompanyListReq, (arr) => {
|
|
|
466
552
|
}
|
|
467
553
|
})
|
|
468
554
|
const {
|
|
469
|
-
data:
|
|
470
|
-
refresh:
|
|
471
|
-
pending:
|
|
555
|
+
data: fetchVisitorListByVehicleNumberReq,
|
|
556
|
+
refresh: fetchVisitorListByVehicleNumberRefresh,
|
|
557
|
+
pending: fetchVisitorListByVehicleNumberPending,
|
|
472
558
|
|
|
473
|
-
} = useLazyAsyncData(`fetch-vehicle-number
|
|
559
|
+
} = useLazyAsyncData(`fetch-visitor-list-by-vehicle-number`, () => {
|
|
474
560
|
if (!visitor.plateNumber) return Promise.resolve(null)
|
|
475
|
-
return
|
|
561
|
+
return getVisitors({ page: 1, limit: 20, site: prop.site, plateNumber: visitor.plateNumber, checkedOut: false, status: "registered" })
|
|
476
562
|
})
|
|
477
563
|
|
|
478
|
-
watch(
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
564
|
+
watch(fetchVisitorListByVehicleNumberReq, (arr: any) => {
|
|
565
|
+
const itemsArray = arr?.items || []
|
|
566
|
+
const isValidArray = Array.isArray(itemsArray)
|
|
567
|
+
matchingPlateNumberNonCheckedOutArr.value = isValidArray ? itemsArray : []
|
|
568
|
+
if(matchingPlateNumberNonCheckedOutArr.value.length > 0) {
|
|
569
|
+
dialog.showNonCheckedOutDialog = true;
|
|
570
|
+
} else {
|
|
571
|
+
dialog.showNonCheckedOutDialog = false;
|
|
485
572
|
}
|
|
486
573
|
})
|
|
487
574
|
|
|
575
|
+
|
|
576
|
+
const debounceSearchVisitorsByPlateNumbers = debounce(() => {
|
|
577
|
+
if (!visitor.plateNumber) {
|
|
578
|
+
matchingPlateNumberNonCheckedOutArr.value = []
|
|
579
|
+
dialog.showNonCheckedOutDialog = false;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
fetchVisitorListByVehicleNumberRefresh();
|
|
583
|
+
}, 300);
|
|
584
|
+
|
|
585
|
+
watch(() => visitor.plateNumber, (newVal) => {
|
|
586
|
+
debounceSearchVisitorsByPlateNumbers();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
async function handleCheckout(visitorId: string) {
|
|
592
|
+
if (!visitorId) {
|
|
593
|
+
errorMessage.value = "Invalid visitor ID. Cannot proceed with checkout.";
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
loading.checkingOut = true;
|
|
599
|
+
const res = await updateVisitor(visitorId as string, {
|
|
600
|
+
checkOut: new Date().toISOString(),
|
|
601
|
+
});
|
|
602
|
+
if (res) {
|
|
603
|
+
await fetchVisitorListByVehicleNumberRefresh();
|
|
604
|
+
|
|
605
|
+
}
|
|
606
|
+
} catch (error: any) {
|
|
607
|
+
const errorMessage = error?.response?._data?.message;
|
|
608
|
+
console.log("[ERROR]", error);
|
|
609
|
+
errorMessage.value =
|
|
610
|
+
errorMessage || "An error occurred while checking out the vehicle.";
|
|
611
|
+
} finally {
|
|
612
|
+
loading.checkingOut = false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
|
|
488
619
|
const debounceFetchCompany = debounce(async () => fetchCompanyListRefresh(), 200)
|
|
489
620
|
|
|
490
621
|
watch(companyNameInput, async (val) => {
|
|
@@ -496,19 +627,7 @@ watch(companyNameInput, async (val) => {
|
|
|
496
627
|
})
|
|
497
628
|
|
|
498
629
|
|
|
499
|
-
const debounceSearchVehicleUsers = debounce(() => {
|
|
500
|
-
if (!visitor.plateNumber) {
|
|
501
|
-
vehicleNumberUserItems.value = [];
|
|
502
|
-
dialog.vehicleNumberUsersList = false;
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
fetchVehicleNumberUserRefresh();
|
|
506
|
-
}, 300);
|
|
507
630
|
|
|
508
|
-
watch(() => visitor.plateNumber, (newVal) => {
|
|
509
|
-
if (currentAutofillSource.value && currentAutofillSource.value !== "vehicleNumber") return;
|
|
510
|
-
debounceSearchVehicleUsers();
|
|
511
|
-
});
|
|
512
631
|
|
|
513
632
|
|
|
514
633
|
function handleAutofillDataViaVehicleNumber(item: TPeople) {
|
|
@@ -549,6 +668,7 @@ const {
|
|
|
549
668
|
data: siteData,
|
|
550
669
|
refresh: refreshSiteData,
|
|
551
670
|
status: blockStatus,
|
|
671
|
+
pending: siteDataPending,
|
|
552
672
|
} = useLazyAsyncData(`fetch-site-data-${prop.site}`, () =>
|
|
553
673
|
getSiteById(prop.site)
|
|
554
674
|
);
|
|
@@ -580,7 +700,7 @@ const {
|
|
|
580
700
|
watch(
|
|
581
701
|
siteData,
|
|
582
702
|
(newVal) => {
|
|
583
|
-
const siteDataValue = newVal as
|
|
703
|
+
const siteDataValue = newVal as TSite;
|
|
584
704
|
if (siteDataValue) {
|
|
585
705
|
const numberOfBlocks = siteDataValue.metadata?.block || 0;
|
|
586
706
|
for (let i = 1; i <= numberOfBlocks; i++) {
|
|
@@ -589,6 +709,9 @@ watch(
|
|
|
589
709
|
value: i,
|
|
590
710
|
});
|
|
591
711
|
}
|
|
712
|
+
|
|
713
|
+
deliveryCompanyList.value = Array.isArray(siteDataValue?.deliveryCompanyList) ? siteDataValue.deliveryCompanyList : [];
|
|
714
|
+
|
|
592
715
|
} else {
|
|
593
716
|
blocksArray.value = [];
|
|
594
717
|
}
|
|
@@ -766,12 +889,20 @@ async function submit() {
|
|
|
766
889
|
|
|
767
890
|
|
|
768
891
|
if (prop.type === "contractor") {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
892
|
+
// contractor type logic payload
|
|
893
|
+
payload = {
|
|
894
|
+
...payload,
|
|
895
|
+
members: visitor.members,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if(prop.type === "delivery"){
|
|
900
|
+
payload = {
|
|
901
|
+
...payload,
|
|
902
|
+
company: deliveryCompany.value
|
|
774
903
|
}
|
|
904
|
+
}
|
|
905
|
+
|
|
775
906
|
try {
|
|
776
907
|
const res = await createVisitor(payload);
|
|
777
908
|
if (res) {
|
|
@@ -801,9 +932,9 @@ onMounted(() => {
|
|
|
801
932
|
contractorStep.value = 1;
|
|
802
933
|
currentAutofillSource.value = null;
|
|
803
934
|
|
|
804
|
-
if(prop.mode === 'register' && prop.visitorData) {
|
|
805
|
-
console.log('Register mode, prefill visitor data', prop.visitorData?.plateNumber
|
|
806
|
-
|
|
935
|
+
if (prop.mode === 'register' && prop.visitorData) {
|
|
936
|
+
console.log('Register mode, prefill visitor data', prop.visitorData?.plateNumber)
|
|
937
|
+
visitor.plateNumber = prop.visitorData?.plateNumber || ""
|
|
807
938
|
}
|
|
808
939
|
});
|
|
809
940
|
</script>
|
|
@@ -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 {
|
|
@@ -55,10 +55,7 @@ export default function useAccessManagement() {
|
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function getAllAccessCardsCounts(params: {
|
|
59
|
-
site: string;
|
|
60
|
-
userType: string;
|
|
61
|
-
}) {
|
|
58
|
+
function getAllAccessCardsCounts(params: { site: string; userType: string }) {
|
|
62
59
|
return useNuxtApp().$api<Record<string, any>>(
|
|
63
60
|
`/api/access-management/all-access-cards-counts`,
|
|
64
61
|
{
|
|
@@ -71,14 +68,16 @@ export default function useAccessManagement() {
|
|
|
71
68
|
);
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
function getUserTypeAccessCards(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
function getUserTypeAccessCards(
|
|
72
|
+
params: {
|
|
73
|
+
page?: number;
|
|
74
|
+
limit?: number;
|
|
75
|
+
search?: string;
|
|
76
|
+
organization?: string;
|
|
77
|
+
userType?: string;
|
|
78
|
+
site?: string;
|
|
79
|
+
} = {}
|
|
80
|
+
) {
|
|
82
81
|
return useNuxtApp().$api<Record<string, any>>(
|
|
83
82
|
`/api/access-management/user-type-access-cards`,
|
|
84
83
|
{
|
|
@@ -200,12 +199,14 @@ export default function useAccessManagement() {
|
|
|
200
199
|
);
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
function getVisitorAccessCards(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
function getVisitorAccessCards(
|
|
203
|
+
params: {
|
|
204
|
+
page?: number;
|
|
205
|
+
limit?: number;
|
|
206
|
+
site?: string;
|
|
207
|
+
search?: string;
|
|
208
|
+
} = {}
|
|
209
|
+
) {
|
|
209
210
|
return useNuxtApp().$api<Record<string, any>>(
|
|
210
211
|
`/api/access-management/visitor-access-cards`,
|
|
211
212
|
{
|
|
@@ -221,6 +222,45 @@ export default function useAccessManagement() {
|
|
|
221
222
|
);
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
function getAvailableContractorCards(params: {
|
|
226
|
+
type: "NFC" | "QRCODE";
|
|
227
|
+
siteId: string;
|
|
228
|
+
unitId: string;
|
|
229
|
+
page?: number;
|
|
230
|
+
limit?: number;
|
|
231
|
+
}) {
|
|
232
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
233
|
+
`/api/access-management/available-card-contractor/${params.type}`,
|
|
234
|
+
{
|
|
235
|
+
method: "GET",
|
|
236
|
+
query: {
|
|
237
|
+
siteId: params.siteId,
|
|
238
|
+
unitId: params.unitId,
|
|
239
|
+
page: params.page ?? 1,
|
|
240
|
+
limit: params.limit ?? 100,
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
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
|
+
|
|
224
264
|
return {
|
|
225
265
|
getDoorAccessLevels,
|
|
226
266
|
getLiftAccessLevels,
|
|
@@ -238,5 +278,7 @@ export default function useAccessManagement() {
|
|
|
238
278
|
getVisitorAccessCards,
|
|
239
279
|
saveVisitorAccessCardQrTag,
|
|
240
280
|
getAllVisitorAccessCardsQrTags,
|
|
281
|
+
getAvailableContractorCards,
|
|
282
|
+
generateQrVms,
|
|
241
283
|
};
|
|
242
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>) {
|
|
@@ -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,
|