@7365admin1/layer-common 3.0.10 → 3.0.12
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/DashboardMain.vue +620 -133
- package/components/IncidentReport/IncidentInformation.vue +1 -1
- package/components/IncidentReport/IncidentInformationDownload.vue +1 -1
- package/components/Input/InputPhoneNumberV2.vue +33 -29
- package/components/Input/Password.vue +1 -0
- package/components/Layout/NavigationDrawer.vue +4 -1
- package/components/OnlineFormFill.vue +70 -7
- package/components/VehicleForm.vue +6 -4
- package/components/VehicleManagement.vue +16 -1
- package/components/VisitorDataFromScannedQRCodeForm.vue +15 -2
- package/components/VisitorManagement.vue +19 -4
- package/composables/useAttendancePermission.ts +5 -5
- package/composables/useBulletinBoardPermission.ts +5 -5
- package/composables/useCommonPermission.ts +41 -1
- package/composables/useSettingsPermission.ts +11 -11
- package/composables/useVisitor.ts +1 -0
- package/package.json +1 -1
- package/types/visitor.d.ts +1 -0
|
@@ -64,7 +64,11 @@
|
|
|
64
64
|
size="small"
|
|
65
65
|
:style="
|
|
66
66
|
selectedRange === range.value
|
|
67
|
-
? {
|
|
67
|
+
? {
|
|
68
|
+
backgroundColor: '#e2e8f0',
|
|
69
|
+
color: '#0b2f47',
|
|
70
|
+
fontWeight: '600',
|
|
71
|
+
}
|
|
68
72
|
: { backgroundColor: 'transparent', color: '#64748b' }
|
|
69
73
|
"
|
|
70
74
|
>
|
|
@@ -92,7 +96,13 @@
|
|
|
92
96
|
lg="3"
|
|
93
97
|
class="d-flex"
|
|
94
98
|
>
|
|
95
|
-
<v-card
|
|
99
|
+
<v-card
|
|
100
|
+
flat
|
|
101
|
+
border
|
|
102
|
+
rounded="lg"
|
|
103
|
+
class="pa-4 flex-grow-1"
|
|
104
|
+
style="min-height: 158px"
|
|
105
|
+
>
|
|
96
106
|
<div class="d-flex align-start justify-space-between">
|
|
97
107
|
<div
|
|
98
108
|
class="d-inline-flex align-center justify-center rounded-lg"
|
|
@@ -127,7 +137,9 @@
|
|
|
127
137
|
>
|
|
128
138
|
<template #prepend>
|
|
129
139
|
<v-icon
|
|
130
|
-
:icon="
|
|
140
|
+
:icon="
|
|
141
|
+
card.filter.model === option ? 'mdi-check' : 'mdi-blank'
|
|
142
|
+
"
|
|
131
143
|
size="16"
|
|
132
144
|
/>
|
|
133
145
|
</template>
|
|
@@ -143,7 +155,9 @@
|
|
|
143
155
|
{{ card.title }}
|
|
144
156
|
</p>
|
|
145
157
|
<template
|
|
146
|
-
v-if="
|
|
158
|
+
v-if="
|
|
159
|
+
card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
|
|
160
|
+
"
|
|
147
161
|
>
|
|
148
162
|
<div class="d-flex flex-column ga-2 mt-2">
|
|
149
163
|
<div
|
|
@@ -284,7 +298,12 @@
|
|
|
284
298
|
>
|
|
285
299
|
<span
|
|
286
300
|
class="rounded-circle"
|
|
287
|
-
style="
|
|
301
|
+
style="
|
|
302
|
+
width: 12px;
|
|
303
|
+
height: 12px;
|
|
304
|
+
display: inline-block;
|
|
305
|
+
background-color: #ff9b9b;
|
|
306
|
+
"
|
|
288
307
|
/>
|
|
289
308
|
{{ primaryLegendLabel }}
|
|
290
309
|
</span>
|
|
@@ -293,7 +312,12 @@
|
|
|
293
312
|
>
|
|
294
313
|
<span
|
|
295
314
|
class="rounded-circle"
|
|
296
|
-
style="
|
|
315
|
+
style="
|
|
316
|
+
width: 12px;
|
|
317
|
+
height: 12px;
|
|
318
|
+
display: inline-block;
|
|
319
|
+
background-color: #e53935;
|
|
320
|
+
"
|
|
297
321
|
/>
|
|
298
322
|
{{ secondaryLegendLabel }}
|
|
299
323
|
</span>
|
|
@@ -334,7 +358,8 @@
|
|
|
334
358
|
</div>
|
|
335
359
|
<div class="text-caption text-blue-grey mt-1 text-truncate">
|
|
336
360
|
{{ item.subtitle
|
|
337
|
-
}}<template v-if="item.subtitle && item.time">
|
|
361
|
+
}}<template v-if="item.subtitle && item.time">
|
|
362
|
+
• </template
|
|
338
363
|
>{{ item.time }}
|
|
339
364
|
</div>
|
|
340
365
|
</div>
|
|
@@ -429,25 +454,50 @@
|
|
|
429
454
|
v-for="item in feedbackItems"
|
|
430
455
|
:key="item.id"
|
|
431
456
|
class="d-flex align-center justify-space-between"
|
|
432
|
-
style="
|
|
457
|
+
style="
|
|
458
|
+
padding: 16px 20px;
|
|
459
|
+
border: 1px solid #e2e8f0;
|
|
460
|
+
border-radius: 12px;
|
|
461
|
+
margin-bottom: 12px;
|
|
462
|
+
background-color: #ffffff;
|
|
463
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
464
|
+
"
|
|
433
465
|
>
|
|
434
466
|
<div style="flex: 1; min-width: 140px">
|
|
435
467
|
<strong
|
|
436
|
-
style="
|
|
468
|
+
style="
|
|
469
|
+
display: block;
|
|
470
|
+
font-size: 15px;
|
|
471
|
+
font-weight: 700;
|
|
472
|
+
color: #0b2f47;
|
|
473
|
+
margin-bottom: 4px;
|
|
474
|
+
"
|
|
437
475
|
>{{ item.title }}</strong
|
|
438
476
|
>
|
|
439
|
-
<span style="font-size: 13px; color: #94a3b8">{{
|
|
477
|
+
<span style="font-size: 13px; color: #94a3b8">{{
|
|
478
|
+
item.subtitle
|
|
479
|
+
}}</span>
|
|
440
480
|
</div>
|
|
441
481
|
|
|
442
|
-
<div
|
|
482
|
+
<div
|
|
483
|
+
style="flex: 1; display: flex; align-items: center; gap: 12px"
|
|
484
|
+
>
|
|
443
485
|
<AvatarMain :name="item.person" :size="36" />
|
|
444
|
-
<span style="font-size: 14px; color: #64748b">{{
|
|
486
|
+
<span style="font-size: 14px; color: #64748b">{{
|
|
487
|
+
item.person
|
|
488
|
+
}}</span>
|
|
445
489
|
</div>
|
|
446
490
|
|
|
447
491
|
<div style="flex: 1; display: flex; justify-content: center">
|
|
448
492
|
<div
|
|
449
493
|
:style="getStatusPillStyle(item.status)"
|
|
450
|
-
style="
|
|
494
|
+
style="
|
|
495
|
+
padding: 6px 16px;
|
|
496
|
+
border-radius: 20px;
|
|
497
|
+
font-size: 13px;
|
|
498
|
+
font-weight: 500;
|
|
499
|
+
text-transform: capitalize;
|
|
500
|
+
"
|
|
451
501
|
>
|
|
452
502
|
{{ item.status || "To-Do" }}
|
|
453
503
|
</div>
|
|
@@ -459,7 +509,12 @@
|
|
|
459
509
|
class="text-none font-weight-medium"
|
|
460
510
|
size="small"
|
|
461
511
|
color="#3b82f6"
|
|
462
|
-
style="
|
|
512
|
+
style="
|
|
513
|
+
border-color: #e2e8f0;
|
|
514
|
+
border-radius: 8px;
|
|
515
|
+
padding: 0 16px;
|
|
516
|
+
height: 32px;
|
|
517
|
+
"
|
|
463
518
|
@click="goToListItem(item, 'feedbacks')"
|
|
464
519
|
>View</v-btn
|
|
465
520
|
>
|
|
@@ -586,7 +641,10 @@
|
|
|
586
641
|
`figma-icon-badge-${getListTone(item)}`,
|
|
587
642
|
]"
|
|
588
643
|
>
|
|
589
|
-
<v-icon
|
|
644
|
+
<v-icon
|
|
645
|
+
:icon="item.icon || 'mdi-alert-circle-outline'"
|
|
646
|
+
size="15"
|
|
647
|
+
/>
|
|
590
648
|
</span>
|
|
591
649
|
<div class="figma-list-main">
|
|
592
650
|
<strong>{{ item.title }}</strong>
|
|
@@ -661,7 +719,9 @@
|
|
|
661
719
|
</div>
|
|
662
720
|
|
|
663
721
|
<div class="feedback-person">
|
|
664
|
-
<span class="feedback-avatar">{{
|
|
722
|
+
<span class="feedback-avatar">{{
|
|
723
|
+
getInitials(item.person)
|
|
724
|
+
}}</span>
|
|
665
725
|
<span>{{ item.person }}</span>
|
|
666
726
|
</div>
|
|
667
727
|
|
|
@@ -703,19 +763,29 @@
|
|
|
703
763
|
</button>
|
|
704
764
|
</div>
|
|
705
765
|
<div class="figma-panel-body">
|
|
706
|
-
<div
|
|
707
|
-
v-if="activePatrolItems.length"
|
|
708
|
-
class="figma-list mt-2"
|
|
709
|
-
>
|
|
766
|
+
<div v-if="activePatrolItems.length" class="figma-list mt-2">
|
|
710
767
|
<div
|
|
711
768
|
v-for="item in activePatrolItems"
|
|
712
769
|
:key="item.id || item._id"
|
|
713
770
|
class="d-flex align-center justify-space-between"
|
|
714
|
-
style="
|
|
771
|
+
style="
|
|
772
|
+
padding: 16px 20px;
|
|
773
|
+
border: 1px solid #e2e8f0;
|
|
774
|
+
border-radius: 12px;
|
|
775
|
+
margin-bottom: 12px;
|
|
776
|
+
background-color: #ffffff;
|
|
777
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
778
|
+
"
|
|
715
779
|
>
|
|
716
780
|
<div style="flex: 1; min-width: 140px">
|
|
717
781
|
<strong
|
|
718
|
-
style="
|
|
782
|
+
style="
|
|
783
|
+
display: block;
|
|
784
|
+
font-size: 15px;
|
|
785
|
+
font-weight: 700;
|
|
786
|
+
color: #0b2f47;
|
|
787
|
+
margin-bottom: 4px;
|
|
788
|
+
"
|
|
719
789
|
>{{ item.title || item.name }}</strong
|
|
720
790
|
>
|
|
721
791
|
<span style="font-size: 13px; color: #94a3b8">{{
|
|
@@ -723,8 +793,13 @@
|
|
|
723
793
|
}}</span>
|
|
724
794
|
</div>
|
|
725
795
|
|
|
726
|
-
<div
|
|
727
|
-
|
|
796
|
+
<div
|
|
797
|
+
style="flex: 1; display: flex; align-items: center; gap: 12px"
|
|
798
|
+
>
|
|
799
|
+
<AvatarMain
|
|
800
|
+
:name="item.person || item.assignedTo"
|
|
801
|
+
:size="36"
|
|
802
|
+
/>
|
|
728
803
|
<span style="font-size: 14px; color: #64748b">{{
|
|
729
804
|
item.person || item.assignedTo
|
|
730
805
|
}}</span>
|
|
@@ -733,7 +808,13 @@
|
|
|
733
808
|
<div style="flex: 1; display: flex; justify-content: center">
|
|
734
809
|
<div
|
|
735
810
|
:style="getPatrolStatusStyle(item.status)"
|
|
736
|
-
style="
|
|
811
|
+
style="
|
|
812
|
+
padding: 6px 16px;
|
|
813
|
+
border-radius: 20px;
|
|
814
|
+
font-size: 13px;
|
|
815
|
+
font-weight: 500;
|
|
816
|
+
text-transform: capitalize;
|
|
817
|
+
"
|
|
737
818
|
>
|
|
738
819
|
{{ item.status }}
|
|
739
820
|
</div>
|
|
@@ -745,7 +826,12 @@
|
|
|
745
826
|
class="text-none font-weight-medium"
|
|
746
827
|
size="small"
|
|
747
828
|
color="#3b82f6"
|
|
748
|
-
style="
|
|
829
|
+
style="
|
|
830
|
+
border-color: #e2e8f0;
|
|
831
|
+
border-radius: 8px;
|
|
832
|
+
padding: 0 16px;
|
|
833
|
+
height: 32px;
|
|
834
|
+
"
|
|
749
835
|
@click="goToDashboardRoute('virtual-patrol')"
|
|
750
836
|
>View</v-btn
|
|
751
837
|
>
|
|
@@ -775,17 +861,37 @@
|
|
|
775
861
|
v-for="item in securityStaffItems"
|
|
776
862
|
:key="item._id || item.id"
|
|
777
863
|
class="d-flex align-center justify-space-between"
|
|
778
|
-
style="
|
|
864
|
+
style="
|
|
865
|
+
padding: 16px 20px;
|
|
866
|
+
border: 1px solid #e2e8f0;
|
|
867
|
+
border-radius: 12px;
|
|
868
|
+
margin-bottom: 12px;
|
|
869
|
+
background-color: #ffffff;
|
|
870
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
871
|
+
"
|
|
779
872
|
>
|
|
780
873
|
<div style="display: flex; align-items: center; gap: 16px">
|
|
781
874
|
<AvatarMain
|
|
782
|
-
:name="
|
|
875
|
+
:name="
|
|
876
|
+
item.personName ||
|
|
877
|
+
item.employeeName ||
|
|
878
|
+
item.userName ||
|
|
879
|
+
'Unassigned'
|
|
880
|
+
"
|
|
783
881
|
:size="40"
|
|
784
882
|
/>
|
|
785
883
|
<div>
|
|
786
884
|
<strong
|
|
787
|
-
style="
|
|
788
|
-
|
|
885
|
+
style="
|
|
886
|
+
display: block;
|
|
887
|
+
font-size: 15px;
|
|
888
|
+
font-weight: 700;
|
|
889
|
+
color: #0b2f47;
|
|
890
|
+
margin-bottom: 4px;
|
|
891
|
+
"
|
|
892
|
+
>{{
|
|
893
|
+
item.userName || item.personName || "Unassigned"
|
|
894
|
+
}}</strong
|
|
789
895
|
>
|
|
790
896
|
<span style="font-size: 13px; color: #94a3b8">{{
|
|
791
897
|
item.role || item.designation || "Officer"
|
|
@@ -796,7 +902,12 @@
|
|
|
796
902
|
<div style="flex: 0 0 auto">
|
|
797
903
|
<div
|
|
798
904
|
:style="getStaffStatusPillStyle(item.status)"
|
|
799
|
-
style="
|
|
905
|
+
style="
|
|
906
|
+
padding: 6px 16px;
|
|
907
|
+
border-radius: 20px;
|
|
908
|
+
font-size: 13px;
|
|
909
|
+
font-weight: 500;
|
|
910
|
+
"
|
|
800
911
|
>
|
|
801
912
|
{{ item.status || "On duty" }}
|
|
802
913
|
</div>
|
|
@@ -820,25 +931,50 @@
|
|
|
820
931
|
v-for="item in feedbackItems"
|
|
821
932
|
:key="item.id"
|
|
822
933
|
class="d-flex align-center justify-space-between"
|
|
823
|
-
style="
|
|
934
|
+
style="
|
|
935
|
+
padding: 16px 20px;
|
|
936
|
+
border: 1px solid #e2e8f0;
|
|
937
|
+
border-radius: 12px;
|
|
938
|
+
margin-bottom: 12px;
|
|
939
|
+
background-color: #ffffff;
|
|
940
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
941
|
+
"
|
|
824
942
|
>
|
|
825
943
|
<div style="flex: 1; min-width: 140px">
|
|
826
944
|
<strong
|
|
827
|
-
style="
|
|
945
|
+
style="
|
|
946
|
+
display: block;
|
|
947
|
+
font-size: 15px;
|
|
948
|
+
font-weight: 700;
|
|
949
|
+
color: #0b2f47;
|
|
950
|
+
margin-bottom: 4px;
|
|
951
|
+
"
|
|
828
952
|
>{{ item.title }}</strong
|
|
829
953
|
>
|
|
830
|
-
<span style="font-size: 13px; color: #94a3b8">{{
|
|
954
|
+
<span style="font-size: 13px; color: #94a3b8">{{
|
|
955
|
+
item.subtitle
|
|
956
|
+
}}</span>
|
|
831
957
|
</div>
|
|
832
958
|
|
|
833
|
-
<div
|
|
959
|
+
<div
|
|
960
|
+
style="flex: 1; display: flex; align-items: center; gap: 12px"
|
|
961
|
+
>
|
|
834
962
|
<AvatarMain :name="item.person" :size="36" />
|
|
835
|
-
<span style="font-size: 14px; color: #64748b">{{
|
|
963
|
+
<span style="font-size: 14px; color: #64748b">{{
|
|
964
|
+
item.person
|
|
965
|
+
}}</span>
|
|
836
966
|
</div>
|
|
837
967
|
|
|
838
968
|
<div style="flex: 1; display: flex; justify-content: center">
|
|
839
969
|
<div
|
|
840
970
|
:style="getStatusPillStyle(item.status)"
|
|
841
|
-
style="
|
|
971
|
+
style="
|
|
972
|
+
padding: 6px 16px;
|
|
973
|
+
border-radius: 20px;
|
|
974
|
+
font-size: 13px;
|
|
975
|
+
font-weight: 500;
|
|
976
|
+
text-transform: capitalize;
|
|
977
|
+
"
|
|
842
978
|
>
|
|
843
979
|
{{ item.status || "To-Do" }}
|
|
844
980
|
</div>
|
|
@@ -850,7 +986,12 @@
|
|
|
850
986
|
class="text-none font-weight-medium"
|
|
851
987
|
size="small"
|
|
852
988
|
color="#3b82f6"
|
|
853
|
-
style="
|
|
989
|
+
style="
|
|
990
|
+
border-color: #e2e8f0;
|
|
991
|
+
border-radius: 8px;
|
|
992
|
+
padding: 0 16px;
|
|
993
|
+
height: 32px;
|
|
994
|
+
"
|
|
854
995
|
@click="goToListItem(item, 'feedbacks')"
|
|
855
996
|
>View</v-btn
|
|
856
997
|
>
|
|
@@ -1085,6 +1226,58 @@ const {
|
|
|
1085
1226
|
getDashboardHygieneFeedbacks,
|
|
1086
1227
|
} = useDashboard();
|
|
1087
1228
|
|
|
1229
|
+
const { userAppRole } = useLocalSetup();
|
|
1230
|
+
const { hasPermission } = usePermission();
|
|
1231
|
+
const { dashboardPermissions } = useCommonPermissions();
|
|
1232
|
+
|
|
1233
|
+
function canViewWidget(key: string) {
|
|
1234
|
+
if (!userAppRole.value) return true;
|
|
1235
|
+
if (userAppRole.value.permissions?.includes("*")) return true;
|
|
1236
|
+
|
|
1237
|
+
const widgetToModulePermissionMap: Record<string, string[]> = {
|
|
1238
|
+
workOrders: ["work-orders:see-all-work-orders"],
|
|
1239
|
+
incidents: ["incident-reports:see-incident-reports", "incident-reports:see-all-incident-reports"],
|
|
1240
|
+
visitors: ["visitor:see-all-visitor"],
|
|
1241
|
+
facilityBookings: ["facility-booking:see-all-facility-booking"],
|
|
1242
|
+
patrolCompliance: ["virtual-patrol:see-all-virtual-patrol-logs", "virtual-patrol:see-all-virtual-patrol-route"],
|
|
1243
|
+
staffStatus: ["attendance:see-all-attendance"],
|
|
1244
|
+
attendance: ["attendance:see-all-attendance"],
|
|
1245
|
+
feedbacks: ["feedbacks:see-all-feedbacks"],
|
|
1246
|
+
upcomingEvents: ["bulletin-boards:see-all-bulletin-boards", "bulletin-board:see-all-bulletin-board"],
|
|
1247
|
+
workOrderStatus: ["work-orders:see-all-work-orders"],
|
|
1248
|
+
activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs", "virtual-patrol:see-all-virtual-patrol-route"],
|
|
1249
|
+
taskSchedule: ["cleaning-schedule-mgmt:see-all-schedules"]
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
const modulePerms = widgetToModulePermissionMap[key];
|
|
1253
|
+
if (modulePerms && modulePerms.some(p => userAppRole.value.permissions?.includes(p))) {
|
|
1254
|
+
return true;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const actionMap: Record<string, string> = {
|
|
1258
|
+
activity: "view-activity",
|
|
1259
|
+
taskSchedule: "view-task-schedule",
|
|
1260
|
+
staffStatus: "view-attendance-widget",
|
|
1261
|
+
attendance: "view-attendance-widget",
|
|
1262
|
+
feedbacks: "view-feedbacks-widget",
|
|
1263
|
+
upcomingEvents: "view-upcoming-events",
|
|
1264
|
+
todayReminders: "view-today-reminders",
|
|
1265
|
+
todayAttentions: "view-today-attentions",
|
|
1266
|
+
workOrderStatus: "view-work-order-status",
|
|
1267
|
+
activePatrol: "view-active-patrol",
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
const action = actionMap[key];
|
|
1271
|
+
if (!action) return true;
|
|
1272
|
+
|
|
1273
|
+
return hasPermission(
|
|
1274
|
+
userAppRole.value,
|
|
1275
|
+
{ dashboard: dashboardPermissions },
|
|
1276
|
+
"dashboard",
|
|
1277
|
+
action
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1088
1281
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
1089
1282
|
|
|
1090
1283
|
const router = useRouter();
|
|
@@ -1139,7 +1332,11 @@ const { data: getDashboardReq, pending: loadingMetrics } =
|
|
|
1139
1332
|
`get-dashboard-${props.site}-${props.serviceType}`,
|
|
1140
1333
|
() =>
|
|
1141
1334
|
props.serviceType && isHygieneMode.value
|
|
1142
|
-
? getDashboardHygiene(
|
|
1335
|
+
? getDashboardHygiene(
|
|
1336
|
+
props.site,
|
|
1337
|
+
currentPeriod.value,
|
|
1338
|
+
props.serviceType as TServiceType
|
|
1339
|
+
)
|
|
1143
1340
|
: Promise.resolve(null),
|
|
1144
1341
|
{
|
|
1145
1342
|
watch: [() => props.site, currentPeriod, () => props.serviceType],
|
|
@@ -1156,7 +1353,9 @@ const { data: getDashboardAltReq, pending: loadingAltMetrics } =
|
|
|
1156
1353
|
props.site,
|
|
1157
1354
|
props.serviceType as TServiceType,
|
|
1158
1355
|
currentPeriod.value,
|
|
1159
|
-
currentVisitorTypeParam.value
|
|
1356
|
+
currentVisitorTypeParam.value
|
|
1357
|
+
? { type: currentVisitorTypeParam.value }
|
|
1358
|
+
: {}
|
|
1160
1359
|
)
|
|
1161
1360
|
: Promise.resolve(null),
|
|
1162
1361
|
{
|
|
@@ -1251,10 +1450,19 @@ const finalLoading = computed(() => {
|
|
|
1251
1450
|
);
|
|
1252
1451
|
}
|
|
1253
1452
|
if (isPropertyMode.value) {
|
|
1254
|
-
return
|
|
1453
|
+
return (
|
|
1454
|
+
loadingAltMetrics.value ||
|
|
1455
|
+
loadingWeeklyActivity.value ||
|
|
1456
|
+
loadingFeedbacks.value
|
|
1457
|
+
);
|
|
1255
1458
|
}
|
|
1256
1459
|
// security
|
|
1257
|
-
return
|
|
1460
|
+
return (
|
|
1461
|
+
loadingAltMetrics.value ||
|
|
1462
|
+
loadingWeeklyActivity.value ||
|
|
1463
|
+
loadingAttendance.value ||
|
|
1464
|
+
loadingFeedbacks.value
|
|
1465
|
+
);
|
|
1258
1466
|
});
|
|
1259
1467
|
|
|
1260
1468
|
// ─── Merged Dashboard Data ────────────────────────────────────────────────────
|
|
@@ -1317,9 +1525,21 @@ const finalDashboardData = computed<Record<string, any>>(() => {
|
|
|
1317
1525
|
...data,
|
|
1318
1526
|
activityChart,
|
|
1319
1527
|
feedbackItems: feedbacksReq.value?.items ?? [],
|
|
1320
|
-
openWorkOrder: data.openWorkOrder ?? {
|
|
1321
|
-
|
|
1322
|
-
|
|
1528
|
+
openWorkOrder: data.openWorkOrder ?? {
|
|
1529
|
+
count: 0,
|
|
1530
|
+
inProgress: 0,
|
|
1531
|
+
percentage: 0,
|
|
1532
|
+
},
|
|
1533
|
+
openIncidents: data.openIncidents ?? {
|
|
1534
|
+
count: 0,
|
|
1535
|
+
highSeverity: 0,
|
|
1536
|
+
percentage: 0,
|
|
1537
|
+
},
|
|
1538
|
+
visitors: data.visitors ?? {
|
|
1539
|
+
count: 0,
|
|
1540
|
+
currentlyOnSite: 0,
|
|
1541
|
+
percentage: 0,
|
|
1542
|
+
},
|
|
1323
1543
|
facilityBooking: data.facilityBooking ?? {
|
|
1324
1544
|
count: 0,
|
|
1325
1545
|
ongoing: 0,
|
|
@@ -1526,28 +1746,106 @@ watch(
|
|
|
1526
1746
|
const customizeWidgets = computed(() => {
|
|
1527
1747
|
if (isPropertyMode.value) {
|
|
1528
1748
|
return [
|
|
1529
|
-
{
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
{
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1749
|
+
{
|
|
1750
|
+
key: "workOrders",
|
|
1751
|
+
title: "Open Work Orders",
|
|
1752
|
+
description:
|
|
1753
|
+
"Track active work orders, overdue items, and what's in progress.",
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
key: "incidents",
|
|
1757
|
+
title: "Open Incidents",
|
|
1758
|
+
description: "Monitor reported incidents and flag high severity cases.",
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
key: "visitors",
|
|
1762
|
+
title: "Visitors",
|
|
1763
|
+
description:
|
|
1764
|
+
"See today's visitor traffic and who is currently on site.",
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
key: "facilityBookings",
|
|
1768
|
+
title: "Facility Bookings",
|
|
1769
|
+
description: "View booking volume and approvals waiting for action.",
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
key: "activity",
|
|
1773
|
+
title: "This Week Activity (Line Chart)",
|
|
1774
|
+
description: "Compare incidents and work orders over time.",
|
|
1775
|
+
},
|
|
1776
|
+
{
|
|
1777
|
+
key: "upcomingEvents",
|
|
1778
|
+
title: "Upcoming Events",
|
|
1779
|
+
description: "Quick view of scheduled events happening soon.",
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
key: "todayReminders",
|
|
1783
|
+
title: "Today's Reminder",
|
|
1784
|
+
description: "Your next tasks and time sensitive alerts for today.",
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
key: "todayAttentions",
|
|
1788
|
+
title: "Today's Attentions",
|
|
1789
|
+
description:
|
|
1790
|
+
"Items that require review overdue, pending, or unacknowledged.",
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
key: "workOrderStatus",
|
|
1794
|
+
title: "Work Order Status (Progress Bar)",
|
|
1795
|
+
description: "Breakdown of work orders by status.",
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
key: "feedbacks",
|
|
1799
|
+
title: "Feedbacks",
|
|
1800
|
+
description: "Recent feedback items and current resolution status.",
|
|
1801
|
+
},
|
|
1539
1802
|
];
|
|
1540
1803
|
}
|
|
1541
1804
|
if (isSecurityMode.value) {
|
|
1542
1805
|
return [
|
|
1543
|
-
{
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
{
|
|
1550
|
-
|
|
1806
|
+
{
|
|
1807
|
+
key: "workOrders",
|
|
1808
|
+
title: "Open Work Orders",
|
|
1809
|
+
description:
|
|
1810
|
+
"Track active work orders, overdue items, and what's in progress.",
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
key: "incidents",
|
|
1814
|
+
title: "Open Incidents",
|
|
1815
|
+
description: "Monitor reported incidents and flag high severity cases.",
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
key: "visitors",
|
|
1819
|
+
title: "Visitors",
|
|
1820
|
+
description:
|
|
1821
|
+
"See today's visitor traffic and who is currently on site.",
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
key: "patrolCompliance",
|
|
1825
|
+
title: "Patrol Compliance",
|
|
1826
|
+
description: "View missed checkpoints and ongoing patrols.",
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
key: "activity",
|
|
1830
|
+
title: "This Week Activity (Line Chart)",
|
|
1831
|
+
description: "Compare incidents and work orders over time.",
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
key: "activePatrol",
|
|
1835
|
+
title: "Active Patrol",
|
|
1836
|
+
description:
|
|
1837
|
+
"Quick view of scheduled active patrol routes happening soon.",
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
key: "staffStatus",
|
|
1841
|
+
title: "Staff Status",
|
|
1842
|
+
description: "Current attendance and status of staff.",
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
key: "feedbacks",
|
|
1846
|
+
title: "Feedbacks",
|
|
1847
|
+
description: "Recent feedback and resolution status.",
|
|
1848
|
+
},
|
|
1551
1849
|
];
|
|
1552
1850
|
}
|
|
1553
1851
|
// Hygiene / others
|
|
@@ -1557,18 +1855,39 @@ const customizeWidgets = computed(() => {
|
|
|
1557
1855
|
title: card.title,
|
|
1558
1856
|
description: `Show ${card.title} metrics for the selected period.`,
|
|
1559
1857
|
})),
|
|
1560
|
-
{
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1858
|
+
{
|
|
1859
|
+
key: "activity",
|
|
1860
|
+
title: "Activity Chart",
|
|
1861
|
+
description: "Compare activity trends over time.",
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
key: "taskSchedule",
|
|
1865
|
+
title: primaryListConfig.value.title,
|
|
1866
|
+
description: "Show current tasks and their latest status.",
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
key: "staffStatus",
|
|
1870
|
+
title: "Staff Total Attendance",
|
|
1871
|
+
description: "See who is on duty, on break, or absent.",
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
key: "feedbacks",
|
|
1875
|
+
title: "Feedbacks",
|
|
1876
|
+
description: "Show recent feedback and resolution state.",
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
key: "recentActivity",
|
|
1880
|
+
title: "Recent Activity",
|
|
1881
|
+
description: "Latest updates across the service.",
|
|
1882
|
+
},
|
|
1565
1883
|
];
|
|
1566
1884
|
});
|
|
1567
1885
|
|
|
1568
1886
|
const filteredCustomizeWidgets = computed(() => {
|
|
1569
1887
|
const search = widgetSearch.value.trim().toLowerCase();
|
|
1570
|
-
|
|
1571
|
-
return
|
|
1888
|
+
let list = customizeWidgets.value.filter((w) => canViewWidget(w.key));
|
|
1889
|
+
if (!search) return list;
|
|
1890
|
+
return list.filter(
|
|
1572
1891
|
(w) =>
|
|
1573
1892
|
w.title.toLowerCase().includes(search) ||
|
|
1574
1893
|
w.description.toLowerCase().includes(search)
|
|
@@ -1578,14 +1897,24 @@ const filteredCustomizeWidgets = computed(() => {
|
|
|
1578
1897
|
// ─── KPI Metrics ─────────────────────────────────────────────────────────────
|
|
1579
1898
|
|
|
1580
1899
|
const defaultVisitorOptions = [
|
|
1581
|
-
"All",
|
|
1900
|
+
"All",
|
|
1901
|
+
"Drive-In",
|
|
1902
|
+
"Walk-In",
|
|
1903
|
+
"Pick-up",
|
|
1904
|
+
"Drop-Off",
|
|
1905
|
+
"Delivery",
|
|
1906
|
+
"Contractor",
|
|
1582
1907
|
];
|
|
1583
1908
|
|
|
1584
1909
|
const visitorItems = computed(() =>
|
|
1585
1910
|
getDashboardArray(["visitorItems", "visitorsList", "visitorTransactionsList"])
|
|
1586
1911
|
);
|
|
1587
1912
|
const visitorOptions = computed(() =>
|
|
1588
|
-
buildFilterOptions(
|
|
1913
|
+
buildFilterOptions(
|
|
1914
|
+
defaultVisitorOptions,
|
|
1915
|
+
visitorItems.value,
|
|
1916
|
+
getVisitorFilterValue
|
|
1917
|
+
)
|
|
1589
1918
|
);
|
|
1590
1919
|
|
|
1591
1920
|
// Property-specific: facility booking metric with client-side filter
|
|
@@ -1602,13 +1931,26 @@ const facilityOptions = computed(() =>
|
|
|
1602
1931
|
defaultFacilityOptions,
|
|
1603
1932
|
facilityBookingItems.value,
|
|
1604
1933
|
(item: any) =>
|
|
1605
|
-
item.facilityName ||
|
|
1934
|
+
item.facilityName ||
|
|
1935
|
+
item.facility?.name ||
|
|
1936
|
+
item.facilityInfo?.name ||
|
|
1937
|
+
item.name ||
|
|
1938
|
+
""
|
|
1606
1939
|
)
|
|
1607
1940
|
);
|
|
1608
1941
|
|
|
1609
1942
|
function getPropertyKpiCards() {
|
|
1610
|
-
const workOrderMetric = getMetric([
|
|
1611
|
-
|
|
1943
|
+
const workOrderMetric = getMetric([
|
|
1944
|
+
"openWorkOrder",
|
|
1945
|
+
"workOrders",
|
|
1946
|
+
"openWorkOrders",
|
|
1947
|
+
"workOrder",
|
|
1948
|
+
]);
|
|
1949
|
+
const incidentMetric = getMetric([
|
|
1950
|
+
"openIncidents",
|
|
1951
|
+
"incidents",
|
|
1952
|
+
"incidentReports",
|
|
1953
|
+
]);
|
|
1612
1954
|
const visitorMetric = getFilteredVisitorMetric();
|
|
1613
1955
|
const facilityMetric = computeFacilityMetric();
|
|
1614
1956
|
|
|
@@ -1684,7 +2026,11 @@ function computeFacilityMetric(): DashboardMetric {
|
|
|
1684
2026
|
if (selected === "All") return metric;
|
|
1685
2027
|
const items = facilityBookingItems.value.filter((item: any) => {
|
|
1686
2028
|
const val = String(
|
|
1687
|
-
item.facilityName ||
|
|
2029
|
+
item.facilityName ||
|
|
2030
|
+
item.facility?.name ||
|
|
2031
|
+
item.facilityInfo?.name ||
|
|
2032
|
+
item.name ||
|
|
2033
|
+
""
|
|
1688
2034
|
).toLowerCase();
|
|
1689
2035
|
return val === selected.toLowerCase();
|
|
1690
2036
|
});
|
|
@@ -1693,18 +2039,33 @@ function computeFacilityMetric(): DashboardMetric {
|
|
|
1693
2039
|
count: items.length,
|
|
1694
2040
|
ongoing: items.filter((i: any) => {
|
|
1695
2041
|
const s = String(i.status || "").toLowerCase();
|
|
1696
|
-
return
|
|
2042
|
+
return (
|
|
2043
|
+
s.includes("ongoing") ||
|
|
2044
|
+
s.includes("approved") ||
|
|
2045
|
+
s.includes("in-progress")
|
|
2046
|
+
);
|
|
1697
2047
|
}).length,
|
|
1698
2048
|
waitingApproval: items.filter((i: any) => {
|
|
1699
2049
|
const s = String(i.status || "").toLowerCase();
|
|
1700
|
-
return
|
|
2050
|
+
return (
|
|
2051
|
+
s.includes("pending") || s.includes("review") || s.includes("approval")
|
|
2052
|
+
);
|
|
1701
2053
|
}).length,
|
|
1702
2054
|
};
|
|
1703
2055
|
}
|
|
1704
2056
|
|
|
1705
2057
|
function getSecurityKpiCards() {
|
|
1706
|
-
const workOrderMetric = getMetric([
|
|
1707
|
-
|
|
2058
|
+
const workOrderMetric = getMetric([
|
|
2059
|
+
"openWorkOrder",
|
|
2060
|
+
"workOrders",
|
|
2061
|
+
"openWorkOrders",
|
|
2062
|
+
"workOrder",
|
|
2063
|
+
]);
|
|
2064
|
+
const incidentMetric = getMetric([
|
|
2065
|
+
"openIncidents",
|
|
2066
|
+
"incidents",
|
|
2067
|
+
"incidentReports",
|
|
2068
|
+
]);
|
|
1708
2069
|
const patrolMetric = getMetric(["patrolCompliance", "patrol"]);
|
|
1709
2070
|
const visitorMetric = getFilteredVisitorMetric();
|
|
1710
2071
|
|
|
@@ -1943,18 +2304,33 @@ const workOrderStatus = computed(() => {
|
|
|
1943
2304
|
const max = Math.max(completed, inProgress, pending, 1);
|
|
1944
2305
|
|
|
1945
2306
|
return [
|
|
1946
|
-
{
|
|
1947
|
-
|
|
1948
|
-
|
|
2307
|
+
{
|
|
2308
|
+
label: "Completed",
|
|
2309
|
+
value: completed,
|
|
2310
|
+
percent: (completed / max) * 100,
|
|
2311
|
+
color: "#2fb84d",
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
label: "In Progress",
|
|
2315
|
+
value: inProgress,
|
|
2316
|
+
percent: (inProgress / max) * 100,
|
|
2317
|
+
color: "#f12d2d",
|
|
2318
|
+
},
|
|
2319
|
+
{
|
|
2320
|
+
label: "Pending",
|
|
2321
|
+
value: pending,
|
|
2322
|
+
percent: (pending / max) * 100,
|
|
2323
|
+
color: "#ff8900",
|
|
2324
|
+
},
|
|
1949
2325
|
];
|
|
1950
2326
|
});
|
|
1951
2327
|
|
|
1952
2328
|
// Security-mode lists
|
|
1953
|
-
const activePatrolItems = computed<any[]>(
|
|
1954
|
-
finalDashboardData.value?.activePatrolItems ?? []
|
|
2329
|
+
const activePatrolItems = computed<any[]>(
|
|
2330
|
+
() => finalDashboardData.value?.activePatrolItems ?? []
|
|
1955
2331
|
);
|
|
1956
|
-
const securityStaffItems = computed<any[]>(
|
|
1957
|
-
attendanceReq.value?.items ?? []
|
|
2332
|
+
const securityStaffItems = computed<any[]>(
|
|
2333
|
+
() => attendanceReq.value?.items ?? []
|
|
1958
2334
|
);
|
|
1959
2335
|
|
|
1960
2336
|
// ─── ResizeObserver ───────────────────────────────────────────────────────────
|
|
@@ -2020,19 +2396,25 @@ function getMetricMeta(metric: DashboardMetric) {
|
|
|
2020
2396
|
if (typeof metric.highSeverity !== "undefined")
|
|
2021
2397
|
parts.push(`${formatNumber(toNumber(metric.highSeverity))} high severity`);
|
|
2022
2398
|
if (typeof metric.currentlyOnSite !== "undefined")
|
|
2023
|
-
parts.push(
|
|
2399
|
+
parts.push(
|
|
2400
|
+
`${formatNumber(toNumber(metric.currentlyOnSite))} currently on site`
|
|
2401
|
+
);
|
|
2024
2402
|
if (
|
|
2025
2403
|
typeof (metric as any).missedCheckpoints !== "undefined" ||
|
|
2026
2404
|
typeof (metric as any).missed !== "undefined"
|
|
2027
2405
|
) {
|
|
2028
2406
|
parts.push(
|
|
2029
|
-
`${formatNumber(
|
|
2407
|
+
`${formatNumber(
|
|
2408
|
+
toNumber((metric as any).missedCheckpoints ?? (metric as any).missed)
|
|
2409
|
+
)} checkpoint missed`
|
|
2030
2410
|
);
|
|
2031
2411
|
}
|
|
2032
2412
|
if (typeof metric.ongoing !== "undefined")
|
|
2033
2413
|
parts.push(`${formatNumber(toNumber(metric.ongoing))} ongoing`);
|
|
2034
2414
|
if (typeof metric.waitingApproval !== "undefined")
|
|
2035
|
-
parts.push(
|
|
2415
|
+
parts.push(
|
|
2416
|
+
`${formatNumber(toNumber(metric.waitingApproval))} waiting approval`
|
|
2417
|
+
);
|
|
2036
2418
|
if (typeof metric.lowStock !== "undefined")
|
|
2037
2419
|
parts.push(`${formatNumber(toNumber(metric.lowStock))} low stock`);
|
|
2038
2420
|
if (typeof metric.outOfStock !== "undefined")
|
|
@@ -2057,8 +2439,9 @@ function getFilteredVisitorMetric(): DashboardMetric {
|
|
|
2057
2439
|
count: toNumber(metric.count ?? items.length),
|
|
2058
2440
|
currentlyOnSite: toNumber(
|
|
2059
2441
|
metric.currentlyOnSite ??
|
|
2060
|
-
items.filter(
|
|
2061
|
-
.
|
|
2442
|
+
items.filter(
|
|
2443
|
+
(item: any) => !isClosedStatus(item.status) && !item.checkOut
|
|
2444
|
+
).length
|
|
2062
2445
|
),
|
|
2063
2446
|
};
|
|
2064
2447
|
}
|
|
@@ -2117,7 +2500,12 @@ function getListSubtitle(context: string, item: any) {
|
|
|
2117
2500
|
if (context === "todayReminders" || context === "todayAttentions") {
|
|
2118
2501
|
return formatDashboardTime(time) || item.description || item.status || "";
|
|
2119
2502
|
}
|
|
2120
|
-
return
|
|
2503
|
+
return (
|
|
2504
|
+
item.description ||
|
|
2505
|
+
item.category ||
|
|
2506
|
+
item.location ||
|
|
2507
|
+
formatDashboardTime(time)
|
|
2508
|
+
);
|
|
2121
2509
|
}
|
|
2122
2510
|
|
|
2123
2511
|
function getListTime(item: any) {
|
|
@@ -2143,13 +2531,29 @@ function getListIcon(context: string) {
|
|
|
2143
2531
|
|
|
2144
2532
|
function getListTone(item: DashboardListItem) {
|
|
2145
2533
|
if (item.tone) return item.tone;
|
|
2146
|
-
const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${
|
|
2147
|
-
|
|
2534
|
+
const text = `${item.title} ${item.subtitle || ""} ${item.icon || ""} ${
|
|
2535
|
+
item.color || ""
|
|
2536
|
+
}`.toLowerCase();
|
|
2537
|
+
if (
|
|
2538
|
+
text.includes("alert") ||
|
|
2539
|
+
text.includes("incident") ||
|
|
2540
|
+
text.includes("overdue") ||
|
|
2541
|
+
text.includes("pending")
|
|
2542
|
+
)
|
|
2148
2543
|
return "danger";
|
|
2149
|
-
if (
|
|
2544
|
+
if (
|
|
2545
|
+
text.includes("booking") ||
|
|
2546
|
+
text.includes("facility") ||
|
|
2547
|
+
text.includes("approved") ||
|
|
2548
|
+
text.includes("complete")
|
|
2549
|
+
)
|
|
2150
2550
|
return "success";
|
|
2151
2551
|
if (text.includes("visitor") || text.includes("check")) return "warning";
|
|
2152
|
-
if (
|
|
2552
|
+
if (
|
|
2553
|
+
text.includes("calendar") ||
|
|
2554
|
+
text.includes("event") ||
|
|
2555
|
+
text.includes("reminder")
|
|
2556
|
+
)
|
|
2153
2557
|
return "info";
|
|
2154
2558
|
return "neutral";
|
|
2155
2559
|
}
|
|
@@ -2158,8 +2562,19 @@ function getListTone(item: DashboardListItem) {
|
|
|
2158
2562
|
|
|
2159
2563
|
function normalizeTaskItem(item: Record<string, any>, index: number): TaskItem {
|
|
2160
2564
|
const title =
|
|
2161
|
-
item.title ||
|
|
2162
|
-
|
|
2565
|
+
item.title ||
|
|
2566
|
+
item.subject ||
|
|
2567
|
+
item.name ||
|
|
2568
|
+
item.taskName ||
|
|
2569
|
+
item.description ||
|
|
2570
|
+
"Task";
|
|
2571
|
+
const subtitle =
|
|
2572
|
+
item.subtitle ||
|
|
2573
|
+
item.area ||
|
|
2574
|
+
item.location ||
|
|
2575
|
+
item.asset ||
|
|
2576
|
+
item.category ||
|
|
2577
|
+
"";
|
|
2163
2578
|
const person =
|
|
2164
2579
|
item.person ||
|
|
2165
2580
|
item.assigneeName ||
|
|
@@ -2173,14 +2588,22 @@ function normalizeTaskItem(item: Record<string, any>, index: number): TaskItem {
|
|
|
2173
2588
|
title,
|
|
2174
2589
|
subtitle,
|
|
2175
2590
|
person,
|
|
2176
|
-
status: normalizeStatus(
|
|
2591
|
+
status: normalizeStatus(
|
|
2592
|
+
item.status || item.state || item.taskStatus || "To-Do"
|
|
2593
|
+
),
|
|
2177
2594
|
time:
|
|
2178
|
-
item.time ||
|
|
2595
|
+
item.time ||
|
|
2596
|
+
item.startTime ||
|
|
2597
|
+
item.scheduleTime ||
|
|
2598
|
+
formatActivityTime(item),
|
|
2179
2599
|
raw: item,
|
|
2180
2600
|
};
|
|
2181
2601
|
}
|
|
2182
2602
|
|
|
2183
|
-
function normalizeStaffItem(
|
|
2603
|
+
function normalizeStaffItem(
|
|
2604
|
+
item: Record<string, any>,
|
|
2605
|
+
index: number
|
|
2606
|
+
): StaffItem {
|
|
2184
2607
|
const name =
|
|
2185
2608
|
item.name ||
|
|
2186
2609
|
item.employeeName ||
|
|
@@ -2201,14 +2624,21 @@ function normalizeStaffItem(item: Record<string, any>, index: number): StaffItem
|
|
|
2201
2624
|
name,
|
|
2202
2625
|
role,
|
|
2203
2626
|
status: normalizeStatus(
|
|
2204
|
-
item.statusLabel ||
|
|
2627
|
+
item.statusLabel ||
|
|
2628
|
+
item.status ||
|
|
2629
|
+
item.currentStatus ||
|
|
2630
|
+
item.attendanceStatus ||
|
|
2631
|
+
"Off duty"
|
|
2205
2632
|
),
|
|
2206
2633
|
totalHours: item.totalHours || 0,
|
|
2207
2634
|
raw: item,
|
|
2208
2635
|
};
|
|
2209
2636
|
}
|
|
2210
2637
|
|
|
2211
|
-
function normalizeFeedbackItem(
|
|
2638
|
+
function normalizeFeedbackItem(
|
|
2639
|
+
item: Record<string, any>,
|
|
2640
|
+
index: number
|
|
2641
|
+
): FeedbackItem {
|
|
2212
2642
|
const title = item.title || item.subject || item.description || "Feedback";
|
|
2213
2643
|
const subject = item.category || item.type || item.service || "Subject";
|
|
2214
2644
|
const app = item.app || item.source || item.channel || "";
|
|
@@ -2234,20 +2664,23 @@ function normalizeFeedbackItem(item: Record<string, any>, index: number): Feedba
|
|
|
2234
2664
|
// Feedback tone helper (for property-mode pill classes)
|
|
2235
2665
|
function getFeedbackTone(status: any) {
|
|
2236
2666
|
const s = String(status || "").toLowerCase();
|
|
2237
|
-
if (s.includes("complete") || s.includes("resolved") || s.includes("done"))
|
|
2667
|
+
if (s.includes("complete") || s.includes("resolved") || s.includes("done"))
|
|
2668
|
+
return "completed";
|
|
2238
2669
|
if (s.includes("progress") || s.includes("review")) return "progress";
|
|
2239
2670
|
if (s.includes("reject") || s.includes("cancel")) return "danger";
|
|
2240
2671
|
return "todo";
|
|
2241
2672
|
}
|
|
2242
2673
|
|
|
2243
2674
|
function getInitials(name?: string) {
|
|
2244
|
-
return
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2675
|
+
return (
|
|
2676
|
+
String(name || "")
|
|
2677
|
+
.trim()
|
|
2678
|
+
.split(/\s+/)
|
|
2679
|
+
.filter(Boolean)
|
|
2680
|
+
.slice(0, 2)
|
|
2681
|
+
.map((w) => w.charAt(0).toUpperCase())
|
|
2682
|
+
.join("") || "-"
|
|
2683
|
+
);
|
|
2251
2684
|
}
|
|
2252
2685
|
|
|
2253
2686
|
// ─── Status Style Helpers ────────────────────────────────────────────────────
|
|
@@ -2278,8 +2711,7 @@ function getStaffStatusPillStyle(status: any) {
|
|
|
2278
2711
|
return { backgroundColor: "#72e3a1", color: "#0f5132" };
|
|
2279
2712
|
if (s === "on break" || s === "on-break")
|
|
2280
2713
|
return { backgroundColor: "#fde68a", color: "#92400e" };
|
|
2281
|
-
if (s === "absent")
|
|
2282
|
-
return { backgroundColor: "#fecaca", color: "#b91c1c" };
|
|
2714
|
+
if (s === "absent") return { backgroundColor: "#fecaca", color: "#b91c1c" };
|
|
2283
2715
|
return { backgroundColor: "#e2e8f0", color: "#64748b" };
|
|
2284
2716
|
}
|
|
2285
2717
|
|
|
@@ -2332,9 +2764,20 @@ function normalizeActivityChart(source: any) {
|
|
|
2332
2764
|
const labels = Array.isArray(source?.labels)
|
|
2333
2765
|
? source.labels.map((l: any) => String(l))
|
|
2334
2766
|
: [];
|
|
2335
|
-
const secondary = extractChartSeries(source, [
|
|
2767
|
+
const secondary = extractChartSeries(source, [
|
|
2768
|
+
"closedWorkOrder",
|
|
2769
|
+
"closedWorkOrders",
|
|
2770
|
+
"closed_work_orders",
|
|
2771
|
+
]);
|
|
2336
2772
|
const hasClosedSeries = secondary.length > 0;
|
|
2337
|
-
const primary = extractChartSeries(source, [
|
|
2773
|
+
const primary = extractChartSeries(source, [
|
|
2774
|
+
"openWorkOrder",
|
|
2775
|
+
"openWorkOrders",
|
|
2776
|
+
"open_work_orders",
|
|
2777
|
+
"workOrders",
|
|
2778
|
+
"workOrder",
|
|
2779
|
+
"work_orders",
|
|
2780
|
+
]);
|
|
2338
2781
|
const secondaryCandidate = hasClosedSeries
|
|
2339
2782
|
? { label: "Closed Work Order", series: secondary }
|
|
2340
2783
|
: getSecondaryChartSeries(source);
|
|
@@ -2350,7 +2793,10 @@ function normalizeActivityChart(source: any) {
|
|
|
2350
2793
|
|
|
2351
2794
|
function getSecondaryChartSeries(source: any) {
|
|
2352
2795
|
const candidates = [
|
|
2353
|
-
{
|
|
2796
|
+
{
|
|
2797
|
+
label: "Task Completed",
|
|
2798
|
+
keys: ["taskCompleted", "completedTasks", "completed"],
|
|
2799
|
+
},
|
|
2354
2800
|
{ label: "Incident", keys: ["incidents", "incident", "incidentReports"] },
|
|
2355
2801
|
{ label: "Feedback", keys: ["feedbacks", "feedback", "feedbackTickets"] },
|
|
2356
2802
|
];
|
|
@@ -2368,7 +2814,9 @@ function extractChartSeries(source: any, keys: string[]) {
|
|
|
2368
2814
|
}
|
|
2369
2815
|
if (Array.isArray(source?.datasets)) {
|
|
2370
2816
|
const dataset = source.datasets.find((item: any) => {
|
|
2371
|
-
const keyText = String(
|
|
2817
|
+
const keyText = String(
|
|
2818
|
+
item?.key || item?.label || item?.name || ""
|
|
2819
|
+
).toLowerCase();
|
|
2372
2820
|
return keys.some((key) => keyText.includes(key.toLowerCase()));
|
|
2373
2821
|
});
|
|
2374
2822
|
if (Array.isArray(dataset?.data)) return dataset.data.map(toNumber);
|
|
@@ -2378,7 +2826,9 @@ function extractChartSeries(source: any, keys: string[]) {
|
|
|
2378
2826
|
|
|
2379
2827
|
function normalizeSeries(value: any, targetLength: number) {
|
|
2380
2828
|
const source = Array.isArray(value) ? value : [];
|
|
2381
|
-
return Array.from({ length: targetLength }, (_, index) =>
|
|
2829
|
+
return Array.from({ length: targetLength }, (_, index) =>
|
|
2830
|
+
toNumber(source[index])
|
|
2831
|
+
);
|
|
2382
2832
|
}
|
|
2383
2833
|
|
|
2384
2834
|
// ─── Filter Helpers ───────────────────────────────────────────────────────────
|
|
@@ -2397,7 +2847,9 @@ function buildFilterOptions(
|
|
|
2397
2847
|
}
|
|
2398
2848
|
|
|
2399
2849
|
function getVisitorFilterValue(item: Record<string, any>) {
|
|
2400
|
-
const raw = String(
|
|
2850
|
+
const raw = String(
|
|
2851
|
+
item.type || item.visitorType || item.category || ""
|
|
2852
|
+
).toLowerCase();
|
|
2401
2853
|
if (raw.includes("contractor")) return "Contractor";
|
|
2402
2854
|
if (raw.includes("walk")) return "Walk-In";
|
|
2403
2855
|
if (raw.includes("delivery")) return "Delivery";
|
|
@@ -2453,6 +2905,7 @@ function goToListItem(item: TaskItem | FeedbackItem, routeKey: string) {
|
|
|
2453
2905
|
// ─── Widget Management ────────────────────────────────────────────────────────
|
|
2454
2906
|
|
|
2455
2907
|
function isWidgetVisible(key: string) {
|
|
2908
|
+
if (!canViewWidget(key)) return false;
|
|
2456
2909
|
return visibleWidgetKeys.value.includes(key);
|
|
2457
2910
|
}
|
|
2458
2911
|
|
|
@@ -2541,7 +2994,12 @@ function normalizeStatus(value: any) {
|
|
|
2541
2994
|
|
|
2542
2995
|
function getStatusTone(status: any) {
|
|
2543
2996
|
const s = String(status || "").toLowerCase();
|
|
2544
|
-
if (
|
|
2997
|
+
if (
|
|
2998
|
+
s.includes("complete") ||
|
|
2999
|
+
s.includes("done") ||
|
|
3000
|
+
s.includes("resolved") ||
|
|
3001
|
+
s.includes("closed")
|
|
3002
|
+
)
|
|
2545
3003
|
return "success";
|
|
2546
3004
|
if (s.includes("progress") || s.includes("active") || s.includes("ongoing"))
|
|
2547
3005
|
return "info";
|
|
@@ -2631,7 +3089,6 @@ function formatDashboardTime(value?: string) {
|
|
|
2631
3089
|
</script>
|
|
2632
3090
|
|
|
2633
3091
|
<style scoped>
|
|
2634
|
-
/* ── Figma Panel Styles (Property & Security modes) ────────────────────────── */
|
|
2635
3092
|
.dashboard-card {
|
|
2636
3093
|
background: #fff;
|
|
2637
3094
|
border: 1px solid #d9e0e7;
|
|
@@ -2793,11 +3250,26 @@ function formatDashboardTime(value?: string) {
|
|
|
2793
3250
|
width: 28px;
|
|
2794
3251
|
}
|
|
2795
3252
|
|
|
2796
|
-
.figma-icon-badge-danger {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
.figma-icon-badge-
|
|
3253
|
+
.figma-icon-badge-danger {
|
|
3254
|
+
background: #ffe0de;
|
|
3255
|
+
color: #d72f2f;
|
|
3256
|
+
}
|
|
3257
|
+
.figma-icon-badge-info {
|
|
3258
|
+
background: #e8e2ff;
|
|
3259
|
+
color: #6d4bd8;
|
|
3260
|
+
}
|
|
3261
|
+
.figma-icon-badge-success {
|
|
3262
|
+
background: #ddf7e6;
|
|
3263
|
+
color: #229852;
|
|
3264
|
+
}
|
|
3265
|
+
.figma-icon-badge-warning {
|
|
3266
|
+
background: #ffeed4;
|
|
3267
|
+
color: #c57916;
|
|
3268
|
+
}
|
|
3269
|
+
.figma-icon-badge-neutral {
|
|
3270
|
+
background: #edf2f6;
|
|
3271
|
+
color: #607386;
|
|
3272
|
+
}
|
|
2801
3273
|
|
|
2802
3274
|
.figma-status-stack {
|
|
2803
3275
|
display: grid;
|
|
@@ -2805,7 +3277,8 @@ function formatDashboardTime(value?: string) {
|
|
|
2805
3277
|
padding: 6px 2px 0;
|
|
2806
3278
|
}
|
|
2807
3279
|
|
|
2808
|
-
.figma-status-row {
|
|
3280
|
+
.figma-status-row {
|
|
3281
|
+
}
|
|
2809
3282
|
|
|
2810
3283
|
.figma-status-label {
|
|
2811
3284
|
align-items: center;
|
|
@@ -2862,7 +3335,9 @@ function formatDashboardTime(value?: string) {
|
|
|
2862
3335
|
box-shadow: 0 6px 14px rgba(15, 44, 69, 0.04);
|
|
2863
3336
|
}
|
|
2864
3337
|
|
|
2865
|
-
.feedback-summary {
|
|
3338
|
+
.feedback-summary {
|
|
3339
|
+
min-width: 0;
|
|
3340
|
+
}
|
|
2866
3341
|
|
|
2867
3342
|
.feedback-summary strong {
|
|
2868
3343
|
color: #132b43;
|
|
@@ -2922,10 +3397,22 @@ function formatDashboardTime(value?: string) {
|
|
|
2922
3397
|
white-space: nowrap;
|
|
2923
3398
|
}
|
|
2924
3399
|
|
|
2925
|
-
.feedback-status-pill-completed {
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
3400
|
+
.feedback-status-pill-completed {
|
|
3401
|
+
background: #72e3a1;
|
|
3402
|
+
color: #0f5132;
|
|
3403
|
+
}
|
|
3404
|
+
.feedback-status-pill-progress {
|
|
3405
|
+
background: #fde68a;
|
|
3406
|
+
color: #92400e;
|
|
3407
|
+
}
|
|
3408
|
+
.feedback-status-pill-danger {
|
|
3409
|
+
background: #fecaca;
|
|
3410
|
+
color: #b91c1c;
|
|
3411
|
+
}
|
|
3412
|
+
.feedback-status-pill-todo {
|
|
3413
|
+
background: #e2e8f0;
|
|
3414
|
+
color: #64748b;
|
|
3415
|
+
}
|
|
2929
3416
|
|
|
2930
3417
|
.feedback-view-button {
|
|
2931
3418
|
appearance: none;
|