@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.
@@ -64,7 +64,11 @@
64
64
  size="small"
65
65
  :style="
66
66
  selectedRange === range.value
67
- ? { backgroundColor: '#e2e8f0', color: '#0b2f47', fontWeight: '600' }
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 flat border rounded="lg" class="pa-4 flex-grow-1" style="min-height: 158px">
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="card.filter.model === option ? 'mdi-check' : 'mdi-blank'"
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="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)"
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="width: 12px; height: 12px; display: inline-block; background-color: #ff9b9b"
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="width: 12px; height: 12px; display: inline-block; background-color: #e53935"
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"> &bull; </template
361
+ }}<template v-if="item.subtitle && item.time">
362
+ &bull; </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="padding: 16px 20px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; background-color: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.02)"
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="display: block; font-size: 15px; font-weight: 700; color: #0b2f47; margin-bottom: 4px"
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">{{ item.subtitle }}</span>
477
+ <span style="font-size: 13px; color: #94a3b8">{{
478
+ item.subtitle
479
+ }}</span>
440
480
  </div>
441
481
 
442
- <div style="flex: 1; display: flex; align-items: center; gap: 12px">
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">{{ item.person }}</span>
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="padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize"
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="border-color: #e2e8f0; border-radius: 8px; padding: 0 16px; height: 32px"
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 :icon="item.icon || 'mdi-alert-circle-outline'" size="15" />
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">{{ getInitials(item.person) }}</span>
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="padding: 16px 20px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; background-color: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.02)"
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="display: block; font-size: 15px; font-weight: 700; color: #0b2f47; margin-bottom: 4px"
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 style="flex: 1; display: flex; align-items: center; gap: 12px">
727
- <AvatarMain :name="item.person || item.assignedTo" :size="36" />
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="padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize"
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="border-color: #e2e8f0; border-radius: 8px; padding: 0 16px; height: 32px"
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="padding: 16px 20px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; background-color: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.02)"
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="item.personName || item.employeeName || item.userName || 'Unassigned'"
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="display: block; font-size: 15px; font-weight: 700; color: #0b2f47; margin-bottom: 4px"
788
- >{{ item.userName || item.personName || "Unassigned" }}</strong
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="padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500"
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="padding: 16px 20px; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; background-color: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.02)"
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="display: block; font-size: 15px; font-weight: 700; color: #0b2f47; margin-bottom: 4px"
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">{{ item.subtitle }}</span>
954
+ <span style="font-size: 13px; color: #94a3b8">{{
955
+ item.subtitle
956
+ }}</span>
831
957
  </div>
832
958
 
833
- <div style="flex: 1; display: flex; align-items: center; gap: 12px">
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">{{ item.person }}</span>
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="padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; text-transform: capitalize"
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="border-color: #e2e8f0; border-radius: 8px; padding: 0 16px; height: 32px"
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(props.site, currentPeriod.value, props.serviceType as TServiceType)
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 ? { type: 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 loadingAltMetrics.value || loadingWeeklyActivity.value || loadingFeedbacks.value;
1453
+ return (
1454
+ loadingAltMetrics.value ||
1455
+ loadingWeeklyActivity.value ||
1456
+ loadingFeedbacks.value
1457
+ );
1255
1458
  }
1256
1459
  // security
