Web: Gruppentabelle auf volle Breite, Karten darunter

Die Seite hatte die Tabelle links und eine feste rechte Spalte (24% der
Breite). Seit die Tabelle Obergruppen, Bestand mit Einheit und die
Gebinde-Erlaeuterung traegt, war die rechte Spalte teurer als sie half - und sie
stand auch dann da, wenn gar keine Gruppe ausgewaehlt war.

Jetzt bekommt die Tabelle die volle Breite. "Neue Gruppe" steht darunter, die
Karten zur ausgewaehlten Gruppe (EAN-Codes, Mindestbestand) erscheinen beim
"verwalten" ebenfalls darunter, nebeneinander. Kein Springen des Layouts beim
Auswaehlen, weil die Tabelle ihre Breite nie aendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-16 09:44:29 +02:00
parent 3de168b598
commit 302e7625e5

View File

@@ -246,55 +246,57 @@ export default function Groups() {
</div>
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
<div className="grid-2 wide-aside">
<div className="card">
<DataTable id="groups" columns={columns} rows={baumZeilen} loading={loading}
getRowKey={(g) => g._pfad}
tree={{ column: "name", idOf: (g) => g._pfad, parentIdOf: (g) => g._elternPfad }}
empty="Noch keine Gruppen." />
</div>
{/* Die Tabelle bekommt die volle Breite bei vielen Spalten (Obergruppen,
Bestand, Einheit) war die rechte Spalte teurer als sie half. Alles
Übrige steht darunter. */}
<div className="card">
<DataTable id="groups" columns={columns} rows={baumZeilen} loading={loading}
getRowKey={(g) => g._pfad}
tree={{ column: "name", idOf: (g) => g._pfad, parentIdOf: (g) => g._elternPfad }}
empty="Noch keine Gruppen." />
</div>
<div>
{selected && (
<section className="card">
<div className="card-head">
<Icon name="search" />
<h2>EAN-Codes der Gruppe: {selected.name}</h2>
</div>
<BarcodeList
barcodes={selected.barcodes || []}
productBarcodes={selected.product_barcodes || []}
disabled={!isAdmin}
hint={"Codes der zugeordneten Artikel stehen automatisch hier, mit Marke und " +
"Artikelnamen; entfernen lassen sie sich nur, indem der Artikel die Gruppe " +
"wechselt. Zusätzlich sind Codes möglich, die noch zu keinem Artikel " +
"gehören für die gibt es eine Notiz, weil ihnen der Artikelname fehlt. " +
"Wird so ein Code beim Einlagern gescannt, landet der neu angelegte Artikel " +
"automatisch hier."}
onAdd={async (body) => {
try {
await api.addGroupBarcode(selected.id, body);
await load();
} catch (err) { setError(err.message); }
}}
onEditNote={async (code, note) => {
try {
await api.updateGroupBarcodeNote(selected.id, code, { note });
await load();
} catch (err) { setError(err.message); }
}}
onDelete={async (code) => {
try {
await api.deleteGroupBarcode(selected.id, code);
await load();
} catch (err) { setError(err.message); }
}}
/>
<button className="btn ghost" onClick={() => setSelectedId(null)}>Schließen</button>
</section>
)}
{/* Karten zur AUSGEWÄHLTEN Gruppe erscheinen erst beim „verwalten". */}
{selected && (
<div className="grid-2" style={{ marginTop: "var(--sp-3)" }}>
<section className="card">
<div className="card-head">
<Icon name="search" />
<h2>EAN-Codes der Gruppe: {selected.name}</h2>
</div>
<BarcodeList
barcodes={selected.barcodes || []}
productBarcodes={selected.product_barcodes || []}
disabled={!isAdmin}
hint={"Codes der zugeordneten Artikel stehen automatisch hier, mit Marke und " +
"Artikelnamen; entfernen lassen sie sich nur, indem der Artikel die Gruppe " +
"wechselt. Zusätzlich sind Codes möglich, die noch zu keinem Artikel " +
"gehören für die gibt es eine Notiz, weil ihnen der Artikelname fehlt. " +
"Wird so ein Code beim Einlagern gescannt, landet der neu angelegte Artikel " +
"automatisch hier."}
onAdd={async (body) => {
try {
await api.addGroupBarcode(selected.id, body);
await load();
} catch (err) { setError(err.message); }
}}
onEditNote={async (code, note) => {
try {
await api.updateGroupBarcodeNote(selected.id, code, { note });
await load();
} catch (err) { setError(err.message); }
}}
onDelete={async (code) => {
try {
await api.deleteGroupBarcode(selected.id, code);
await load();
} catch (err) { setError(err.message); }
}}
/>
<button className="btn ghost" onClick={() => setSelectedId(null)}>Schließen</button>
</section>
{selected && isAdmin && (
{isAdmin && (
<section className="card">
<div className="card-head">
<Icon name="location" />
@@ -318,51 +320,52 @@ export default function Groups() {
/>
</section>
)}
{isAdmin && (
<form className="card" onSubmit={add}>
<div className="card-head"><Icon name="tag" /><h2>Neue Gruppe</h2></div>
<label>
Name
<input placeholder="z.B. Mehl" value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} required />
</label>
<div className="row">
<label className="grow">
Mindestbestand (optional)
<input type="number" step="any" value={form.min_stock}
onChange={(e) => setForm({ ...form, min_stock: e.target.value })} placeholder="z.B. 2" />
</label>
<label className="grow">
Einheit
<select value={form.min_stock_unit_id}
onChange={(e) => setForm({ ...form, min_stock_unit_id: e.target.value })}>
<option value=""> Basiseinheit </option>
{units.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
</select>
</label>
</div>
<label>
Obergruppen (optional)
<GroupParentSelect
groups={groups}
selfId={null}
value={form.parent_ids}
onChange={(ids) => setForm({ ...form, parent_ids: ids })}
/>
</label>
<button className="btn primary"><Icon name="plus" size={16} />Anlegen</button>
<p className="muted small">
Bestand und Mindestbestand einer Gruppe zählen auch die Artikel ihrer
Untergruppen. Eine Gruppe darf unter mehreren Obergruppen hängen
Grillwurst etwa unter Wurst <em>und</em> unter Grillgut.
Gezählt werden nur Artikel, die zur gewählten Einheit passen, auch in
den Untergruppen. Namen lassen sich in der Tabelle direkt ändern.
</p>
</form>
)}
</div>
</div>
)}
{isAdmin && (
<form className="card" onSubmit={add}
style={{ marginTop: "var(--sp-3)", maxWidth: 560 }}>
<div className="card-head"><Icon name="tag" /><h2>Neue Gruppe</h2></div>
<label>
Name
<input placeholder="z.B. Mehl" value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} required />
</label>
<div className="row">
<label className="grow">
Mindestbestand (optional)
<input type="number" step="any" value={form.min_stock}
onChange={(e) => setForm({ ...form, min_stock: e.target.value })} placeholder="z.B. 2" />
</label>
<label className="grow">
Einheit
<select value={form.min_stock_unit_id}
onChange={(e) => setForm({ ...form, min_stock_unit_id: e.target.value })}>
<option value=""> Basiseinheit </option>
{units.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
</select>
</label>
</div>
<label>
Obergruppen (optional)
<GroupParentSelect
groups={groups}
selfId={null}
value={form.parent_ids}
onChange={(ids) => setForm({ ...form, parent_ids: ids })}
/>
</label>
<button className="btn primary"><Icon name="plus" size={16} />Anlegen</button>
<p className="muted small">
Bestand und Mindestbestand einer Gruppe zählen auch die Artikel ihrer
Untergruppen. Eine Gruppe darf unter mehreren Obergruppen hängen
Grillwurst etwa unter Wurst <em>und</em> unter Grillgut.
Gezählt werden nur Artikel, die zur gewählten Einheit passen, auch in
den Untergruppen. Namen lassen sich in der Tabelle direkt ändern.
</p>
</form>
)}
</div>
);
}