diff --git a/edit-server.svg b/edit-server.svg new file mode 100644 index 0000000..a368c41 --- /dev/null +++ b/edit-server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/open-box (1).svg b/open-box (1).svg new file mode 100644 index 0000000..9915bb3 --- /dev/null +++ b/open-box (1).svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/open-box.svg b/open-box.svg new file mode 100644 index 0000000..fe06e05 --- /dev/null +++ b/open-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/components/BewegungIcon.jsx b/web/src/components/BewegungIcon.jsx new file mode 100644 index 0000000..f108e15 --- /dev/null +++ b/web/src/components/BewegungIcon.jsx @@ -0,0 +1,24 @@ +import Icon from "./Icon"; + +/** + * Die Art einer Bestandsbewegung als Sinnbild statt als Wort. + * + * Kiste mit Pfeil hinein (Einlagern), Kiste mit Pfeil heraus (Auslagern), + * Stapel mit Stift (Korrektur). Die Kachel traegt dieselbe Bedeutung wie + * frueher die Farbe des Badges – gruen rein, orange raus, neutral Korrektur. + * + * Titel und aria-label behalten das Wort: fuer Screenreader und fuer alle, die + * ein Sinnbild nicht auf Anhieb lesen. + */ +const LABEL = { in: "Eingelagert", out: "Ausgelagert", adjust: "Korrektur" }; +const ICON = { in: "einlagern", out: "auslagern", adjust: "korrektur" }; +const TON = { in: "ein", out: "aus", adjust: "" }; + +export default function BewegungIcon({ typ, size = 18 }) { + const wort = LABEL[typ] || typ; + return ( + + + + ); +} diff --git a/web/src/components/Icon.jsx b/web/src/components/Icon.jsx index bd6c0e6..17db8c1 100644 --- a/web/src/components/Icon.jsx +++ b/web/src/components/Icon.jsx @@ -142,7 +142,69 @@ const PATHS = { ), }; + +// Gefuellte Icons mit EIGENEM viewBox. Die uebrigen hier sind Strichzeichnungen +// (fill:none, stroke:currentColor); diese kommen als fertige Grafik und muessen +// deshalb getrennt gehalten werden. +const GEFUELLT = { + // Kiste mit Pfeil HINEIN – aus open-box (1).svg + einlagern: { + viewBox: "0 0 24 24", + node: ( + <> + + + + + + + + ), + }, + // Kiste mit Pfeil HERAUS – aus open-box.svg + auslagern: { + viewBox: "0 0 24 24", + node: ( + <> + + + + + + + + ), + }, + // Stapel mit Stift – aus edit-server.svg + korrektur: { + viewBox: "0 0 64 64", + node: ( + <> + + + + ), + }, +}; + export default function Icon({ name, size = 18, className = "", strokeWidth = 2 }) { + const gefuellt = GEFUELLT[name]; + if (gefuellt) { + return ( + + ); + } const path = PATHS[name]; if (!path) return null; return ( diff --git a/web/src/dashboard/cards.jsx b/web/src/dashboard/cards.jsx index 34a2868..90959c0 100644 --- a/web/src/dashboard/cards.jsx +++ b/web/src/dashboard/cards.jsx @@ -4,6 +4,7 @@ import { api } from "../api"; import Icon from "../components/Icon"; import { CardSkeleton } from "../components/Skeleton"; import ShoppingNeedText from "../components/ShoppingNeedText"; +import BewegungIcon from "../components/BewegungIcon"; import { useSettings } from "../settings"; import { articlePrimary, articleSecondary, fmt, relativeExpiry, @@ -393,33 +394,29 @@ function KarteEinkaufsliste({ props: karteProps }) { ); } -const BEWEGUNG_LABEL = { in: "Eingelagert", out: "Ausgelagert", adjust: "Korrektur" }; + function KarteBewegungen() { const { daten, fehler } = useDaten(() => api.listMovements(20)); return ( -
- - - - {(daten || []).map((m) => ( - - - - - - ))} - -
ZeitpunktAktionProdukt
- {new Date(m.created_at).toLocaleString("de-DE", { - day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", - })} - - - {BEWEGUNG_LABEL[m.type] || m.type} - - {m.product_name}
+ {/* Kachel links, Zeitpunkt ueber Produkt. Die Bewegungsart trug frueher + eine eigene Spalte mit dem Wort „Ausgelagert" – als Sinnbild kostet + sie keine Zeile mehr und bleibt auf einen Blick erkennbar. */} +
+ {(daten || []).map((m) => ( +
+ +
+
+ {new Date(m.created_at).toLocaleString("de-DE", { + day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", + })} +
+
{m.product_name}
+
+
+ ))}
); diff --git a/web/src/pages/History.jsx b/web/src/pages/History.jsx index e1c29f1..932afe1 100644 --- a/web/src/pages/History.jsx +++ b/web/src/pages/History.jsx @@ -1,10 +1,9 @@ import { useEffect, useState } from "react"; import { api } from "../api"; import Icon from "../components/Icon"; +import BewegungIcon from "../components/BewegungIcon"; import { articlePrimary, articleSecondary } from "../units"; -const TYPE_LABEL = { in: "Eingelagert", out: "Ausgelagert", adjust: "Korrektur" }; - export default function History() { const [movements, setMovements] = useState([]); const [error, setError] = useState(null); @@ -39,12 +38,10 @@ export default function History() { {movements.map((m) => ( {new Date(m.created_at).toLocaleString("de-DE")} - - - - {TYPE_LABEL[m.type] || m.type} - - + {/* Nur das Sinnbild – die Tabelle hat noch Menge und + Benutzer zu zeigen, und das Wort stand ohnehin dreimal + untereinander. Titel und aria-label tragen es weiter. */} + {m.product_name} {articlePrimary(m)} diff --git a/web/src/styles.css b/web/src/styles.css index acfaaa4..994d305 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -487,6 +487,28 @@ td select { width: auto; min-width: 0; max-width: 100%; } background: var(--surface-2); color: var(--muted); } .badge.accent { background: var(--accent-soft); color: var(--accent); } + +/* Verlauf: die Bewegungsart als Sinnbild statt als Wort. Ein gefuelltes Icon + in einer getoenten Kachel – die Farbe traegt dieselbe Bedeutung wie vorher + das Badge (gruen rein, orange raus, neutral Korrektur). */ +.bewegung-icon { + display: inline-flex; align-items: center; justify-content: center; + width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto; + background: var(--accent-soft); color: var(--accent); +} +.bewegung-icon.ein { background: #e6f4ec; color: var(--ok); } +.bewegung-icon.aus { background: var(--warn-soft); color: var(--warn); } + +/* Eine Bewegung als Zeile: Kachel links, Zeitpunkt ueber Produkt. */ +.bewegung-liste { display: flex; flex-direction: column; } +.bewegung-zeile { + display: flex; align-items: center; gap: var(--sp-3); + padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); +} +.bewegung-zeile:last-child { border-bottom: 0; } +.bewegung-zeile .text { min-width: 0; } +.bewegung-zeile .zeit { color: var(--muted); font-size: 0.82rem; } +.bewegung-zeile .produkt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .badge.danger { background: var(--danger-soft); color: var(--danger); } .badge.warn { background: var(--warn-soft); color: var(--warn); } .badge.ok { background: #e6f4ec; color: var(--ok); } @@ -996,6 +1018,7 @@ select.zeitraum:hover { color: var(--text); } --shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } .badge.ok { background: #14301f; } + .bewegung-icon.ein { background: #14301f; } .alert.ok { background: #14301f; border-color: #21503a; } } :root[data-theme="dark"] {