@7365admin1/layer-common 3.2.1 → 3.2.2-staging.100

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.
Files changed (123) hide show
  1. package/.changeset/camera-wall-gated-endpoint.md +47 -0
  2. package/.changeset/camera-wall-plain-english.md +60 -0
  3. package/.changeset/camera-wall-selection-and-guidance.md +65 -0
  4. package/.changeset/dark-primary-contrast.md +39 -0
  5. package/.changeset/dashboard-dark-theme-contrast.md +15 -0
  6. package/.changeset/notification-preferences.md +17 -0
  7. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  8. package/.changeset/service-provider-invitation-actions.md +28 -0
  9. package/assets/css/primitives.css +680 -0
  10. package/assets/css/screens.css +541 -0
  11. package/assets/css/shell.css +258 -0
  12. package/assets/css/tokens.css +220 -0
  13. package/components/AccessCardQrTagging.vue +1 -1
  14. package/components/AccessManagement.vue +53 -72
  15. package/components/AppButton.vue +52 -0
  16. package/components/AppCard.vue +20 -0
  17. package/components/AppDateField.vue +35 -0
  18. package/components/AppField.vue +46 -0
  19. package/components/AppKpiTile.vue +65 -0
  20. package/components/AppSegmented.vue +38 -0
  21. package/components/AppSelect.vue +203 -0
  22. package/components/AreaChecklistHistoryMain.vue +6 -2
  23. package/components/AreaMain.vue +9 -17
  24. package/components/AttendanceMain.vue +4 -3
  25. package/components/Avatar/Main.vue +54 -2
  26. package/components/BillingMain.vue +26 -30
  27. package/components/BuildingManagement/buildings.vue +27 -38
  28. package/components/BuildingManagement/units.vue +26 -42
  29. package/components/BulletinBoardManagement.vue +23 -10
  30. package/components/CameraWall.vue +1034 -0
  31. package/components/CameraWallTile.vue +818 -0
  32. package/components/CardToolbar.vue +37 -0
  33. package/components/CleaningScheduleMain.vue +29 -46
  34. package/components/ClientMain.vue +90 -78
  35. package/components/DashboardEmptyState.vue +11 -1
  36. package/components/DashboardMain.vue +349 -275
  37. package/components/DashboardPanel.vue +1 -1
  38. package/components/DocumentManagement.vue +129 -93
  39. package/components/EntryPassInformation.vue +4 -4
  40. package/components/EntryPassMain.vue +49 -27
  41. package/components/EquipmentItemMain.vue +4 -6
  42. package/components/EquipmentManagementMain.vue +9 -7
  43. package/components/FeedbackMain.vue +42 -56
  44. package/components/FormDialog.vue +60 -21
  45. package/components/HidAccessLogDashboard.vue +119 -44
  46. package/components/HidCameraCaptureDialog.vue +199 -0
  47. package/components/HidIntercomManagement.vue +825 -202
  48. package/components/HidQrCodeConfiguration.vue +927 -93
  49. package/components/HidServiceSettingsPanel.vue +20 -1
  50. package/components/HidUserEnrollment.vue +454 -106
  51. package/components/InvitationMain.vue +46 -61
  52. package/components/Layout/Header.vue +289 -37
  53. package/components/Layout/NavigationDrawer.vue +340 -30
  54. package/components/ManageChecklistMain.vue +17 -17
  55. package/components/MemberMain.vue +79 -60
  56. package/components/MyAttendanceMain.vue +3 -5
  57. package/components/NavigationItem.vue +9 -0
  58. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  59. package/components/NotificationSettings.vue +361 -0
  60. package/components/OnlineFormConfigurationForm.vue +620 -224
  61. package/components/OnlineFormFill.vue +36 -8
  62. package/components/OnlineFormsConfiguration.vue +7 -3
  63. package/components/PageHeader.vue +43 -0
  64. package/components/PassInformation.vue +104 -45
  65. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  66. package/components/QrTemplate/PrintDialog.vue +209 -196
  67. package/components/RolePermissionMain.vue +184 -91
  68. package/components/ScanVisitorQRCode.vue +35 -7
  69. package/components/ScheduleAreaMain.vue +15 -49
  70. package/components/ScheduleTaskMain.vue +23 -26
  71. package/components/ScheduleTastTicketMain.vue +18 -43
  72. package/components/ServiceProviderInvitationPrompt.vue +150 -0
  73. package/components/ServiceProviderMain.vue +346 -157
  74. package/components/SiteSettings.vue +25 -10
  75. package/components/SpecificAttr.vue +10 -2
  76. package/components/StatusChip.vue +93 -0
  77. package/components/SwitchContext.vue +33 -1
  78. package/components/TableHygiene.vue +29 -46
  79. package/components/TableList.vue +1 -1
  80. package/components/TableListSecondary.vue +1 -1
  81. package/components/TableMain.vue +161 -77
  82. package/components/TableV3.vue +61 -66
  83. package/components/UnitMain.vue +9 -17
  84. package/components/VehicleManagement.vue +20 -9
  85. package/components/VisitorForm.vue +294 -78
  86. package/components/VisitorManagement.vue +221 -71
  87. package/components/VisitorSocketPopUp.vue +56 -2
  88. package/components/WorkOrder/Main.vue +42 -51
  89. package/composables/useComment.ts +3 -3
  90. package/composables/useHidAmico.ts +151 -0
  91. package/composables/useHidNavigation.ts +0 -7
  92. package/composables/useLocalAuth.ts +9 -36
  93. package/composables/useNotificationPreference.ts +87 -0
  94. package/composables/useOnlineFormPages.ts +2 -0
  95. package/composables/useOrg.ts +1 -1
  96. package/composables/useRole.ts +22 -0
  97. package/composables/useServiceProviderInvitation.ts +145 -0
  98. package/composables/useSipWebPhone.ts +336 -0
  99. package/composables/useSiteSettings.ts +50 -0
  100. package/composables/useThemePreference.ts +63 -0
  101. package/composables/useVisitor.ts +6 -1
  102. package/composables/useVisitorSocket.ts +26 -0
  103. package/composables/useWebUsb.ts +127 -37
  104. package/nuxt.config.ts +31 -0
  105. package/package.json +4 -1
  106. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  107. package/pages/notification-settings.vue +19 -0
  108. package/plugins/secure-member.client.ts +21 -56
  109. package/plugins/vuetify.ts +68 -9
  110. package/test/visitor-socket.test.mjs +36 -0
  111. package/types/org.d.ts +2 -0
  112. package/types/site.d.ts +71 -0
  113. package/utils/camera-wall.test.ts +571 -0
  114. package/utils/camera-wall.ts +926 -0
  115. package/utils/nav-sections.test.ts +190 -0
  116. package/utils/nav-sections.ts +151 -0
  117. package/utils/status.test.ts +77 -0
  118. package/utils/status.ts +90 -0
  119. package/utils/theme.test.ts +534 -0
  120. package/utils/theme.ts +294 -0
  121. package/components/HidIdentityMapping.vue +0 -975
  122. package/middleware/member.ts +0 -4
  123. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
@@ -203,46 +203,36 @@
203
203
 
204
204
  <section v-else class="unified-dashboard pb-8">
