Kurzmeldungen als Overlay, Liniendiagramm fuellt die Karte, Zeitraum ab 1 Tag

Meldungen:
"Gespeichert", "Anordnung gespeichert" und die uebrigen Erfolgsmeldungen standen
als Leiste mitten in der Seite - beim Speichern sprang dadurch das ganze Layout
ein Stueck nach unten. Sie erscheinen jetzt als Overlay am unteren Rand und
verschwinden nach fuenf Sekunden von selbst; ein Kreuz blendet sie sofort aus.
Umgestellt in Uebersicht, Einstellungen, Gefahrenbereich, Benutzer,
Produktformular sowie Ein- und Auslagern.

Bewusst NICHT umgestellt: Fehlermeldungen. Ein Fehler, der nach fuenf Sekunden
verschwindet, ist genau dann weg, wenn man ihn noch braucht - der bleibt an
seiner Stelle stehen.

Rollbalken beim Liniendiagramm:
Den hatte ich mir mit der Zeitraum-Auswahl selbst eingebaut. Das Diagramm hatte
eine feste Hoehe von 200 px; zusammen mit der Auswahl darueber passte das nicht
mehr in die Karte. Jetzt wird auch die Hoehe gemessen, das Diagramm fuellt den
verbleibenden Platz.

Zeitraum "1 Tag":
Ein einzelner Tag ergibt genau einen Messpunkt - eine Linie aus einem Punkt
waere unsinnig. Das Diagramm zeigt in dem Fall die blanke Zahl. Die
Backend-Begrenzung laesst jetzt einen Tag zu.

Geprueft: "npm run build" laeuft durch; kein Verweis mehr auf die entfernten
Meldungszustaende.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-23 10:23:53 +02:00
parent aa58914374
commit 97a6e94ea1
13 changed files with 201 additions and 57 deletions

View File

@@ -274,6 +274,9 @@ function KarteKategorienSaeulen() {
/** Zeitraum-Auswahl für die Verlaufskarten. */
const ZEITRAEUME = [
// 1 Tag ergibt einen einzelnen Wert das Diagramm zeigt dann die blanke Zahl
// statt einer Linie aus einem Punkt.
{ tage: 1, label: "1 Tag" },
{ tage: 7, label: "7 Tage" },
{ tage: 30, label: "30 Tage" },
{ tage: 90, label: "90 Tage" },

View File

@@ -2,19 +2,24 @@ import { useEffect, useRef, useState } from "react";
import { fmt } from "../../units";
import { tokenFarbe } from "../palette";
/** Misst die tatsächliche Breite, damit das SVG in echten Pixeln zeichnet.
* Ein festes viewBox mit Skalierung würde Linienstärken verzerren. */
function useBreite(ref) {
const [breite, setBreite] = useState(0);
/** Misst die tatsächliche Größe, damit das SVG in echten Pixeln zeichnet.
* Ein festes viewBox mit Skalierung würde Linienstärken verzerren.
* Auch die Höhe wird gemessen: Mit fester Höhe passte das Diagramm nicht mehr
* in die Karte, sobald darüber noch eine Auswahl stand es gab einen Rollbalken. */
function useGroesse(ref) {
const [groesse, setGroesse] = useState({ breite: 0, hoehe: 0 });
useEffect(() => {
if (!ref.current) return undefined;
const beobachter = new ResizeObserver(([eintrag]) => {
setBreite(eintrag.contentRect.width);
setGroesse({
breite: eintrag.contentRect.width,
hoehe: eintrag.contentRect.height,
});
});
beobachter.observe(ref.current);
return () => beobachter.disconnect();
}, [ref]);
return breite;
return groesse;
}
/**
@@ -27,19 +32,39 @@ function useBreite(ref) {
* labels: ["01.07.", …] gemeinsame X-Achse
* serien: [{ name, color, werte: number[] }]
*/
export default function Line({ labels = [], serien = [], einheit = "", hoehe = 200 }) {
export default function Line({ labels = [], serien = [], einheit = "" }) {
const wirt = useRef(null);
const breite = useBreite(wirt);
const plot = useRef(null);
const { breite, hoehe } = useGroesse(plot);
const [index, setIndex] = useState(null);
const hatDaten = labels.length > 1 && serien.some((s) => s.werte?.length);
if (!hatDaten) {
const legende = serien.length > 1;
const hatWerte = serien.some((s) => s.werte?.length);
// Ein einzelner Tag ergibt keine Linie dann ist die blanke Zahl die
// ehrlichere Darstellung als eine Linie aus einem Punkt.
if (labels.length === 1 && hatWerte) {
return (
<div className="chart-line einzelwert" ref={wirt}>
{serien.map((s) => (
<div key={s.name} className="einzel-reihe">
<span className="swatch" style={{ background: s.color }} />
<span className="einzel-zahl">{fmt(s.werte[0] ?? 0)}</span>
<span className="muted small">{einheit || s.name}</span>
</div>
))}
<div className="muted small">{labels[0]}</div>
</div>
);
}
if (labels.length <= 1 || !hatWerte) {
return <div className="chart-line" ref={wirt}><div className="empty">Keine Daten vorhanden.</div></div>;
}
const rand = { oben: 12, rechts: 12, unten: 24, links: 44 };
const w = Math.max(breite, 240);
const h = hoehe;
const h = Math.max(hoehe, 120);
const flaecheB = Math.max(w - rand.links - rand.rechts, 10);
const flaecheH = Math.max(h - rand.oben - rand.unten, 10);
@@ -72,7 +97,7 @@ export default function Line({ labels = [], serien = [], einheit = "", hoehe = 2
return (
<div className="chart-line" ref={wirt}>
{serien.length > 1 && (
{legende && (
<ul className="chart-legend inline">
{serien.map((s) => (
<li key={s.name}>
@@ -83,7 +108,7 @@ export default function Line({ labels = [], serien = [], einheit = "", hoehe = 2
</ul>
)}
<div className="line-plot">
<div className="line-plot" ref={plot}>
<svg width={w} height={h} role="img"
aria-label={`Liniendiagramm über ${labels.length} Tage`}
onMouseMove={bewegen} onMouseLeave={() => setIndex(null)}>