From 8f549636a8df0a5898eb96b67b22f7122b0ede5a Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Wed, 22 Jul 2026 21:53:29 +0200 Subject: [PATCH] Marke statt Produktname am EAN-Code, Layout bricht um statt abzuschneiden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Am Gruppen-Code stand der Produktname. Innerhalb einer Gruppe hilft der aber nicht weiter: In der Gruppe "Zucker" heisst jeder Artikel irgendwie "Zucker", in "Mehl" irgendwie "…mehl". Unterscheiden laesst sich das nur ueber die Marke. Der Endpunkt liefert jetzt beides; angezeigt wird die Marke, faellt bei fehlender Marke auf den Namen zurueck und zeigt den vollen Namen im Tooltip. Nebenbei ist die Marke meist deutlich kuerzer als der Name. Abgeschnittene Tabelle: Die Spalte "verwalten" wurde gekappt. Ursache war nicht die Breite an sich, sondern dass Grid-Kinder standardmaessig nicht unter ihre Inhaltsbreite schrumpfen (min-width: auto). Dadurch lief die Tabelle aus ihrer Karte heraus und wurde beschnitten, statt dass das vorhandene overflow-x gegriffen haette. Mit min-width: 0 an den Grid-Kindern scrollt sie jetzt. Zusaetzlich hat die Tabelle eine Mindestbreite von 640px - lieber waagerecht scrollen als Spalten so weit quetschen, bis Text verschwindet. Umbruch statt Quetschen: Die feste Umbruchbreite von 820px passte nicht zur tatsaechlichen Lage - entscheidend ist, wie viel Platz die Spalten wirklich brauchen, nicht wie breit der Bildschirm ist. Jetzt auto-fit mit Mindestbreite je Spalte: Zwei Spalten, solange beide genug Platz haben, sonst automatisch untereinander. Seiten mit inhaltsreichem Seitenblock (EAN-Codes) verlangen mehr und stapeln frueher. Durchgerechnet: Bei 1920 voller Breite zwei Spalten zu je 668px, Tabelle passt. Bei halber Breite (960) eine Spalte zu 676px, Tabelle passt weiterhin. Erst bei sehr schmalen Fenstern (unter etwa 900px Gesamtbreite) scrollt die Tabelle waagerecht. In keinem Fall wird noch etwas abgeschnitten. Getestet: 58 pytest-Tests gruen, Web-Build laeuft durch. Gegen die laufende API geprueft, dass Marke und Name am Code mitkommen und die fehlende Marke sauber auf den Namen zurueckfaellt. Die Breiten sind rechnerisch geprueft, die Darstellung im Browser habe ich nicht selbst angesehen. Co-Authored-By: Claude Opus 4.8 --- backend/app/routers/groups.py | 17 +++++++++++------ backend/app/schemas.py | 4 ++++ web/src/components/BarcodeList.jsx | 11 +++++++++-- web/src/styles.css | 28 ++++++++++++++++++++++------ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/backend/app/routers/groups.py b/backend/app/routers/groups.py index a18fc85..a934f03 100644 --- a/backend/app/routers/groups.py +++ b/backend/app/routers/groups.py @@ -25,17 +25,20 @@ def _group_to_out(db: Session, group: Group) -> GroupOut: out.product_count = len(products) # Zu welchem Artikel gehoert ein Code? Der Gruppen-Code entsteht beim # Zuordnen automatisch; die Herkunft soll trotzdem sichtbar bleiben. - name_zu_code: dict[str, str] = {} + artikel_zu_code: dict[str, Product] = {} for product in products: if product.barcode: - name_zu_code[product.barcode] = product.name + artikel_zu_code[product.barcode] = product for alias in db.query(Barcode).filter(Barcode.product_id == product.id): - name_zu_code[alias.code] = product.name + artikel_zu_code[alias.code] = product out.barcodes = [] for b in db.query(Barcode).filter(Barcode.group_id == group.id).order_by(Barcode.id).all(): eintrag = BarcodeOut.model_validate(b) - eintrag.product_name = name_zu_code.get(b.code) + besitzer = artikel_zu_code.get(b.code) + if besitzer is not None: + eintrag.product_name = besitzer.name + eintrag.product_brand = besitzer.brand out.barcodes.append(eintrag) # Codes der Artikel in dieser Gruppe mitliefern. Sie waren bisher nirgends # sichtbar, wodurch eine Gruppe mit Artikeln "0 EANs" anzeigte. @@ -47,7 +50,8 @@ def _group_to_out(db: Session, group: Group) -> GroupOut: if product.barcode and product.barcode not in schon_da: product_codes.append( ProductBarcodeOut( - code=product.barcode, product_id=product.id, product_name=product.name + code=product.barcode, product_id=product.id, + product_name=product.name, product_brand=product.brand, ) ) for alias in db.query(Barcode).filter(Barcode.product_id == product.id).order_by(Barcode.id): @@ -55,7 +59,8 @@ def _group_to_out(db: Session, group: Group) -> GroupOut: continue product_codes.append( ProductBarcodeOut( - code=alias.code, product_id=product.id, product_name=product.name + code=alias.code, product_id=product.id, + product_name=product.name, product_brand=product.brand, ) ) out.product_barcodes = product_codes diff --git a/backend/app/schemas.py b/backend/app/schemas.py index bfc4924..8752c19 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -47,7 +47,10 @@ class BarcodeOut(BaseModel): code: str note: str | None = None # Nur bei Gruppen-Codes gefüllt: der Artikel, über den der Code hier steht. + # Die Marke unterscheidet innerhalb einer Gruppe besser als der Name – in + # der Gruppe "Mehl" heißen alle Artikel irgendwie "…mehl". product_name: str | None = None + product_brand: str | None = None class ProductBarcodeOut(BaseModel): @@ -60,6 +63,7 @@ class ProductBarcodeOut(BaseModel): code: str product_id: int product_name: str + product_brand: str | None = None # ---- Auth / Users ---- diff --git a/web/src/components/BarcodeList.jsx b/web/src/components/BarcodeList.jsx index 2294fed..b985837 100644 --- a/web/src/components/BarcodeList.jsx +++ b/web/src/components/BarcodeList.jsx @@ -11,6 +11,13 @@ import Icon from "./Icon"; * * onAdd({ code, note }) und onDelete(code) werden vom Aufrufer bereitgestellt. */ +/** Innerhalb einer Gruppe unterscheidet die Marke besser als der Name – + * in der Gruppe "Mehl" heisst jeder Artikel irgendwie "…mehl". Fehlt die + * Marke, bleibt der Name als Rueckfall. */ +function besitzer(b) { + return b.product_brand || b.product_name || ""; +} + export default function BarcodeList({ barcodes = [], productBarcodes = [], onAdd, onDelete, onEditNote, disabled = false, hint, }) { @@ -39,7 +46,7 @@ export default function BarcodeList({ {b.code} Artikel - {b.product_name} + {besitzer(b)} ))} @@ -52,7 +59,7 @@ export default function BarcodeList({ {b.product_name && ( <> Artikel - {b.product_name} + {besitzer(b)} )} {onEditNote && !disabled ? ( diff --git a/web/src/styles.css b/web/src/styles.css index 6e501a1..f7e44d5 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -149,11 +149,25 @@ h2 { font-size: 0.95rem; font-weight: 650; margin: 0 0 var(--sp-3); letter-spaci .card-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); } .card-head h2 { margin: 0; } .card-head .icon { color: var(--muted); } -.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); } +/* Zwei Spalten, solange beide genug Platz haben - sonst automatisch + untereinander. auto-fit statt fester Umbruchbreite, damit es auch passt, + wenn das Fenster nur die halbe Bildschirmbreite einnimmt. + min(100%, …) verhindert, dass die Spalte auf schmalen Geraeten breiter + wird als der Bildschirm. */ +.grid-2 { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); + gap: var(--sp-4); +} +/* Ohne min-width:0 schrumpfen Grid-Kinder nie unter ihre Inhaltsbreite. Genau + daran scheiterte das overflow-x der Tabelle: Sie wurde abgeschnitten, + statt zu scrollen. */ +.grid-2 > * { min-width: 0; } .form-narrow { max-width: 600px; } -.grid-2.wide-aside { grid-template-columns: minmax(0, 1fr) minmax(360px, 0.85fr); } -@media (max-width: 820px) { - .grid-2, .grid-2.wide-aside { grid-template-columns: 1fr; } +/* Seiten mit einem inhaltsreichen Block daneben (EAN-Codes) brauchen mehr + Mindestbreite und stapeln deshalb frueher. */ +.grid-2.wide-aside { + grid-template-columns: repeat(auto-fit, minmax(min(100%, 520px), 1fr)); } /* ---------- Forms ---------- */ @@ -221,8 +235,10 @@ td select { width: auto; min-width: 132px; max-width: 100%; } .link-btn:hover { text-decoration: underline; } /* ---------- Tables ---------- */ -.table-wrap { overflow-x: auto; } -.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; } +.table-wrap { overflow-x: auto; max-width: 100%; } +/* min-width: Lieber waagerecht scrollen als Spalten so weit quetschen, dass + Text abgeschnitten wird. */ +.table { width: 100%; min-width: 640px; border-collapse: collapse; font-size: 0.875rem; } .table th, .table td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border); } .table thead th { color: var(--muted); font-weight: 600; font-size: 0.72rem;