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;