Exportbedingungen: Vergleichsart je Feldart statt nacktem Textfeld

Bisher tippte man den Wert immer von Hand ein - auch bei einem Haken, wo man
wissen musste, dass intern "true" steht, und bei einer Auswahl, wo ein
Vertipper stillschweigend nichts fand. Das ist der unangenehmste Fehlschlag:
Die CSV kommt einfach leer heraus, ohne Hinweis warum.

Jetzt bringt jede Bedingung die passende Eingabe mit:

  Ja/Nein    ein Haken
  Auswahl    "ist gleich" / "ist nicht gleich" plus das Dropdown der Optionen
  Zahl       "ist groesser =" / "ist =" / "ist kleiner =" plus Zahlenfeld
  Text       wie bisher freie Eingabe, leer = ohne Wert

Damit die Oberflaeche das kann, traegt /etiketten/spalten jetzt Art und
Auswahlwerte je Feld mit - vorher gab es nur Schluessel und Titel.

Der Filter bekommt dafuer ein op (=, !=, >=, <=), Vorgabe "=", damit
bestehende Vorlagen unveraendert weiterlaufen. Groesser/kleiner vergleicht
als Zahl und nimmt Komma wie Punkt; Zeilen ohne Wert treffen dabei NIE -
"leer" ist nicht kleiner als drei, es ist unvergleichbar. Eine unbekannte
Vergleichsart faellt auf "=" zurueck statt den Export zu zerreissen.

Die Sicherung traegt das op mit, wie schon das Feld selbst.

