Zeitraum im Kartenkopf; Verlauf feiner als ein Punkt pro Tag
Zeitraum-Auswahl: Sie stand ueber dem Diagramm und nahm ihm Hoehe weg. Jetzt sitzt sie klein im Kartenkopf neben dem Titel - die ganze Kartenhoehe gehoert dem Diagramm. Die Wahl wird in der Anordnung mitgespeichert, bleibt also nach dem Neuladen erhalten; ausserhalb des Bearbeitungsmodus geschieht das still im Hintergrund. Feinere Aufloesung: Ein Punkt je Tag verbarg, WANN ein- und ausgelagert wurde. Die Abtastrate richtet sich jetzt nach dem Zeitraum: bis 2 Tage -> stuendlich bis 14 Tage -> alle 6 Stunden bis 120 Tage -> taeglich darueber -> woechentlich So bleibt die Punktzahl immer zwischen etwa 25 und 105 - fein genug zum Erkennen, grob genug zum Zeichnen. Ein Test haelt diese Spanne fest. Die Zeitachse traegt entsprechend Uhrzeit statt Datum, wenn stuendlich abgetastet wird. Punkte heissen jetzt "at" (Zeitpunkt) statt "date". Zeitstempel aus SQLite kommen ohne Zeitzone zurueck und werden vereinheitlicht, sonst schluege die Differenzbildung fehl. Auswahl erweitert: 24 Stunden, 2 Tage, 7, 30, 90 Tage, 6 Monate, 1 Jahr. Geprueft: "npm run build" laeuft durch. pytest weiterhin nicht ausfuehrbar - kein Python auf diesem Rechner; die Tests wurden an die neue Aufloesung angepasst. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,11 +41,18 @@ function Zustand({ fehler, daten, children, leer = "Keine Daten." }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function kurzesDatum(iso) {
|
||||
const d = new Date(`${iso}T00:00:00`);
|
||||
return Number.isNaN(d.getTime())
|
||||
? iso
|
||||
: d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" });
|
||||
/**
|
||||
* Beschriftung der Zeitachse. Bei kurzen Zeiträumen tastet der Server stündlich
|
||||
* ab – dann ist die Uhrzeit die nützliche Angabe, sonst das Datum.
|
||||
*/
|
||||
function zeitLabel(iso, tage) {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return String(iso);
|
||||
if (tage <= 2) return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
|
||||
if (tage <= 14) {
|
||||
return d.toLocaleString("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit" });
|
||||
}
|
||||
return d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Aktionen
|
||||
@@ -272,11 +279,14 @@ 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" },
|
||||
/**
|
||||
* Auswahlmöglichkeiten für die Verlaufskarten. Die Auswahl selbst sitzt im
|
||||
* Kartenkopf (siehe Dashboard) – so bleibt die ganze Kartenhöhe dem Diagramm.
|
||||
*/
|
||||
export const ZEITRAEUME = [
|
||||
// 1 Tag wird stündlich abgetastet; man sieht also, wann am Tag gelagert wurde.
|
||||
{ tage: 1, label: "24 Stunden" },
|
||||
{ tage: 2, label: "2 Tage" },
|
||||
{ tage: 7, label: "7 Tage" },
|
||||
{ tage: 30, label: "30 Tage" },
|
||||
{ tage: 90, label: "90 Tage" },
|
||||
@@ -284,44 +294,28 @@ const ZEITRAEUME = [
|
||||
{ tage: 365, label: "1 Jahr" },
|
||||
];
|
||||
|
||||
function ZeitraumWahl({ wert, setzen }) {
|
||||
return (
|
||||
<select className="zeitraum" value={wert} onChange={(e) => setzen(Number(e.target.value))}>
|
||||
{ZEITRAEUME.map((z) => <option key={z.tage} value={z.tage}>{z.label}</option>)}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
function KarteBestandsverlauf() {
|
||||
const [tage, setTage] = useState(90);
|
||||
function KarteBestandsverlauf({ tage = 90 }) {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardTimeline(tage), [tage]);
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
|
||||
return (
|
||||
<div className="card-stack">
|
||||
<ZeitraumWahl wert={tage} setzen={setTage} />
|
||||
{fehler ? (
|
||||
<div className="alert error"><Icon name="alert" size={16} />{fehler}</div>
|
||||
) : !daten ? (
|
||||
<div className="empty">Lädt…</div>
|
||||
) : (
|
||||
<Line
|
||||
labels={daten.map((p) => kurzesDatum(p.date))}
|
||||
serien={[{
|
||||
name: "Artikeleinheiten",
|
||||
color: tokenFarbe("--accent", "#3f51b5"),
|
||||
werte: daten.map((p) => p.article_units),
|
||||
}]}
|
||||
einheit="Einheiten"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Line
|
||||
labels={daten.map((p) => zeitLabel(p.at, tage))}
|
||||
serien={[{
|
||||
name: "Artikeleinheiten",
|
||||
color: tokenFarbe("--accent", "#3f51b5"),
|
||||
werte: daten.map((p) => p.article_units),
|
||||
}]}
|
||||
einheit="Einheiten"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function KarteArtikelverlauf() {
|
||||
function KarteArtikelverlauf({ tage = 90 }) {
|
||||
const [produkte, setProdukte] = useState([]);
|
||||
const [gewaehlt, setGewaehlt] = useState("");
|
||||
const [tage, setTage] = useState(90);
|
||||
const { daten, fehler } = useDaten(
|
||||
() => (gewaehlt ? api.dashboardTimeline(tage, gewaehlt) : Promise.resolve([])),
|
||||
[gewaehlt, tage],
|
||||
@@ -337,18 +331,16 @@ function KarteArtikelverlauf() {
|
||||
|
||||
return (
|
||||
<div className="card-stack">
|
||||
<div className="card-toolbar">
|
||||
<select value={gewaehlt} onChange={(e) => setGewaehlt(e.target.value)}>
|
||||
<option value="">– Artikel wählen –</option>
|
||||
{produkte.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
<ZeitraumWahl wert={tage} setzen={setTage} />
|
||||
</div>
|
||||
<select value={gewaehlt} onChange={(e) => setGewaehlt(e.target.value)}
|
||||
style={{ marginTop: 0 }}>
|
||||
<option value="">– Artikel wählen –</option>
|
||||
{produkte.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
{fehler ? (
|
||||
<div className="alert error"><Icon name="alert" size={16} />{fehler}</div>
|
||||
) : (
|
||||
<Line
|
||||
labels={(daten || []).map((p) => kurzesDatum(p.date))}
|
||||
labels={(daten || []).map((p) => zeitLabel(p.at, tage))}
|
||||
serien={[{
|
||||
name: "Bestand",
|
||||
color: tokenFarbe("--accent", "#3f51b5"),
|
||||
@@ -361,28 +353,21 @@ function KarteArtikelverlauf() {
|
||||
);
|
||||
}
|
||||
|
||||
function KarteBewegungsverlauf() {
|
||||
const [tage, setTage] = useState(30);
|
||||
function KarteBewegungsverlauf({ tage = 30 }) {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardActivity(tage), [tage]);
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
|
||||
return (
|
||||
<div className="card-stack">
|
||||
<ZeitraumWahl wert={tage} setzen={setTage} />
|
||||
{fehler ? (
|
||||
<div className="alert error"><Icon name="alert" size={16} />{fehler}</div>
|
||||
) : !daten ? (
|
||||
<div className="empty">Lädt…</div>
|
||||
) : (
|
||||
<Line
|
||||
labels={daten.map((p) => kurzesDatum(p.date))}
|
||||
serien={[
|
||||
{ name: "Eingelagert", color: tokenFarbe("--ok", "#2e7d55"), werte: daten.map((p) => p.checked_in) },
|
||||
{ name: "Ausgelagert", color: tokenFarbe("--warn", "#b06f14"), werte: daten.map((p) => p.checked_out) },
|
||||
]}
|
||||
einheit="Vorgänge"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Line
|
||||
labels={daten.map((p) => zeitLabel(p.at, tage))}
|
||||
serien={[
|
||||
{ name: "Eingelagert", color: tokenFarbe("--ok", "#2e7d55"), werte: daten.map((p) => p.checked_in) },
|
||||
{ name: "Ausgelagert", color: tokenFarbe("--warn", "#b06f14"), werte: daten.map((p) => p.checked_out) },
|
||||
]}
|
||||
einheit="Vorgänge"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -465,19 +450,19 @@ export const KARTEN = {
|
||||
"stock-timeline": {
|
||||
titel: "Bestandsverlauf", icon: "history", vorschau: "linie",
|
||||
beschreibung: "Artikeleinheiten im Lager über 90 Tage",
|
||||
standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
|
||||
zeitraum: 90, standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
|
||||
komponente: KarteBestandsverlauf,
|
||||
},
|
||||
"product-timeline": {
|
||||
titel: "Verlauf eines Artikels", icon: "package", vorschau: "linie",
|
||||
beschreibung: "Ein wählbarer Artikel über 90 Tage",
|
||||
standard: { w: 8, h: 7 }, min: { w: 4, h: 6 },
|
||||
zeitraum: 90, standard: { w: 8, h: 7 }, min: { w: 4, h: 6 },
|
||||
komponente: KarteArtikelverlauf,
|
||||
},
|
||||
"activity-timeline": {
|
||||
titel: "Ein- und Auslagerungen", icon: "history", vorschau: "linien",
|
||||
beschreibung: "Vorgänge je Tag über 30 Tage",
|
||||
standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
|
||||
zeitraum: 30, standard: { w: 8, h: 6 }, min: { w: 4, h: 5 },
|
||||
komponente: KarteBewegungsverlauf,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user