diff --git a/backend/app/routers/transfer.py b/backend/app/routers/transfer.py
index 9f246fd..38df096 100644
--- a/backend/app/routers/transfer.py
+++ b/backend/app/routers/transfer.py
@@ -339,6 +339,17 @@ def _get_or_create_group(db: Session, name: str | None) -> Group | None:
return group
+def _bildadresse_ergaenzen(product: Product, row: dict) -> None:
+ """Fehlende Bildadresse aus dem Backup nachtragen – vorhandene bleibt.
+
+ Nur ergaenzend: Ein Import soll Daten hinzufuegen, nicht stillschweigend
+ etwas ueberschreiben, das jemand von Hand gesetzt hat.
+ """
+ adresse = (row.get("image_url") or "").strip()
+ if adresse and not product.image_url:
+ product.image_url = adresse
+
+
def _get_or_create_product(db: Session, row: dict, created: list[str]) -> Product:
"""Sucht per Barcode, sonst per Name; legt das Produkt sonst an."""
barcode = (row.get("barcode") or "").strip() or None
@@ -346,10 +357,12 @@ def _get_or_create_product(db: Session, row: dict, created: list[str]) -> Produc
if barcode:
product = db.query(Product).filter(Product.barcode == barcode).first()
if product:
+ _bildadresse_ergaenzen(product, row)
return product
if name:
product = db.query(Product).filter(Product.name == name).first()
if product:
+ _bildadresse_ergaenzen(product, row)
return product
if not name:
raise ValueError("Produktname fehlt")
@@ -363,6 +376,10 @@ def _get_or_create_product(db: Session, row: dict, created: list[str]) -> Produc
barcode=barcode,
name=name,
brand=(row.get("marke") or "").strip() or None,
+ # Die Bildadresse stand zwar immer im JSON-Backup, wurde beim Einlesen
+ # aber verworfen - eingelesene Artikel blieben deshalb dauerhaft ohne
+ # Bild. Die lokale Kopie holt sich der Bildabruf beim ersten Ansehen.
+ image_url=(row.get("image_url") or "").strip() or None,
base_unit=BASE_OF_KIND[unit.kind],
display_unit_id=unit.id,
package_size=_num(row.get("packungsgroesse")),
diff --git a/web/src/components/ProduktBild.jsx b/web/src/components/ProduktBild.jsx
index 52c5e57..001b935 100644
--- a/web/src/components/ProduktBild.jsx
+++ b/web/src/components/ProduktBild.jsx
@@ -1,16 +1,14 @@
import { useEffect, useState } from "react";
import { authorizedObjectUrl } from "../api";
+import Icon from "./Icon";
/**
- * Artikelbild aus der eigenen Datenbank.
- *
- * Rendert nichts, solange kein Bild vorliegt – ein Platzhalter für einen
- * Artikel, der nie ein Bild bekommen wird, wäre nur Lärm.
+ * Lädt das Artikelbild aus der eigenen Datenbank.
*
* Die Objekt-URL wird beim Verlassen wieder freigegeben, sonst hält der Browser
* jedes angesehene Bild bis zum Neuladen der Seite im Speicher.
*/
-export default function ProduktBild({ productId, alt, className = "" }) {
+function useBildUrl(productId) {
const [url, setUrl] = useState(null);
useEffect(() => {
@@ -37,6 +35,31 @@ export default function ProduktBild({ productId, alt, className = "" }) {
};
}, [productId]);
+ return url;
+}
+
+/**
+ * Grosses Artikelbild für die Artikelseite.
+ *
+ * Rendert nichts, solange kein Bild vorliegt – ein leerer Rahmen neben einem
+ * Artikel, der nie ein Bild bekommen wird, wäre nur Lärm.
+ */
+export default function ProduktBild({ productId, alt, className = "" }) {
+ const url = useBildUrl(productId);
if (!url) return null;
return ;
}
+
+/**
+ * Kleines Vorschaubild für Listen und Auswahlen.
+ *
+ * Hier gibt es sehr wohl einen Platzhalter: In einer Tabellenspalte müssen alle
+ * Zeilen gleich hoch bleiben, sonst springt die Liste.
+ */
+export function ProduktThumb({ productId, alt }) {
+ const url = useBildUrl(productId);
+ if (!url) {
+ return
;
+}
diff --git a/web/src/pages/CheckIn.jsx b/web/src/pages/CheckIn.jsx
index 3cc6114..61ad06d 100644
--- a/web/src/pages/CheckIn.jsx
+++ b/web/src/pages/CheckIn.jsx
@@ -3,6 +3,7 @@ import { Link } from "react-router-dom";
import { api } from "../api";
import { useAuth } from "../auth";
import Icon from "../components/Icon";
+import { ProduktThumb } from "../components/ProduktBild";
import { useToast } from "../toast";
import { suggestionToProduct } from "../offUtils";
import { asTree } from "../categoryTree";
@@ -277,6 +278,9 @@ export default function CheckIn() {
{suggestion && (