205
205
  <!-- ── Header ──────────────────────────────────────────────────────────── -->
206
- <div
207
- class="d-flex flex-column flex-md-row align-md-start justify-space-between ga-5 mb-6"
206
+ <PageHeader
207
+ :title="dashboardTitle"
208
+ :subtitle="currentSiteName || 'Selected site'"
209
+ subtitle-icon="mdi-map-marker"
208
210
  >
209
- <div>
210
- <h1 class="text-h5 font-weight-black text-blue-grey-darken-4 mb-0">
211
- {{ dashboardTitle }}
212
- </h1>
213
- <div class="d-flex align-center text-blue-grey ga-1 mt-2 text-caption">
214
- <v-icon icon="mdi-map-marker" size="16" />
215
- <span>{{ currentSiteName || "Selected site" }}</span>
216
- </div>
217
- </div>
218
-
219
- <div
220
- v-if="showHeaderActions"
221
- class="d-flex flex-wrap align-center justify-md-end ga-3"
222
- >
223
- <v-btn
224
- variant="outlined"
225
- color="blue-grey-darken-1"
226
- class="text-none font-weight-bold"
227
- prepend-icon="mdi-tune-variant"
211
+ <!--
212
+ The design's three actions: two 38px ghost buttons and the accent
213
+ segmented control. Same dialog, same export menu, same range model -
214
+ `Customize Dashboard` and `Export` shorten to the labels the prototype
215
+ prints, and the export menu keeps both of its formats.
216
+ -->
217
+ <template v-if="showHeaderActions" #actions>
218
+ <AppButton
219
+ variant="ghost"
220
+ icon="mdi-tune-variant"
228
221
  @click="openCustomizeDialog"
229
222
  >
230
- Customize Dashboard
231
- </v-btn>
223
+ Customize
224
+ </AppButton>
232
225
 
233
226
  <v-menu>
234
227
  <template #activator="{ props: menuProps }">
235
- <v-btn
228
+ <AppButton
236
229
  v-bind="menuProps"
237
- variant="outlined"
238
- color="blue-grey-darken-1"
239
- class="text-none font-weight-bold"
240
- append-icon="mdi-chevron-down"
241
- :loading="isExporting"
230
+ variant="ghost"
231
+ icon="mdi-download-outline"
242
232
  :disabled="isExporting"
243
233
  >
244
234
  Export
245
- </v-btn>
235
+ </AppButton>
246
236
  </template>
247
237
 
248
238
  <v-list density="compact" border rounded="lg" elevation="3">
@@ -251,36 +241,9 @@
251
241
  </v-list>
252
242
  </v-menu>
253
243
 
254
- <v-btn-toggle
255
- v-model="selectedRange"
256
- mandatory
257
- density="comfortable"
258
- divided
259
- variant="outlined"
260
- class="bg-white"
261
- style="border-radius: 8px; border-color: #d9e1e8"
262
- >
263
- <v-btn
264
- v-for="range in rangeOptions"
265
- :key="range.value"
266
- :value="range.value"
267
- class="text-none"
268
- size="small"
269
- :style="
270
- selectedRange === range.value
271
- ? {
272
- backgroundColor: '#e2e8f0',
273
- color: '#0b2f47',
274
- fontWeight: '600',
275
- }
276
- : { backgroundColor: 'transparent', color: '#64748b' }
277
- "
278
- >
279
- {{ range.label }}
280
- </v-btn>
281
- </v-btn-toggle>
282
- </div>
283
- </div>
244
+ <AppSegmented v-model="selectedRange" :options="rangeOptions" />
245
+ </template>
246
+ </PageHeader>
284
247
 
285
248
  <v-progress-linear
286
249
  v-if="finalLoading"
@@ -300,22 +263,24 @@
300
263
  lg="3"
301
264
  class="d-flex"
302
265
  >
303
- <v-card
304
- flat
305
- border
306
- rounded="lg"
307
- class="pa-4 flex-grow-1"
308
- style="min-height: 158px"
266
+ <!--
267
+ The same figures the tile has always shown, in the arrangement the
268
+ handoff draws them: icon and label on one row, the number with its
269
+ percentage pill beside it, the sub-line under both. The filter menu
270
+ and the facility "View All" keep their place at the end of the first
271
+ row and do exactly what they did.
272
+ -->
273
+ <AppKpiTile
274
+ :icon="card.icon"
275
+ :tint="card.tint"
276
+ :color="card.color"
277
+ :label="card.title"
278
+ :value="card.value"
279
+ :chip="kpiChipText(card)"
280
+ :down="Number(card.percentage) < 0"
281
+ :sub="card.meta"
309
282
  >
310
- <div class="d-flex align-start justify-space-between">
311
- <div
312
- class="d-inline-flex align-center justify-center rounded-lg"
313
- style="height: 28px; width: 28px; flex-shrink: 0"
314
- :style="{ backgroundColor: card.tint }"
315
- >
316
- <v-icon :icon="card.icon" :color="card.color" size="15" />
317
- </div>
318
-
283
+ <template v-if="card.filter || card.key === 'facilityBookings'" #action>
319
284
  <v-menu v-if="card.filter" v-model="filterMenus[card.key]">
320
285
  <template #activator="{ props: menuProps }">
321
286
  <v-btn
@@ -359,61 +324,24 @@
359
324
  >
360
325
  View All
361
326
  </button>
362
- </div>
327
+ </template>
363
328
 
364
- <div class="mt-6 w-100">
365
- <p
366
- class="text-caption font-weight-black text-uppercase text-blue-grey-darken-2 mb-3"
367
- >
368
- {{ card.title }}
369
- </p>
370
- <template
371
- v-if="
372
- card.key === 'supplyAlert' && Array.isArray(card.raw?.items)
373
- "
374
- >
375
- <div class="d-flex flex-column ga-2 mt-2">
376
- <div
377
- v-for="item in card.raw.items"
378
- :key="item._id || item.name"
379
- class="d-flex justify-space-between text-body-2 text-blue-grey-darken-4 font-weight-medium"
380
- >
381
- <span>{{ item.name }}</span>
382
- <span class="text-blue-grey">Balance: {{ item.qty }}</span>
383
- </div>
384
- </div>
385
- </template>
386
- <template v-else>
387
- <div class="d-flex align-center mb-3" style="min-height: 31px">
388
- <span class="text-h5 font-weight-black text-blue-grey-darken-4">
389
- {{ card.value }}
390
- </span>
391
- </div>
392
- <div class="d-flex align-center flex-wrap ga-2">
393
- <span v-if="card.meta" class="text-caption text-blue-grey">{{
394
- card.meta
395
- }}</span>
396
- <v-chip
397
- :color="card.percentage >= 0 ? 'success' : 'error'"
398
- size="x-small"
399
- variant="flat"
400
- class="font-weight-bold"
401
- >
402
- <v-icon
403
- :icon="
404
- card.percentage >= 0
405
- ? 'mdi-arrow-up-right'
406
- : 'mdi-arrow-down-right'
407
- "
408
- size="13"
409
- start
410
- />
411
- {{ Math.abs(card.percentage) }}%
412
- </v-chip>
329
+ <!-- The supply tile lists items instead of counting one figure. -->
330
+ <template
331
+ v-if="card.key === 'supplyAlert' && Array.isArray(card.raw?.items)"
332
+ >
333
+ <div class="d-flex flex-column ga-2">
334
+ <div
335
+ v-for="item in card.raw.items"
336
+ :key="item._id || item.name"
337
+ class="d-flex justify-space-between text-body-2 text-high-emphasis font-weight-medium"
338
+ >
339
+ <span>{{ item.name }}</span>
340
+ <span class="text-medium-emphasis">Balance: {{ item.qty }}</span>
413
341
  </div>
