Tabellen entzerrt, EAN-Codes mit Marke und Artikelname, Codes-Block nach unten
EAN-Codes:
- Das Kennzeichen "Artikel" ist weg; stattdessen stehen Marke und Artikelname
zusammen ("Zweifel - Moutarde Original Chips"). Die Marke allein reichte nicht,
um einen Code wiederzuerkennen. Hinweistext entsprechend angepasst.
Produktseite:
- "Weitere EAN-Codes" steht jetzt unterhalb von Formular und Chargen statt als
dritte Spalte dazwischen. Die Codes braucht man selten und sie schoben die
beiden wichtigen Bloecke auseinander.
Tabellen:
- "overflow-wrap: anywhere" galt fuer ALLE Zellen und zerriss Woerter mitten
drin ("PROD UKTE", "555 Gram m"). Jetzt nur noch gezielt per Klasse.
- Kopfzeilen und Zahl-plus-Einheit brechen nicht mehr um.
- Eingaben in Tabellen sind kompakter (kleinere Polsterung, Zahlenfelder 8ch),
Auswahlfelder richten sich nach ihrem Text statt 132px Mindestbreite -
dadurch sind die Zeilen deutlich schlanker.
- Kennzeichen wie "niedrig" oder "abgelaufen" sitzen ueber .cell-row in einer
Zeile mit dem Wert und rutschen nicht mehr je nach Zeilenhoehe herum.
Geprueft: "npm run build" laeuft durch. Backend unveraendert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,12 @@ import Icon from "./Icon";
|
|||||||
*
|
*
|
||||||
* onAdd({ code, note }) und onDelete(code) werden vom Aufrufer bereitgestellt.
|
* onAdd({ code, note }) und onDelete(code) werden vom Aufrufer bereitgestellt.
|
||||||
*/
|
*/
|
||||||
/** Innerhalb einer Gruppe unterscheidet die Marke besser als der Name –
|
/** Marke und Artikelname zusammen – die Marke allein reichte nicht, um einen
|
||||||
* in der Gruppe "Mehl" heisst jeder Artikel irgendwie "…mehl". Fehlt die
|
* Code wiederzuerkennen ("Zweifel" gibt es mehrfach), der Name allein auch
|
||||||
* Marke, bleibt der Name als Rueckfall. */
|
* nicht (in "Mehl" heisst alles irgendwie Mehl). Fehlt eines von beiden,
|
||||||
|
* bleibt das andere stehen. */
|
||||||
function besitzer(b) {
|
function besitzer(b) {
|
||||||
return b.product_brand || b.product_name || "";
|
return [b.product_brand, b.product_name].filter(Boolean).join(" · ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BarcodeList({
|
export default function BarcodeList({
|
||||||
@@ -45,8 +46,7 @@ export default function BarcodeList({
|
|||||||
<li key={`p-${b.code}`}>
|
<li key={`p-${b.code}`}>
|
||||||
<span className="code-row">
|
<span className="code-row">
|
||||||
<code className="strong">{b.code}</code>
|
<code className="strong">{b.code}</code>
|
||||||
<span className="badge">Artikel</span>
|
<span className="muted small owner" title={besitzer(b)}>{besitzer(b)}</span>
|
||||||
<span className="muted small owner" title={b.product_name}>{besitzer(b)}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -57,10 +57,7 @@ export default function BarcodeList({
|
|||||||
{b.product_name ? (
|
{b.product_name ? (
|
||||||
/* Gehört der Code zu einem Artikel, sagt dessen Marke bereits,
|
/* Gehört der Code zu einem Artikel, sagt dessen Marke bereits,
|
||||||
worum es geht – eine Notiz waere hier nur Dopplung. */
|
worum es geht – eine Notiz waere hier nur Dopplung. */
|
||||||
<>
|
<span className="muted small owner" title={besitzer(b)}>{besitzer(b)}</span>
|
||||||
<span className="badge">Artikel</span>
|
|
||||||
<span className="muted small owner" title={b.product_name}>{besitzer(b)}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
/* Codes ohne Artikel haben keine Marke. Hier ist die Notiz die
|
/* Codes ohne Artikel haben keine Marke. Hier ist die Notiz die
|
||||||
einzige Angabe, woran man den Code wiedererkennt. */
|
einzige Angabe, woran man den Code wiedererkennt. */
|
||||||
|
|||||||
@@ -105,14 +105,16 @@ export default function Groups() {
|
|||||||
return (
|
return (
|
||||||
<tr key={g.id}>
|
<tr key={g.id}>
|
||||||
<td data-label="Gruppe">
|
<td data-label="Gruppe">
|
||||||
|
<span className="cell-row">
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
<input defaultValue={g.name} style={{ marginTop: 0, minWidth: 130 }}
|
<input defaultValue={g.name} style={{ marginTop: 0, minWidth: 120 }}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const v = e.target.value.trim();
|
const v = e.target.value.trim();
|
||||||
if (v && v !== g.name) patch(g, { name: v });
|
if (v && v !== g.name) patch(g, { name: v });
|
||||||
}} />
|
}} />
|
||||||
) : <span className="strong">{g.name}</span>}
|
) : <span className="strong">{g.name}</span>}
|
||||||
{low && <span className="badge warn" style={{ marginLeft: 6 }}>niedrig</span>}
|
{low && <span className="badge warn">niedrig</span>}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Produkte" className="num muted">{g.product_count}</td>
|
<td data-label="Produkte" className="num muted">{g.product_count}</td>
|
||||||
<td data-label="Bestand" className="num">{fmt(g.stock)} {g.min_stock_unit_name || ""}</td>
|
<td data-label="Bestand" className="num">{fmt(g.stock)} {g.min_stock_unit_name || ""}</td>
|
||||||
@@ -173,10 +175,10 @@ export default function Groups() {
|
|||||||
barcodes={selected.barcodes || []}
|
barcodes={selected.barcodes || []}
|
||||||
productBarcodes={selected.product_barcodes || []}
|
productBarcodes={selected.product_barcodes || []}
|
||||||
disabled={!isAdmin}
|
disabled={!isAdmin}
|
||||||
hint={"Codes der zugeordneten Artikel stehen automatisch hier, gekennzeichnet mit " +
|
hint={"Codes der zugeordneten Artikel stehen automatisch hier, mit Marke und " +
|
||||||
"„Artikel“ und der Marke; entfernen lassen sie sich nur, indem der Artikel " +
|
"Artikelnamen; entfernen lassen sie sich nur, indem der Artikel die Gruppe " +
|
||||||
"die Gruppe wechselt. Zusätzlich sind Codes möglich, die noch zu keinem " +
|
"wechselt. Zusätzlich sind Codes möglich, die noch zu keinem Artikel " +
|
||||||
"Artikel gehören – für die gibt es eine Notiz, weil ihnen die Marke fehlt. " +
|
"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 " +
|
"Wird so ein Code beim Einlagern gescannt, landet der neu angelegte Artikel " +
|
||||||
"automatisch hier."}
|
"automatisch hier."}
|
||||||
onAdd={async (body) => {
|
onAdd={async (body) => {
|
||||||
|
|||||||
@@ -387,6 +387,25 @@ export default function ProductForm() {
|
|||||||
{readOnly && <p className="muted small mt-0">Nur Administratoren können Produkte bearbeiten.</p>}
|
{readOnly && <p className="muted small mt-0">Nur Administratoren können Produkte bearbeiten.</p>}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{!isNew && (
|
||||||
|
<LotsCard
|
||||||
|
product={product}
|
||||||
|
lots={lots}
|
||||||
|
baseShort={baseShort}
|
||||||
|
warnDays={warnDays}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
imageUrl={form.image_url}
|
||||||
|
onChanged={async () => {
|
||||||
|
setLots(await api.listLots(id));
|
||||||
|
setProduct(await api.getProduct(id));
|
||||||
|
}}
|
||||||
|
onError={setError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Unterhalb der beiden Spalten: die Codes braucht man selten, sie sollen
|
||||||
|
Formular und Chargen nicht auseinanderschieben. */}
|
||||||
{!isNew && product && (
|
{!isNew && product && (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card-head"><Icon name="search" /><h2>Weitere EAN-Codes</h2></div>
|
<div className="card-head"><Icon name="search" /><h2>Weitere EAN-Codes</h2></div>
|
||||||
@@ -408,23 +427,6 @@ export default function ProductForm() {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isNew && (
|
|
||||||
<LotsCard
|
|
||||||
product={product}
|
|
||||||
lots={lots}
|
|
||||||
baseShort={baseShort}
|
|
||||||
warnDays={warnDays}
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
imageUrl={form.image_url}
|
|
||||||
onChanged={async () => {
|
|
||||||
setLots(await api.listLots(id));
|
|
||||||
setProduct(await api.getProduct(id));
|
|
||||||
}}
|
|
||||||
onError={setError}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,12 +100,14 @@ export default function Products() {
|
|||||||
: <span className="thumb thumb-fallback"><Icon name="box" size={16} /></span>}
|
: <span className="thumb thumb-fallback"><Icon name="box" size={16} /></span>}
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Name" className="strong">
|
<td data-label="Name" className="strong">
|
||||||
{p.name}
|
<span className="cell-row">
|
||||||
|
<span>{p.name}</span>
|
||||||
{p.expired_count > 0 && (
|
{p.expired_count > 0 && (
|
||||||
<span className="badge danger" style={{ marginLeft: 6 }}>
|
<span className="badge danger nowrap">
|
||||||
<Icon name="alert" size={12} />{p.expired_count} abgelaufen
|
<Icon name="alert" size={12} />{p.expired_count} abgelaufen
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Marke" className="muted">{p.brand || "–"}</td>
|
<td data-label="Marke" className="muted">{p.brand || "–"}</td>
|
||||||
<td data-label="Kategorie" className="muted">{p.category_name || "–"}</td>
|
<td data-label="Kategorie" className="muted">{p.category_name || "–"}</td>
|
||||||
@@ -119,10 +121,14 @@ export default function Products() {
|
|||||||
{fmt(p.stock / (p.unit_factor || 1))} {p.unit_name || unitShort(p.base_unit)}
|
{fmt(p.stock / (p.unit_factor || 1))} {p.unit_name || unitShort(p.base_unit)}
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Einheiten" className="num">
|
<td data-label="Einheiten" className="num">
|
||||||
|
<span className="cell-row" style={{ justifyContent: "flex-end" }}>
|
||||||
|
<span>
|
||||||
{fmt(p.stock / unitCount(p))}
|
{fmt(p.stock / unitCount(p))}
|
||||||
{p.min_stock != null ? ` / ${fmt(p.min_stock / unitCount(p))}` : ""}{" "}
|
{p.min_stock != null ? ` / ${fmt(p.min_stock / unitCount(p))}` : ""}{" "}
|
||||||
{countLabel(p)}
|
{countLabel(p)}
|
||||||
{low && <span className="badge warn" style={{ marginLeft: 6 }}>niedrig</span>}
|
</span>
|
||||||
|
{low && <span className="badge warn">niedrig</span>}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="num"><Link to={`/products/${p.id}`}>Details</Link></td>
|
<td className="num"><Link to={`/products/${p.id}`}>Details</Link></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -203,7 +203,9 @@ td .field-inline { margin-bottom: 0; flex-wrap: wrap; }
|
|||||||
.field-inline > input { flex: 1 1 auto; min-width: 78px; }
|
.field-inline > input { flex: 1 1 auto; min-width: 78px; }
|
||||||
.field-inline > select { flex: 0 0 auto; width: auto; min-width: 132px; }
|
.field-inline > select { flex: 0 0 auto; width: auto; min-width: 132px; }
|
||||||
/* Auswahlfelder in Tabellen richten sich nach ihrem Text statt nach der Zelle. */
|
/* Auswahlfelder in Tabellen richten sich nach ihrem Text statt nach der Zelle. */
|
||||||
td select { width: auto; min-width: 132px; max-width: 100%; }
|
/* Auswahlfelder richten sich nach ihrem Text. Die fruehere Mindestbreite von
|
||||||
|
132px machte schmale Spalten (z.B. "Einheit") unnoetig breit. */
|
||||||
|
td select { width: auto; min-width: 0; max-width: 100%; }
|
||||||
/* Button-Paare (z.B. Speichern/Abbrechen) gleich breit halten. */
|
/* Button-Paare (z.B. Speichern/Abbrechen) gleich breit halten. */
|
||||||
.btn-pair { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }
|
.btn-pair { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }
|
||||||
.btn-pair .btn { flex: 1 1 auto; min-width: 104px; justify-content: center; }
|
.btn-pair .btn { flex: 1 1 auto; min-width: 104px; justify-content: center; }
|
||||||
@@ -242,14 +244,28 @@ td select { width: auto; min-width: 132px; max-width: 100%; }
|
|||||||
.table-wrap { max-width: 100%; container-type: inline-size; }
|
.table-wrap { max-width: 100%; container-type: inline-size; }
|
||||||
.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
||||||
.table th, .table td {
|
.table th, .table td {
|
||||||
text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border);
|
text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border);
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
}
|
||||||
|
/* Frueher stand hier "overflow-wrap: anywhere" fuer ALLE Zellen. Das zerriss
|
||||||
|
Woerter mitten drin ("PROD UKTE", "555 Gram m"), sobald eine Spalte knapp
|
||||||
|
wurde. Umbrechen nur noch dort, wo wirklich lange Zeichenketten stehen. */
|
||||||
|
.break-anywhere { overflow-wrap: anywhere; }
|
||||||
|
.nowrap { white-space: nowrap; }
|
||||||
.table thead th {
|
.table thead th {
|
||||||
color: var(--muted); font-weight: 600; font-size: 0.72rem;
|
color: var(--muted); font-weight: 600; font-size: 0.72rem;
|
||||||
text-transform: uppercase; letter-spacing: 0.04em;
|
text-transform: uppercase; letter-spacing: 0.04em;
|
||||||
border-bottom: 1px solid var(--border-strong);
|
border-bottom: 1px solid var(--border-strong);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
/* Werte aus Zahl + Einheit gehoeren zusammen und duerfen nicht umbrechen. */
|
||||||
|
.table td.num { white-space: nowrap; }
|
||||||
|
/* Eingaben in Tabellen kompakt halten - mit voller Feldhoehe blaeht sich jede
|
||||||
|
Zeile auf und die Spalten werden unnoetig breit. */
|
||||||
|
.table td input, .table td select { padding: 5px 8px; font-size: 0.85rem; }
|
||||||
|
.table td input[type="number"] { width: 8ch; min-width: 0; }
|
||||||
|
/* Zelle mit Eingabe plus Kennzeichen (z.B. "niedrig"): eine Zeile, damit das
|
||||||
|
Kennzeichen immer an derselben Stelle sitzt statt mal darunter zu rutschen. */
|
||||||
|
.cell-row { display: flex; align-items: center; gap: 6px; }
|
||||||
.table tbody tr:last-child td { border-bottom: none; }
|
.table tbody tr:last-child td { border-bottom: none; }
|
||||||
.table tbody tr:hover { background: var(--surface-2); }
|
.table tbody tr:hover { background: var(--surface-2); }
|
||||||
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||||
|
|||||||
Reference in New Issue
Block a user