From b903de384185fd77c133702e625d7197b0d1c76a Mon Sep 17 00:00:00 2001
From: Scarriffle
Date: Fri, 2 Oct 2026 15:09:53 +0200
Subject: [PATCH] Masse einer Kategorie: nur zeigen, was etwas bedeutet
Jedes angelegte Mass bekam eine Zeile, und jede Zeile drei gestapelte
Reihen (Name, Menge, Art) plus Muelleimer. Bei sechs Massen sah das aus
wie ein Formular voller Pflichtangaben - und keine davon ist eine.
Jetzt drei Sorten:
- Hier eingetragen: bearbeitbare Zeile mit Muelleimer.
- Von oben geerbt: eine Zeile zum Lesen ("Essloeffel (EL) 10 g geerbt")
mit Knopf "abweichen", der sie bei Bedarf zur Eingabezeile macht.
- Alles andere: steckt in der Auswahl "Mass hinzufuegen" und stoert
nicht. Hinzufuegen klappt eine leere Zeile auf; das Kreuz daneben
klappt sie wieder zu, ohne etwas zu speichern.
Dazu das Zeilenlayout auf EINE Zeile gebracht: Name, Menge, Art, Aktion
nebeneinander, Spalten duerfen schrumpfen, Einheiten als Kurzform
(g/ml/Stk) - in der schmalen Seitenkarte ist das lesbarer, der Name
steht ja daneben.
Von Hand geprueft: Eine Kategorie mit zwei eingetragenen Massen zeigt
zwei Zeilen statt sechs, die Unterkategorie drei geerbte Lesezeilen und
kein einziges Eingabefeld. "Tasse" ueber die Auswahl hinzugefuegt, 200
ml eingetragen, gespeichert. Zeilenhoehe 29 bzw. 40 Pixel, kein
Umbruch bei 270 Pixel Kartenbreite.
Co-Authored-By: Claude Opus 5
---
web/src/pages/Categories.jsx | 148 +++++++++++++++++++++++++++--------
web/src/styles.css | 20 +++++
2 files changed, 135 insertions(+), 33 deletions(-)
diff --git a/web/src/pages/Categories.jsx b/web/src/pages/Categories.jsx
index cb2a80b..e971b50 100644
--- a/web/src/pages/Categories.jsx
+++ b/web/src/pages/Categories.jsx
@@ -421,7 +421,6 @@ function CategoryFields({ category, isAdmin, onClose }) {
);
}
-const BASIS_LABEL = { gram: "Gramm", milliliter: "Milliliter", piece: "Stück" };
const BASIS_KURZ = { gram: "g", milliliter: "ml", piece: "Stk" };
/**
@@ -443,6 +442,10 @@ function CategoryMasse({ category, isAdmin, onClose, onFrist }) {
const [frist, setFrist] = useState(category.open_days ?? "");
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
+ // Masse, die als leere Eingabezeile offen stehen, weil jemand sie gerade
+ // hinzugefuegt hat. Alles andere ohne Wert bleibt unsichtbar: Ein Dutzend
+ // leere Felder sehen aus wie Pflichtangaben, und keines davon ist eine.
+ const [offen, setOffen] = useState(() => new Set());
async function load() {
setLoading(true);
@@ -475,10 +478,19 @@ function CategoryMasse({ category, isAdmin, onClose, onFrist }) {
setError(null);
try {
await api.deleteCategoryMeasure(category.id, mass.id);
+ setOffen((alt) => { const neu = new Set(alt); neu.delete(mass.id); return neu; });
load();
} catch (err) { setError(err.message); }
}
+ function aufklappen(id) {
+ setOffen((alt) => new Set(alt).add(Number(id)));
+ }
+
+ function zuklappen(id) {
+ setOffen((alt) => { const neu = new Set(alt); neu.delete(id); return neu; });
+ }
+
async function fristSpeichern() {
const roh = String(frist).trim();
const zahl = roh === "" ? null : Number(roh);
@@ -525,22 +537,20 @@ function CategoryMasse({ category, isAdmin, onClose, onFrist }) {
Es gibt noch keine Maße. Angelegt werden sie unter Stammdaten ▸ Maße.
)}
- {!loading && masse.map((m) => {
- const w = wertFuer(m.id);
- return (
- speichern(m, menge, basis)}
- onEntfernen={async () => {
- const ok = await confirm({
- title: `„${m.name}" hier entfernen?`,
- message: "Danach erbt die Kategorie den Wert von oben – oder das Maß "
- + "gilt hier gar nicht mehr.",
- confirmLabel: "Entfernen",
- });
- if (ok) entfernen(m);
- }} />
- );
- })}
+ {!loading && masse.length > 0 && (
+ {
+ const ok = await confirm({
+ title: `„${m.name}" hier entfernen?`,
+ message: "Danach erbt die Kategorie den Wert von oben – oder das Maß "
+ + "gilt hier gar nicht mehr.",
+ confirmLabel: "Entfernen",
+ });
+ if (ok) entfernen(m);
+ }} />
+ )}
„1 EL Mehl ≈ 10 g", „1 Tasse Milch ≈ 200 ml". Damit lässt sich beim
Verbrauchen „2 EL" buchen, statt Gramm zu schätzen. Die Art (Gramm,
@@ -551,6 +561,80 @@ function CategoryMasse({ category, isAdmin, onClose, onFrist }) {
);
}
+/**
+ * Die Maße einer Kategorie – aber nur die, die etwas bedeuten.
+ *
+ * Es gibt beliebig viele Maß-Namen; für eine einzelne Kategorie sind fast immer
+ * nur zwei oder drei davon sinnvoll. Jedes als leeres Feld hinzustellen sah aus
+ * wie ein Formular voller Pflichtangaben, und keine davon ist eine. Deshalb drei
+ * Sorten:
+ *
+ * - **hier eingetragen** – bearbeitbare Zeile mit Mülleimer,
+ * - **von oben geerbt** – eine Lesezeile mit Herkunft und „abweichen",
+ * - **alles andere** – steckt in der Auswahl „Maß hinzufügen" und stört nicht.
+ */
+function MassListe({
+ masse, wertFuer, istEigen, offen, isAdmin,
+ onAufklappen, onZuklappen, onSpeichern, onEntfernen,
+}) {
+ const eigene = masse.filter((m) => istEigen(wertFuer(m.id)) || offen.has(m.id));
+ const geerbte = masse.filter((m) => !eigene.includes(m) && wertFuer(m.id) !== null);
+ const uebrige = masse.filter((m) => !eigene.includes(m) && !geerbte.includes(m));
+
+ return (
+ <>
+ {eigene.map((m) => {
+ const w = wertFuer(m.id);
+ return (
+ onSpeichern(m, menge, basis)}
+ onEntfernen={() => (istEigen(w) ? onEntfernen(m) : onZuklappen(m.id))} />
+ );
+ })}
+
+ {geerbte.map((m) => {
+ const w = wertFuer(m.id);
+ return (
+