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;