diff --git a/web/src/dashboard/KartenConfig.jsx b/web/src/dashboard/KartenConfig.jsx index c43c804..2f58099 100644 --- a/web/src/dashboard/KartenConfig.jsx +++ b/web/src/dashboard/KartenConfig.jsx @@ -1,6 +1,8 @@ +import { useMemo, useState } from "react"; import Icon from "../components/Icon"; import { ZEITRAEUME } from "./cards"; import { locationOptions } from "../locationPath"; +import { artikelLabel } from "../units"; /** * Konfig-Dialog einer Karte – wie in HomeAssistant: ein eigener Dialog mit den @@ -17,14 +19,9 @@ export default function KartenConfig({ function feld(f) { if (f.type === "product") { return ( - + onProps({ [f.key]: id ?? undefined })} /> ); } if (f.type === "location") { @@ -91,3 +88,55 @@ export default function KartenConfig({ ); } + +/** + * Artikel aussuchen – mit Suchfeld und sprechenden Zeilen. + * + * Eine blanke Auswahlliste ueber den ganzen Katalog ist unbrauchbar: Sie ist + * hunderte Zeilen lang, und weil nur der Name darin stand, standen "Pesto + * Rosso" und "Pesto Rosso" untereinander, ohne Unterschied. Jetzt steht Marke + * und Gebindegroesse dabei, und das Suchfeld daruber kuerzt die Liste auf das, + * was man meint. + */ +function ArtikelWahl({ label, produkte, wert, onWahl }) { + const [suche, setSuche] = useState(""); + + const gefiltert = useMemo(() => { + const begriffe = suche.toLowerCase().split(/\s+/).filter(Boolean); + if (begriffe.length === 0) return produkte; + return produkte.filter((p) => { + const text = artikelLabel(p).toLowerCase(); + return begriffe.every((b) => text.includes(b)); + }); + }, [produkte, suche]); + + // Der gewaehlte Artikel muss in der Liste bleiben, auch wenn die Suche ihn + // gerade ausblendet - sonst zeigt das Feld nichts an und ein Klick daneben + // wuerde die Wahl verwerfen. + const gewaehlt = produkte.find((p) => String(p.id) === String(wert)); + const sichtbar = gewaehlt && !gefiltert.includes(gewaehlt) + ? [gewaehlt, ...gefiltert] + : gefiltert; + + return ( +
+ + +

+ {suche + ? `${gefiltert.length} von ${produkte.length} Artikeln` + : `${produkte.length} Artikel`} +

+
+ ); +} diff --git a/web/src/pages/Dashboard.jsx b/web/src/pages/Dashboard.jsx index 33eb1c9..9501603 100644 --- a/web/src/pages/Dashboard.jsx +++ b/web/src/pages/Dashboard.jsx @@ -9,6 +9,7 @@ import { useToast } from "../toast"; import { KARTEN, KARTEN_IDS } from "../dashboard/cards"; import KartenConfig from "../dashboard/KartenConfig"; import Vorschau from "../dashboard/Vorschau"; +import { artikelLabel } from "../units"; const Raster = WidthProvider(GridLayout); @@ -380,7 +381,11 @@ export default function Dashboard() { const ort = eintrag.props?.location_id ? locations.find((l) => l.id === eintrag.props.location_id) : null; - const zusatz = artikel?.name || ort?.name; // im Titel gezeigt + // Im Titel steht die Marke mit, sonst heissen zwei Karten gleich, + // wenn zwei Hersteller denselben Artikelnamen fuehren. Die + // Gebindegroesse bliebe dort zu lang - die steht in der Auswahl. + const zusatz = (artikel ? artikelLabel(artikel, { groesse: false }) : "") + || ort?.name; return (
diff --git a/web/src/units.js b/web/src/units.js index 8f79567..fcac66f 100644 --- a/web/src/units.js +++ b/web/src/units.js @@ -114,6 +114,23 @@ export function stockLabelMitZweit(p) { return zweit ? `${stockLabel(p)} · ${zweit}` : stockLabel(p); } +/** + * Artikelname, wie man ihn in einer Auswahlliste braucht: "Pesto Rosso · + * Barilla · 190 g". + * + * Der blosse Name reicht nicht. Ein Vorrat enthaelt denselben Artikel von + * mehreren Herstellern und in mehreren Groessen; zwei Zeilen "Pesto Rosso" + * untereinander sind dann nicht zu unterscheiden, und man waehlt die falsche. + * Marke und Gebindegroesse sind genau die beiden Angaben, die sie trennen. + */ +export function artikelLabel(p, { groesse = true } = {}) { + if (!p) return ""; + const gebinde = groesse && p.package_size > 0 + ? `${fmt(p.package_size)} ${unitShort(p.base_unit)}` + : ""; + return [p.name, p.brand, gebinde].filter(Boolean).join(" · "); +} + // Auswahlmöglichkeiten für Menge beim Ein-/Auslagern eines Produkts. export function unitOptions(product) { const opts = [{ value: baseUnitToInput(product.base_unit), label: unitShort(product.base_unit) }];