414
- </template>
415
- </div>
416
- </v-card>
342
+ </div>
343
+ </template>
344
+ </AppKpiTile>
417
345
  </v-col>
418
346
  </v-row>
419
347
 
@@ -421,12 +349,10 @@
421
349
  <v-card
422
350
  v-if="isWidgetVisible('activity')"
423
351
  flat
424
- border
425
- rounded="lg"
426
- class="px-5 pt-4 pb-3 mt-3"
352
+ class="dashboard-card px-5 pt-4 pb-3 mt-3"
427
353
  >
428
354
  <div class="d-flex align-center justify-space-between">
429
- <h2 class="text-body-1 font-weight-black text-blue-grey-darken-4 mb-0">
355
+ <h2 class="dashboard-card-title mb-0">
430
356
  {{ activityTitle }}
431
357
  </h2>
432
358
  </div>
@@ -444,7 +370,7 @@
444
370
  :y1="tick"
445
371
  :x2="chartWidth - 20"
446
372
  :y2="tick"
447
- style="stroke: #e9eef2; stroke-width: 1"
373
+ style="stroke: rgba(var(--v-theme-on-surface), 0.12); stroke-width: 1"
448
374
  />
449
375
  <text
450
376
  v-for="label in chartYLabels"
@@ -453,7 +379,7 @@
453
379
  :y="label.y"
454
380
  text-anchor="end"
455
381
  alignment-baseline="middle"
456
- style="fill: #7e8b96; font-size: 11px; font-weight: 500"
382
+ style="fill: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); font-size: 11px; font-weight: 500"
457
383
  >
458
384
  {{ label.text }}
459
385
  </text>
@@ -483,7 +409,7 @@
483
409
  :cx="point.x"
484
410
  :cy="point.y"
485
411
  r="4"
486
- style="fill: #ff9b9b; stroke: #fff; stroke-width: 2"
412
+ style="fill: #ff9b9b; stroke: rgb(var(--v-theme-surface)); stroke-width: 2"
487
413
  />
488
414
  <circle
489
415
  v-for="point in secondaryDots"
@@ -491,7 +417,7 @@
491
417
  :cx="point.x"
492
418
  :cy="point.y"
493
419
  r="4"
494
- style="fill: #e53935; stroke: #fff; stroke-width: 2"
420
+ style="fill: #e53935; stroke: rgb(var(--v-theme-surface)); stroke-width: 2"
495
421
  />
496
422
  <text
497
423
  v-for="label in chartAxisLabels"
@@ -499,7 +425,7 @@
499
425
  :x="label.x"
500
426
  y="246"
501
427
  text-anchor="middle"
502
- style="fill: #7e8b96; font-size: 12px; font-weight: 600"
428
+ style="fill: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); font-size: 12px; font-weight: 600"
503
429
  >
504
430
  {{ label.text }}
505
431
  </text>
@@ -507,7 +433,7 @@
507
433
 
508
434
  <div class="d-flex align-center justify-center ga-5 mt-2">
509
435
  <span
510
- class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
436
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-medium-emphasis"
511
437
  >
512
438
  <span
513
439
  class="rounded-circle"
@@ -521,7 +447,7 @@
521
447
  {{ primaryLegendLabel }}
522
448
  </span>
523
449
  <span
524
- class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-blue-grey"
450
+ class="d-inline-flex align-center ga-2 text-caption font-weight-bold text-medium-emphasis"
525
451
  >
526
452
  <span
527
453
  class="rounded-circle"
@@ -558,18 +484,16 @@
558
484
  v-for="item in taskScheduleItems"
559
485
  :key="item.id"
560
486
  flat
561
- border
562
- rounded="lg"
563
- class="pa-3"
487
+ class="dashboard-row-card pa-3"
564
488
  >
565
489
  <div class="d-flex align-center flex-wrap ga-3">
566
490
  <div class="flex-grow-1" style="min-width: 0">
567
491
  <div
568
- class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
492
+ class="text-body-2 font-weight-bold text-high-emphasis text-truncate"
569
493
  >
570
494
  {{ item.title }}
571
495
  </div>
572
- <div class="text-caption text-blue-grey mt-1 text-truncate">
496
+ <div class="text-caption text-medium-emphasis mt-1 text-truncate">
573
497
  {{ item.subtitle
574
498
  }}<template v-if="item.subtitle && item.time">
575
499
  &bull; </template
@@ -577,7 +501,7 @@
577
501
  </div>
578
502
  </div>
579
503
  <div
580
- class="d-flex align-center ga-2 text-caption text-blue-grey"
504
+ class="d-flex align-center ga-2 text-caption text-medium-emphasis"
581
505
  >
582
506
  <AvatarMain :name="item.person" :size="30" />
