import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api";
import Icon from "../components/Icon";
import { useSettings } from "../settings";
import {
amountText, articlePrimary, articleSecondary, fmt, relativeExpiry,
} from "../units";
import Bars from "./charts/Bars";
import Donut from "./charts/Donut";
import Line from "./charts/Line";
import Wasserfall from "./charts/Wasserfall";
import { istDunkel, mitFarben, tokenFarbe } from "./palette";
/**
* Verzeichnis aller Karten der Startseite.
*
* Eine Karte ist ein Eintrag hier – mehr braucht es nicht, um sie im
* Hinzufügen-Menü und im Raster verfügbar zu machen. Jede Karte lädt ihre
* eigenen Daten; so kostet eine nicht genutzte Karte auch keine Abfrage.
*/
// ---------------------------------------------------------------- Helfer
function useDaten(laden, abhaengig = []) {
const [daten, setDaten] = useState(null);
const [fehler, setFehler] = useState(null);
useEffect(() => {
let aktiv = true;
laden()
.then((d) => aktiv && setDaten(d))
.catch((e) => aktiv && setFehler(e.message));
return () => { aktiv = false; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, abhaengig);
return { daten, fehler };
}
function Zustand({ fehler, daten, children, leer = "Keine Daten." }) {
if (fehler) return
{fehler}
;
if (daten == null) return
Lädt…
;
if (Array.isArray(daten) && daten.length === 0) return
{leer}
;
return children;
}
/**
* 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
/**
* Artikelsuche mit Tastaturbedienung, wie sie die Aktionskarten brauchen.
* Erst ab zwei Zeichen wird gesucht – sonst laedt jede Karte beim Oeffnen die
* halbe Artikelliste.
*/
function Artikelsuche({ gewaehlt, onWaehlen }) {
const [text, setText] = useState("");
const [treffer, setTreffer] = useState([]);
useEffect(() => {
if (gewaehlt || text.trim().length < 2) { setTreffer([]); return undefined; }
let aktiv = true;
const zeit = setTimeout(() => {
api.listProducts(text.trim())
.then((liste) => aktiv && setTreffer(liste.slice(0, 6)))
.catch(() => aktiv && setTreffer([]));
}, 250);
return () => { aktiv = false; clearTimeout(zeit); };
}, [text, gewaehlt]);
if (gewaehlt) {
return (
);
}
/**
* Ein fest eingestellter Artikel im Blick: Bestand jetzt, mit Hinweis auf den
* Mindestbestand. Welcher Artikel gemeint ist, steht in den Karten-Props und
* wird deshalb mitgespeichert.
*/
function KarteArtikel({ props: karteProps }) {
const produktId = karteProps?.product_id;
const { daten, fehler } = useDaten(
() => (produktId ? api.getProduct(produktId) : Promise.resolve(null)),
[produktId],
);
if (!produktId) {
return
Kein Artikel gewählt – über „Anordnen“ einstellen.
;
}
return (
{daten && (
{daten.name}
{articlePrimary(daten)}
{articleSecondary(daten)}
)}
);
}
function KarteAktionen() {
return (
Einlagern
Auslagern
);
}
// ---------------------------------------------------------------- Kennzahlen
function Kennzahl({ wert, label, icon, ton }) {
return (
>
);
}
/**
* 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" },
{ tage: 180, label: "6 Monate" },
{ tage: 365, label: "1 Jahr" },
];
function KarteBestandsverlauf({ tage = 90 }) {
const { daten, fehler } = useDaten(() => api.dashboardTimeline(tage), [tage]);
if (fehler) return
{fehler}
;
if (!daten) return
Lädt…
;
return (
zeitLabel(p.at, tage))}
serien={[{
name: "Artikeleinheiten",
color: tokenFarbe("--accent", "#3f51b5"),
werte: daten.map((p) => p.article_units),
}]}
einheit="Einheiten"
/>
);
}
/**
* Verlauf eines Artikels. Welcher gemeint ist, steht in den Karten-Props und
* ist damit gespeichert - frueher lag die Wahl nur in useState und war nach
* jedem Neuladen wieder weg, weshalb sich damit nichts beobachten liess.
*/
function KarteArtikelverlauf({ tage = 90, props: karteProps }) {
const gewaehlt = karteProps?.product_id ?? "";
const { daten, fehler } = useDaten(
() => (gewaehlt ? api.dashboardTimeline(tage, gewaehlt) : Promise.resolve([])),
[gewaehlt, tage],
);
if (!gewaehlt) {
return
Kein Artikel gewählt – über „Anordnen“ einstellen.
);
}
/**
* Ein- und Auslagerungen als Brücke: die Bestandslinie plus die Balken, die
* jeden ihrer Sprünge erklären. Die Mengen kommen deshalb in Artikeleinheiten
* und nicht als Anzahl Vorgänge – sonst lägen Balken und Linie auf zwei Skalen.
*/
function KarteBewegungsverlauf({ tage = 30 }) {
const { daten, fehler } = useDaten(() => api.dashboardFlow(tage), [tage]);
if (fehler) return