Die Kategorie bestimmt die Verwaltungsart (food/object). Gegenstaende: Menge je Lagerort statt Chargen/MHD, Umlagern (ohne Grund) und Entfernen mit Pflicht-Grund samt Entnahme-Statistik. Beliebig viele eigene Felder je Kategorie (vererbt an Unterkategorien), "gekauft bei" ueber eine verwaltbare Shop-Liste und ein Produktlink. Barcode-Lookup zusaetzlich ueber Open Products Facts. Der Lebensmittel-Teil (Chargen/MHD/FEFO) bleibt unveraendert. Umgesetzt in Backend (FastAPI, +Tests gruen), Web (React) und iOS (SwiftUI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
3.2 KiB
JavaScript
100 lines
3.2 KiB
JavaScript
// Selbst definierte Felder: Typen-Registry und die passenden Eingabe-Elemente.
|
||
// Bewusst schlank gehalten und aus einer Konfiguration heraus gerendert – analog
|
||
// zur Karten-Registry des Dashboards (dashboard/cards.jsx).
|
||
|
||
// Auswahl im Feld-Verwaltungs-Dialog (Reihenfolge = Anzeige).
|
||
export const FIELD_TYPES = [
|
||
{ value: "text", label: "Text (einzeilig)" },
|
||
{ value: "textarea", label: "Text (mehrzeilig)" },
|
||
{ value: "number", label: "Zahl (mit Einheit)" },
|
||
{ value: "date", label: "Datum" },
|
||
{ value: "select", label: "Auswahlliste" },
|
||
{ value: "boolean", label: "Ja/Nein" },
|
||
];
|
||
|
||
export function fieldTypeLabel(value) {
|
||
return FIELD_TYPES.find((t) => t.value === value)?.label || value;
|
||
}
|
||
|
||
/** Eine einzelne Eingabe passend zum Feldtyp. */
|
||
export function FieldInput({ field, value, onChange, disabled }) {
|
||
const v = value ?? "";
|
||
switch (field.field_type) {
|
||
case "textarea":
|
||
return (
|
||
<textarea rows={3} value={v} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.value)} />
|
||
);
|
||
case "number":
|
||
return (
|
||
<span className="field-inline">
|
||
<input type="number" step="any" value={v} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.value)} />
|
||
{field.unit && <span className="muted">{field.unit}</span>}
|
||
</span>
|
||
);
|
||
case "date":
|
||
return (
|
||
<input type="date" value={v} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.value)} />
|
||
);
|
||
case "select":
|
||
return (
|
||
<select value={v} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.value)}>
|
||
<option value="">– keine –</option>
|
||
{(field.options || []).map((o) => (
|
||
<option key={o} value={o}>{o}</option>
|
||
))}
|
||
</select>
|
||
);
|
||
case "boolean":
|
||
return (
|
||
<label className="check-inline">
|
||
<input type="checkbox" checked={v === "true"} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.checked ? "true" : "false")} />
|
||
<span className="muted">Ja</span>
|
||
</label>
|
||
);
|
||
default:
|
||
return (
|
||
<input value={v} disabled={disabled}
|
||
onChange={(e) => onChange(e.target.value)} />
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Rendert die effektiven (vererbten) Felder einer Kategorie als Formularblock.
|
||
*
|
||
* ``values`` ist eine Map { String(feld_id): Wert }, wie sie das Backend liefert
|
||
* und erwartet. ``onChange(feldId, wert)`` meldet Änderungen zurück.
|
||
*/
|
||
export function DynamicFields({ fields, values, onChange, disabled }) {
|
||
if (!fields || fields.length === 0) return null;
|
||
return (
|
||
<div className="stack">
|
||
{fields.map((f) => (
|
||
<label key={f.id}>
|
||
<span className="feldkopf">
|
||
{f.label}
|
||
{f.field_type === "number" && f.unit ? ` (${f.unit})` : ""}
|
||
{f.required ? " *" : ""}
|
||
{f.inherited && (
|
||
<span className="badge nowrap" title={"geerbt von einer Oberkategorie"}>
|
||
geerbt
|
||
</span>
|
||
)}
|
||
</span>
|
||
<FieldInput
|
||
field={f}
|
||
value={values[String(f.id)]}
|
||
onChange={(val) => onChange(String(f.id), val)}
|
||
disabled={disabled}
|
||
/>
|
||
</label>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|