feat: Chargen- und Erntebilder einzeln pro Kachel pflegbar

Die Chargenseite (/charge/[code]) zeigt zwei Bildkacheln: links Ernte und
Jahr, rechts den Standort. Gefuellt wurden sie aus einer Bilder-LISTE, in
der allein die Reihenfolge entschied, welches Bild wohin kommt.

Damit liess sich das zweite Bild nicht allein setzen. Wer nur ein Foto vom
Standort pflegen wollte, bekam es auf Position 0 — also in die linke
Kachel, wo Ernte und Jahr stehen sollten. Welche Kachel ein Bild fuellt,
war im Formular ausserdem nirgends erkennbar.

Jetzt zwei benannte Slots statt einer Liste, im Chargen- wie im
Ernten-Formular:

  Bild 1 — linke Kachel auf der Chargenseite
  Bild 2 — rechte Kachel auf der Chargenseite (Standort)

Jeder Slot hat eine eigene Vorschau, einen eigenen Auswahl- und einen
eigenen Entfernen-Knopf. Beide tragen denselben Feldnamen "images" und
werden in dieser Reihenfolge uebermittelt.

Dafuer brauchte es einen positionserhaltenden Parser: strArray() trimmt
und filtert leere Werte heraus, wodurch ein leerer erster Slot
verschwunden und der zweite nach vorn gerutscht waere — genau der Fehler,
der behoben werden sollte. Neu ist strArraySlots(), das leere Slots als ""
stehen laesst und nur am Ende stehende entfernt (ein leeres Feld ergibt
weiterhin [] statt ["", ""]).

Die Konsumenten mussten entsprechend von "??" auf "||" wechseln: ein im
Admin geleerter Slot kommt als leerer String an, nicht als undefined. Mit
"??" haette dieser leere String als gueltiger Wert gegolten und das
Erntebild darunter verdeckt (charge/[code], ernten, ernten/[year]/[type]).

strArray/strArraySlots sind nach lib/form-fields.ts gewandert und werden
aus _shared.ts re-exportiert — saemtliche bestehenden Importe bleiben
unveraendert. Grund: _shared.ts zieht ueber lib/prisma.ts einen
Datenbank-Adapter nach und liess sich ohne DATABASE_URL nicht testen. Als
reine Funktionen ohne Datenbankbezug sind die beiden jetzt direkt
pruefbar, passend zur bereits dokumentierten Teststrategie des Projekts.

Sieben neue Tests decken den Kernfall ab (leerer erster Slot bleibt
offen), mit Gegenprobe, dass strArray denselben Fall weiterhin
zusammenrutschen laesst.

Produktbilder bleiben bewusst bei der bisherigen Liste: dort ist die
Bilderzahl offen und die Reihenfolge traegt keine Kachel-Bedeutung.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5 committed 2026-09-15 21:10:12 +02:00
1 parent 979d39e677
commit ff365c3cb1
12 files changed
+182 -27

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
/**
* Reine Leser fuer FormData-Werte — ohne Datenbank- oder Next.js-Bezug,
* damit sie sich unabhaengig testen lassen (siehe
* app/admin/actions/image-slots.test.ts). Die Admin-Actions importieren sie
* weiterhin ueber admin/actions/_shared.ts, das sie re-exportiert.
*/
/** Alle Werte eines wiederholten Feldes, getrimmt, ohne leere Eintraege. */
export function strArray(formData: FormData, key: string): string[] {
return formData
.getAll(key)
.map((value) => String(value).trim())
.filter(Boolean);
}
/**
* Wie strArray, aber POSITIONSERHALTEND: ein leerer Eintrag bleibt als ""
* stehen, statt herauszufallen.
*
* Gedacht fuer Felder, bei denen die Position eine Bedeutung traegt. Chargen-
* und Erntebilder sind genau so ein Fall: Slot 1 fuellt die linke, Slot 2 die
* rechte Kachel auf /charge/[code]. Mit strArray liesse sich das zweite Bild
* gar nicht allein setzen — ohne erstes Bild rueckte das zweite auf Position 0
* und landete in der falschen Kachel.
*
* Nur am Ende stehende leere Slots werden entfernt, damit ein vollstaendig
* leeres Feld weiterhin ein leeres Array ergibt statt ["", ""].
*/
export function strArraySlots(formData: FormData, key: string): string[] {
const slots = formData.getAll(key).map((value) => String(value).trim());
while (slots.length > 0 && slots[slots.length - 1] === "") slots.pop();
return slots;
}