583
507
  <span class="text-truncate" style="max-width: 100px">{{
@@ -624,25 +548,22 @@
624
548
  v-for="item in staffStatusItems"
625
549
  :key="item.id"
626
550
  flat
627
- border
628
- rounded="lg"
629
- class="pa-3"
551
+ class="dashboard-row-card pa-3"
630
552
  >
631
553
  <div class="d-flex align-start ga-3">
632
554
  <AvatarMain :name="item.name" :size="32" />
633
555
  <div class="flex-grow-1" style="min-width: 0">
634
556
  <div
635
- class="text-body-2 font-weight-bold text-blue-grey-darken-4 text-truncate"
557
+ class="text-body-2 font-weight-bold text-high-emphasis text-truncate"
636
558
  >
637
559
  {{ item.name }}
638
560
  </div>
639
- <div class="text-caption text-blue-grey mt-1 text-truncate">
561
+ <div class="text-caption text-medium-emphasis mt-1 text-truncate">
640
562
  {{ item.role }}
641
563
  </div>
642
564
  </div>
643
565
  <v-chip
644
566
  size="small"
645
- color="blue-grey-darken-1"
646
567
  variant="tonal"
647
568
  label
648
569
  class="ml-auto flex-shrink-0 font-weight-bold"
@@ -666,13 +587,13 @@
666
587
  <div
667
588
  v-for="item in feedbackItems"
668
589
  :key="item.id"
669
- class="d-flex align-center justify-space-between"
590
+ class="d-flex align-center justify-space-between dashboard-feedback-row"
670
591
  style="
671
592
  padding: 16px 20px;
672
- border: 1px solid #e2e8f0;
593
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
673
594
  border-radius: 12px;
674
595
  margin-bottom: 12px;
675
- background-color: #ffffff;
596
+ background-color: rgb(var(--v-theme-surface-bright));
676
597
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
677
598
  "
678
599
  >
@@ -682,12 +603,12 @@
682
603
  display: block;
683
604
  font-size: 15px;
684
605
  font-weight: 700;
685
- color: #0b2f47;
606
+ color: rgb(var(--v-theme-on-surface));
686
607
  margin-bottom: 4px;
687
608
  "
688
609
  >{{ item.title }}</strong
689
610
  >
690
- <span style="font-size: 13px; color: #94a3b8">{{
611
+ <span style="font-size: 13px; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))">{{
691
612
  item.subtitle
692
613
  }}</span>
693
614
  </div>
@@ -696,7 +617,7 @@
696
617
  style="flex: 1; display: flex; align-items: center; gap: 12px"
697
618
  >
698
619
  <AvatarMain :name="item.person" :size="36" />
699
- <span style="font-size: 14px; color: #64748b">{{
620
+ <span style="font-size: 14px; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))">{{
700
621
  item.person
701
622
  }}</span>
702
623
  </div>
@@ -721,9 +642,9 @@
721
642
  variant="outlined"
722
643
  class="text-none font-weight-medium"
723
644
  size="small"
724
- color="#3b82f6"
645
+ color="primary"
725
646
  style="
726
- border-color: #e2e8f0;
647
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
727
648
  border-radius: 8px;
728
649
  padding: 0 16px;
729
650
  height: 32px;
@@ -967,7 +888,7 @@
967
888
  <v-col v-if="isWidgetVisible('activePatrol')" cols="12" lg="6">
968
889
  <div class="dashboard-card dashboard-panel figma-panel">
969
890
  <div class="figma-panel-header">
970
- <h2>ACTIVE PATROL</h2>
891
+ <h2>Active Patrol</h2>
971
892
  <button
972
893
  class="figma-panel-action"
973
894
  type="button"
@@ -1026,9 +947,9 @@
1026
947
  variant="outlined"
1027
948
  class="text-none font-weight-medium"
1028
949
  size="small"
1029
- color="#3b82f6"
950
+ color="primary"
1030
951
  style="
1031
- border-color: #e2e8f0;
952
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
1032
953
  border-radius: 8px;
1033
954
  padding: 0 16px;
1034
955
  height: 32px;
@@ -1047,7 +968,7 @@
1047
968
  <v-col v-if="isWidgetVisible('staffStatus')" cols="12" lg="6">
1048
969
  <div class="dashboard-card dashboard-panel figma-panel">
1049
970
  <div class="figma-panel-header">
1050
- <h2>STAFF STATUS</h2>
971
+ <h2>Staff Status</h2>
1051
972
  <button
1052
973
  class="figma-panel-action"
1053
974
  type="button"
@@ -1064,10 +985,10 @@
1064
985
  class="d-flex align-center justify-space-between"
1065
986
  style="
1066
987
  padding: 16px 20px;
1067
- border: 1px solid #e2e8f0;
988
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
1068
989
  border-radius: 12px;
1069
990
  margin-bottom: 12px;
1070
- background-color: #ffffff;
991
+ background-color: rgb(var(--v-theme-surface-bright));
1071
992
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
1072
993
  "
1073
994
  >
@@ -1087,14 +1008,14 @@
1087
1008
  display: block;
1088
1009
  font-size: 15px;
1089
1010
  font-weight: 700;
1090
- color: #0b2f47;
1011
+ color: rgb(var(--v-theme-on-surface));
1091
1012
  margin-bottom: 4px;
1092
1013
  "
1093
1014
  >{{
1094
1015
  item.userName || item.personName || "Unassigned"
1095
1016
  }}</strong
1096
1017
  >
1097
- <span style="font-size: 13px; color: #94a3b8">{{
1018
+ <span style="font-size: 13px; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))">{{
1098
1019
  item.role || item.designation || "Officer"
1099
1020
  }}</span>
1100
1021
  </div>
@@ -1131,13 +1052,13 @@
1131
1052
  <div
1132
1053
  v-for="item in feedbackItems"
1133
1054
  :key="item.id"
1134
- class="d-flex align-center justify-space-between"
1055
+ class="d-flex align-center justify-space-between dashboard-feedback-row"
1135
1056
  style="
1136
1057
  padding: 16px 20px;
1137
- border: 1px solid #e2e8f0;
1058
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
1138
1059
  border-radius: 12px;
1139
1060
  margin-bottom: 12px;
1140
- background-color: #ffffff;
1061
+ background-color: rgb(var(--v-theme-surface-bright));
1141
1062
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
1142
1063
  "
1143
1064
  >
@@ -1147,12 +1068,12 @@
1147
1068
  display: block;
1148
1069
  font-size: 15px;
1149
1070
  font-weight: 700;
1150
- color: #0b2f47;
1071
+ color: rgb(var(--v-theme-on-surface));
1151
1072
  margin-bottom: 4px;
1152
1073
  "
1153
1074
  >{{ item.title }}</strong
1154
1075
  >
1155
- <span style="font-size: 13px; color: #94a3b8">{{
1076
+ <span style="font-size: 13px; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))">{{
1156
1077
  item.subtitle
1157
1078
  }}</span>
1158
1079
  </div>
@@ -1161,7 +1082,7 @@
1161
1082
  style="flex: 1; display: flex; align-items: center; gap: 12px"
1162
1083
  >
1163
1084
  <AvatarMain :name="item.person" :size="36" />
1164
- <span style="font-size: 14px; color: #64748b">{{
1085
+ <span style="font-size: 14px; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))">{{
1165
1086
  item.person
1166
1087
  }}</span>
1167
1088
  </div>
@@ -1186,9 +1107,9 @@
1186
1107
  variant="outlined"
1187
1108
  class="text-none font-weight-medium"
1188
1109
  size="small"
1189
- color="#3b82f6"
1110
+ color="primary"
1190
1111
  style="
1191
- border-color: #e2e8f0;
1112
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
1192
1113
  border-radius: 8px;
1193
1114
  padding: 0 16px;
1194
1115
  height: 32px;
@@ -1211,10 +1132,10 @@
1211
1132
  class="d-flex align-start justify-space-between pa-6 pb-4 border-b"
1212
1133
  >
1213
1134
  <div>
1214
- <div class="text-h6 font-weight-black text-blue-grey-darken-4">
1135
+ <div class="text-h6 font-weight-black text-high-emphasis">
1215
1136
  Customize Dashboard
1216
1137
  </div>
1217
- <div class="text-caption text-blue-grey mt-1">
1138
+ <div class="text-caption text-medium-emphasis mt-1">
1218
1139
  Select which widgets to display on your dashboard
1219
1140
  </div>
1220
1141
  </div>
@@ -1240,7 +1161,6 @@
1240
1161
  <div class="d-flex justify-end mt-3 mb-1">
1241
1162
  <v-btn
1242
1163
  variant="text"
1243
- color="blue-grey-darken-4"
1244
1164
  size="small"
1245
1165
  class="text-none font-weight-bold"
1246
1166
  prepend-icon="mdi-refresh"
@@ -1262,11 +1182,11 @@
1262
1182
  <div class="d-flex align-center justify-space-between ga-4">
1263
1183
  <div>
1264
1184
  <div
1265
- class="text-body-2 font-weight-black text-blue-grey-darken-4"
1185
+ class="text-body-2 font-weight-black text-high-emphasis"
1266
1186
  >
1267
1187
  {{ widget.title }}
1268
1188
  </div>
1269
- <div class="text-caption text-blue-grey mt-1">
1189
+ <div class="text-caption text-medium-emphasis mt-1">
1270
1190
  {{ widget.description }}
1271
1191
  </div>
1272
1192
  </div>
@@ -1292,7 +1212,7 @@
1292
1212
  <v-card-actions class="pa-6 pt-4 border-t justify-end ga-3">
1293
1213
  <v-btn
1294
1214
  variant="text"
1295
- class="text-none font-weight-bold text-blue-grey"
1215
+ class="text-none font-weight-bold text-medium-emphasis"
1296
1216
  @click="cancelCustomize"
1297
1217
  >
1298
1218
  Cancel
@@ -1320,7 +1240,7 @@
1320
1240
  <v-card-title
1321
1241
  class="d-flex align-center justify-space-between pa-6 pb-3"
1322
1242
  >
1323
- <div class="text-body-1 font-weight-black text-blue-grey-darken-4">
1243
+ <div class="text-body-1 font-weight-black text-high-emphasis">
1324
1244
  Facility Booking
1325
1245
  </div>
1326
1246
  <v-btn
@@ -2505,6 +2425,17 @@ const visibleKpiCards = computed(() =>
2505
2425
  kpiCards.value.filter((card) => isWidgetVisible(card.key))
2506
2426
  );
2507
2427
 
2428
+ /**
2429
+ * The percentage pill's label. Same number the chip has always shown, written
2430
+ * the way the handoff prints it - `+0%` / `-6.4%`, the sign carrying what the
2431
+ * arrow icon used to. A card without a percentage gets no pill.
2432
+ */
2433
+ function kpiChipText(card: Record<string, any>): string {
2434
+ const value = Number(card?.percentage);
2435
+ if (!Number.isFinite(value)) return "";
2436
+ return `${value < 0 ? "-" : "+"}${Math.abs(value)}%`;
2437
+ }
2438
+
2508
2439
  // ─── Dashboard Title ──────────────────────────────────────────────────────────
2509
2440
 
2510
2441
  const dashboardTitle = computed(() => {
@@ -3254,31 +3185,37 @@ function getInitials(name?: string) {
3254
3185
  function getStatusPillStyle(status: any) {
3255
3186
  const tone = getStatusTone(status);
3256
3187
  const map: Record<string, { backgroundColor: string; color: string }> = {
3257
- success: { backgroundColor: "#72e3a1", color: "#0f5132" },
3258
- warning: { backgroundColor: "#fde68a", color: "#92400e" },
3259
- error: { backgroundColor: "#fecaca", color: "#b91c1c" },
3260
- neutral: { backgroundColor: "#e2e8f0", color: "#64748b" },
3188
+ success: { backgroundColor: "var(--ok-bg)", color: "var(--ok)" },
3189
+ warning: { backgroundColor: "var(--warn-bg)", color: "var(--warn)" },
3190
+ error: { backgroundColor: "var(--err-bg)", color: "var(--err)" },
3191
+ neutral: { backgroundColor: "var(--hover)", color: "var(--text2)" },
3261
3192
  };
3262
3193
  return map[tone] || map.neutral;
3263
3194
  }
3264
3195
 
3196
+ /**
3197
+ * The pills are drawn by inline style, so the literal colours lived here rather
3198
+ * than in the stylesheet - which is why they survived the dark-mode repair and
3199
+ * still measured 3.86:1. Same three cases, same words, the design's tones.
3200
+ */
3265
3201
  function getPatrolStatusStyle(status: any) {
3266
3202
  const s = String(status || "").toLowerCase();
3267
3203
  if (s === "completed" || s === "complete")
3268
- return { backgroundColor: "#72e3a1", color: "#0f5132" };
3204
+ return { backgroundColor: "var(--ok-bg)", color: "var(--ok)" };
3269
3205
  if (s === "on paused" || s === "paused")
3270
- return { backgroundColor: "#dcdcdc", color: "#64748b" };
3271
- return { backgroundColor: "#e2e8f0", color: "#64748b" };
3206
+ return { backgroundColor: "var(--hover)", color: "var(--text2)" };
3207
+ return { backgroundColor: "var(--hover)", color: "var(--text2)" };
3272
3208
  }
3273
3209
 
3274
3210
  function getStaffStatusPillStyle(status: any) {
3275
3211
  const s = String(status || "").toLowerCase();
3276
3212
  if (s === "on duty" || s === "on-duty")
3277
- return { backgroundColor: "#72e3a1", color: "#0f5132" };
3213
+ return { backgroundColor: "var(--ok-bg)", color: "var(--ok)" };
3278
3214
  if (s === "on break" || s === "on-break")
3279
- return { backgroundColor: "#fde68a", color: "#92400e" };
3280
- if (s === "absent") return { backgroundColor: "#fecaca", color: "#b91c1c" };
3281
- return { backgroundColor: "#e2e8f0", color: "#64748b" };
3215
+ return { backgroundColor: "var(--warn-bg)", color: "var(--warn)" };
3216
+ if (s === "absent")
3217
+ return { backgroundColor: "var(--err-bg)", color: "var(--err)" };
3218
+ return { backgroundColor: "var(--hover)", color: "var(--text2)" };
3282
3219
  }
3283
3220
 
3284
3221
  function getStatusProgressStyle(item: { percent: number; color: string }) {
@@ -4158,37 +4095,98 @@ function formatDashboardTime(value?: string) {
4158
4095
  text-transform: capitalize;
4159
4096
  }
4160
4097
 
4098
+ /* Was five pairs of literal pastels - the same pale green on the dark page,
4099
+ and none of them the tones the rest of the product uses. */
4161
4100
  .badge-success {
4162
- background-color: #d1fae5;
4163
- color: #065f46;
4101
+ background-color: var(--ok-bg);
4102
+ color: var(--ok);
4164
4103
  }
4165
4104
 
4166
4105
  .badge-info {
4167
- background-color: #e0f2fe;
4168
- color: #075985;
4106
+ background-color: var(--info-bg);
4107
+ color: var(--info);
4169
4108
  }
4170
4109
 
4171
4110
  .badge-danger,
4172
4111
  .badge-error {
4173
- background-color: #fee2e2;
4174
- color: #991b1b;
4112
+ background-color: var(--err-bg);
4113
+ color: var(--err);
4175
4114
  }
4176
4115
 
4177
4116
  .badge-warning {
4178
- background-color: #fef3c7;
4179
- color: #92400e;
4117
+ background-color: var(--warn-bg);
4118
+ color: var(--warn);
4180
4119
  }
4181
4120
 
4182
4121
  .badge-neutral {
4183
- background-color: #f1f5f9;
4184
- color: #475569;
4122
+ background-color: var(--hover);
4123
+ color: var(--text2);
4185
4124
  }
4186
4125
 
4126
+ /* ------------------------------------------------------------------ */
4127
+ /* PHASE 5 - the design's card, type and semantic tones. */
4128
+ /* */
4129
+ /* The dark-mode repair earlier today moved these surfaces off literal */
4130
+ /* white onto the Vuetify theme; this keeps that and takes the last */
4131
+ /* step, onto the design tokens themselves - so the dashboard's cards, */
4132
+ /* headings and pills are the same 14px card, the same Manrope and the */
4133
+ /* same four status tones as every other screen. */
4134
+ /* ------------------------------------------------------------------ */
4135
+
4187
4136
  .dashboard-card {
4188
- background: #fff;
4189
- border: 1px solid #d9e0e7;
4190
- border-radius: 8px;
4191
- box-shadow: 0 1px 2px rgba(15, 35, 54, 0.04);
4137
+ background: var(--card);
4138
+ border: 1px solid var(--border);
4139
+ border-radius: var(--r-card);
4140
+ box-shadow: var(--shadow);
4141
+ font-family: var(--font-sans);
4142
+ }
4143
+
4144
+ /* The KPI tile and the rows inside a panel are the same card, one step in. */
4145
+ .dashboard-kpi-card,
4146
+ .dashboard-row-card {
4147
+ background: var(--card);
4148
+ border: 1px solid var(--border);
4149
+ border-radius: var(--r-card);
4150
+ box-shadow: var(--shadow);
4151
+ font-family: var(--font-sans);
4152
+ }
4153
+
4154
+ .dashboard-title {
4155
+ font-family: var(--font-sans);
4156
+ font-size: var(--fs-h1);
4157
+ font-weight: var(--fw-h1);
4158
+ letter-spacing: var(--ls-h1);
4159
+ color: var(--text);
4160
+ }
4161
+
4162
+ .dashboard-card-title {
4163
+ font-family: var(--font-sans);
4164
+ font-size: var(--fs-card-title);
4165
+ font-weight: var(--fw-card-title);
4166
+ letter-spacing: 0;
4167
+ color: var(--text);
4168
+ }
4169
+
4170
+ /* 11px/800 uppercase over a 30px/800 number, tabular so the columns of
4171
+ figures line up - the design's KPI, and the same header type the table
4172
+ card already uses. */
4173
+ .dashboard-kpi-label {
4174
+ font-family: var(--font-sans);
4175
+ font-size: var(--fs-table-head);
4176
+ font-weight: var(--fw-table-head);
4177
+ letter-spacing: var(--ls-table-head);
4178
+ text-transform: uppercase;
4179
+ color: var(--muted);
4180
+ }
4181
+
4182
+ .dashboard-kpi-value {
4183
+ font-family: var(--font-sans);
4184
+ font-size: var(--fs-kpi);
4185
+ font-weight: var(--fw-kpi);
4186
+ letter-spacing: var(--ls-kpi);
4187
+ line-height: 1.1;
4188
+ color: var(--text);
4189
+ font-variant-numeric: tabular-nums;
4192
4190
  }
4193
4191
 
4194
4192
  .dashboard-section-grid {
@@ -4209,7 +4207,7 @@ function formatDashboardTime(value?: string) {
4209
4207
  }
4210
4208
 
4211
4209
  .figma-panel {
4212
- border-color: #dde4ea;
4210
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
4213
4211
  box-shadow: none;
4214
4212
  min-height: 276px;
4215
4213
  padding: 26px 28px 24px;
@@ -4223,14 +4221,17 @@ function formatDashboardTime(value?: string) {
4223
4221
  min-height: 18px;
4224
4222
  }
4225
4223
 
4224
+ /* The panel heading is the design's card title: 15.5px/800, sentence case. It
4225
+ was a 12px/900 uppercase label from the earlier Figma. */
4226
4226
  .figma-panel-header h2 {
4227
- color: #18324a;
4228
- font-size: 12px;
4229
- font-weight: 900;
4227
+ color: var(--text);
4228
+ font-family: var(--font-sans);
4229
+ font-size: var(--fs-card-title);
4230
+ font-weight: var(--fw-card-title);
4230
4231
  letter-spacing: 0;
4231
4232
  line-height: 1.2;
4232
4233
  margin: 0;
4233
- text-transform: uppercase;
4234
+ text-transform: none;
4234
4235
  }
4235
4236
 
4236
4237
  .figma-panel-action,
@@ -4238,7 +4239,7 @@ function formatDashboardTime(value?: string) {
4238
4239
  appearance: none;
4239
4240
  background: transparent;
4240
4241
  border: 0;
4241
- color: #1976d2;
4242
+ color: rgb(var(--v-theme-primary));
4242
4243
  cursor: pointer;
4243
4244
  font-size: 12px;
4244
4245
  font-weight: 800;
@@ -4248,7 +4249,7 @@ function formatDashboardTime(value?: string) {
4248
4249
  }
4249
4250
 
4250
4251
  .facility-booking-modal {
4251
- border: 1px solid #d9e0e7;
4252
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
4252
4253
  box-shadow: 0 18px 48px rgba(15, 35, 54, 0.18);
4253
4254
  }
4254
4255
 
@@ -4269,7 +4270,7 @@ function formatDashboardTime(value?: string) {
4269
4270
  }
4270
4271
 
4271
4272
  .facility-booking-summary-label {
4272
- color: #1565c0;
4273
+ color: rgb(var(--v-theme-primary));
4273
4274
  font-size: 16px;
4274
4275
  font-weight: 500;
4275
4276
  line-height: 1.25;
@@ -4289,7 +4290,7 @@ function formatDashboardTime(value?: string) {
4289
4290
  }
4290
4291
 
4291
4292
  .facility-booking-summary-dots {
4292
- border-bottom: 2px dotted #9aa3ad;
4293
+ border-bottom: 2px dotted rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4293
4294
  display: block;
4294
4295
  height: 1px;
4295
4296
  min-width: 32px;
@@ -4297,7 +4298,7 @@ function formatDashboardTime(value?: string) {
4297
4298
 
4298
4299
  .facility-booking-summary-value,
4299
4300
  .facility-booking-total-label {
4300
- color: #0b2f47;
4301
+ color: rgb(var(--v-theme-on-surface));
4301
4302
  font-size: 16px;
4302
4303
  font-weight: 800;
4303
4304
  line-height: 1.25;
@@ -4320,8 +4321,8 @@ function formatDashboardTime(value?: string) {
4320
4321
 
4321
4322
  .figma-list-row {
4322
4323
  align-items: center;
4323
- background: #fff;
4324
- border: 1px solid #e1e7ed;
4324
+ background: rgb(var(--v-theme-surface-bright));
4325
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
4325
4326
  border-radius: 6px;
4326
4327
  display: grid;
4327
4328
  gap: 10px;
@@ -4332,7 +4333,7 @@ function formatDashboardTime(value?: string) {
4332
4333
  }
4333
4334
 
4334
4335
  .figma-list-row:hover {
4335
- border-color: #d5e0ea;
4336
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
4336
4337
  box-shadow: 0 6px 14px rgba(15, 44, 69, 0.05);
4337
4338
  }
4338
4339
 
@@ -4341,7 +4342,7 @@ function formatDashboardTime(value?: string) {
4341
4342
  }
4342
4343
 
4343
4344
  .figma-list-main strong {
4344
- color: #152f47;
4345
+ color: rgb(var(--v-theme-on-surface));
4345
4346
  display: block;
4346
4347
  font-size: 12px;
4347
4348
  font-weight: 800;
@@ -4352,7 +4353,7 @@ function formatDashboardTime(value?: string) {
4352
4353
  }
4353
4354
 
4354
4355
  .figma-list-main p {
4355
- color: #7b8895;
4356
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4356
4357
  font-size: 11px;
4357
4358
  font-weight: 700;
4358
4359
  line-height: 1.3;
@@ -4377,30 +4378,30 @@ function formatDashboardTime(value?: string) {
4377
4378
 
4378
4379
  .figma-event-dot-info,
4379
4380
  .figma-event-dot-neutral {
4380
- background: #c9e6ff;
4381
- border: 2px solid #77bef9;
4381
+ background: var(--info-bg);
4382
+ border: 2px solid var(--info);
4382
4383
  }
4383
4384
 
4384
4385
  .figma-event-dot-success {
4385
- background: #2fb84d;
4386
- border: 2px solid #c9efd4;
4386
+ background: var(--ok-bg);
4387
+ border: 2px solid var(--ok);
4387
4388
  }
4388
4389
 
4389
4390
  .figma-event-dot-danger {
4390
- background: #ffd9d6;
4391
- border: 2px solid #f9a6a0;
4391
+ background: var(--err-bg);
4392
+ border: 2px solid var(--err);
4392
4393
  }
4393
4394
 
4394
4395
  .figma-event-dot-warning {
4395
- background: #ffe9c7;
4396
- border: 2px solid #f9c97c;
4396
+ background: var(--warn-bg);
4397
+ border: 2px solid var(--warn);
4397
4398
  }
4398
4399
 
4399
4400
  .active-patrol-row {
4400
4401
  align-items: center;
4401
- background-color: #ffffff;
4402
- border: 1px solid #e2e8f0;
4403
- border-radius: 8px;
4402
+ background-color: var(--card);
4403
+ border: 1px solid var(--border);
4404
+ border-radius: var(--r-inner-lg);
4404
4405
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
4405
4406
  display: grid;
4406
4407
  gap: 10px;
@@ -4416,7 +4417,7 @@ function formatDashboardTime(value?: string) {
4416
4417
  }
4417
4418
 
4418
4419
  .active-patrol-main strong {
4419
- color: #0b2f47;
4420
+ color: rgb(var(--v-theme-on-surface));
4420
4421
  display: block;
4421
4422
  font-size: 13px;
4422
4423
  font-weight: 700;
@@ -4426,7 +4427,7 @@ function formatDashboardTime(value?: string) {
4426
4427
  }
4427
4428
 
4428
4429
  .active-patrol-main span {
4429
- color: #94a3b8;
4430
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4430
4431
  display: block;
4431
4432
  font-size: 11px;
4432
4433
  line-height: 1.25;
@@ -4442,8 +4443,8 @@ function formatDashboardTime(value?: string) {
4442
4443
 
4443
4444
  .active-patrol-assignee {
4444
4445
  align-items: center;
4445
- background: #f8fafc;
4446
- border: 1px solid #e7edf3;
4446
+ background: rgba(var(--v-theme-on-surface), 0.05);
4447
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
4447
4448
  border-radius: 999px;
4448
4449
  display: inline-flex;
4449
4450
  flex: 1 1 auto;
@@ -4453,8 +4454,23 @@ function formatDashboardTime(value?: string) {
4453
4454
  padding: 2px 8px 2px 3px;
4454
4455
  }
4455
4456
 
4457
+ /**
4458
+ * Carried from Phase 1: `AvatarMain` colours each person from their NAME, and
4459
+ * writes WHITE initials on whatever tint it picks - 2.34:1 on this dashboard.
4460
+ * That is right in a members list, where the colour tells two people apart,
4461
+ * and wrong on a dashboard, where the same face appears in four panels.
4462
+ *
4463
+ * The component is left alone - Phase 2 took exactly this route for the top
4464
+ * bar's own avatar - and the dashboard paints every avatar it draws with the
4465
+ * design's accent pair instead. One rule, all seven call sites.
4466
+ */
4467
+ .unified-dashboard :deep(.v-avatar) {
4468
+ background: var(--accent-soft) !important;
4469
+ color: var(--accent-text) !important;
4470
+ }
4471
+
4456
4472
  .active-patrol-assignee span {
4457
- color: #475569;
4473
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4458
4474
  font-size: 11px;
4459
4475
  font-weight: 600;
4460
4476
  line-height: 1.2;
@@ -4484,10 +4500,10 @@ function formatDashboardTime(value?: string) {
4484
4500
 
4485
4501
  .active-patrol-more-pill {
4486
4502
  align-items: center;
4487
- background: #eef3f8;
4488
- border: 1px solid #dbe4ed;
4503
+ background: rgba(var(--v-theme-on-surface), 0.05);
4504
+ border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
4489
4505
  border-radius: 999px;
4490
- color: #607386;
4506
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4491
4507
  display: inline-flex;
4492
4508
  flex: 0 0 auto;
4493
4509
  font-size: 11px;
@@ -4507,11 +4523,11 @@ function formatDashboardTime(value?: string) {
4507
4523
  }
4508
4524
 
4509
4525
  .active-patrol-tooltip::after {
4510
- background: #0f2638;
4511
- border-radius: 6px;
4526
+ background: var(--text);
4527
+ border-radius: var(--r-inner-sm);
4512
4528
  bottom: calc(100% + 8px);
4513
- box-shadow: 0 8px 24px rgba(15, 38, 56, 0.18);
4514
- color: #ffffff;
4529
+ box-shadow: var(--shadow-menu);
4530
+ color: var(--card);
4515
4531
  content: attr(data-tooltip);
4516
4532
  font-size: 11px;
4517
4533
  font-weight: 600;
@@ -4541,6 +4557,28 @@ function formatDashboardTime(value?: string) {
4541
4557
  }
4542
4558
  }
4543
4559
 
4560
+ /**
4561
+ * Carried from Phase 1: the dashboard scrolled sideways at 360. This row is a
4562
+ * four-column grid whose last two columns are `max-content`, so on a phone the
4563
+ * assignee pill, the status pill and the action button cannot fit beside a
4564
+ * 120px title and the row pushes ~39px past the viewport.
4565
+ *
4566
+ * Below 600 the four columns become one. Nothing is hidden, dropped or
4567
+ * reordered - the same four blocks, stacked in the same order.
4568
+ */
4569
+ @media (max-width: 599.98px) {
4570
+ .active-patrol-row {
4571
+ grid-template-columns: minmax(0, 1fr);
4572
+ }
4573
+
4574
+ .active-patrol-action,
4575
+ .active-patrol-statuses,
4576
+ .active-patrol-assignees {
4577
+ justify-self: start;
4578
+ text-align: left;
4579
+ }
4580
+ }
4581
+
4544
4582
  .figma-icon-badge {
4545
4583
  align-items: center;
4546
4584
  border-radius: 50%;
@@ -4552,28 +4590,28 @@ function formatDashboardTime(value?: string) {
4552
4590
  }
4553
4591
 
4554
4592
  .figma-icon-badge-danger {
4555
- background: #ffe0de;
4556
- color: #d72f2f;
4593
+ background: var(--err-bg);
4594
+ color: var(--err);
4557
4595
  }
4558
4596
 
4559
4597
  .figma-icon-badge-info {
4560
- background: #e8e2ff;
4561
- color: #6d4bd8;
4598
+ background: var(--info-bg);
4599
+ color: var(--info);
4562
4600
  }
4563
4601
 
4564
4602
  .figma-icon-badge-success {
4565
- background: #ddf7e6;
4566
- color: #229852;
4603
+ background: var(--ok-bg);
4604
+ color: var(--ok);
4567
4605
  }
4568
4606
 
4569
4607
  .figma-icon-badge-warning {
4570
- background: #ffeed4;
4571
- color: #c57916;
4608
+ background: var(--warn-bg);
4609
+ color: var(--warn);
4572
4610
  }
4573
4611
 
4574
4612
  .figma-icon-badge-neutral {
4575
- background: #edf2f6;
4576
- color: #607386;
4613
+ background: var(--hover);
4614
+ color: var(--text2);
4577
4615
  }
4578
4616
 
4579
4617
  .figma-status-stack {
@@ -4587,7 +4625,7 @@ function formatDashboardTime(value?: string) {
4587
4625
 
4588
4626
  .figma-status-label {
4589
4627
  align-items: center;
4590
- color: #263d53;
4628
+ color: rgb(var(--v-theme-on-surface));
4591
4629
  display: flex;
4592
4630
  font-size: 12px;
4593
4631
  font-weight: 800;
@@ -4597,13 +4635,13 @@ function formatDashboardTime(value?: string) {
4597
4635
  }
4598
4636
 
4599
4637
  .figma-status-label strong {
4600
- color: #18324a;
4638
+ color: rgb(var(--v-theme-on-surface));
4601
4639
  font-size: 12px;
4602
4640
  font-weight: 900;
4603
4641
  }
4604
4642
 
4605
4643
  .figma-progress-track {
4606
- background: #edf2f6;
4644
+ background: rgba(var(--v-theme-on-surface), 0.08);
4607
4645
  border-radius: 999px;
4608
4646
  height: 6px;
4609
4647
  overflow: hidden;
@@ -4624,9 +4662,9 @@ function formatDashboardTime(value?: string) {
4624
4662
 
4625
4663
  .feedback-row {
4626
4664
  align-items: center;
4627
- background: #fff;
4628
- border: 1px solid #e0e5ea;
4629
- border-radius: 8px;
4665
+ background: var(--card);
4666
+ border: 1px solid var(--border);
4667
+ border-radius: var(--r-inner-lg);
4630
4668
  display: grid;
4631
4669
  gap: 16px;
4632
4670
  grid-template-columns: minmax(180px, 225px) minmax(130px, 175px) 100px auto;
@@ -4635,8 +4673,41 @@ function formatDashboardTime(value?: string) {
4635
4673
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
4636
4674
  }
4637
4675
 
4676
+ /**
4677
+ * The property-mode feedback row is four flex children that each ask for at
4678
+ * least 140px, so on a 360px phone the status pill and the View button were
4679
+ * clipped by the card edge - visible, but cut in half. It wraps now, and below
4680
+ * 600 each block takes the full width: same four blocks, same order.
4681
+ */
4682
+ .dashboard-feedback-row {
4683
+ flex-wrap: wrap;
4684
+ gap: 12px;
4685
+ }
4686
+
4687
+ @media (max-width: 599.98px) {
4688
+ .dashboard-feedback-row > div {
4689
+ flex: 1 1 100% !important;
4690
+ justify-content: flex-start !important;
4691
+ text-align: left !important;
4692
+ }
4693
+ }
4694
+
4695
+ /* Same story as the patrol row: four fixed-ish columns need ~600px, so on a
4696
+ phone the status pill and the View button were clipped by the card edge.
4697
+ One column below 600, same blocks, same order. */
4698
+ @media (max-width: 599.98px) {
4699
+ .feedback-row {
4700
+ grid-template-columns: minmax(0, 1fr);
4701
+ }
4702
+
4703
+ .feedback-status-pill,
4704
+ .feedback-action {
4705
+ justify-self: start;
4706
+ }
4707
+ }
4708
+
4638
4709
  .feedback-row:hover {
4639
- border-color: #d8e3ec;
4710
+ border-color: rgba(var(--v-border-color), var(--v-border-opacity));
4640
4711
  box-shadow: 0 6px 14px rgba(15, 44, 69, 0.04);
4641
4712
  }
4642
4713
 
@@ -4645,7 +4716,7 @@ function formatDashboardTime(value?: string) {
4645
4716
  }
4646
4717
 
4647
4718
  .feedback-summary strong {
4648
- color: #132b43;
4719
+ color: rgb(var(--v-theme-on-surface));
4649
4720
  display: block;
4650
4721
  font-size: 12px;
4651
4722
  font-weight: 800;
@@ -4656,7 +4727,7 @@ function formatDashboardTime(value?: string) {
4656
4727
  }
4657
4728
 
4658
4729
  .feedback-summary p {
4659
- color: #888;
4730
+ color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
4660
4731
  font-size: 12px;
4661
4732
  font-weight: 500;
4662
4733
  line-height: 1.35;
@@ -4679,11 +4750,13 @@ function formatDashboardTime(value?: string) {
4679
4750
  white-space: nowrap;
4680
4751
  }
4681
4752
 
4753
+ /* Carried from Phase 1: the initials measured 1.26:1 in dark mode, because the
4754
+ tint was a literal light blue whatever the theme. */
4682
4755
  .feedback-avatar {
4683
4756
  align-items: center;
4684
- background: #d1e8ff;
4757
+ background: var(--accent-soft);
4685
4758
  border-radius: 50%;
4686
- color: #1d5f99;
4759
+ color: var(--accent-text);
4687
4760
  display: inline-flex;
4688
4761
  flex-shrink: 0;
4689
4762
  font-size: 11px;
@@ -4702,31 +4775,32 @@ function formatDashboardTime(value?: string) {
4702
4775
  white-space: nowrap;
4703
4776
  }
4704
4777
 
4778
+ /* The other two carried Phase 1 items: hard-coded chips. */
4705
4779
  .feedback-status-pill-completed {
4706
- background: #72e3a1;
4707
- color: #0f5132;
4780
+ background: var(--ok-bg);
4781
+ color: var(--ok);
4708
4782
  }
4709
4783
 
4710
4784
  .feedback-status-pill-progress {
4711
- background: #fde68a;
4712
- color: #92400e;
4785
+ background: var(--warn-bg);
4786
+ color: var(--warn);
4713
4787
  }
4714
4788
 
4715
4789
  .feedback-status-pill-danger {
4716
- background: #fecaca;
4717
- color: #b91c1c;
4790
+ background: var(--err-bg);
4791
+ color: var(--err);
4718
4792
  }
4719
4793
 
4720
4794
  .feedback-status-pill-todo {
4721
- background: #e2e8f0;
4722
- color: #64748b;
4795
+ background: var(--hover);
4796
+ color: var(--text2);
4723
4797
  }
4724
4798
 
4725
4799
  .feedback-view-button {
4726
4800
  appearance: none;
4727
4801
  background: transparent;
4728
4802
  border: 0;
4729
- color: #2196f3;
4803
+ color: rgb(var(--v-theme-primary));
4730
4804
  cursor: pointer;
4731
4805
  font-size: 12px;
4732
4806
  font-weight: 800;