Add configurable water-softener diagram to Dashboard

Per-asset diagram_config (JSONB) maps live MQTT field_keys to brine/resin
tank roles, editable on the Assets page with dropdowns populated from that
asset's actual telemetry. Dashboard renders brine + 1-2 resin tanks with
color-coded fill levels, an "in service" tank indicator, and an animated
flow/regen indicator when regeneration is active.
This commit is contained in:
jtricerolph 2026-07-30 09:02:12 +00:00
parent eca07750d2
commit 4f048a9629
9 changed files with 4779 additions and 11 deletions

View file

@ -209,6 +209,111 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; }
.asset-field-key { color: var(--text-mid); }
.asset-field-value { font-weight: 600; color: var(--text-dark); }
/* ── Water softener diagram ────────────────────────────────── */
.softener-diagram-empty {
font-size: 12px;
color: var(--text-mid);
background: var(--body-bg);
border: 1px dashed var(--card-border);
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.softener-diagram { margin-bottom: 12px; }
.softener-regen-banner {
display: flex;
align-items: center;
gap: 6px;
font-size: 11.5px;
font-weight: 600;
color: var(--gold);
background: rgba(201,168,76,.12);
border-radius: 6px;
padding: 4px 8px;
margin-bottom: 8px;
width: fit-content;
}
.softener-diagram-row {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.softener-flow {
display: flex;
align-items: center;
color: var(--card-border);
flex-shrink: 0;
}
.softener-flow svg { margin-left: -6px; }
.softener-flow-active svg { color: var(--app-primary); animation: softener-flow-pulse 1.1s ease-in-out infinite; }
.softener-flow-active svg:nth-child(2) { animation-delay: .15s; }
.softener-flow-active svg:nth-child(3) { animation-delay: .3s; }
@keyframes softener-flow-pulse {
0%, 100% { opacity: .25; }
50% { opacity: 1; }
}
.softener-tank {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 76px;
flex-shrink: 0;
}
.softener-tank-standby { opacity: .5; }
.softener-tank-active .softener-tank-shell { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.softener-badge {
position: absolute;
top: -9px;
z-index: 1;
display: flex;
align-items: center;
gap: 3px;
background: var(--gold);
color: var(--navy);
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .02em;
border-radius: 20px;
padding: 2px 7px;
white-space: nowrap;
}
.softener-badge-regen { background: var(--app-primary); color: #fff; }
.softener-tank-shell {
position: relative;
width: 56px;
height: 90px;
border: 2px solid var(--card-border);
border-radius: 10px 10px 6px 6px;
background: var(--body-bg);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.softener-tank-fill {
position: absolute;
bottom: 0; left: 0; right: 0;
transition: height .5s ease;
}
.softener-fill-ok { background: var(--sev-ok); }
.softener-fill-warning { background: var(--sev-warning); }
.softener-fill-critical { background: var(--sev-critical); }
.softener-fill-unknown { background: var(--card-border); height: 0 !important; }
.softener-tank-pct {
position: relative;
z-index: 1;
font-size: 12px;
font-weight: 700;
color: var(--text-dark);
text-shadow: 0 1px 2px rgba(255,255,255,.6);
}
.softener-tank-label { font-size: 11px; font-weight: 600; color: var(--text-dark); margin-top: 6px; }
.softener-tank-value { font-size: 10px; color: var(--text-mid); }
/* ── Badges ────────────────────────────────────────────────── */
.badge {
display: inline-flex;