@7365admin1/layer-common 3.1.3-staging.44 → 3.1.3
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 +0 -11
- package/CHANGELOG.md +6 -0
- package/components/AttachmentsViewer.vue +4 -22
- package/components/DashboardMain.vue +87 -228
- package/components/ImageCarousel.vue +5 -23
- package/components/ManageChecklistMain.vue +25 -32
- package/components/PhotoUpload.vue +4 -19
- package/components/RolePermissionFormPreviewUpdate.vue +1 -1
- package/components/ServiceProviderMain.vue +26 -43
- package/components/VisitorForm.vue +18 -78
- package/components/VisitorManagement.vue +16 -39
- package/composables/useCleaningSchedulePermission.ts +51 -16
- package/composables/useServiceProvider.ts +0 -21
- package/package.json +1 -1
|
@@ -34,14 +34,3 @@ jobs:
|
|
|
34
34
|
|
|
35
35
|
- name: Publish to npm with staging tag
|
|
36
36
|
run: npm publish --tag staging --registry https://registry.npmjs.org
|
|
37
|
-
|
|
38
|
-
- name: Notify Discord on failure
|
|
39
|
-
if: failure()
|
|
40
|
-
env:
|
|
41
|
-
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
|
42
|
-
run: |
|
|
43
|
-
MESSAGE="@everyone :rotating_light: **Publish Staging** failed in \`${{ github.repository }}\` (\`${{ github.workflow }}\`)
|
|
44
|
-
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
45
|
-
curl -s -X POST -H "Content-Type: application/json" \
|
|
46
|
-
-d "$(jq -n --arg content "$MESSAGE" '{content: $content}')" \
|
|
47
|
-
"$DISCORD_WEBHOOK_URL"
|
package/CHANGELOG.md
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
<v-list-item class="cursor-pointer" @click="openMediaViewer(item._id, item.mimeType)">
|
|
49
49
|
<template #prepend>
|
|
50
50
|
<v-avatar size="36" rounded="sm" class="mr-3">
|
|
51
|
-
<v-img :src="
|
|
51
|
+
<v-img :src="getFileUrl(item._id)" cover>
|
|
52
52
|
<template #error>
|
|
53
53
|
<v-icon color="green" size="20">mdi-image</v-icon>
|
|
54
54
|
</template>
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
</v-row>
|
|
98
98
|
|
|
99
99
|
<!-- Media Carousel (images + videos) -->
|
|
100
|
-
<ImageCarousel v-model="showCarousel" :active-file-id="activeFileId" :files="carouselFiles"
|
|
100
|
+
<ImageCarousel v-model="showCarousel" :active-file-id="activeFileId" :files="carouselFiles" />
|
|
101
101
|
|
|
102
102
|
<!-- Document Viewer -->
|
|
103
103
|
<DocumentViewer v-model="showDocumentViewer" :file-id="activeFileId" :file-name="activeFileName"
|
|
@@ -123,18 +123,10 @@ const props = defineProps({
|
|
|
123
123
|
showFileTypeTitle: {
|
|
124
124
|
type: Boolean,
|
|
125
125
|
default: false,
|
|
126
|
-
},
|
|
127
|
-
anpr: {
|
|
128
|
-
type: Boolean,
|
|
129
|
-
default: false
|
|
130
|
-
},
|
|
131
|
-
site: {
|
|
132
|
-
type: String,
|
|
133
|
-
default: ""
|
|
134
126
|
}
|
|
135
127
|
});
|
|
136
128
|
|
|
137
|
-
const { getFileUrl, urlToFile, getFileById
|
|
129
|
+
const { getFileUrl, urlToFile, getFileById } = useFile();
|
|
138
130
|
|
|
139
131
|
const rawFileArray = ref<{ file: File, _id: string, preview: boolean, mimeType: string }[]>([]);
|
|
140
132
|
const loading = ref(true);
|
|
@@ -167,12 +159,6 @@ function openDocumentViewer(id: string, name: string, mimeType: string) {
|
|
|
167
159
|
showDocumentViewer.value = true;
|
|
168
160
|
}
|
|
169
161
|
|
|
170
|
-
const imageCover = (id: string) => {
|
|
171
|
-
if(props.anpr && props.site){
|
|
172
|
-
return getFileUrlAnpr(`${props.site}/${id}`)
|
|
173
|
-
} else return getFileUrl(id)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
162
|
function getFileIcon(mimeType: string): string {
|
|
177
163
|
if (mimeType.startsWith('image/')) return 'mdi-image';
|
|
178
164
|
if (mimeType.startsWith('video/')) return 'mdi-video';
|
|
@@ -220,11 +206,7 @@ watchEffect(async () => {
|
|
|
220
206
|
|
|
221
207
|
try {
|
|
222
208
|
// Try to get the actual file with proper MIME type detection from blob
|
|
223
|
-
|
|
224
|
-
let fileUrl = ""
|
|
225
|
-
if(props.anpr && props.site){
|
|
226
|
-
fileUrl = getFileUrlAnpr(`${props.site}/${item.id}`)
|
|
227
|
-
} else fileUrl = getFileUrl(item.id);
|
|
209
|
+
const fileUrl = getFileUrl(item.id);
|
|
228
210
|
file = await urlToFile(fileUrl, item.name);
|
|
229
211
|
mimeType = file.type || 'application/octet-stream';
|
|
230
212
|
|
|
@@ -984,44 +984,60 @@
|
|
|
984
984
|
<div
|
|
985
985
|
v-for="item in formattedActivePatrolItems"
|
|
986
986
|
:key="item.id || item._id"
|
|
987
|
-
class="
|
|
987
|
+
class="d-flex align-center justify-space-between"
|
|
988
|
+
style="
|
|
989
|
+
padding: 16px 20px;
|
|
990
|
+
border: 1px solid #e2e8f0;
|
|
991
|
+
border-radius: 12px;
|
|
992
|
+
margin-bottom: 12px;
|
|
993
|
+
background-color: #ffffff;
|
|
994
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
995
|
+
"
|
|
988
996
|
>
|
|
989
|
-
<div
|
|
990
|
-
<strong
|
|
991
|
-
|
|
997
|
+
<div style="flex: 1; min-width: 140px">
|
|
998
|
+
<strong
|
|
999
|
+
style="
|
|
1000
|
+
display: block;
|
|
1001
|
+
font-size: 15px;
|
|
1002
|
+
font-weight: 700;
|
|
1003
|
+
color: #0b2f47;
|
|
1004
|
+
margin-bottom: 4px;
|
|
1005
|
+
"
|
|
1006
|
+
>{{ item.title || item.name }}</strong
|
|
1007
|
+
>
|
|
1008
|
+
<span style="font-size: 13px; color: #94a3b8">{{
|
|
1009
|
+
item.subtitle || item.location || ""
|
|
1010
|
+
}}</span>
|
|
992
1011
|
</div>
|
|
993
1012
|
|
|
994
|
-
<div
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
>
|
|
1004
|
-
+{{ item.extraAssigneeCount }}
|
|
1005
|
-
</span>
|
|
1013
|
+
<div
|
|
1014
|
+
style="flex: 1; display: flex; align-items: center; gap: 12px"
|
|
1015
|
+
>
|
|
1016
|
+
<AvatarMain
|
|
1017
|
+
:name="item.person || item.assignedTo"
|
|
1018
|
+
:size="36"
|
|
1019
|
+
/>
|
|
1020
|
+
<span style="font-size: 14px; color: #64748b">{{
|
|
1021
|
+
item.person || item.assignedTo
|
|
1022
|
+
}}</span>
|
|
1006
1023
|
</div>
|
|
1007
1024
|
|
|
1008
|
-
<div
|
|
1025
|
+
<div style="flex: 1; display: flex; justify-content: center">
|
|
1009
1026
|
<div
|
|
1010
|
-
|
|
1011
|
-
|
|
1027
|
+
:style="getPatrolStatusStyle(item.status)"
|
|
1028
|
+
style="
|
|
1029
|
+
padding: 6px 16px;
|
|
1030
|
+
border-radius: 20px;
|
|
1031
|
+
font-size: 13px;
|
|
1032
|
+
font-weight: 500;
|
|
1033
|
+
text-transform: capitalize;
|
|
1034
|
+
"
|
|
1012
1035
|
>
|
|
1013
|
-
{{ item.
|
|
1036
|
+
{{ item.status }}
|
|
1014
1037
|
</div>
|
|
1015
|
-
<span
|
|
1016
|
-
v-if="item.extraStatusCount > 0"
|
|
1017
|
-
class="active-patrol-more-pill active-patrol-tooltip"
|
|
1018
|
-
:data-tooltip="item.extraStatuses.join(', ')"
|
|
1019
|
-
>
|
|
1020
|
-
+{{ item.extraStatusCount }}
|
|
1021
|
-
</span>
|
|
1022
1038
|
</div>
|
|
1023
1039
|
|
|
1024
|
-
<div
|
|
1040
|
+
<div style="flex: 0 0 auto; text-align: right">
|
|
1025
1041
|
<v-btn
|
|
1026
1042
|
variant="outlined"
|
|
1027
1043
|
class="text-none font-weight-medium"
|
|
@@ -1529,16 +1545,30 @@ const { userAppRole } = useLocalSetup();
|
|
|
1529
1545
|
const { downloadPDF } = usePDFDownload();
|
|
1530
1546
|
const route = useRoute();
|
|
1531
1547
|
|
|
1548
|
+
const dashboardWidgetPermissions: Record<string, string> = {
|
|
1549
|
+
activity: "dashboard:view-activity",
|
|
1550
|
+
recentActivity: "dashboard:view-activity",
|
|
1551
|
+
taskSchedule: "dashboard:view-task-schedule",
|
|
1552
|
+
staffStatus: "dashboard:view-attendance-widget",
|
|
1553
|
+
attendance: "dashboard:view-attendance-widget",
|
|
1554
|
+
feedbacks: "dashboard:view-feedbacks-widget",
|
|
1555
|
+
workOrderStatus: "dashboard:view-work-order-status",
|
|
1556
|
+
activePatrol: "dashboard:view-active-patrol",
|
|
1557
|
+
upcomingEvents: "dashboard:view-upcoming-events",
|
|
1558
|
+
todayReminders: "dashboard:view-today-reminders",
|
|
1559
|
+
todayAttentions: "dashboard:view-today-attentions",
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1532
1562
|
const propertyWidgetPermissions: Record<string, string[]> = {
|
|
1533
1563
|
workOrders: [
|
|
1534
|
-
"
|
|
1564
|
+
"workOrder:see-all-work-orders",
|
|
1535
1565
|
"work-order:see-all-work-orders",
|
|
1536
1566
|
],
|
|
1537
1567
|
incidents: ["incident-reports:see-incident-reports"],
|
|
1538
1568
|
visitors: ["visitorManagement:see-all-visitor"],
|
|
1539
1569
|
facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
|
|
1540
1570
|
activity: [
|
|
1541
|
-
"
|
|
1571
|
+
"workOrder:see-all-work-orders",
|
|
1542
1572
|
"work-order:see-all-work-orders",
|
|
1543
1573
|
],
|
|
1544
1574
|
upcomingEvents: ["event-mgmt:see-all-event"],
|
|
@@ -1548,7 +1578,7 @@ const propertyWidgetPermissions: Record<string, string[]> = {
|
|
|
1548
1578
|
"facility-booking-mgmt:see-all-facility-booking",
|
|
1549
1579
|
],
|
|
1550
1580
|
workOrderStatus: [
|
|
1551
|
-
"
|
|
1581
|
+
"workOrder:see-all-work-orders",
|
|
1552
1582
|
"work-order:see-all-work-orders",
|
|
1553
1583
|
],
|
|
1554
1584
|
feedbacks: ["feedbacks:see-all-feedback"],
|
|
@@ -1573,14 +1603,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
|
|
|
1573
1603
|
"work_order:see-all-work-orders",
|
|
1574
1604
|
"work-order:see-all-work-orders",
|
|
1575
1605
|
"work-orders:see-all-work-orders",
|
|
1576
|
-
"work_orders:see-all-work-orders",
|
|
1577
1606
|
],
|
|
1578
|
-
|
|
1579
|
-
"
|
|
1580
|
-
"
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
"
|
|
1607
|
+
taskSchedule: [
|
|
1608
|
+
"cleaning-schedule-mgmt:see-all-schedules",
|
|
1609
|
+
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
1610
|
+
],
|
|
1611
|
+
taskCompleted: [
|
|
1612
|
+
"cleaning-schedule-mgmt:see-all-schedules",
|
|
1613
|
+
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
1584
1614
|
],
|
|
1585
1615
|
staffStatus: ["attendance-mgmt:see-all-attendance"],
|
|
1586
1616
|
attendance: ["attendance-mgmt:see-all-attendance"],
|
|
@@ -1603,14 +1633,9 @@ const moduleWidgetPermissions: Record<string, string[]> = {
|
|
|
1603
1633
|
activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
|
|
1604
1634
|
};
|
|
1605
1635
|
|
|
1606
|
-
function hasSchedulePermission(permissions: string[], action: string) {
|
|
1607
|
-
return permissions.some((permission) =>
|
|
1608
|
-
permission.endsWith(`-schedule-mgmt:${action}`)
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
1636
|
function canViewWidget(key: string) {
|
|
1613
1637
|
const permissions = userAppRole.value?.permissions;
|
|
1638
|
+
const dashboardPermission = dashboardWidgetPermissions[key];
|
|
1614
1639
|
const modulePermissions = isPropertyMode.value
|
|
1615
1640
|
? propertyWidgetPermissions[key]
|
|
1616
1641
|
: moduleWidgetPermissions[key];
|
|
@@ -1618,22 +1643,20 @@ function canViewWidget(key: string) {
|
|
|
1618
1643
|
if (!permissions) return true;
|
|
1619
1644
|
if (permissions.includes("*")) return true;
|
|
1620
1645
|
|
|
1621
|
-
if (
|
|
1622
|
-
!isPropertyMode.value &&
|
|
1623
|
-
["taskSchedule", "taskCompleted"].includes(key)
|
|
1624
|
-
) {
|
|
1625
|
-
return hasSchedulePermission(permissions, "see-all-schedules");
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
1646
|
if (isPropertyMode.value && modulePermissions) {
|
|
1629
1647
|
return modulePermissions.some((permission) =>
|
|
1630
1648
|
permissions.includes(permission)
|
|
1631
1649
|
);
|
|
1632
1650
|
}
|
|
1633
1651
|
|
|
1634
|
-
if (modulePermissions) {
|
|
1635
|
-
return
|
|
1636
|
-
permissions.includes(
|
|
1652
|
+
if (dashboardPermission || modulePermissions) {
|
|
1653
|
+
return (
|
|
1654
|
+
(dashboardPermission && permissions.includes(dashboardPermission)) ||
|
|
1655
|
+
Boolean(
|
|
1656
|
+
modulePermissions?.some((permission) =>
|
|
1657
|
+
permissions.includes(permission)
|
|
1658
|
+
)
|
|
1659
|
+
)
|
|
1637
1660
|
);
|
|
1638
1661
|
}
|
|
1639
1662
|
|
|
@@ -2751,23 +2774,18 @@ const activePatrolItems = computed<any[]>(
|
|
|
2751
2774
|
|
|
2752
2775
|
const formattedActivePatrolItems = computed<any[]>(() => {
|
|
2753
2776
|
return activePatrolItems.value.map((item) => {
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2777
|
+
let status = item.status;
|
|
2778
|
+
if (Array.isArray(status)) {
|
|
2779
|
+
status = status.join(", ");
|
|
2780
|
+
}
|
|
2781
|
+
let person = item.person || item.assignedTo || "Unassigned";
|
|
2782
|
+
if (Array.isArray(person)) {
|
|
2783
|
+
person = person.join(", ");
|
|
2784
|
+
}
|
|
2759
2785
|
return {
|
|
2760
2786
|
...item,
|
|
2761
|
-
status
|
|
2762
|
-
|
|
2763
|
-
primaryStatus: statuses[0] || "Pending",
|
|
2764
|
-
extraStatuses: statuses.slice(1),
|
|
2765
|
-
extraStatusCount: Math.max(0, statuses.length - 1),
|
|
2766
|
-
person: assignees.join(", "),
|
|
2767
|
-
assignees,
|
|
2768
|
-
primaryAssignee: assignees[0] || "Unassigned",
|
|
2769
|
-
extraAssignees: assignees.slice(1),
|
|
2770
|
-
extraAssigneeCount: Math.max(0, assignees.length - 1),
|
|
2787
|
+
status,
|
|
2788
|
+
person,
|
|
2771
2789
|
};
|
|
2772
2790
|
});
|
|
2773
2791
|
});
|
|
@@ -3937,20 +3955,6 @@ function normalizeStatus(value: any) {
|
|
|
3937
3955
|
return text.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3938
3956
|
}
|
|
3939
3957
|
|
|
3940
|
-
function normalizeListValue(value: any) {
|
|
3941
|
-
if (Array.isArray(value)) {
|
|
3942
|
-
return value
|
|
3943
|
-
.flatMap((entry) => normalizeListValue(entry))
|
|
3944
|
-
.map((entry) => String(entry).trim())
|
|
3945
|
-
.filter(Boolean);
|
|
3946
|
-
}
|
|
3947
|
-
|
|
3948
|
-
return String(value || "")
|
|
3949
|
-
.split(",")
|
|
3950
|
-
.map((entry) => entry.trim())
|
|
3951
|
-
.filter(Boolean);
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
3958
|
function getStatusTone(status: any) {
|
|
3955
3959
|
const s = String(status || "").toLowerCase();
|
|
3956
3960
|
if (
|
|
@@ -4418,151 +4422,6 @@ function formatDashboardTime(value?: string) {
|
|
|
4418
4422
|
border: 2px solid #f9c97c;
|
|
4419
4423
|
}
|
|
4420
4424
|
|
|
4421
|
-
.active-patrol-row {
|
|
4422
|
-
align-items: center;
|
|
4423
|
-
background-color: #ffffff;
|
|
4424
|
-
border: 1px solid #e2e8f0;
|
|
4425
|
-
border-radius: 8px;
|
|
4426
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
4427
|
-
display: grid;
|
|
4428
|
-
gap: 10px;
|
|
4429
|
-
grid-template-columns: 150px minmax(0, 1fr) max-content max-content;
|
|
4430
|
-
margin-bottom: 12px;
|
|
4431
|
-
min-height: 64px;
|
|
4432
|
-
overflow: visible;
|
|
4433
|
-
padding: 12px 14px;
|
|
4434
|
-
}
|
|
4435
|
-
|
|
4436
|
-
.active-patrol-main {
|
|
4437
|
-
min-width: 0;
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
|
-
.active-patrol-main strong {
|
|
4441
|
-
color: #0b2f47;
|
|
4442
|
-
display: block;
|
|
4443
|
-
font-size: 13px;
|
|
4444
|
-
font-weight: 700;
|
|
4445
|
-
line-height: 1.25;
|
|
4446
|
-
margin-bottom: 3px;
|
|
4447
|
-
overflow-wrap: anywhere;
|
|
4448
|
-
}
|
|
4449
|
-
|
|
4450
|
-
.active-patrol-main span {
|
|
4451
|
-
color: #94a3b8;
|
|
4452
|
-
display: block;
|
|
4453
|
-
font-size: 11px;
|
|
4454
|
-
line-height: 1.25;
|
|
4455
|
-
overflow-wrap: anywhere;
|
|
4456
|
-
}
|
|
4457
|
-
|
|
4458
|
-
.active-patrol-assignees {
|
|
4459
|
-
align-items: center;
|
|
4460
|
-
display: flex;
|
|
4461
|
-
gap: 6px;
|
|
4462
|
-
min-width: 0;
|
|
4463
|
-
}
|
|
4464
|
-
|
|
4465
|
-
.active-patrol-assignee {
|
|
4466
|
-
align-items: center;
|
|
4467
|
-
background: #f8fafc;
|
|
4468
|
-
border: 1px solid #e7edf3;
|
|
4469
|
-
border-radius: 999px;
|
|
4470
|
-
display: inline-flex;
|
|
4471
|
-
flex: 1 1 auto;
|
|
4472
|
-
gap: 6px;
|
|
4473
|
-
max-width: 100%;
|
|
4474
|
-
min-width: 0;
|
|
4475
|
-
padding: 2px 8px 2px 3px;
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
.active-patrol-assignee span {
|
|
4479
|
-
color: #475569;
|
|
4480
|
-
font-size: 11px;
|
|
4481
|
-
font-weight: 600;
|
|
4482
|
-
line-height: 1.2;
|
|
4483
|
-
min-width: 0;
|
|
4484
|
-
overflow: hidden;
|
|
4485
|
-
text-overflow: ellipsis;
|
|
4486
|
-
white-space: nowrap;
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
|
-
.active-patrol-statuses {
|
|
4490
|
-
align-items: center;
|
|
4491
|
-
display: flex;
|
|
4492
|
-
gap: 6px;
|
|
4493
|
-
justify-content: flex-start;
|
|
4494
|
-
min-width: 0;
|
|
4495
|
-
}
|
|
4496
|
-
|
|
4497
|
-
.active-patrol-status-pill {
|
|
4498
|
-
border-radius: 20px;
|
|
4499
|
-
font-size: 11px;
|
|
4500
|
-
font-weight: 600;
|
|
4501
|
-
line-height: 1.2;
|
|
4502
|
-
padding: 5px 10px;
|
|
4503
|
-
text-transform: capitalize;
|
|
4504
|
-
white-space: nowrap;
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
|
-
.active-patrol-more-pill {
|
|
4508
|
-
align-items: center;
|
|
4509
|
-
background: #eef3f8;
|
|
4510
|
-
border: 1px solid #dbe4ed;
|
|
4511
|
-
border-radius: 999px;
|
|
4512
|
-
color: #607386;
|
|
4513
|
-
display: inline-flex;
|
|
4514
|
-
flex: 0 0 auto;
|
|
4515
|
-
font-size: 11px;
|
|
4516
|
-
font-weight: 800;
|
|
4517
|
-
height: 24px;
|
|
4518
|
-
justify-content: center;
|
|
4519
|
-
line-height: 1;
|
|
4520
|
-
min-width: 30px;
|
|
4521
|
-
padding: 0 8px;
|
|
4522
|
-
position: relative;
|
|
4523
|
-
white-space: nowrap;
|
|
4524
|
-
}
|
|
4525
|
-
|
|
4526
|
-
.active-patrol-action {
|
|
4527
|
-
justify-self: end;
|
|
4528
|
-
text-align: right;
|
|
4529
|
-
}
|
|
4530
|
-
|
|
4531
|
-
.active-patrol-tooltip::after {
|
|
4532
|
-
background: #0f2638;
|
|
4533
|
-
border-radius: 6px;
|
|
4534
|
-
bottom: calc(100% + 8px);
|
|
4535
|
-
box-shadow: 0 8px 24px rgba(15, 38, 56, 0.18);
|
|
4536
|
-
color: #ffffff;
|
|
4537
|
-
content: attr(data-tooltip);
|
|
4538
|
-
font-size: 11px;
|
|
4539
|
-
font-weight: 600;
|
|
4540
|
-
left: 50%;
|
|
4541
|
-
line-height: 1.35;
|
|
4542
|
-
max-width: 260px;
|
|
4543
|
-
opacity: 0;
|
|
4544
|
-
padding: 7px 9px;
|
|
4545
|
-
pointer-events: none;
|
|
4546
|
-
position: absolute;
|
|
4547
|
-
text-align: left;
|
|
4548
|
-
transform: translate(-50%, 4px);
|
|
4549
|
-
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
4550
|
-
white-space: normal;
|
|
4551
|
-
width: max-content;
|
|
4552
|
-
z-index: 5;
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
.active-patrol-tooltip:hover::after {
|
|
4556
|
-
opacity: 1;
|
|
4557
|
-
transform: translate(-50%, 0);
|
|
4558
|
-
}
|
|
4559
|
-
|
|
4560
|
-
@media (max-width: 960px) {
|
|
4561
|
-
.active-patrol-row {
|
|
4562
|
-
grid-template-columns: 120px minmax(0, 1fr) max-content max-content;
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
|
|
4566
4425
|
.figma-icon-badge {
|
|
4567
4426
|
align-items: center;
|
|
4568
4427
|
border-radius: 50%;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<template v-if="fileTypes?.[x] === 'image'">
|
|
8
8
|
<v-carousel-item height="100%" width="100%" rounded="lg">
|
|
9
9
|
<v-row no-gutters class="w-100 h-100" align="center">
|
|
10
|
-
<v-img :lazy-src="
|
|
10
|
+
<v-img :lazy-src="getFileUrl(x)" :src="getFileUrl(x)" width="70%" height="70%"
|
|
11
11
|
:alt="'Image Viewer Card -' + index"></v-img>
|
|
12
12
|
</v-row>
|
|
13
13
|
<template v-slot:placeholder>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<v-carousel-item>
|
|
22
22
|
<v-row no-gutters class="h-100 w-100" align="center" justify="center">
|
|
23
23
|
<video width="80%" height="80%" controls>
|
|
24
|
-
<source :src="
|
|
24
|
+
<source :src="getFileUrl(x)" />
|
|
25
25
|
</video>
|
|
26
26
|
</v-row>
|
|
27
27
|
<template v-slot:placeholder>
|
|
@@ -78,19 +78,11 @@ const props = defineProps({
|
|
|
78
78
|
files: {
|
|
79
79
|
type: Array as PropType<string[]>,
|
|
80
80
|
default: []
|
|
81
|
-
},
|
|
82
|
-
site: {
|
|
83
|
-
type: String,
|
|
84
|
-
default: ""
|
|
85
|
-
},
|
|
86
|
-
anpr: {
|
|
87
|
-
type: Boolean,
|
|
88
|
-
default: false
|
|
89
81
|
}
|
|
90
82
|
});
|
|
91
83
|
|
|
92
84
|
|
|
93
|
-
const { getFileUrl, urlToFile
|
|
85
|
+
const { getFileUrl, urlToFile } = useFile()
|
|
94
86
|
const overlay = defineModel({ required: true, default: false });
|
|
95
87
|
const activeIndex = ref(0);
|
|
96
88
|
const fileTypes = ref<Record<string, "image" | "video" | "other">>({});
|
|
@@ -115,13 +107,10 @@ async function resolveFileTypes() {
|
|
|
115
107
|
|
|
116
108
|
for (const x of props.files) {
|
|
117
109
|
try {
|
|
118
|
-
|
|
119
|
-
if (props.anpr && props.site) {
|
|
120
|
-
url = getFileUrlAnpr(`${props.site}/${x}`)
|
|
121
|
-
} else url = getFileUrl(x);
|
|
110
|
+
const url = getFileUrl(x);
|
|
122
111
|
const file = await urlToFile(url, x);
|
|
123
112
|
const type = file?.type;
|
|
124
|
-
|
|
113
|
+
|
|
125
114
|
|
|
126
115
|
if (type?.startsWith("video")) fileTypes.value[x] = "video";
|
|
127
116
|
else if (type?.startsWith("image")) fileTypes.value[x] = "image";
|
|
@@ -140,13 +129,6 @@ watch(
|
|
|
140
129
|
);
|
|
141
130
|
|
|
142
131
|
|
|
143
|
-
const getFinalUrl = (id: string) => {
|
|
144
|
-
if (props.anpr && props.site) {
|
|
145
|
-
return getFileUrlAnpr(`${props.site}/${id}`)
|
|
146
|
-
} else return getFileUrl(id)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
132
|
</script>
|
|
151
133
|
|
|
152
134
|
<style scoped>
|
|
@@ -304,38 +304,31 @@
|
|
|
304
304
|
|
|
305
305
|
<v-divider />
|
|
306
306
|
|
|
307
|
-
<v-
|
|
308
|
-
<v-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
@click="submitRejectDialog"
|
|
333
|
-
>
|
|
334
|
-
Confirm
|
|
335
|
-
</v-btn>
|
|
336
|
-
</v-col>
|
|
337
|
-
</v-row>
|
|
338
|
-
</v-toolbar>
|
|
307
|
+
<v-card-actions class="pa-0">
|
|
308
|
+
<v-btn
|
|
309
|
+
color="grey"
|
|
310
|
+
variant="flat"
|
|
311
|
+
rounded="0"
|
|
312
|
+
class="flex-grow-1 text-none"
|
|
313
|
+
height="48"
|
|
314
|
+
:disabled="rejectModal.submitting"
|
|
315
|
+
@click="closeRejectDialog"
|
|
316
|
+
>
|
|
317
|
+
Cancel
|
|
318
|
+
</v-btn>
|
|
319
|
+
<v-btn
|
|
320
|
+
color="error"
|
|
321
|
+
variant="flat"
|
|
322
|
+
rounded="0"
|
|
323
|
+
class="flex-grow-1 text-none"
|
|
324
|
+
height="48"
|
|
325
|
+
:loading="rejectModal.submitting"
|
|
326
|
+
:disabled="!isRejectModalValid || rejectModal.submitting"
|
|
327
|
+
@click="submitRejectDialog"
|
|
328
|
+
>
|
|
329
|
+
Confirm
|
|
330
|
+
</v-btn>
|
|
331
|
+
</v-card-actions>
|
|
339
332
|
</v-card>
|
|
340
333
|
</v-dialog>
|
|
341
334
|
|
|
@@ -59,21 +59,6 @@
|
|
|
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
|
-
|
|
77
62
|
<v-dialog v-model="photoOptionsDialog" max-width="420">
|
|
78
63
|
<v-card>
|
|
79
64
|
<v-card-title class="d-flex align-center pa-4">
|
|
@@ -211,8 +196,6 @@ const photos = ref<string[]>([...props.modelValue]);
|
|
|
211
196
|
const photosIds = ref<string[]>([...props.photoIds]);
|
|
212
197
|
const photoOptionsDialog = ref(false);
|
|
213
198
|
const showCameraDialog = ref(false);
|
|
214
|
-
const showPhotoPreview = ref(false);
|
|
215
|
-
const selectedPhoto = ref("");
|
|
216
199
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
217
200
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
|
218
201
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
|
@@ -222,8 +205,10 @@ const uploading = ref(false);
|
|
|
222
205
|
const { addFile } = useFile();
|
|
223
206
|
|
|
224
207
|
function viewPhoto(photoData: string) {
|
|
225
|
-
|
|
226
|
-
|
|
208
|
+
const link = document.createElement("a");
|
|
209
|
+
link.href = photoData;
|
|
210
|
+
link.target = "_blank";
|
|
211
|
+
link.click();
|
|
227
212
|
}
|
|
228
213
|
|
|
229
214
|
watch(
|
|
@@ -734,7 +734,7 @@ const {
|
|
|
734
734
|
updateStatus: updateServiceProviderStatus,
|
|
735
735
|
invite,
|
|
736
736
|
} = useServiceProvider();
|
|
737
|
-
const {
|
|
737
|
+
const { getAllByUserId } = useMember();
|
|
738
738
|
|
|
739
739
|
const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
|
|
740
740
|
useVerification();
|
|
@@ -757,9 +757,6 @@ 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();
|
|
763
760
|
|
|
764
761
|
async function checkExistingAccount(email: string) {
|
|
765
762
|
existingAccount.value = null;
|
|
@@ -770,54 +767,40 @@ async function checkExistingAccount(email: string) {
|
|
|
770
767
|
checkingExistingAccount.value = true;
|
|
771
768
|
|
|
772
769
|
try {
|
|
773
|
-
// Check if user exists
|
|
774
770
|
const user = await getUserByEmail(email);
|
|
775
771
|
|
|
776
772
|
if (!user?._id) return;
|
|
777
773
|
|
|
778
|
-
|
|
779
|
-
const serviceProviders = await getByEmail({
|
|
780
|
-
email,
|
|
781
|
-
orgId: props.orgId,
|
|
782
|
-
siteId: props.siteId,
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
console.log("serviceProviders", serviceProviders);
|
|
786
|
-
|
|
787
|
-
if (serviceProviders?.length) {
|
|
788
|
-
messageServiceProvider.value =
|
|
789
|
-
"This service provider already exists.";
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
774
|
+
const members = await getAllByUserId(user._id);
|
|
792
775
|
|
|
793
|
-
|
|
794
|
-
|
|
776
|
+
const appMember = (members.items ?? []).find(
|
|
777
|
+
(member: any) => member.type === serviceProviderInvite.value.app
|
|
778
|
+
);
|
|
795
779
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
780
|
+
if (!appMember) {
|
|
781
|
+
existingAccount.value = null;
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
799
784
|
|
|
800
|
-
|
|
785
|
+
if (
|
|
786
|
+
appMember.org === props.orgId &&
|
|
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
|
+
}
|
|
801
794
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
795
|
+
existingAccount.value = {
|
|
796
|
+
_id: appMember.org,
|
|
797
|
+
text: appMember.orgName,
|
|
798
|
+
org: appMember.org,
|
|
799
|
+
orgName: appMember.orgName,
|
|
800
|
+
siteId: appMember.siteId,
|
|
801
|
+
siteName: appMember.siteName,
|
|
802
|
+
};
|
|
811
803
|
|
|
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
|
-
};
|
|
821
804
|
} catch (error) {
|
|
822
805
|
existingAccount.value = null;
|
|
823
806
|
} finally {
|
|
@@ -36,17 +36,10 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<v-list v-if="nricSearchResults.length" class="pa-1 phone-search-results">
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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>
|
|
39
|
+
<v-list-item v-for="(item, index) in nricSearchResults" :key="item._id || index"
|
|
40
|
+
@click="selectNricSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
41
|
+
<VisitorSearchResultSummary :item="item" />
|
|
42
|
+
</v-list-item>
|
|
50
43
|
</v-list>
|
|
51
44
|
</v-menu>
|
|
52
45
|
</v-col>
|
|
@@ -65,17 +58,10 @@
|
|
|
65
58
|
</template>
|
|
66
59
|
|
|
67
60
|
<v-list v-if="phoneSearchResults.length" class="pa-1 phone-search-results">
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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>
|
|
61
|
+
<v-list-item v-for="(item, index) in phoneSearchResults" :key="item._id || index"
|
|
62
|
+
@click="selectPhoneNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
63
|
+
<VisitorSearchResultSummary :item="item" />
|
|
64
|
+
</v-list-item>
|
|
79
65
|
</v-list>
|
|
80
66
|
</v-menu>
|
|
81
67
|
</v-col>
|
|
@@ -91,23 +77,15 @@
|
|
|
91
77
|
<v-menu v-model="vehicleSearchMenu" location="bottom" offset-y :close-on-content-click="false">
|
|
92
78
|
<template #activator="{ props }">
|
|
93
79
|
<InputVehicleNumber v-bind="props" v-model="visitor.plateNumber" density="comfortable"
|
|
94
|
-
@update:model-value="handleUpdateVehicleNumber" @focus="
|
|
80
|
+
@update:model-value="handleUpdateVehicleNumber" @focus="isPlateNumberFieldFocused = true"
|
|
95
81
|
@blur="isPlateNumberFieldFocused = false" />
|
|
96
82
|
</template>
|
|
97
83
|
|
|
98
84
|
<v-list v-if="vehicleSearchResults.length" class="pa-1 phone-search-results">
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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>
|
|
85
|
+
<v-list-item v-for="(item, index) in vehicleSearchResults" :key="item._id || index"
|
|
86
|
+
@click="selectPlateNumberSearchResult(item)" class="cursor-pointer nric-search-item">
|
|
87
|
+
<VisitorSearchResultSummary :item="item" />
|
|
88
|
+
</v-list-item>
|
|
111
89
|
</v-list>
|
|
112
90
|
</v-menu>
|
|
113
91
|
</v-col>
|
|
@@ -330,10 +308,10 @@
|
|
|
330
308
|
|
|
331
309
|
<div class="nric-selected-person d-flex align-center justify-space-between ga-3 mb-5">
|
|
332
310
|
<span>{{ selectedNricSearchResult.name || "Unknown" }}</span>
|
|
333
|
-
<span
|
|
311
|
+
<span class="text-no-wrap">NRIC: {{ selectedNricSearchResult.nric || "N/A" }}</span>
|
|
334
312
|
</div>
|
|
335
313
|
|
|
336
|
-
<template v-if="
|
|
314
|
+
<template v-if="!visitor.contact && selectedNricContactOptions.length >= 1">
|
|
337
315
|
<h3 class="nric-options-section-title font-weight-bold mb-2">Contact</h3>
|
|
338
316
|
<v-radio-group v-model="selectedNricContact" hide-details color="success" class="nric-option-group">
|
|
339
317
|
<v-radio v-for="contact in selectedNricContactOptions" :key="contact" :label="contact" :value="contact"
|
|
@@ -341,7 +319,7 @@
|
|
|
341
319
|
</v-radio-group>
|
|
342
320
|
</template>
|
|
343
321
|
|
|
344
|
-
<template v-if="
|
|
322
|
+
<template v-if="!visitor.plateNumber && selectedNricPlateOptions.length >= 1">
|
|
345
323
|
<h3 class="nric-options-section-title font-weight-bold mb-2 mt-5">Vehicle</h3>
|
|
346
324
|
<v-radio-group v-model="selectedNricPlate" hide-details color="success" class="nric-option-group">
|
|
347
325
|
<v-radio v-for="plate in selectedNricPlateOptions" :key="plate" :label="plate" :value="plate"
|
|
@@ -1066,8 +1044,8 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1066
1044
|
const selectedPlate =
|
|
1067
1045
|
findMatchingPlateOption(plateOptions, visitor.plateNumber) || plateOptions[0] || "";
|
|
1068
1046
|
|
|
1069
|
-
const needsContactChoice =
|
|
1070
|
-
const needsPlateChoice =
|
|
1047
|
+
const needsContactChoice = !visitor.contact?.trim() && contactOptions.length > 1;
|
|
1048
|
+
const needsPlateChoice = !visitor.plateNumber?.trim() && plateOptions.length > 1;
|
|
1071
1049
|
|
|
1072
1050
|
if (needsContactChoice || needsPlateChoice) {
|
|
1073
1051
|
nricSearchMenu.value = false;
|
|
@@ -1088,23 +1066,6 @@ function selectAutofillSearchResult(item: Partial<TPeople>, source: Exclude<Auto
|
|
|
1088
1066
|
});
|
|
1089
1067
|
}
|
|
1090
1068
|
|
|
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
|
-
|
|
1108
1069
|
function closeNricAutofillOptions() {
|
|
1109
1070
|
dialog.nricAutofillOptions = false;
|
|
1110
1071
|
selectedNricSearchResult.value = null;
|
|
@@ -1542,15 +1503,6 @@ function handleUpdateVehicleNumber() {
|
|
|
1542
1503
|
debounceFetchPlateNumber();
|
|
1543
1504
|
}
|
|
1544
1505
|
|
|
1545
|
-
const handleFocusPlateNumber = () => {
|
|
1546
|
-
isPlateNumberFieldFocused.value = true;
|
|
1547
|
-
if (prop.mode === 'register') {
|
|
1548
|
-
|
|
1549
|
-
handleUpdateVehicleNumber();
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
1506
|
function backToSelection() {
|
|
1555
1507
|
emit("back");
|
|
1556
1508
|
message.value = "";
|
|
@@ -2156,22 +2108,10 @@ onMounted(() => {
|
|
|
2156
2108
|
}
|
|
2157
2109
|
|
|
2158
2110
|
.phone-search-results {
|
|
2159
|
-
display: flex;
|
|
2160
|
-
flex-direction: column;
|
|
2161
2111
|
max-height: 400px;
|
|
2162
|
-
overflow: hidden;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
.search-results-scroll {
|
|
2166
|
-
flex: 1 1 auto;
|
|
2167
|
-
min-height: 0;
|
|
2168
2112
|
overflow-y: auto;
|
|
2169
2113
|
}
|
|
2170
2114
|
|
|
2171
|
-
.search-results-close {
|
|
2172
|
-
flex: 0 0 auto;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
2115
|
.nric-options-card {
|
|
2176
2116
|
border-radius: 16px !important;
|
|
2177
2117
|
}
|
|
@@ -675,24 +675,6 @@
|
|
|
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>
|
|
696
678
|
|
|
697
679
|
<span
|
|
698
680
|
v-else-if="selectedVisitorObject[key]"
|
|
@@ -843,8 +825,10 @@
|
|
|
843
825
|
item-title="label"
|
|
844
826
|
item-value="value"
|
|
845
827
|
v-model="pass.status"
|
|
846
|
-
:disabled="
|
|
847
|
-
|
|
828
|
+
:disabled="
|
|
829
|
+
selectedVisitorObject.checkOut || pass.status == 'Removed'
|
|
830
|
+
"
|
|
831
|
+
></v-select>
|
|
848
832
|
<v-textarea
|
|
849
833
|
v-if="pass.status === 'Lost' || pass.status === 'Damaged'"
|
|
850
834
|
no-resize
|
|
@@ -852,7 +836,8 @@
|
|
|
852
836
|
class="w-100"
|
|
853
837
|
density="compact"
|
|
854
838
|
v-model="pass.remarks"
|
|
855
|
-
|
|
839
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
840
|
+
></v-textarea>
|
|
856
841
|
</v-row>
|
|
857
842
|
</div>
|
|
858
843
|
<div v-if="keyReturnStatuses.length > 0" class="mb-2">
|
|
@@ -879,8 +864,10 @@
|
|
|
879
864
|
item-title="label"
|
|
880
865
|
item-value="value"
|
|
881
866
|
v-model="key.status"
|
|
882
|
-
:disabled="
|
|
883
|
-
|
|
867
|
+
:disabled="
|
|
868
|
+
selectedVisitorObject.checkOut || key.status == 'Removed'
|
|
869
|
+
"
|
|
870
|
+
></v-select>
|
|
884
871
|
<v-textarea
|
|
885
872
|
v-if="key.status === 'Lost' || key.status === 'Damaged'"
|
|
886
873
|
no-resize
|
|
@@ -888,7 +875,8 @@
|
|
|
888
875
|
class="w-100"
|
|
889
876
|
density="compact"
|
|
890
877
|
v-model="key.remarks"
|
|
891
|
-
|
|
878
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
879
|
+
></v-textarea>
|
|
892
880
|
</v-row>
|
|
893
881
|
</div>
|
|
894
882
|
|
|
@@ -898,11 +886,11 @@
|
|
|
898
886
|
color="blue"
|
|
899
887
|
density="comfortable"
|
|
900
888
|
class="text-capitalize"
|
|
889
|
+
:disabled="selectedVisitorObject.checkOut"
|
|
901
890
|
:loading="loading.updatingPassKeys"
|
|
902
891
|
@click.stop="handleUpdatePassKeys"
|
|
892
|
+
>Update Pass/Keys</v-btn
|
|
903
893
|
>
|
|
904
|
-
Update Pass/Keys
|
|
905
|
-
</v-btn>
|
|
906
894
|
</v-row>
|
|
907
895
|
</v-card-text>
|
|
908
896
|
<v-toolbar class="pa-0" density="compact">
|
|
@@ -912,9 +900,8 @@
|
|
|
912
900
|
variant="text"
|
|
913
901
|
block
|
|
914
902
|
@click="dialog.returnPassesKeys = false"
|
|
903
|
+
>Close</v-btn
|
|
915
904
|
>
|
|
916
|
-
Close
|
|
917
|
-
</v-btn>
|
|
918
905
|
</v-col>
|
|
919
906
|
<v-col cols="6">
|
|
920
907
|
<v-btn
|
|
@@ -928,9 +915,8 @@
|
|
|
928
915
|
!canConfirmCheckout || selectedVisitorObject.checkOut
|
|
929
916
|
"
|
|
930
917
|
@click="proceedCheckout"
|
|
918
|
+
>Confirm Checkout</v-btn
|
|
931
919
|
>
|
|
932
|
-
Confirm Checkout
|
|
933
|
-
</v-btn>
|
|
934
920
|
</v-col>
|
|
935
921
|
</v-row>
|
|
936
922
|
</v-toolbar>
|
|
@@ -1456,15 +1442,6 @@ function mappedAttachments(attachments: string[]) {
|
|
|
1456
1442
|
});
|
|
1457
1443
|
}
|
|
1458
1444
|
|
|
1459
|
-
function mappedSnapshots(attachments: string[]) {
|
|
1460
|
-
return attachments.map((x, index) => {
|
|
1461
|
-
return {
|
|
1462
|
-
id: x,
|
|
1463
|
-
name: `Snapshot - ${x}`,
|
|
1464
|
-
};
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
1445
|
function normalizeDateOnly(value: string) {
|
|
1469
1446
|
if (!value) return "";
|
|
1470
1447
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
@@ -1,23 +1,58 @@
|
|
|
1
1
|
export function useCleaningSchedulePermission() {
|
|
2
|
+
const { hasPermission } = usePermission();
|
|
3
|
+
const { permissions } = useCleaningPermission();
|
|
4
|
+
|
|
2
5
|
const { userAppRole } = useLocalSetup();
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const canViewSchedules = computed(() => {
|
|
8
|
+
if (!userAppRole.value) return false;
|
|
9
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
10
|
+
return hasPermission(userAppRole.value, permissions, "cleaning-schedule-mgmt", "see-all-schedules");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const canViewScheduleDetails = computed(() => {
|
|
14
|
+
if (!userAppRole.value) return false;
|
|
15
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
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
|
+
});
|
|
13
48
|
|
|
14
49
|
return {
|
|
15
|
-
canViewSchedules
|
|
16
|
-
canViewScheduleDetails
|
|
17
|
-
canDownloadSchedule
|
|
18
|
-
canManageScheduleTasks
|
|
19
|
-
canGenerateChecklist
|
|
20
|
-
canViewHistory
|
|
21
|
-
canAddRemarks
|
|
50
|
+
canViewSchedules,
|
|
51
|
+
canViewScheduleDetails,
|
|
52
|
+
canDownloadSchedule,
|
|
53
|
+
canManageScheduleTasks,
|
|
54
|
+
canGenerateChecklist,
|
|
55
|
+
canViewHistory,
|
|
56
|
+
canAddRemarks,
|
|
22
57
|
};
|
|
23
58
|
}
|
|
@@ -236,26 +236,6 @@ export default function useServiceProvider() {
|
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
async function getByEmail({
|
|
240
|
-
email = "",
|
|
241
|
-
orgId = "",
|
|
242
|
-
siteId = "",
|
|
243
|
-
serviceProviderOrgId = "",
|
|
244
|
-
} = {}) {
|
|
245
|
-
return useNuxtApp().$api<Record<string, any>>(
|
|
246
|
-
"/api/service-providers/email",
|
|
247
|
-
{
|
|
248
|
-
method: "GET",
|
|
249
|
-
query: {
|
|
250
|
-
email,
|
|
251
|
-
...(orgId ? { orgId } : {}),
|
|
252
|
-
...(siteId ? { siteId } : {}),
|
|
253
|
-
...(serviceProviderOrgId ? { serviceProviderOrgId } : {}),
|
|
254
|
-
},
|
|
255
|
-
}
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
239
|
return {
|
|
260
240
|
serviceProviders,
|
|
261
241
|
serviceProvider,
|
|
@@ -274,6 +254,5 @@ export default function useServiceProvider() {
|
|
|
274
254
|
add,
|
|
275
255
|
addViaInvite,
|
|
276
256
|
updateStatus,
|
|
277
|
-
getByEmail,
|
|
278
257
|
};
|
|
279
258
|
}
|