@7365admin1/layer-common 3.2.1-staging.75 → 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.
- package/.changeset/notification-preferences.md +17 -0
- package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
- package/CHANGELOG.md +6 -0
- package/assets/css/primitives.css +680 -0
- package/assets/css/screens.css +541 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +220 -0
- package/components/AccessCardQrTagging.vue +1 -1
- package/components/AccessManagement.vue +53 -72
- package/components/AppButton.vue +52 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +35 -0
- package/components/AppField.vue +46 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +203 -0
- package/components/AreaChecklistHistoryMain.vue +6 -2
- package/components/AreaMain.vue +9 -17
- package/components/AttendanceMain.vue +4 -3
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BuildingManagement/buildings.vue +27 -38
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BulletinBoardManagement.vue +23 -10
- package/components/CardToolbar.vue +37 -0
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/ClientMain.vue +90 -78
- package/components/DashboardEmptyState.vue +11 -1
- package/components/DashboardMain.vue +285 -207
- package/components/DocumentManagement.vue +129 -93
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +4 -6
- package/components/EquipmentManagementMain.vue +9 -7
- package/components/FeedbackMain.vue +35 -49
- package/components/FormDialog.vue +60 -21
- package/components/HidIntercomManagement.vue +40 -24
- package/components/HidQrCodeConfiguration.vue +24 -2
- package/components/HidUserEnrollment.vue +68 -31
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +289 -37
- package/components/Layout/NavigationDrawer.vue +299 -30
- package/components/ManageChecklistMain.vue +17 -17
- package/components/MemberMain.vue +79 -60
- package/components/MyAttendanceMain.vue +3 -5
- package/components/Nfc/NFCPatrolRouteMain.vue +64 -96
- package/components/NotificationSettings.vue +361 -0
- package/components/OnlineFormsConfiguration.vue +1 -1
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +104 -45
- package/components/RolePermissionMain.vue +184 -91
- package/components/ScanVisitorQRCode.vue +35 -7
- package/components/ScheduleAreaMain.vue +15 -49
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ScheduleTastTicketMain.vue +18 -43
- package/components/ServiceProviderMain.vue +113 -168
- package/components/SiteSettings.vue +21 -8
- package/components/SpecificAttr.vue +10 -2
- package/components/StatusChip.vue +93 -0
- package/components/SwitchContext.vue +33 -1
- package/components/TableHygiene.vue +29 -46
- package/components/TableList.vue +1 -1
- package/components/TableListSecondary.vue +1 -1
- package/components/TableMain.vue +161 -77
- package/components/TableV3.vue +61 -66
- package/components/UnitMain.vue +9 -17
- package/components/VehicleManagement.vue +20 -9
- package/components/VisitorForm.vue +100 -7
- package/components/VisitorManagement.vue +219 -68
- package/components/WorkOrder/Main.vue +42 -51
- package/composables/useHidAmico.ts +26 -2
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOrg.ts +1 -1
- package/composables/useRole.ts +22 -0
- package/composables/useSipWebPhone.ts +28 -3
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/nuxt.config.ts +31 -0
- package/package.json +3 -2
- package/pages/notification-settings.vue +19 -0
- package/plugins/vuetify.ts +32 -1
- package/types/org.d.ts +2 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/status.test.ts +77 -0
- package/utils/status.ts +90 -0
- package/utils/theme.test.ts +325 -7
- package/utils/theme.ts +268 -137
|
@@ -203,44 +203,36 @@
|
|
|
203
203
|
|
|
204
204
|
<section v-else class="unified-dashboard pb-8">
|
|
205
205
|
<!-- ── Header ──────────────────────────────────────────────────────────── -->
|
|
206
|
-
<
|
|
207
|
-
|
|
206
|
+
<PageHeader
|
|
207
|
+
:title="dashboardTitle"
|
|
208
|
+
:subtitle="currentSiteName || 'Selected site'"
|
|
209
|
+
subtitle-icon="mdi-map-marker"
|
|
208
210
|
>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
class="text-none font-weight-bold"
|
|
226
|
-
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"
|
|
227
221
|
@click="openCustomizeDialog"
|
|
228
222
|
>
|
|
229
|
-
Customize
|
|
230
|
-
</
|
|
223
|
+
Customize
|
|
224
|
+
</AppButton>
|
|
231
225
|
|
|
232
226
|
<v-menu>
|
|
233
227
|
<template #activator="{ props: menuProps }">
|
|
234
|
-
<
|
|
228
|
+
<AppButton
|
|
235
229
|
v-bind="menuProps"
|
|
236
|
-
variant="
|
|
237
|
-
|
|
238
|
-
append-icon="mdi-chevron-down"
|
|
239
|
-
:loading="isExporting"
|
|
230
|
+
variant="ghost"
|
|
231
|
+
icon="mdi-download-outline"
|
|
240
232
|
:disabled="isExporting"
|
|
241
233
|
>
|
|
242
234
|
Export
|
|
243
|
-
</
|
|
235
|
+
</AppButton>
|
|
244
236
|
</template>
|
|
245
237
|
|
|
246
238
|
<v-list density="compact" border rounded="lg" elevation="3">
|
|
@@ -249,36 +241,9 @@
|
|
|
249
241
|
</v-list>
|
|
250
242
|
</v-menu>
|
|
251
243
|
|
|
252
|
-
<v-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
density="comfortable"
|
|
256
|
-
divided
|
|
257
|
-
variant="outlined"
|
|
258
|
-
class="bg-surface"
|
|
259
|
-
style="border-radius: 8px"
|
|
260
|
-
>
|
|
261
|
-
<v-btn
|
|
262
|
-
v-for="range in rangeOptions"
|
|
263
|
-
:key="range.value"
|
|
264
|
-
:value="range.value"
|
|
265
|
-
class="text-none"
|
|
266
|
-
size="small"
|
|
267
|
-
:style="
|
|
268
|
-
selectedRange === range.value
|
|
269
|
-
? {
|
|
270
|
-
backgroundColor: 'rgba(var(--v-theme-on-surface), 0.12)',
|
|
271
|
-
color: 'rgb(var(--v-theme-on-surface))',
|
|
272
|
-
fontWeight: '600',
|
|
273
|
-
}
|
|
274
|
-
: { backgroundColor: 'transparent', color: 'rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity))' }
|
|
275
|
-
"
|
|
276
|
-
>
|
|
277
|
-
{{ range.label }}
|
|
278
|
-
</v-btn>
|
|
279
|
-
</v-btn-toggle>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
244
|
+
<AppSegmented v-model="selectedRange" :options="rangeOptions" />
|
|
245
|
+
</template>
|
|
246
|
+
</PageHeader>
|
|
282
247
|
|
|
283
248
|
<v-progress-linear
|
|
284
249
|
v-if="finalLoading"
|
|
@@ -298,22 +263,24 @@
|
|
|
298
263
|
lg="3"
|
|
299
264
|
class="d-flex"
|
|
300
265
|
>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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"
|
|
307
282
|
>
|
|
308
|
-
<
|
|
309
|
-
<div
|
|
310
|
-
class="d-inline-flex align-center justify-center rounded-lg"
|
|
311
|
-
style="height: 28px; width: 28px; flex-shrink: 0"
|
|
312
|
-
:style="{ backgroundColor: card.tint }"
|
|
313
|
-
>
|
|
314
|
-
<v-icon :icon="card.icon" :color="card.color" size="15" />
|
|
315
|
-
</div>
|
|
316
|
-
|
|
283
|
+
<template v-if="card.filter || card.key === 'facilityBookings'" #action>
|
|
317
284
|
<v-menu v-if="card.filter" v-model="filterMenus[card.key]">
|
|
318
285
|
<template #activator="{ props: menuProps }">
|
|
319
286
|
<v-btn
|
|
@@ -357,61 +324,24 @@
|
|
|
357
324
|
>
|
|
358
325
|
View All
|
|
359
326
|
</button>
|
|
360
|
-
</
|
|
327
|
+
</template>
|
|
361
328
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
<div
|
|
375
|
-
v-for="item in card.raw.items"
|
|
376
|
-
:key="item._id || item.name"
|
|
377
|
-
class="d-flex justify-space-between text-body-2 text-high-emphasis font-weight-medium"
|
|
378
|
-
>
|
|
379
|
-
<span>{{ item.name }}</span>
|
|
380
|
-
<span class="text-medium-emphasis">Balance: {{ item.qty }}</span>
|
|
381
|
-
</div>
|
|
382
|
-
</div>
|
|
383
|
-
</template>
|
|
384
|
-
<template v-else>
|
|
385
|
-
<div class="d-flex align-center mb-3" style="min-height: 31px">
|
|
386
|
-
<span class="text-h5 font-weight-black text-high-emphasis">
|
|
387
|
-
{{ card.value }}
|
|
388
|
-
</span>
|
|
389
|
-
</div>
|
|
390
|
-
<div class="d-flex align-center flex-wrap ga-2">
|
|
391
|
-
<span v-if="card.meta" class="text-caption text-medium-emphasis">{{
|
|
392
|
-
card.meta
|
|
393
|
-
}}</span>
|
|
394
|
-
<v-chip
|
|
395
|
-
:color="card.percentage >= 0 ? 'success' : 'error'"
|
|
396
|
-
size="x-small"
|
|
397
|
-
variant="flat"
|
|
398
|
-
class="font-weight-bold"
|
|
399
|
-
>
|
|
400
|
-
<v-icon
|
|
401
|
-
:icon="
|
|
402
|
-
card.percentage >= 0
|
|
403
|
-
? 'mdi-arrow-up-right'
|
|
404
|
-
: 'mdi-arrow-down-right'
|
|
405
|
-
"
|
|
406
|
-
size="13"
|
|
407
|
-
start
|
|
408
|
-
/>
|
|
409
|
-
{{ Math.abs(card.percentage) }}%
|
|
410
|
-
</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>
|
|
411
341
|
</div>
|
|
412
|
-
</
|
|
413
|
-
</
|
|
414
|
-
</
|
|
342
|
+
</div>
|
|
343
|
+
</template>
|
|
344
|
+
</AppKpiTile>
|
|
415
345
|
</v-col>
|
|
416
346
|
</v-row>
|
|
417
347
|
|
|
@@ -419,12 +349,10 @@
|
|
|
419
349
|
<v-card
|
|
420
350
|
v-if="isWidgetVisible('activity')"
|
|
421
351
|
flat
|
|
422
|
-
|
|
423
|
-
rounded="lg"
|
|
424
|
-
class="px-5 pt-4 pb-3 mt-3"
|
|
352
|
+
class="dashboard-card px-5 pt-4 pb-3 mt-3"
|
|
425
353
|
>
|
|
426
354
|
<div class="d-flex align-center justify-space-between">
|
|
427
|
-
<h2 class="
|
|
355
|
+
<h2 class="dashboard-card-title mb-0">
|
|
428
356
|
{{ activityTitle }}
|
|
429
357
|
</h2>
|
|
430
358
|
</div>
|
|
@@ -556,9 +484,7 @@
|
|
|
556
484
|
v-for="item in taskScheduleItems"
|
|
557
485
|
:key="item.id"
|
|
558
486
|
flat
|
|
559
|
-
|
|
560
|
-
rounded="lg"
|
|
561
|
-
class="pa-3"
|
|
487
|
+
class="dashboard-row-card pa-3"
|
|
562
488
|
>
|
|
563
489
|
<div class="d-flex align-center flex-wrap ga-3">
|
|
564
490
|
<div class="flex-grow-1" style="min-width: 0">
|
|
@@ -622,9 +548,7 @@
|
|
|
622
548
|
v-for="item in staffStatusItems"
|
|
623
549
|
:key="item.id"
|
|
624
550
|
flat
|
|
625
|
-
|
|
626
|
-
rounded="lg"
|
|
627
|
-
class="pa-3"
|
|
551
|
+
class="dashboard-row-card pa-3"
|
|
628
552
|
>
|
|
629
553
|
<div class="d-flex align-start ga-3">
|
|
630
554
|
<AvatarMain :name="item.name" :size="32" />
|
|
@@ -663,7 +587,7 @@
|
|
|
663
587
|
<div
|
|
664
588
|
v-for="item in feedbackItems"
|
|
665
589
|
:key="item.id"
|
|
666
|
-
class="d-flex align-center justify-space-between"
|
|
590
|
+
class="d-flex align-center justify-space-between dashboard-feedback-row"
|
|
667
591
|
style="
|
|
668
592
|
padding: 16px 20px;
|
|
669
593
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
@@ -964,7 +888,7 @@
|
|
|
964
888
|
<v-col v-if="isWidgetVisible('activePatrol')" cols="12" lg="6">
|
|
965
889
|
<div class="dashboard-card dashboard-panel figma-panel">
|
|
966
890
|
<div class="figma-panel-header">
|
|
967
|
-
<h2>
|
|
891
|
+
<h2>Active Patrol</h2>
|
|
968
892
|
<button
|
|
969
893
|
class="figma-panel-action"
|
|
970
894
|
type="button"
|
|
@@ -1044,7 +968,7 @@
|
|
|
1044
968
|
<v-col v-if="isWidgetVisible('staffStatus')" cols="12" lg="6">
|
|
1045
969
|
<div class="dashboard-card dashboard-panel figma-panel">
|
|
1046
970
|
<div class="figma-panel-header">
|
|
1047
|
-
<h2>
|
|
971
|
+
<h2>Staff Status</h2>
|
|
1048
972
|
<button
|
|
1049
973
|
class="figma-panel-action"
|
|
1050
974
|
type="button"
|
|
@@ -1128,7 +1052,7 @@
|
|
|
1128
1052
|
<div
|
|
1129
1053
|
v-for="item in feedbackItems"
|
|
1130
1054
|
:key="item.id"
|
|
1131
|
-
class="d-flex align-center justify-space-between"
|
|
1055
|
+
class="d-flex align-center justify-space-between dashboard-feedback-row"
|
|
1132
1056
|
style="
|
|
1133
1057
|
padding: 16px 20px;
|
|
1134
1058
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
@@ -2501,6 +2425,17 @@ const visibleKpiCards = computed(() =>
|
|
|
2501
2425
|
kpiCards.value.filter((card) => isWidgetVisible(card.key))
|
|
2502
2426
|
);
|
|
2503
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
|
+
|
|
2504
2439
|
// ─── Dashboard Title ──────────────────────────────────────────────────────────
|
|
2505
2440
|
|
|
2506
2441
|
const dashboardTitle = computed(() => {
|
|
@@ -3250,31 +3185,37 @@ function getInitials(name?: string) {
|
|
|
3250
3185
|
function getStatusPillStyle(status: any) {
|
|
3251
3186
|
const tone = getStatusTone(status);
|
|
3252
3187
|
const map: Record<string, { backgroundColor: string; color: string }> = {
|
|
3253
|
-
success: { backgroundColor: "
|
|
3254
|
-
warning: { backgroundColor: "
|
|
3255
|
-
error: { backgroundColor: "
|
|
3256
|
-
neutral: { backgroundColor: "
|
|
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)" },
|
|
3257
3192
|
};
|
|
3258
3193
|
return map[tone] || map.neutral;
|
|
3259
3194
|
}
|
|
3260
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
|
+
*/
|
|
3261
3201
|
function getPatrolStatusStyle(status: any) {
|
|
3262
3202
|
const s = String(status || "").toLowerCase();
|
|
3263
3203
|
if (s === "completed" || s === "complete")
|
|
3264
|
-
return { backgroundColor: "
|
|
3204
|
+
return { backgroundColor: "var(--ok-bg)", color: "var(--ok)" };
|
|
3265
3205
|
if (s === "on paused" || s === "paused")
|
|
3266
|
-
return { backgroundColor: "
|
|
3267
|
-
return { backgroundColor: "
|
|
3206
|
+
return { backgroundColor: "var(--hover)", color: "var(--text2)" };
|
|
3207
|
+
return { backgroundColor: "var(--hover)", color: "var(--text2)" };
|
|
3268
3208
|
}
|
|
3269
3209
|
|
|
3270
3210
|
function getStaffStatusPillStyle(status: any) {
|
|
3271
3211
|
const s = String(status || "").toLowerCase();
|
|
3272
3212
|
if (s === "on duty" || s === "on-duty")
|
|
3273
|
-
return { backgroundColor: "
|
|
3213
|
+
return { backgroundColor: "var(--ok-bg)", color: "var(--ok)" };
|
|
3274
3214
|
if (s === "on break" || s === "on-break")
|
|
3275
|
-
return { backgroundColor: "
|
|
3276
|
-
if (s === "absent")
|
|
3277
|
-
|
|
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)" };
|
|
3278
3219
|
}
|
|
3279
3220
|
|
|
3280
3221
|
function getStatusProgressStyle(item: { percent: number; color: string }) {
|
|
@@ -4154,37 +4095,98 @@ function formatDashboardTime(value?: string) {
|
|
|
4154
4095
|
text-transform: capitalize;
|
|
4155
4096
|
}
|
|
4156
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. */
|
|
4157
4100
|
.badge-success {
|
|
4158
|
-
background-color:
|
|
4159
|
-
color:
|
|
4101
|
+
background-color: var(--ok-bg);
|
|
4102
|
+
color: var(--ok);
|
|
4160
4103
|
}
|
|
4161
4104
|
|
|
4162
4105
|
.badge-info {
|
|
4163
|
-
background-color:
|
|
4164
|
-
color:
|
|
4106
|
+
background-color: var(--info-bg);
|
|
4107
|
+
color: var(--info);
|
|
4165
4108
|
}
|
|
4166
4109
|
|
|
4167
4110
|
.badge-danger,
|
|
4168
4111
|
.badge-error {
|
|
4169
|
-
background-color:
|
|
4170
|
-
color:
|
|
4112
|
+
background-color: var(--err-bg);
|
|
4113
|
+
color: var(--err);
|
|
4171
4114
|
}
|
|
4172
4115
|
|
|
4173
4116
|
.badge-warning {
|
|
4174
|
-
background-color:
|
|
4175
|
-
color:
|
|
4117
|
+
background-color: var(--warn-bg);
|
|
4118
|
+
color: var(--warn);
|
|
4176
4119
|
}
|
|
4177
4120
|
|
|
4178
4121
|
.badge-neutral {
|
|
4179
|
-
background-color:
|
|
4180
|
-
color:
|
|
4122
|
+
background-color: var(--hover);
|
|
4123
|
+
color: var(--text2);
|
|
4181
4124
|
}
|
|
4182
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
|
+
|
|
4183
4136
|
.dashboard-card {
|
|
4184
|
-
background:
|
|
4185
|
-
border: 1px solid
|
|
4186
|
-
border-radius:
|
|
4187
|
-
box-shadow:
|
|
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;
|
|
4188
4190
|
}
|
|
4189
4191
|
|
|
4190
4192
|
.dashboard-section-grid {
|
|
@@ -4219,14 +4221,17 @@ function formatDashboardTime(value?: string) {
|
|
|
4219
4221
|
min-height: 18px;
|
|
4220
4222
|
}
|
|
4221
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. */
|
|
4222
4226
|
.figma-panel-header h2 {
|
|
4223
|
-
color:
|
|
4224
|
-
font-
|
|
4225
|
-
font-
|
|
4227
|
+
color: var(--text);
|
|
4228
|
+
font-family: var(--font-sans);
|
|
4229
|
+
font-size: var(--fs-card-title);
|
|
4230
|
+
font-weight: var(--fw-card-title);
|
|
4226
4231
|
letter-spacing: 0;
|
|
4227
4232
|
line-height: 1.2;
|
|
4228
4233
|
margin: 0;
|
|
4229
|
-
text-transform:
|
|
4234
|
+
text-transform: none;
|
|
4230
4235
|
}
|
|
4231
4236
|
|
|
4232
4237
|
.figma-panel-action,
|
|
@@ -4373,30 +4378,30 @@ function formatDashboardTime(value?: string) {
|
|
|
4373
4378
|
|
|
4374
4379
|
.figma-event-dot-info,
|
|
4375
4380
|
.figma-event-dot-neutral {
|
|
4376
|
-
background:
|
|
4377
|
-
border: 2px solid
|
|
4381
|
+
background: var(--info-bg);
|
|
4382
|
+
border: 2px solid var(--info);
|
|
4378
4383
|
}
|
|
4379
4384
|
|
|
4380
4385
|
.figma-event-dot-success {
|
|
4381
|
-
background:
|
|
4382
|
-
border: 2px solid
|
|
4386
|
+
background: var(--ok-bg);
|
|
4387
|
+
border: 2px solid var(--ok);
|
|
4383
4388
|
}
|
|
4384
4389
|
|
|
4385
4390
|
.figma-event-dot-danger {
|
|
4386
|
-
background:
|
|
4387
|
-
border: 2px solid
|
|
4391
|
+
background: var(--err-bg);
|
|
4392
|
+
border: 2px solid var(--err);
|
|
4388
4393
|
}
|
|
4389
4394
|
|
|
4390
4395
|
.figma-event-dot-warning {
|
|
4391
|
-
background:
|
|
4392
|
-
border: 2px solid
|
|
4396
|
+
background: var(--warn-bg);
|
|
4397
|
+
border: 2px solid var(--warn);
|
|
4393
4398
|
}
|
|
4394
4399
|
|
|
4395
4400
|
.active-patrol-row {
|
|
4396
4401
|
align-items: center;
|
|
4397
|
-
background-color:
|
|
4398
|
-
border: 1px solid
|
|
4399
|
-
border-radius:
|
|
4402
|
+
background-color: var(--card);
|
|
4403
|
+
border: 1px solid var(--border);
|
|
4404
|
+
border-radius: var(--r-inner-lg);
|
|
4400
4405
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
4401
4406
|
display: grid;
|
|
4402
4407
|
gap: 10px;
|
|
@@ -4449,6 +4454,21 @@ function formatDashboardTime(value?: string) {
|
|
|
4449
4454
|
padding: 2px 8px 2px 3px;
|
|
4450
4455
|
}
|
|
4451
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
|
+
|
|
4452
4472
|
.active-patrol-assignee span {
|
|
4453
4473
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
|
4454
4474
|
font-size: 11px;
|
|
@@ -4503,11 +4523,11 @@ function formatDashboardTime(value?: string) {
|
|
|
4503
4523
|
}
|
|
4504
4524
|
|
|
4505
4525
|
.active-patrol-tooltip::after {
|
|
4506
|
-
background:
|
|
4507
|
-
border-radius:
|
|
4526
|
+
background: var(--text);
|
|
4527
|
+
border-radius: var(--r-inner-sm);
|
|
4508
4528
|
bottom: calc(100% + 8px);
|
|
4509
|
-
box-shadow:
|
|
4510
|
-
color:
|
|
4529
|
+
box-shadow: var(--shadow-menu);
|
|
4530
|
+
color: var(--card);
|
|
4511
4531
|
content: attr(data-tooltip);
|
|
4512
4532
|
font-size: 11px;
|
|
4513
4533
|
font-weight: 600;
|
|
@@ -4537,6 +4557,28 @@ function formatDashboardTime(value?: string) {
|
|
|
4537
4557
|
}
|
|
4538
4558
|
}
|
|
4539
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
|
+
|
|
4540
4582
|
.figma-icon-badge {
|
|
4541
4583
|
align-items: center;
|
|
4542
4584
|
border-radius: 50%;
|
|
@@ -4548,28 +4590,28 @@ function formatDashboardTime(value?: string) {
|
|
|
4548
4590
|
}
|
|
4549
4591
|
|
|
4550
4592
|
.figma-icon-badge-danger {
|
|
4551
|
-
background:
|
|
4552
|
-
color:
|
|
4593
|
+
background: var(--err-bg);
|
|
4594
|
+
color: var(--err);
|
|
4553
4595
|
}
|
|
4554
4596
|
|
|
4555
4597
|
.figma-icon-badge-info {
|
|
4556
|
-
background:
|
|
4557
|
-
color:
|
|
4598
|
+
background: var(--info-bg);
|
|
4599
|
+
color: var(--info);
|
|
4558
4600
|
}
|
|
4559
4601
|
|
|
4560
4602
|
.figma-icon-badge-success {
|
|
4561
|
-
background:
|
|
4562
|
-
color:
|
|
4603
|
+
background: var(--ok-bg);
|
|
4604
|
+
color: var(--ok);
|
|
4563
4605
|
}
|
|
4564
4606
|
|
|
4565
4607
|
.figma-icon-badge-warning {
|
|
4566
|
-
background:
|
|
4567
|
-
color:
|
|
4608
|
+
background: var(--warn-bg);
|
|
4609
|
+
color: var(--warn);
|
|
4568
4610
|
}
|
|
4569
4611
|
|
|
4570
4612
|
.figma-icon-badge-neutral {
|
|
4571
|
-
background:
|
|
4572
|
-
color:
|
|
4613
|
+
background: var(--hover);
|
|
4614
|
+
color: var(--text2);
|
|
4573
4615
|
}
|
|
4574
4616
|
|
|
4575
4617
|
.figma-status-stack {
|
|
@@ -4620,9 +4662,9 @@ function formatDashboardTime(value?: string) {
|
|
|
4620
4662
|
|
|
4621
4663
|
.feedback-row {
|
|
4622
4664
|
align-items: center;
|
|
4623
|
-
background:
|
|
4624
|
-
border: 1px solid
|
|
4625
|
-
border-radius:
|
|
4665
|
+
background: var(--card);
|
|
4666
|
+
border: 1px solid var(--border);
|
|
4667
|
+
border-radius: var(--r-inner-lg);
|
|
4626
4668
|
display: grid;
|
|
4627
4669
|
gap: 16px;
|
|
4628
4670
|
grid-template-columns: minmax(180px, 225px) minmax(130px, 175px) 100px auto;
|
|
@@ -4631,6 +4673,39 @@ function formatDashboardTime(value?: string) {
|
|
|
4631
4673
|
transition: border-color 0.16s ease, box-shadow 0.16s ease;
|
|
4632
4674
|
}
|
|
4633
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
|
+
|
|
4634
4709
|
.feedback-row:hover {
|
|
4635
4710
|
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
|
|
4636
4711
|
box-shadow: 0 6px 14px rgba(15, 44, 69, 0.04);
|
|
@@ -4675,11 +4750,13 @@ function formatDashboardTime(value?: string) {
|
|
|
4675
4750
|
white-space: nowrap;
|
|
4676
4751
|
}
|
|
4677
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. */
|
|
4678
4755
|
.feedback-avatar {
|
|
4679
4756
|
align-items: center;
|
|
4680
|
-
background:
|
|
4757
|
+
background: var(--accent-soft);
|
|
4681
4758
|
border-radius: 50%;
|
|
4682
|
-
color:
|
|
4759
|
+
color: var(--accent-text);
|
|
4683
4760
|
display: inline-flex;
|
|
4684
4761
|
flex-shrink: 0;
|
|
4685
4762
|
font-size: 11px;
|
|
@@ -4698,24 +4775,25 @@ function formatDashboardTime(value?: string) {
|
|
|
4698
4775
|
white-space: nowrap;
|
|
4699
4776
|
}
|
|
4700
4777
|
|
|
4778
|
+
/* The other two carried Phase 1 items: hard-coded chips. */
|
|
4701
4779
|
.feedback-status-pill-completed {
|
|
4702
|
-
background:
|
|
4703
|
-
color:
|
|
4780
|
+
background: var(--ok-bg);
|
|
4781
|
+
color: var(--ok);
|
|
4704
4782
|
}
|
|
4705
4783
|
|
|
4706
4784
|
.feedback-status-pill-progress {
|
|
4707
|
-
background:
|
|
4708
|
-
color:
|
|
4785
|
+
background: var(--warn-bg);
|
|
4786
|
+
color: var(--warn);
|
|
4709
4787
|
}
|
|
4710
4788
|
|
|
4711
4789
|
.feedback-status-pill-danger {
|
|
4712
|
-
background:
|
|
4713
|
-
color:
|
|
4790
|
+
background: var(--err-bg);
|
|
4791
|
+
color: var(--err);
|
|
4714
4792
|
}
|
|
4715
4793
|
|
|
4716
4794
|
.feedback-status-pill-todo {
|
|
4717
|
-
background:
|
|
4718
|
-
color:
|
|
4795
|
+
background: var(--hover);
|
|
4796
|
+
color: var(--text2);
|
|
4719
4797
|
}
|
|
4720
4798
|
|
|
4721
4799
|
.feedback-view-button {
|