Skip to content

Commit a9aa83f

Browse files
author
=
committed
Refine mobile plan layout and dashboard status
Improve compact plan controls and timeline labels, and show dashboard configuration status as pending until Predbat has supplied its first update.
1 parent da3d874 commit a9aa83f

8 files changed

Lines changed: 383 additions & 61 deletions

File tree

‎apps/predbat/frontend.zip‎

1.27 KB
Binary file not shown.

‎frontend/src/components/MetricsPanel.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default function MetricsPanel({ lastStarted }: { lastStarted: string | nu
7070
const services = Object.entries(metrics.api_services).sort(([first], [second]) => first.localeCompare(second))
7171
const conflicts = [...metrics.control_conflicts_events].reverse()
7272
const quotaPercent = metrics.solcast_api_limit > 0 ? Math.min(100, metrics.solcast_api_used / metrics.solcast_api_limit * 100) : 0
73+
const metricsReady = metrics.last_update_timestamp > 0
7374

7475
return (
7576
<section className="metrics-panel" aria-labelledby="metrics-heading">
@@ -85,7 +86,12 @@ export default function MetricsPanel({ lastStarted }: { lastStarted: string | nu
8586
<h3>System health</h3>
8687
<div className="metrics-grid">
8788
<MetricCard label="Predbat" value={Object.values(metrics.up).some(Boolean) ? 'Running' : 'Stopped'} detail={`Version ${metricVersion(metrics.up)}`} tone={Object.values(metrics.up).some(Boolean) ? 'good' : 'bad'} />
88-
<MetricCard label="Configuration" value={metrics.config_valid ? 'Valid' : 'Invalid'} detail={metrics.config_warnings ? `${metrics.config_warnings} warnings` : 'No warnings'} tone={metrics.config_valid ? (metrics.config_warnings ? 'warning' : 'good') : 'bad'} />
89+
<MetricCard
90+
label="Configuration"
91+
value={metricsReady ? (metrics.config_valid ? 'Valid' : 'Invalid') : 'Pending'}
92+
detail={metricsReady ? (metrics.config_warnings ? `${metrics.config_warnings} warnings` : 'No warnings') : 'Awaiting first Predbat update'}
93+
tone={metricsReady ? (metrics.config_valid ? (metrics.config_warnings ? 'warning' : 'good') : 'bad') : undefined}
94+
/>
8995
<MetricCard label="Plan" value={metrics.plan_valid ? 'Valid' : 'Stale'} detail={`${metrics.plan_age_minutes.toFixed(0)} minutes old`} tone={metrics.plan_valid ? 'good' : 'warning'} />
9096
<MetricCard label="Uptime" value={formatUptime(lastStarted, refreshedAt)} />
9197
<MetricCard label="Errors" value={errors.toFixed(0)} tone={errors ? 'warning' : 'good'} />

‎frontend/src/components/PlanTable.css‎

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,18 @@
312312
gap: 0.3rem;
313313
}
314314

315+
.plan-column-icon {
316+
display: inline-flex;
317+
align-items: center;
318+
justify-content: center;
319+
height: 1em;
320+
line-height: 1;
321+
}
322+
323+
.plan-column-icon.is-blue {
324+
color: var(--color-current);
325+
}
326+
315327
.plan-column-label-mobile {
316328
display: none;
317329
}
@@ -464,6 +476,49 @@
464476
display: inline-block;
465477
}
466478

479+
.plan-action-tooltip {
480+
position: absolute;
481+
bottom: calc(100% + 0.6rem);
482+
left: 50%;
483+
z-index: 50;
484+
display: block;
485+
width: max-content;
486+
max-width: calc(100vw - 1rem);
487+
padding: 0.55rem 0.65rem;
488+
border: 1px solid var(--color-border);
489+
border-radius: 7px;
490+
background: color-mix(in srgb, var(--color-surface) 96%, var(--color-text) 4%);
491+
box-shadow: 0 8px 24px color-mix(in srgb, var(--color-text) 16%, transparent);
492+
color: var(--color-text-secondary);
493+
font-size: 0.7rem;
494+
font-weight: 400;
495+
line-height: 1.4;
496+
text-align: left;
497+
text-transform: none;
498+
letter-spacing: normal;
499+
white-space: normal;
500+
pointer-events: none;
501+
opacity: 0;
502+
visibility: hidden;
503+
transform: translate(calc(-50% + var(--plan-action-tooltip-shift, 0px)), 4px);
504+
transition:
505+
opacity 0.12s ease,
506+
transform 0.12s ease,
507+
visibility 0.12s ease;
508+
}
509+
510+
.plan-action-control:hover .plan-action-tooltip,
511+
.plan-action-control:focus-within .plan-action-tooltip {
512+
opacity: 1;
513+
visibility: visible;
514+
transform: translate(calc(-50% + var(--plan-action-tooltip-shift, 0px)), 0);
515+
}
516+
517+
.plan-action-control.is-open .plan-action-tooltip {
518+
opacity: 0;
519+
visibility: hidden;
520+
}
521+
467522
/*
468523
* The action is now both an explanation target and,
469524
* for current/future rows, the manual-override control.
@@ -551,11 +606,6 @@
551606
* Don't leave the explanation tooltip sitting over the menu
552607
* once the action has been clicked.
553608
*/
554-
.plan-action-control.is-open .plan-tooltip-trigger::after {
555-
opacity: 0 !important;
556-
visibility: hidden !important;
557-
}
558-
559609
/*
560610
* Manual action picker.
561611
*/
@@ -565,11 +615,13 @@
565615
left: 0;
566616
z-index: 100;
567617
width: 240px;
618+
max-width: calc(100vw - 1rem);
568619
padding: 0.35rem;
569620
border: 1px solid var(--color-border);
570621
border-radius: 8px;
571622
background: var(--color-surface);
572623
box-shadow: 0 10px 30px color-mix(in srgb, var(--color-text) 22%, transparent);
624+
transform: translateX(var(--plan-action-menu-shift, 0));
573625
}
574626

575627
.plan-action-menu-reason {
@@ -585,6 +637,15 @@
585637
background: var(--color-border);
586638
}
587639

640+
.plan-action-menu-title {
641+
padding: 0.1rem 0.5rem 0.3rem;
642+
color: var(--color-text-muted);
643+
font-size: 0.66rem;
644+
font-weight: 700;
645+
letter-spacing: 0.06em;
646+
text-transform: uppercase;
647+
}
648+
588649
.plan-action-menu button {
589650
display: grid;
590651
grid-template-columns:
@@ -616,6 +677,14 @@
616677
color: var(--color-text);
617678
}
618679

680+
.plan-action-menu-icon.is-charge {
681+
color: var(--color-flow-battery-charge);
682+
}
683+
684+
.plan-action-menu-icon.is-export {
685+
color: var(--color-flow-export);
686+
}
687+
619688
.plan-action-menu-check {
620689
justify-self: end;
621690
}
@@ -719,6 +788,14 @@
719788
font-weight: 700;
720789
}
721790

791+
.plan-cell-popover-title {
792+
color: var(--color-text-muted);
793+
font-size: 0.66rem;
794+
font-weight: 700;
795+
letter-spacing: 0.06em;
796+
text-transform: uppercase;
797+
}
798+
722799
.plan-rate-input {
723800
display: flex;
724801
align-items: center;
@@ -734,6 +811,7 @@
734811
background: var(--color-bg);
735812
color: var(--color-text);
736813
font: inherit;
814+
text-align: right;
737815
}
738816

739817
.plan-rate-input > span {
@@ -818,3 +896,80 @@
818896
color: var(--color-car);
819897
font-weight: 700;
820898
}
899+
900+
@keyframes plan-action-sheet-enter {
901+
from {
902+
transform: translateY(100%);
903+
}
904+
905+
to {
906+
transform: translateY(0);
907+
}
908+
}
909+
910+
@media (max-width: 700px) {
911+
/* The bottom sheet already displays the explanation, so a hover tooltip adds no value. */
912+
.plan-action-tooltip {
913+
display: none;
914+
}
915+
916+
.plan-action-menu {
917+
position: fixed;
918+
inset: auto 0 0;
919+
width: auto;
920+
max-width: none;
921+
max-height: min(75dvh, 32rem);
922+
padding: 0.7rem;
923+
padding-bottom: max(0.7rem, env(safe-area-inset-bottom));
924+
overflow-y: auto;
925+
border-radius: 1rem 1rem 0 0;
926+
box-shadow: 0 -12px 36px color-mix(in srgb, var(--color-text) 26%, transparent);
927+
transform: none;
928+
animation: plan-action-sheet-enter 0.18s ease-out;
929+
}
930+
931+
.plan-action-menu-reason {
932+
padding: 0.5rem 0.55rem 0.65rem;
933+
font-size: 0.78rem;
934+
}
935+
936+
.plan-action-menu-title {
937+
padding: 0.2rem 0.55rem 0.45rem;
938+
font-size: 0.7rem;
939+
}
940+
941+
.plan-action-menu button {
942+
min-height: 2.75rem;
943+
padding: 0.65rem 0.6rem;
944+
font-size: 0.84rem;
945+
}
946+
947+
.plan-cell-popover {
948+
position: fixed;
949+
inset: auto 0 0;
950+
z-index: 110;
951+
width: auto;
952+
max-width: none;
953+
padding: 0.8rem;
954+
padding-bottom: max(0.8rem, env(safe-area-inset-bottom));
955+
border-radius: 1rem 1rem 0 0;
956+
box-shadow: 0 -12px 36px color-mix(in srgb, var(--color-text) 26%, transparent);
957+
transform: none;
958+
animation: plan-action-sheet-enter 0.18s ease-out;
959+
}
960+
961+
.plan-cell-popover-title {
962+
font-size: 0.7rem;
963+
}
964+
965+
.plan-cell-popover label {
966+
gap: 0.45rem;
967+
font-size: 0.82rem;
968+
}
969+
970+
.plan-cell-popover button {
971+
min-height: 2.75rem;
972+
padding: 0.65rem;
973+
font-size: 0.84rem;
974+
}
975+
}

0 commit comments

Comments
 (0)