@7365admin1/layer-common 3.1.3 → 3.1.4-staging.47
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/.github/workflows/publish-staging.yml +11 -0
- package/components/AttachmentsViewer.vue +22 -4
- package/components/DashboardMain.vue +228 -87
- package/components/ImageCarousel.vue +23 -5
- package/components/ManageChecklistMain.vue +32 -25
- package/components/MemberMain.vue +17 -15
- package/components/Nfc/NFCPatrolRouteMain.vue +52 -7
- package/components/PhotoUpload.vue +19 -4
- package/components/RolePermissionFormPreviewUpdate.vue +1 -1
- package/components/ServiceProviderMain.vue +43 -26
- package/components/VisitorForm.vue +78 -18
- package/components/VisitorManagement.vue +39 -16
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/composables/useServiceProvider.ts +21 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<v-row no-gutters class="mb-4" align="center">
|
|
5
5
|
<v-col cols="12" md="6">
|
|
6
6
|
<v-btn
|
|
7
|
+
v-if="props.canCreateNfcPatrol"
|
|
7
8
|
rounded="pill"
|
|
8
9
|
variant="outlined"
|
|
9
10
|
|
|
@@ -82,7 +83,12 @@
|
|
|
82
83
|
</v-col>
|
|
83
84
|
|
|
84
85
|
<!-- Add Dialog -->
|
|
85
|
-
<v-dialog
|
|
86
|
+
<v-dialog
|
|
87
|
+
v-if="props.canCreateNfcPatrol"
|
|
88
|
+
v-model="dialogAdd"
|
|
89
|
+
persistent
|
|
90
|
+
max-width="900"
|
|
91
|
+
>
|
|
86
92
|
<NFCPatrolRouteForm
|
|
87
93
|
title="Add Route"
|
|
88
94
|
:site="props.site"
|
|
@@ -94,7 +100,12 @@
|
|
|
94
100
|
</v-dialog>
|
|
95
101
|
|
|
96
102
|
<!-- Edit Dialog -->
|
|
97
|
-
<v-dialog
|
|
103
|
+
<v-dialog
|
|
104
|
+
v-if="props.canUpdateNfcPatrol"
|
|
105
|
+
v-model="dialogEdit"
|
|
106
|
+
persistent
|
|
107
|
+
max-width="900"
|
|
108
|
+
>
|
|
98
109
|
<NFCPatrolRouteForm
|
|
99
110
|
title="Edit Route"
|
|
100
111
|
:site="props.site"
|
|
@@ -190,7 +201,10 @@
|
|
|
190
201
|
|
|
191
202
|
<v-toolbar class="pa-0" density="compact">
|
|
192
203
|
<v-row no-gutters>
|
|
193
|
-
<v-col
|
|
204
|
+
<v-col
|
|
205
|
+
:cols="props.canUpdateNfcPatrol || props.canDeleteNfcPatrol ? 6 : 12"
|
|
206
|
+
class="pa-0"
|
|
207
|
+
>
|
|
194
208
|
<v-btn
|
|
195
209
|
block
|
|
196
210
|
variant="text"
|
|
@@ -203,7 +217,11 @@
|
|
|
203
217
|
</v-btn>
|
|
204
218
|
</v-col>
|
|
205
219
|
|
|
206
|
-
<v-col
|
|
220
|
+
<v-col
|
|
221
|
+
v-if="props.canUpdateNfcPatrol || props.canDeleteNfcPatrol"
|
|
222
|
+
cols="6"
|
|
223
|
+
class="pa-0"
|
|
224
|
+
>
|
|
207
225
|
<v-menu>
|
|
208
226
|
<template #activator="{ props }">
|
|
209
227
|
<v-btn
|
|
@@ -220,13 +238,17 @@
|
|
|
220
238
|
</template>
|
|
221
239
|
|
|
222
240
|
<v-list class="pa-0">
|
|
223
|
-
<v-list-item @click="openDialogEdit()">
|
|
241
|
+
<v-list-item v-if="props.canUpdateNfcPatrol" @click="openDialogEdit()">
|
|
224
242
|
<v-list-item-title class="text-subtitle-2">
|
|
225
243
|
Edit Route
|
|
226
244
|
</v-list-item-title>
|
|
227
245
|
</v-list-item>
|
|
228
246
|
|
|
229
|
-
<v-list-item
|
|
247
|
+
<v-list-item
|
|
248
|
+
v-if="props.canDeleteNfcPatrol"
|
|
249
|
+
@click="openDialogDelete()"
|
|
250
|
+
class="text-red"
|
|
251
|
+
>
|
|
230
252
|
<v-list-item-title class="text-subtitle-2">
|
|
231
253
|
Delete Route
|
|
232
254
|
</v-list-item-title>
|
|
@@ -240,7 +262,12 @@
|
|
|
240
262
|
</v-dialog>
|
|
241
263
|
|
|
242
264
|
<!-- Delete Dialog -->
|
|
243
|
-
<v-dialog
|
|
265
|
+
<v-dialog
|
|
266
|
+
v-if="props.canDeleteNfcPatrol"
|
|
267
|
+
v-model="dialogDelete"
|
|
268
|
+
persistent
|
|
269
|
+
width="540"
|
|
270
|
+
>
|
|
244
271
|
<v-card width="100%">
|
|
245
272
|
<v-card-text
|
|
246
273
|
style="max-height: 100vh; overflow-y: auto"
|
|
@@ -304,6 +331,18 @@ const props = defineProps({
|
|
|
304
331
|
type: String,
|
|
305
332
|
required: true,
|
|
306
333
|
},
|
|
334
|
+
canCreateNfcPatrol: {
|
|
335
|
+
type: Boolean,
|
|
336
|
+
default: false,
|
|
337
|
+
},
|
|
338
|
+
canUpdateNfcPatrol: {
|
|
339
|
+
type: Boolean,
|
|
340
|
+
default: false,
|
|
341
|
+
},
|
|
342
|
+
canDeleteNfcPatrol: {
|
|
343
|
+
type: Boolean,
|
|
344
|
+
default: false,
|
|
345
|
+
},
|
|
307
346
|
headers: {
|
|
308
347
|
type: Array as PropType<Array<any>>,
|
|
309
348
|
default: () => [
|
|
@@ -406,6 +445,8 @@ const messageSnackbar = ref(false);
|
|
|
406
445
|
const messageColor = ref("");
|
|
407
446
|
|
|
408
447
|
async function openDialogEdit() {
|
|
448
|
+
if (!props.canUpdateNfcPatrol) return;
|
|
449
|
+
|
|
409
450
|
try {
|
|
410
451
|
// Fetch the full route details from the API
|
|
411
452
|
const response = await getRouteById(selectedRoute.value._id);
|
|
@@ -431,6 +472,8 @@ async function openDialogEdit() {
|
|
|
431
472
|
}
|
|
432
473
|
|
|
433
474
|
function openDialogDelete() {
|
|
475
|
+
if (!props.canDeleteNfcPatrol) return;
|
|
476
|
+
|
|
434
477
|
dialogDelete.value = true;
|
|
435
478
|
|
|
436
479
|
if (dialogPreview.value) dialogPreview.value = false;
|
|
@@ -502,6 +545,8 @@ function handleRowClick(_: any, data: any) {
|
|
|
502
545
|
}
|
|
503
546
|
|
|
504
547
|
async function submitDelete() {
|
|
548
|
+
if (!props.canDeleteNfcPatrol) return;
|
|
549
|
+
|
|
505
550
|
try {
|
|
506
551
|
await deleteRouteById(selectedRoute.value._id ?? "");
|
|
507
552
|
await getRoutesRefresh();
|
|
@@ -59,6 +59,21 @@
|
|
|
59
59
|
</v-col>
|
|
60
60
|
</v-row>
|
|
61
61
|
|
|
62
|
+
<v-dialog v-model="showPhotoPreview" max-width="900">
|
|
63
|
+
<v-card>
|
|
64
|
+
<v-card-actions class="pa-2 justify-end">
|
|
65
|
+
<v-btn
|
|
66
|
+
icon="mdi-close"
|
|
67
|
+
variant="text"
|
|
68
|
+
@click="showPhotoPreview = false"
|
|
69
|
+
/>
|
|
70
|
+
</v-card-actions>
|
|
71
|
+
<v-card-text class="pa-2 pt-0">
|
|
72
|
+
<v-img :src="selectedPhoto" contain max-height="80vh" />
|
|
73
|
+
</v-card-text>
|
|
74
|
+
</v-card>
|
|
75
|
+
</v-dialog>
|
|
76
|
+
|
|
62
77
|
<v-dialog v-model="photoOptionsDialog" max-width="420">
|
|
63
78
|
<v-card>
|
|
64
79
|
<v-card-title class="d-flex align-center pa-4">
|
|
@@ -196,6 +211,8 @@ const photos = ref<string[]>([...props.modelValue]);
|
|
|
196
211
|
const photosIds = ref<string[]>([...props.photoIds]);
|
|
197
212
|
const photoOptionsDialog = ref(false);
|
|
198
213
|
const showCameraDialog = ref(false);
|
|
214
|
+
const showPhotoPreview = ref(false);
|
|
215
|
+
const selectedPhoto = ref("");
|
|
199
216
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
200
217
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
|
201
218
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
|
@@ -205,10 +222,8 @@ const uploading = ref(false);
|
|
|
205
222
|
const { addFile } = useFile();
|
|
206
223
|
|
|
207
224
|
function viewPhoto(photoData: string) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
link.target = "_blank";
|
|
211
|
-
link.click();
|
|
225
|
+
selectedPhoto.value = photoData;
|
|
226
|
+
showPhotoPreview.value = true;
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
watch(
|
|
@@ -734,7 +734,7 @@ const {
|
|
|
734
734
|
updateStatus: updateServiceProviderStatus,
|
|
735
735
|
invite,
|
|
736
736
|
} = useServiceProvider();
|
|
737
|
-
const { getAllByUserId } = useMember();
|
|
737
|
+
const { getAll: getAllMembers, getAllByUserId } = useMember();
|
|
738
738
|
|
|
739
739
|
const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
|
|
740
740
|
useVerification();
|
|
@@ -757,6 +757,9 @@ const createInviteDialog = ref(false);
|
|
|
757
757
|
// const loadingInviteRoles = ref(false);
|
|
758
758
|
// const serviceProviderInviteRoles = ref<Array<Record<string, any>>>([]);
|
|
759
759
|
|
|
760
|
+
const { getByUserType } = useMember();
|
|
761
|
+
const { getOrgs } = useOrg();
|
|
762
|
+
const { getByEmail } = useServiceProvider();
|
|
760
763
|
|
|
761
764
|
async function checkExistingAccount(email: string) {
|
|
762
765
|
existingAccount.value = null;
|
|
@@ -767,40 +770,54 @@ async function checkExistingAccount(email: string) {
|
|
|
767
770
|
checkingExistingAccount.value = true;
|
|
768
771
|
|
|
769
772
|
try {
|
|
773
|
+
// Check if user exists
|
|
770
774
|
const user = await getUserByEmail(email);
|
|
771
775
|
|
|
772
776
|
if (!user?._id) return;
|
|
773
777
|
|
|
774
|
-
|
|
778
|
+
// Check if this service provider already exists in current org/site
|
|
779
|
+
const serviceProviders = await getByEmail({
|
|
780
|
+
email,
|
|
781
|
+
orgId: props.orgId,
|
|
782
|
+
siteId: props.siteId,
|
|
783
|
+
});
|
|
775
784
|
|
|
776
|
-
|
|
777
|
-
(member: any) => member.type === serviceProviderInvite.value.app
|
|
778
|
-
);
|
|
785
|
+
console.log("serviceProviders", serviceProviders);
|
|
779
786
|
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
787
|
+
if (serviceProviders?.length) {
|
|
788
|
+
messageServiceProvider.value =
|
|
789
|
+
"This service provider already exists.";
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
784
792
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
(appMember.siteId || "") === (props.siteId || "")
|
|
788
|
-
) {
|
|
789
|
-
existingAccount.value = null;
|
|
790
|
-
messageServiceProvider.value =
|
|
791
|
-
"This user is already a member of this service provider.";
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
793
|
+
// Check existing memberships
|
|
794
|
+
const members = await getAllByUserId(user._id);
|
|
794
795
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
siteId: appMember.siteId,
|
|
801
|
-
siteName: appMember.siteName,
|
|
802
|
-
};
|
|
796
|
+
const appMember = (members.items ?? []).find(
|
|
797
|
+
(member: any) => member.type === serviceProviderInvite.value.app
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
if (!appMember) return;
|
|
803
801
|
|
|
802
|
+
// Already belongs to the same org/site
|
|
803
|
+
if (
|
|
804
|
+
appMember.org === props.orgId &&
|
|
805
|
+
(appMember.siteId || "") === (props.siteId || "")
|
|
806
|
+
) {
|
|
807
|
+
messageServiceProvider.value =
|
|
808
|
+
"This user is already a member of this service provider.";
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Existing account in another org/site
|
|
813
|
+
existingAccount.value = {
|
|
814
|
+
_id: appMember.org,
|
|
815
|
+
text: appMember.orgName,
|
|
816
|
+
org: appMember.org,
|
|
817
|
+
orgName: appMember.orgName,
|
|
818
|
+
siteId: appMember.siteId,
|
|
819
|
+
siteName: appMember.siteName,
|
|
820
|
+
};
|
|
804
821
|
} catch (error) {
|
|
805
822
|
existingAccount.value = null;
|
|
806
823
|
} finally {
|
|
@@ -36,10 +36,17 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<v-list v-if="nricSearchResults.length" class="pa-1 phone-search-results">
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
<div class="search-results-scroll">
|
|
40
|
+
<v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
|
|
41
|
+
@click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
42
|
+
<VisitorSearchResultSummary :item="item" />
|
|
43
|
+
</v-list-item>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="search-results-close pa-1">
|
|
46
|
+
<v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('nric')">
|
|
47
|
+
Close
|
|
48
|
+
</v-btn>
|
|
49
|
+
</div>
|
|
43
50
|
</v-list>
|
|
44
51
|
</v-menu>
|
|
45
52
|
</v-col>
|
|
@@ -58,10 +65,17 @@
|
|
|
58
65
|
</template>
|
|
59
66
|
|
|
60
67
|
<v-list v-if="phoneSearchResults.length" class="pa-1 phone-search-results">
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
<div class="search-results-scroll">
|
|
69
|
+
<v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
|
|
70
|
+
@click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
71
|
+
<VisitorSearchResultSummary :item="item" />
|
|
72
|
+
</v-list-item>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="search-results-close pa-1">
|
|
75
|
+
<v-btn block variant="flat" color="red" @click.stop="closeAutofillSearchResults('contact')">
|
|
76
|
+
Close
|
|
77
|
+
</v-btn>
|
|
78
|
+
</div>
|
|
65
79
|
</v-list>
|
|
66
80
|
</v-menu>
|
|
67
81
|
</v-col>
|
|
@@ -77,15 +91,23 @@
|
|
|
77
91
|
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
78
92
|
<template #activator="{ props }">
|
|
79
93
|
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
80
|
-
@update:model-value="handleUpdateVehicleNumber" @focus="
|
|
94
|
+
@update:model-value="handleUpdateVehicleNumber" @focus="handleFocusPlateNumber"
|
|
81
95
|
@blur="isPlateNumberFieldFocused = false" />
|
|
82
96
|
</template>
|
|
83
97
|
|
|
84
98
|
<v-list v-if="vehicleSearchResults.length" class="pa-1 phone-search-results">
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
<div class="search-results-scroll">
|
|
100
|
+
<v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
|
|
101
|
+
@click="selectPlateNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
102
|
+
<VisitorSearchResultSummary :item="item" />
|
|
103
|
+
</v-list-item>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="search-results-close pa-1">
|
|
106
|
+
<v-btn block variant="flat" color="red"
|
|
107
|
+
@click.stop="closeAutofillSearchResults('vehicleNumber')">
|
|
108
|
+
Close
|
|
109
|
+
</v-btn>
|
|
110
|
+
</div>
|
|
89
111
|
</v-list>
|
|
90
112
|
</v-menu>
|
|
91
113
|
</v-col>
|
|
@@ -308,10 +330,10 @@
|
|
|
308
330
|
|
|
309
331
|
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
310
332
|
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
311
|
-
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
333
|
+
<span v-if="!visitor.nric || currentAutofillSource === 'nric'" class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
312
334
|
</div>
|
|
313
335
|
|
|
314
|
-
<template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
|
|
336
|
+
<template v-if="(!visitor.contact || currentAutofillSource === 'contact') && selectedNricContactOptions.length >= 1">
|
|
315
337
|
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
316
338
|
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
317
339
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
@@ -319,7 +341,7 @@
|
|
|
319
341
|
</v-radio-group>
|
|
320
342
|
</template>
|
|
321
343
|
|
|
322
|
-
<template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
|
|
344
|
+
<template v-if="(!visitor.plateNumber || currentAutofillSource === 'vehicleNumber') && selectedNricPlateOptions.length >= 1">
|
|
323
345
|
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
324
346
|
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
325
347
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
@@ -1044,8 +1066,8 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1044
1066
|
const selectedPlate =
|
|
1045
1067
|
findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
|
|
1046
1068
|
|
|
1047
|
-
const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
|
|
1048
|
-
const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
|
|
1069
|
+
const needsContactChoice = (!visitor.contact?.trim() || source === 'contact') && contactOptions.length > 1;
|
|
1070
|
+
const needsPlateChoice = (!visitor.plateNumber?.trim() || source === 'vehicleNumber') && plateOptions.length > 1;
|
|
1049
1071
|
|
|
1050
1072
|
if (needsContactChoice || needsPlateChoice) {
|
|
1051
1073
|
nricSearchMenu.value = false;
|
|
@@ -1066,6 +1088,23 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1066
1088
|
});
|
|
1067
1089
|
}
|
|
1068
1090
|
|
|
1091
|
+
function closeAutofillSearchResults(source: Exclude<AutofillSource, null>) {
|
|
1092
|
+
if (source === "nric") {
|
|
1093
|
+
nricSearchMenu.value = false;
|
|
1094
|
+
nricSearchResults.value = [];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (source === "contact") {
|
|
1098
|
+
phoneSearchMenu.value = false;
|
|
1099
|
+
phoneSearchResults.value = [];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (source === "vehicleNumber") {
|
|
1103
|
+
vehicleSearchMenu.value = false;
|
|
1104
|
+
vehicleSearchResults.value = [];
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1069
1108
|
function closeNricAutofillOptions() {
|
|
1070
1109
|
dialog.nricAutofillOptions = false;
|
|
1071
1110
|
selectedNricSearchResult.value = null;
|
|
@@ -1503,6 +1542,15 @@ function handleUpdateVehicleNumber() {
|
|
|
1503
1542
|
debounceFetchPlateNumber();
|
|
1504
1543
|
}
|
|
1505
1544
|
|
|
1545
|
+
const handleFocusPlateNumber = () => {
|
|
1546
|
+
isPlateNumberFieldFocused.value = true;
|
|
1547
|
+
if (prop.mode === 'register') {
|
|
1548
|
+
|
|
1549
|
+
handleUpdateVehicleNumber();
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
|
|
1506
1554
|
function backToSelection() {
|
|
1507
1555
|
emit("back");
|
|
1508
1556
|
message.value = "";
|
|
@@ -2108,10 +2156,22 @@ onMounted(() => {
|
|
|
2108
2156
|
}
|
|
2109
2157
|
|
|
2110
2158
|
.phone-search-results {
|
|
2159
|
+
display: flex;
|
|
2160
|
+
flex-direction: column;
|
|
2111
2161
|
max-height: 400px;
|
|
2162
|
+
overflow: hidden;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.search-results-scroll {
|
|
2166
|
+
flex: 1 1 auto;
|
|
2167
|
+
min-height: 0;
|
|
2112
2168
|
overflow-y: auto;
|
|
2113
2169
|
}
|
|
2114
2170
|
|
|
2171
|
+
.search-results-close {
|
|
2172
|
+
flex: 0 0 auto;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2115
2175
|
.nric-options-card {
|
|
2116
2176
|
border-radius: 16px !important;
|
|
2117
2177
|
}
|
|
@@ -675,6 +675,24 @@
|
|
|
675
675
|
/>
|
|
676
676
|
</div>
|
|
677
677
|
</span>
|
|
678
|
+
<span
|
|
679
|
+
v-else-if="
|
|
680
|
+
key === 'snapshotEntryImage' || key === 'snapshotExitImage'
|
|
681
|
+
"
|
|
682
|
+
class="d-flex flex-column"
|
|
683
|
+
>
|
|
684
|
+
<strong class="w-full">{{ label }}:</strong>
|
|
685
|
+
<div class="d-flex flex-wrap ga-2">
|
|
686
|
+
<AttachmentsViewer
|
|
687
|
+
:files="
|
|
688
|
+
mappedSnapshots([selectedVisitorObject.snapshotEntryImage])
|
|
689
|
+
"
|
|
690
|
+
anpr
|
|
691
|
+
:site="siteId"
|
|
692
|
+
title="Snapshot"
|
|
693
|
+
/>
|
|
694
|
+
</div>
|
|
695
|
+
</span>
|
|
678
696
|
|
|
679
697
|
<span
|
|
680
698
|
v-else-if="selectedVisitorObject[key]"
|
|
@@ -825,10 +843,8 @@
|
|
|
825
843
|
item-title="label"
|
|
826
844
|
item-value="value"
|
|
827
845
|
v-model="pass.status"
|
|
828
|
-
:disabled="
|
|
829
|
-
|
|
830
|
-
"
|
|
831
|
-
></v-select>
|
|
846
|
+
:disabled="pass.status == 'Removed'"
|
|
847
|
+
/>
|
|
832
848
|
<v-textarea
|
|
833
849
|
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
834
850
|
no-resize
|
|
@@ -836,8 +852,7 @@
|
|
|
836
852
|
class="w-100"
|
|
837
853
|
density="compact"
|
|
838
854
|
v-model="pass.remarks"
|
|
839
|
-
|
|
840
|
-
></v-textarea>
|
|
855
|
+
/>
|
|
841
856
|
</v-row>
|
|
842
857
|
</div>
|
|
843
858
|
<div v-if="keyReturnStatuses.length > 0" class="mb-2">
|
|
@@ -864,10 +879,8 @@
|
|
|
864
879
|
item-title="label"
|
|
865
880
|
item-value="value"
|
|
866
881
|
v-model="key.status"
|
|
867
|
-
:disabled="
|
|
868
|
-
|
|
869
|
-
"
|
|
870
|
-
></v-select>
|
|
882
|
+
:disabled="key.status == 'Removed'"
|
|
883
|
+
/>
|
|
871
884
|
<v-textarea
|
|
872
885
|
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
873
886
|
no-resize
|
|
@@ -875,8 +888,7 @@
|
|
|
875
888
|
class="w-100"
|
|
876
889
|
density="compact"
|
|
877
890
|
v-model="key.remarks"
|
|
878
|
-
|
|
879
|
-
></v-textarea>
|
|
891
|
+
/>
|
|
880
892
|
</v-row>
|
|
881
893
|
</div>
|
|
882
894
|
|
|
@@ -886,11 +898,11 @@
|
|
|
886
898
|
color="blue"
|
|
887
899
|
density="comfortable"
|
|
888
900
|
class="text-capitalize"
|
|
889
|
-
:disabled="selectedVisitorObject.checkOut"
|
|
890
901
|
:loading="loading.updatingPassKeys"
|
|
891
902
|
@click.stop="handleUpdatePassKeys"
|
|
892
|
-
>Update Pass/Keys</v-btn
|
|
893
903
|
>
|
|
904
|
+
Update Pass/Keys
|
|
905
|
+
</v-btn>
|
|
894
906
|
</v-row>
|
|
895
907
|
</v-card-text>
|
|
896
908
|
<v-toolbar class="pa-0" density="compact">
|
|
@@ -900,8 +912,9 @@
|
|
|
900
912
|
variant="text"
|
|
901
913
|
block
|
|
902
914
|
@click="dialog.returnPassesKeys = false"
|
|
903
|
-
>Close</v-btn
|
|
904
915
|
>
|
|
916
|
+
Close
|
|
917
|
+
</v-btn>
|
|
905
918
|
</v-col>
|
|
906
919
|
<v-col cols="6">
|
|
907
920
|
<v-btn
|
|
@@ -915,8 +928,9 @@
|
|
|
915
928
|
!canConfirmCheckout || selectedVisitorObject.checkOut
|
|
916
929
|
"
|
|
917
930
|
@click="proceedCheckout"
|
|
918
|
-
>Confirm Checkout</v-btn
|
|
919
931
|
>
|
|
932
|
+
Confirm Checkout
|
|
933
|
+
</v-btn>
|
|
920
934
|
</v-col>
|
|
921
935
|
</v-row>
|
|
922
936
|
</v-toolbar>
|
|
@@ -1442,6 +1456,15 @@ function mappedAttachments(attachments: string[]) {
|
|
|
1442
1456
|
});
|
|
1443
1457
|
}
|
|
1444
1458
|
|
|
1459
|
+
function mappedSnapshots(attachments: string[]) {
|
|
1460
|
+
return attachments.map((x, index) => {
|
|
1461
|
+
return {
|
|
1462
|
+
id: x,
|
|
1463
|
+
name: `Snapshot - ${x}`,
|
|
1464
|
+
};
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1445
1468
|
function normalizeDateOnly(value: string) {
|
|
1446
1469
|
if (!value) return "";
|
|
1447
1470
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
@@ -1,58 +1,23 @@
|
|
|
1
1
|
export function useCleaningSchedulePermission() {
|
|
2
|
-
const { hasPermission } = usePermission();
|
|
3
|
-
const { permissions } = useCleaningPermission();
|
|
4
|
-
|
|
5
2
|
const { userAppRole } = useLocalSetup();
|
|
6
3
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-schedule-details");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const canDownloadSchedule = computed(() => {
|
|
20
|
-
if (!userAppRole.value) return false;
|
|
21
|
-
if (userAppRole.value.permissions.includes("*")) return true;
|
|
22
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "download-schedule");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const canManageScheduleTasks = computed(() => {
|
|
26
|
-
if (!userAppRole.value) return false;
|
|
27
|
-
if (userAppRole.value.permissions.includes("*")) return true;
|
|
28
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "manage-schedule-tasks");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const canGenerateChecklist = computed(() => {
|
|
32
|
-
if (!userAppRole.value) return false;
|
|
33
|
-
if (userAppRole.value.permissions.includes("*")) return true;
|
|
34
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "generate-checklist");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const canViewHistory = computed(() => {
|
|
38
|
-
if (!userAppRole.value) return false;
|
|
39
|
-
if (userAppRole.value.permissions.includes("*")) return true;
|
|
40
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "view-history");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const canAddRemarks = computed(() => {
|
|
44
|
-
if (!userAppRole.value) return false;
|
|
45
|
-
if (userAppRole.value.permissions.includes("*")) return true;
|
|
46
|
-
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "add-remarks");
|
|
47
|
-
});
|
|
4
|
+
const can = (action: string) =>
|
|
5
|
+
computed(() => {
|
|
6
|
+
const permissions = userAppRole.value?.permissions;
|
|
7
|
+
if (!permissions) return false;
|
|
8
|
+
if (permissions.includes("*")) return true;
|
|
9
|
+
return permissions.some((permission) =>
|
|
10
|
+
permission.endsWith(`-schedule-mgmt:${action}`),
|
|
11
|
+
);
|
|
12
|
+
});
|
|
48
13
|
|
|
49
14
|
return {
|
|
50
|
-
canViewSchedules,
|
|
51
|
-
canViewScheduleDetails,
|
|
52
|
-
canDownloadSchedule,
|
|
53
|
-
canManageScheduleTasks,
|
|
54
|
-
canGenerateChecklist,
|
|
55
|
-
canViewHistory,
|
|
56
|
-
canAddRemarks,
|
|
15
|
+
canViewSchedules: can("see-all-schedules"),
|
|
16
|
+
canViewScheduleDetails: can("see-schedule-details"),
|
|
17
|
+
canDownloadSchedule: can("download-schedule"),
|
|
18
|
+
canManageScheduleTasks: can("manage-schedule-tasks"),
|
|
19
|
+
canGenerateChecklist: can("generate-checklist"),
|
|
20
|
+
canViewHistory: can("view-history"),
|
|
21
|
+
canAddRemarks: can("add-remarks"),
|
|
57
22
|
};
|
|
58
23
|
}
|