@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
|
@@ -34,3 +34,14 @@ 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"
|
|
@@ -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="imageCover(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" :anpr="anpr" :site="site" />
|
|
101
101
|
|
|
102
102
|
<!-- Document Viewer -->
|
|
103
103
|
<DocumentViewer v-model="showDocumentViewer" :file-id="activeFileId" :file-name="activeFileName"
|
|
@@ -123,10 +123,18 @@ 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: ""
|
|
126
134
|
}
|
|
127
135
|
});
|
|
128
136
|
|
|
129
|
-
const { getFileUrl, urlToFile, getFileById } = useFile();
|
|
137
|
+
const { getFileUrl, urlToFile, getFileById, getFileUrlAnpr } = useFile();
|
|
130
138
|
|
|
131
139
|
const rawFileArray = ref<{ file: File, _id: string, preview: boolean, mimeType: string }[]>([]);
|
|
132
140
|
const loading = ref(true);
|
|
@@ -159,6 +167,12 @@ function openDocumentViewer(id: string, name: string, mimeType: string) {
|
|
|
159
167
|
showDocumentViewer.value = true;
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
const imageCover = (id: string) => {
|
|
171
|
+
if(props.anpr && props.site){
|
|
172
|
+
return getFileUrlAnpr(`${props.site}/${id}`)
|
|
173
|
+
} else return getFileUrl(id)
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
function getFileIcon(mimeType: string): string {
|
|
163
177
|
if (mimeType.startsWith('image/')) return 'mdi-image';
|
|
164
178
|
if (mimeType.startsWith('video/')) return 'mdi-video';
|
|
@@ -206,7 +220,11 @@ watchEffect(async () => {
|
|
|
206
220
|
|
|
207
221
|
try {
|
|
208
222
|
// Try to get the actual file with proper MIME type detection from blob
|
|
209
|
-
const fileUrl = getFileUrl(item.id);
|
|
223
|
+
// const fileUrl = getFileUrl(item.id);
|
|
224
|
+
let fileUrl = ""
|
|
225
|
+
if(props.anpr && props.site){
|
|
226
|
+
fileUrl = getFileUrlAnpr(`${props.site}/${item.id}`)
|
|
227
|
+
} else fileUrl = getFileUrl(item.id);
|
|
210
228
|
file = await urlToFile(fileUrl, item.name);
|
|
211
229
|
mimeType = file.type || 'application/octet-stream';
|
|
212
230
|
|
|
@@ -984,60 +984,44 @@
|
|
|
984
984
|
<div
|
|
985
985
|
v-for="item in formattedActivePatrolItems"
|
|
986
986
|
:key="item.id || item._id"
|
|
987
|
-
class="
|
|
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
|
-
"
|
|
987
|
+
class="active-patrol-row"
|
|
996
988
|
>
|
|
997
|
-
<div
|
|
998
|
-
<strong
|
|
999
|
-
|
|
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>
|
|
989
|
+
<div class="active-patrol-main">
|
|
990
|
+
<strong>{{ item.title || item.name }}</strong>
|
|
991
|
+
<span>{{ item.subtitle || item.location || "" }}</span>
|
|
1011
992
|
</div>
|
|
1012
993
|
|
|
1013
|
-
<div
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
item.
|
|
1022
|
-
|
|
994
|
+
<div class="active-patrol-assignees">
|
|
995
|
+
<div class="active-patrol-assignee">
|
|
996
|
+
<AvatarMain :name="item.primaryAssignee" :size="24" />
|
|
997
|
+
<span>{{ item.primaryAssignee }}</span>
|
|
998
|
+
</div>
|
|
999
|
+
<span
|
|
1000
|
+
v-if="item.extraAssigneeCount > 0"
|
|
1001
|
+
class="active-patrol-more-pill active-patrol-tooltip"
|
|
1002
|
+
:data-tooltip="item.extraAssignees.join(', ')"
|
|
1003
|
+
>
|
|
1004
|
+
+{{ item.extraAssigneeCount }}
|
|
1005
|
+
</span>
|
|
1023
1006
|
</div>
|
|
1024
1007
|
|
|
1025
|
-
<div
|
|
1008
|
+
<div class="active-patrol-statuses">
|
|
1026
1009
|
<div
|
|
1027
|
-
|
|
1028
|
-
style="
|
|
1029
|
-
padding: 6px 16px;
|
|
1030
|
-
border-radius: 20px;
|
|
1031
|
-
font-size: 13px;
|
|
1032
|
-
font-weight: 500;
|
|
1033
|
-
text-transform: capitalize;
|
|
1034
|
-
"
|
|
1010
|
+
class="active-patrol-status-pill"
|
|
1011
|
+
:style="getPatrolStatusStyle(item.primaryStatus)"
|
|
1035
1012
|
>
|
|
1036
|
-
{{ item.
|
|
1013
|
+
{{ item.primaryStatus }}
|
|
1037
1014
|
</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>
|
|
1038
1022
|
</div>
|
|
1039
1023
|
|
|
1040
|
-
<div
|
|
1024
|
+
<div class="active-patrol-action">
|
|
1041
1025
|
<v-btn
|
|
1042
1026
|
variant="outlined"
|
|
1043
1027
|
class="text-none font-weight-medium"
|
|
@@ -1545,30 +1529,16 @@ const { userAppRole } = useLocalSetup();
|
|
|
1545
1529
|
const { downloadPDF } = usePDFDownload();
|
|
1546
1530
|
const route = useRoute();
|
|
1547
1531
|
|
|
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
|
-
|
|
1562
1532
|
const propertyWidgetPermissions: Record<string, string[]> = {
|
|
1563
1533
|
workOrders: [
|
|
1564
|
-
"
|
|
1534
|
+
"work_orders:see-all-work-orders",
|
|
1565
1535
|
"work-order:see-all-work-orders",
|
|
1566
1536
|
],
|
|
1567
1537
|
incidents: ["incident-reports:see-incident-reports"],
|
|
1568
1538
|
visitors: ["visitorManagement:see-all-visitor"],
|
|
1569
1539
|
facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
|
|
1570
1540
|
activity: [
|
|
1571
|
-
"
|
|
1541
|
+
"work_orders:see-all-work-orders",
|
|
1572
1542
|
"work-order:see-all-work-orders",
|
|
1573
1543
|
],
|
|
1574
1544
|
upcomingEvents: ["event-mgmt:see-all-event"],
|
|
@@ -1578,7 +1548,7 @@ const propertyWidgetPermissions: Record<string, string[]> = {
|
|
|
1578
1548
|
"facility-booking-mgmt:see-all-facility-booking",
|
|
1579
1549
|
],
|
|
1580
1550
|
workOrderStatus: [
|
|
1581
|
-
"
|
|
1551
|
+
"work_orders:see-all-work-orders",
|
|
1582
1552
|
"work-order:see-all-work-orders",
|
|
1583
1553
|
],
|
|
1584
1554
|
feedbacks: ["feedbacks:see-all-feedback"],
|
|
@@ -1603,14 +1573,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
|
|
|
1603
1573
|
"work_order:see-all-work-orders",
|
|
1604
1574
|
"work-order:see-all-work-orders",
|
|
1605
1575
|
"work-orders:see-all-work-orders",
|
|
1576
|
+
"work_orders:see-all-work-orders",
|
|
1606
1577
|
],
|
|
1607
|
-
|
|
1608
|
-
"
|
|
1609
|
-
"
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
"
|
|
1613
|
-
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
1578
|
+
recentActivity: [
|
|
1579
|
+
"work_orders:see-all-work-orders",
|
|
1580
|
+
"work_order:see-all-work-orders",
|
|
1581
|
+
"work-order:see-all-work-orders",
|
|
1582
|
+
"work-orders:see-all-work-orders",
|
|
1583
|
+
"work_orders:see-all-work-orders",
|
|
1614
1584
|
],
|
|
1615
1585
|
staffStatus: ["attendance-mgmt:see-all-attendance"],
|
|
1616
1586
|
attendance: ["attendance-mgmt:see-all-attendance"],
|
|
@@ -1633,9 +1603,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
|
|
|
1633
1603
|
activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
|
|
1634
1604
|
};
|
|
1635
1605
|
|
|
1606
|
+
function hasSchedulePermission(permissions: string[], action: string) {
|
|
1607
|
+
return permissions.some((permission) =>
|
|
1608
|
+
permission.endsWith(`-schedule-mgmt:${action}`)
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1636
1612
|
function canViewWidget(key: string) {
|
|
1637
1613
|
const permissions = userAppRole.value?.permissions;
|
|
1638
|
-
const dashboardPermission = dashboardWidgetPermissions[key];
|
|
1639
1614
|
const modulePermissions = isPropertyMode.value
|
|
1640
1615
|
? propertyWidgetPermissions[key]
|
|
1641
1616
|
: moduleWidgetPermissions[key];
|
|
@@ -1643,20 +1618,22 @@ function canViewWidget(key: string) {
|
|
|
1643
1618
|
if (!permissions) return true;
|
|
1644
1619
|
if (permissions.includes("*")) return true;
|
|
1645
1620
|
|
|
1621
|
+
if (
|
|
1622
|
+
!isPropertyMode.value &&
|
|
1623
|
+
["taskSchedule", "taskCompleted"].includes(key)
|
|
1624
|
+
) {
|
|
1625
|
+
return hasSchedulePermission(permissions, "see-all-schedules");
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1646
1628
|
if (isPropertyMode.value && modulePermissions) {
|
|
1647
1629
|
return modulePermissions.some((permission) =>
|
|
1648
1630
|
permissions.includes(permission)
|
|
1649
1631
|
);
|
|
1650
1632
|
}
|
|
1651
1633
|
|
|
1652
|
-
if (
|
|
1653
|
-
return (
|
|
1654
|
-
|
|
1655
|
-
Boolean(
|
|
1656
|
-
modulePermissions?.some((permission) =>
|
|
1657
|
-
permissions.includes(permission)
|
|
1658
|
-
)
|
|
1659
|
-
)
|
|
1634
|
+
if (modulePermissions) {
|
|
1635
|
+
return modulePermissions.some((permission) =>
|
|
1636
|
+
permissions.includes(permission)
|
|
1660
1637
|
);
|
|
1661
1638
|
}
|
|
1662
1639
|
|
|
@@ -2774,18 +2751,23 @@ const activePatrolItems = computed<any[]>(
|
|
|
2774
2751
|
|
|
2775
2752
|
const formattedActivePatrolItems = computed<any[]>(() => {
|
|
2776
2753
|
return activePatrolItems.value.map((item) => {
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
if (Array.isArray(person)) {
|
|
2783
|
-
person = person.join(", ");
|
|
2784
|
-
}
|
|
2754
|
+
const statuses = normalizeListValue(item.status || "Pending");
|
|
2755
|
+
const assignees = normalizeListValue(
|
|
2756
|
+
item.person || item.assignedTo || "Unassigned"
|
|
2757
|
+
);
|
|
2758
|
+
|
|
2785
2759
|
return {
|
|
2786
2760
|
...item,
|
|
2787
|
-
status,
|
|
2788
|
-
|
|
2761
|
+
status: statuses.join(", "),
|
|
2762
|
+
statuses,
|
|
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),
|
|
2789
2771
|
};
|
|
2790
2772
|
});
|
|
2791
2773
|
});
|
|
@@ -3955,6 +3937,20 @@ function normalizeStatus(value: any) {
|
|
|
3955
3937
|
return text.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3956
3938
|
}
|
|
3957
3939
|
|
|
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
|
+
|
|
3958
3954
|
function getStatusTone(status: any) {
|
|
3959
3955
|
const s = String(status || "").toLowerCase();
|
|
3960
3956
|
if (
|
|
@@ -4422,6 +4418,151 @@ function formatDashboardTime(value?: string) {
|
|
|
4422
4418
|
border: 2px solid #f9c97c;
|
|
4423
4419
|
}
|
|
4424
4420
|
|
|
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
|
+
|
|
4425
4566
|
.figma-icon-badge {
|
|
4426
4567
|
align-items: center;
|
|
4427
4568
|
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="getFinalUrl(x)" :src="getFinalUrl(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="getFinalUrl(x)" />
|
|
25
25
|
</video>
|
|
26
26
|
</v-row>
|
|
27
27
|
<template v-slot:placeholder>
|
|
@@ -78,11 +78,19 @@ 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
|
|
81
89
|
}
|
|
82
90
|
});
|
|
83
91
|
|
|
84
92
|
|
|
85
|
-
const { getFileUrl, urlToFile } = useFile()
|
|
93
|
+
const { getFileUrl, urlToFile, getFileUrlAnpr } = useFile()
|
|
86
94
|
const overlay = defineModel({ required: true, default: false });
|
|
87
95
|
const activeIndex = ref(0);
|
|
88
96
|
const fileTypes = ref<Record<string, "image" | "video" | "other">>({});
|
|
@@ -107,10 +115,13 @@ async function resolveFileTypes() {
|
|
|
107
115
|
|
|
108
116
|
for (const x of props.files) {
|
|
109
117
|
try {
|
|
110
|
-
|
|
118
|
+
let url = ""
|
|
119
|
+
if (props.anpr && props.site) {
|
|
120
|
+
url = getFileUrlAnpr(`${props.site}/${x}`)
|
|
121
|
+
} else url = getFileUrl(x);
|
|
111
122
|
const file = await urlToFile(url, x);
|
|
112
123
|
const type = file?.type;
|
|
113
|
-
|
|
124
|
+
|
|
114
125
|
|
|
115
126
|
if (type?.startsWith("video")) fileTypes.value[x] = "video";
|
|
116
127
|
else if (type?.startsWith("image")) fileTypes.value[x] = "image";
|
|
@@ -129,6 +140,13 @@ watch(
|
|
|
129
140
|
);
|
|
130
141
|
|
|
131
142
|
|
|
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
|
+
|
|
132
150
|
</script>
|
|
133
151
|
|
|
134
152
|
<style scoped>
|
|
@@ -304,31 +304,38 @@
|
|
|
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
|
-
class="
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
307
|
+
<v-toolbar class="pa-0" density="compact">
|
|
308
|
+
<v-row no-gutters>
|
|
309
|
+
<v-col cols="6" class="pa-0">
|
|
310
|
+
<v-btn
|
|
311
|
+
block
|
|
312
|
+
variant="text"
|
|
313
|
+
class="text-none"
|
|
314
|
+
size="large"
|
|
315
|
+
height="48"
|
|
316
|
+
:disabled="rejectModal.submitting"
|
|
317
|
+
@click="closeRejectDialog"
|
|
318
|
+
>
|
|
319
|
+
Cancel
|
|
320
|
+
</v-btn>
|
|
321
|
+
</v-col>
|
|
322
|
+
|
|
323
|
+
<v-col cols="6" class="pa-0">
|
|
324
|
+
<v-btn
|
|
325
|
+
block
|
|
326
|
+
variant="flat"
|
|
327
|
+
color="black"
|
|
328
|
+
class="text-none font-weight-bold rounded-0"
|
|
329
|
+
height="48"
|
|
330
|
+
:loading="rejectModal.submitting"
|
|
331
|
+
:disabled="!isRejectModalValid || rejectModal.submitting"
|
|
332
|
+
@click="submitRejectDialog"
|
|
333
|
+
>
|
|
334
|
+
Confirm
|
|
335
|
+
</v-btn>
|
|
336
|
+
</v-col>
|
|
337
|
+
</v-row>
|
|
338
|
+
</v-toolbar>
|
|
332
339
|
</v-card>
|
|
333
340
|
</v-dialog>
|
|
334
341
|
|
|
@@ -149,21 +149,23 @@
|
|
|
149
149
|
</template>
|
|
150
150
|
|
|
151
151
|
<template #footer>
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
152
|
+
<div class="d-flex justify-center ga-3 w-100">
|
|
153
|
+
<v-btn
|
|
154
|
+
variant="text"
|
|
155
|
+
@click="confirmDialog = false"
|
|
156
|
+
:disabled="updateLoading"
|
|
157
|
+
>
|
|
158
|
+
Close
|
|
159
|
+
</v-btn>
|
|
160
|
+
<v-btn
|
|
161
|
+
color="primary"
|
|
162
|
+
variant="flat"
|
|
163
|
+
@click="handleUpdateMemberStatus"
|
|
164
|
+
:loading="updateLoading"
|
|
165
|
+
>
|
|
166
|
+
{{ updateActionText }} Member
|
|
167
|
+
</v-btn>
|
|
168
|
+
</div>
|
|
167
169
|
</template>
|
|
168
170
|
</ConfirmDialog>
|
|
169
171
|
|