1257
- return loadingAltMetrics.value || loadingWeeklyActivity.value || loadingAttendance.value || loadingFeedbacks.value;
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 ?? { count: 0, inProgress: 0, percentage: 0 },
1321
- openIncidents: data.openIncidents ?? { count: 0, highSeverity: 0, percentage: 0 },
1322
- visitors: data.visitors ?? { count: 0, currentlyOnSite: 0, percentage: 0 },
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
- { key: "workOrders", title: "Open Work Orders", description: "Track active work orders, overdue items, and what's in progress." },
1530
- { key: "incidents", title: "Open Incidents", description: "Monitor reported incidents and flag high severity cases." },
1531
- { key: "visitors", title: "Visitors", description: "See today's visitor traffic and who is currently on site." },
1532
- { key: "facilityBookings", title: "Facility Bookings", description: "View booking volume and approvals waiting for action." },
1533
- { key: "activity", title: "This Week Activity (Line Chart)", description: "Compare incidents and work orders over time." },
1534
- { key: "upcomingEvents", title: "Upcoming Events", description: "Quick view of scheduled events happening soon." },
1535
- { key: "todayReminders", title: "Today's Reminder", description: "Your next tasks and time sensitive alerts for today." },
1536
- { key: "todayAttentions", title: "Today's Attentions", description: "Items that require review overdue, pending, or unacknowledged." },
1537
- { key: "workOrderStatus", title: "Work Order Status (Progress Bar)", description: "Breakdown of work orders by status." },
1538
- { key: "feedbacks", title: "Feedbacks", description: "Recent feedback items and current resolution status." },
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
- { key: "workOrders", title: "Open Work Orders", description: "Track active work orders, overdue items, and what's in progress." },
1544
- { key: "incidents", title: "Open Incidents", description: "Monitor reported incidents and flag high severity cases." },
1545
- { key: "visitors", title: "Visitors", description: "See today's visitor traffic and who is currently on site." },
1546
- { key: "patrolCompliance", title: "Patrol Compliance", description: "View missed checkpoints and ongoing patrols." },
1547
- { key: "activity", title: "This Week Activity (Line Chart)", description: "Compare incidents and work orders over time." },
1548
- { key: "activePatrol", title: "Active Patrol", description: "Quick view of scheduled active patrol routes happening soon." },
1549
- { key: "staffStatus", title: "Staff Status", description: "Current attendance and status of staff." },
1550
- { key: "feedbacks", title: "Feedbacks", description: "Recent feedback and resolution status." },
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
- { key: "activity", title: "Activity Chart", description: "Compare activity trends over time." },
1561
- { key: "taskSchedule", title: primaryListConfig.value.title, description: "Show current tasks and their latest status." },
1562
- { key: "staffStatus", title: "Staff Total Attendance", description: "See who is on duty, on break, or absent." },
1563
- { key: "feedbacks", title: "Feedbacks", description: "Show recent feedback and resolution state." },
1564
- { key: "recentActivity", title: "Recent Activity", description: "Latest updates across the service." },
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
- if (!search) return customizeWidgets.value;
1571
- return customizeWidgets.value.filter(
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", "Drive-In", "Walk-In", "Pick-up", "Drop-Off", "Delivery", "Contractor",
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(defaultVisitorOptions, visitorItems.value, getVisitorFilterValue)
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 || item.facility?.name || item.facilityInfo?.name || item.name || ""
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(["openWorkOrder", "workOrders", "openWorkOrders", "workOrder"]);
1611
- const incidentMetric = getMetric(["openIncidents", "incidents", "incidentReports"]);
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 || item.facility?.name || item.facilityInfo?.name || item.name || ""
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 s.includes("ongoing") || s.includes("approved") || s.includes("in-progress");
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 s.includes("pending") || s.includes("review") || s.includes("approval");
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(["openWorkOrder", "workOrders", "openWorkOrders", "workOrder"]);
1707
- const incidentMetric = getMetric(["openIncidents", "incidents", "incidentReports"]);
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
- { label: "Completed", value: completed, percent: (completed / max) * 100, color: "#2fb84d" },
1947
- { label: "In Progress", value: inProgress, percent: (inProgress / max) * 100, color: "#f12d2d" },
1948
- { label: "Pending", value: pending, percent: (pending / max) * 100, color: "#ff8900" },
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(`${formatNumber(toNumber(metric.currentlyOnSite))} currently on site`);
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(toNumber((metric as any).missedCheckpoints ?? (metric as any).missed))} checkpoint missed`
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(`${formatNumber(toNumber(metric.waitingApproval))} waiting approval`);
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((item: any) => !isClosedStatus(item.status) && !item.checkOut)
2061
- .length
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 item.description || item.category || item.location || formatDashboardTime(time);
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 || ""} ${item.color || ""}`.toLowerCase();
2147
- if (text.includes("alert") || text.includes("incident") || text.includes("overdue") || text.includes("pending"))
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 (text.includes("booking") || text.includes("facility") || text.includes("approved") || text.includes("complete"))
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 (text.includes("calendar") || text.includes("event") || text.includes("reminder"))
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 || item.subject || item.name || item.taskName || item.description || "Task";
2162
- const subtitle = item.subtitle || item.area || item.location || item.asset || item.category || "";
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(item.status || item.state || item.taskStatus || "To-Do"),
2591
+ status: normalizeStatus(
2592
+ item.status || item.state || item.taskStatus || "To-Do"
2593
+ ),
2177
2594
  time:
2178
- item.time || item.startTime || item.scheduleTime || formatActivityTime(item),
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(item: Record<string, any>, index: number): StaffItem {
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 || item.status || item.currentStatus || item.attendanceStatus || "Off duty"
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(item: Record<string, any>, index: number): FeedbackItem {
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")) return "completed";
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 String(name || "")
2245
- .trim()
2246
- .split(/\s+/)
2247
- .filter(Boolean)
2248
- .slice(0, 2)
2249
- .map((w) => w.charAt(0).toUpperCase())
2250
- .join("") || "-";
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, ["closedWorkOrder", "closedWorkOrders", "closed_work_orders"]);
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, ["openWorkOrder", "openWorkOrders", "open_work_orders", "workOrders", "workOrder", "work_orders"]);
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
- { label: "Task Completed", keys: ["taskCompleted", "completedTasks", "completed"] },
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(item?.key || item?.label || item?.name || "").toLowerCase();
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) => toNumber(source[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(item.type || item.visitorType || item.category || "").toLowerCase();
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 (s.includes("complete") || s.includes("done") || s.includes("resolved") || s.includes("closed"))
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 { background: #ffe0de; color: #d72f2f; }
2797
- .figma-icon-badge-info { background: #e8e2ff; color: #6d4bd8; }
2798
- .figma-icon-badge-success { background: #ddf7e6; color: #229852; }
2799
- .figma-icon-badge-warning { background: #ffeed4; color: #c57916; }
2800
- .figma-icon-badge-neutral { background: #edf2f6; color: #607386; }
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 { min-width: 0; }
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 { background: #72e3a1; color: #0f5132; }
2926
- .feedback-status-pill-progress { background: #fde68a; color: #92400e; }
2927
- .feedback-status-pill-danger { background: #fecaca; color: #b91c1c; }
2928
- .feedback-status-pill-todo { background: #e2e8f0; color: #64748b; }
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;