Sieben neue Tests, 547 gruen. Diesmal zusaetzlich in der laufenden Oberflaeche
durchgespielt: Alle vier Feldarten zeigen die richtige Eingabe, der Filter
wird gespeichert, und die CSV enthaelt bei "Laenge >= 5" genau die Artikel
mit 5 und 12 - nicht den mit 2 und nicht den ohne Wert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-16 16:18:48 +02:00
co-authored by Claude Opus 5
parent dd57d3356a
commit 8de222072c
5 changed files with 250 additions and 31 deletions
+84 -16
View File
@@ -155,6 +155,65 @@ export default function EtikettenVorlagen({ onError }) {
}
/** Bearbeiten einer einzelnen Vorlage. */
/**
* Die Eingabe einer Filterbedingung, passend zur Art des Feldes.
*
* Ohne das waere jede Bedingung ein nacktes Textfeld: Man muesste wissen, dass
* ein Haken intern „true" heisst und wie eine Auswahl genau geschrieben ist.
* Ein Vertipper faende dann stillschweigend nichts – der unangenehmste
* Fehlschlag, weil die CSV einfach leer herauskommt.
*/
function FilterEingabe({ feld, bedingung, onAendern }) {
const typ = feld?.typ || "text";
if (typ === "boolean") {
return (
<label className="check-inline" style={{ margin: 0 }}>
<input type="checkbox" checked={bedingung.wert === "true"}
onChange={(e) => onAendern({ op: "=", wert: e.target.checked ? "true" : "false" })} />
<span className="muted small">{bedingung.wert === "true" ? "ja" : "nein"}</span>
</label>
);
}
if (typ === "select") {
return (
<>
<select value={bedingung.op} style={{ width: 110 }}
onChange={(e) => onAendern({ op: e.target.value })}>
<option value="=">ist gleich</option>
<option value="!=">ist nicht gleich</option>
</select>
<select value={bedingung.wert} style={{ width: 120 }}
onChange={(e) => onAendern({ wert: e.target.value })}>
{(feld.optionen || []).map((o) => <option key={o} value={o}>{o}</option>)}
</select>
</>
);
}
if (typ === "number") {
return (
<>
<select value={bedingung.op} style={{ width: 110 }}
onChange={(e) => onAendern({ op: e.target.value })}>
<option value=">=">ist größer =</option>
<option value="=">ist =</option>
<option value="<=">ist kleiner =</option>
</select>
<input value={bedingung.wert} inputMode="decimal" style={{ width: 90 }}
onChange={(e) => onAendern({ wert: e.target.value })} />
</>
);
}
// Text, mehrzeiliger Text und Datum: freie Eingabe, leer = ohne Wert.
return (
<input value={bedingung.wert} placeholder="ohne Wert" style={{ width: 120 }}
onChange={(e) => onAendern({ wert: e.target.value })} />
);
}
function VorlageEditor({ vorlage, onGespeichert, onError }) {
const toast = useToast();
const [art, setArt] = useState(vorlage.art);
@@ -265,18 +324,21 @@ function VorlageEditor({ vorlage, onGespeichert, onError }) {
Nur Zeilen mit bestimmten Feldwerten (alle Bedingungen müssen zutreffen)
</span>
<ul className="clean-list" style={{ marginTop: "var(--sp-2)" }}>
{feldfilter.map((f, i) => (
<li key={f.feld_id}>
<span style={{ flex: 1, minWidth: 0 }}>{titelVon(`feld:${f.feld_id}`)}</span>
<input value={f.wert} placeholder="ohne Wert" style={{ width: 120 }}
onChange={(e) => setFeldfilter(feldfilter.map((x, j) =>
(j === i ? { ...x, wert: e.target.value } : x)))} />
<button type="button" className="btn-icon danger" title="Bedingung entfernen"
onClick={() => setFeldfilter(feldfilter.filter((_, j) => j !== i))}>
<Icon name="close" size={14} />
</button>
</li>
))}
{feldfilter.map((f, i) => {
const feld = moeglich.find((m) => m.schluessel === `feld:${f.feld_id}`);
const aendern = (teil) => setFeldfilter(feldfilter.map((x, j) =>
(j === i ? { ...x, ...teil } : x)));
return (
<li key={f.feld_id}>
<span style={{ flex: 1, minWidth: 0 }}>{titelVon(`feld:${f.feld_id}`)}</span>
<FilterEingabe feld={feld} bedingung={f} onAendern={aendern} />
<button type="button" className="btn-icon danger" title="Bedingung entfernen"
onClick={() => setFeldfilter(feldfilter.filter((_, j) => j !== i))}>
<Icon name="close" size={14} />
</button>
</li>
);
})}
</ul>
<select value="" style={{ marginTop: "var(--sp-1)" }}
onChange={(e) => {
@@ -284,8 +346,13 @@ function VorlageEditor({ vorlage, onGespeichert, onError }) {
if (!id) return;
// Ein Feld nur einmal: zwei Bedingungen darauf koennten sich nie
// beide erfuellen.
const feld = moeglich.find((m) => m.schluessel === e.target.value);
// Ja/Nein startet als gesetzter Haken, eine Auswahl auf ihrem
// ersten Wert - so steht nie eine Bedingung da, die nichts trifft.
const start = feld?.typ === "boolean" ? "true"
: feld?.typ === "select" ? (feld.optionen?.[0] || "") : "";
setFeldfilter([...feldfilter.filter((x) => x.feld_id !== id),
{ feld_id: id, wert: "" }]);
{ feld_id: id, op: "=", wert: start }]);
}}>
<option value="">+ Bedingung auf einem eigenen Feld …</option>
{moeglich.filter((m) => m.schluessel.startsWith("feld:")).map((m) => (
@@ -293,9 +360,10 @@ function VorlageEditor({ vorlage, onGespeichert, onError }) {
))}
</select>
<p className="muted small">
Wert genau so eingeben, wie er im Feld steht; bei Ja/Nein-Feldern
„ja" oder „nein". <strong>Leer heißt: ohne Wert</strong> – also alle,
bei denen das Feld nicht ausgefüllt ist.
Bei Textfeldern genau so eingeben, wie es im Feld steht;{" "}
<strong>leer heißt dort: ohne Wert</strong> – also alle, bei denen
das Feld nicht ausgefüllt ist. Größer/kleiner vergleicht als Zahl;
Zeilen ohne Wert treffen dabei nie.
</p>
</>
)}