Web: eigene Felder nachträglich bearbeiten

Backend und API-Client konnten Felddefinitionen laengst aendern (PATCH), nur die
Oberflaeche bot bisher nur Anlegen und Loeschen. In der Felder-Verwaltung einer
Kategorie gibt es jetzt je Feld einen Bearbeiten-Knopf: er laedt Name, Typ,
Einheit, Auswahlmoeglichkeiten und Pflicht ins Formular; Speichern aktualisiert
das Feld (oder Abbrechen). Geerbte und eingebaute Felder bleiben unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-26 13:11:29 +02:00
parent dc66a6d7ff
commit 272609e70a

View File

@@ -287,6 +287,7 @@ function CategoryFields({ category, isAdmin, onClose }) {
const confirm = useConfirm(); const confirm = useConfirm();
const [fields, setFields] = useState([]); const [fields, setFields] = useState([]);
const [form, setForm] = useState(EMPTY_FIELD); const [form, setForm] = useState(EMPTY_FIELD);
const [editId, setEditId] = useState(null); // null = neues Feld, sonst bearbeiten
const [error, setError] = useState(null); const [error, setError] = useState(null);
async function load() { async function load() {
@@ -298,21 +299,45 @@ function CategoryFields({ category, isAdmin, onClose }) {
} }
useEffect(() => { load(); }, [category.id]); useEffect(() => { load(); }, [category.id]);
async function add(e) { // Ein bestehendes Feld in das Formular laden (mehrfach getrennte Optionen
// wieder als Komma-Liste).
function startEdit(f) {
setEditId(f.id);
setForm({
label: f.label,
field_type: f.field_type,
unit: f.unit || "",
options: (f.options || []).join(", "),
required: f.required,
});
setError(null);
}
function cancelEdit() {
setEditId(null);
setForm(EMPTY_FIELD);
}
async function submit(e) {
e.preventDefault(); e.preventDefault();
setError(null); setError(null);
const body = {
label: form.label,
field_type: form.field_type,
unit: form.field_type === "number" ? (form.unit || null) : null,
options: form.field_type === "select"
? form.options.split(",").map((o) => o.trim()).filter(Boolean)
: null,
required: form.required,
};
try { try {
await api.createFieldDefinition({ if (editId) {
category_id: category.id, await api.updateFieldDefinition(editId, body);
label: form.label, } else {
field_type: form.field_type, await api.createFieldDefinition({ category_id: category.id, ...body });
unit: form.field_type === "number" ? (form.unit || null) : null, }
options: form.field_type === "select"
? form.options.split(",").map((o) => o.trim()).filter(Boolean)
: null,
required: form.required,
});
setForm(EMPTY_FIELD); setForm(EMPTY_FIELD);
setEditId(null);
load(); load();
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
@@ -351,7 +376,7 @@ function CategoryFields({ category, isAdmin, onClose }) {
) : ( ) : (
<ul className="clean-list"> <ul className="clean-list">
{fields.map((f) => ( {fields.map((f) => (
<li key={f.id} className="cell-row"> <li key={f.id} className={`cell-row ${editId === f.id ? "row-active" : ""}`}>
<span className="strong">{f.label}</span> <span className="strong">{f.label}</span>
<span className="badge nowrap">{fieldTypeLabel(f.field_type)}</span> <span className="badge nowrap">{fieldTypeLabel(f.field_type)}</span>
{f.field_type === "number" && f.unit && <span className="muted small">{f.unit}</span>} {f.field_type === "number" && f.unit && <span className="muted small">{f.unit}</span>}
@@ -360,10 +385,16 @@ function CategoryFields({ category, isAdmin, onClose }) {
<span className="badge nowrap" title="stammt aus einer Oberkategorie">geerbt</span> <span className="badge nowrap" title="stammt aus einer Oberkategorie">geerbt</span>
) : ( ) : (
isAdmin && ( isAdmin && (
<button className="btn-icon danger" style={{ marginLeft: "auto" }} <span style={{ marginLeft: "auto", display: "flex", gap: 4 }}>
title="Feld löschen" onClick={() => remove(f)}> {!f.is_builtin && (
<Icon name="trash" size={15} /> <button className="btn-icon" title="Feld bearbeiten" onClick={() => startEdit(f)}>
</button> <Icon name="edit" size={15} />
</button>
)}
<button className="btn-icon danger" title="Feld löschen" onClick={() => remove(f)}>
<Icon name="trash" size={15} />
</button>
</span>
) )
)} )}
</li> </li>
@@ -372,7 +403,12 @@ function CategoryFields({ category, isAdmin, onClose }) {
)} )}
{isAdmin && ( {isAdmin && (
<form onSubmit={add} className="stack" style={{ marginTop: "var(--sp-3)" }}> <form onSubmit={submit} className="stack" style={{ marginTop: "var(--sp-3)" }}>
{editId && (
<div className="muted small" style={{ display: "flex", alignItems: "center", gap: 6 }}>
<Icon name="edit" size={14} />Feld bearbeiten
</div>
)}
<label> <label>
Feldname Feldname
<input value={form.label} placeholder="z.B. Kapazität" <input value={form.label} placeholder="z.B. Kapazität"
@@ -406,7 +442,15 @@ function CategoryFields({ category, isAdmin, onClose }) {
onChange={(e) => setForm({ ...form, required: e.target.checked })} /> onChange={(e) => setForm({ ...form, required: e.target.checked })} />
<span>Pflichtfeld</span> <span>Pflichtfeld</span>
</label> </label>
<button className="btn primary"><Icon name="plus" size={16} />Feld hinzufügen</button> <div className="field-inline">
<button className="btn primary">
<Icon name={editId ? "check" : "plus"} size={16} />
{editId ? "Änderungen speichern" : "Feld hinzufügen"}
</button>
{editId && (
<button type="button" className="btn ghost" onClick={cancelEdit}>Abbrechen</button>
)}
</div>
</form> </form>
)} )}
</div> </div>