import { useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import { api } from "../api"; import { useAuth } from "../auth"; import Icon from "../components/Icon"; import DataTable from "../components/DataTable"; import { ProduktThumb } from "../components/ProduktBild"; import { locationOptions, locationPathById } from "../locationPath"; import { categoryInfoMap } from "../categoryPath"; import CategoryPathLabel, { pathParts } from "../components/CategoryPathLabel"; /** * Liste aller Einzelstücke (physische Exemplare, jedes mit eigener UID/Lagerort) – * anders als „Produkte", das nur die Katalog-Typen zeigt. Über die DataTable je * Spalte filterbar, u.a. nach Lagerort. Lagerort und Shop sind inline änderbar. */ export default function ItemList() { const { isAdmin } = useAuth(); const [items, setItems] = useState([]); const [locations, setLocations] = useState([]); const [shops, setShops] = useState([]); const [categories, setCategories] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); async function load() { setLoading(true); try { const [is, ls, ss, cs] = await Promise.all([ api.listAllItems(), api.listLocations(), api.listShops(), api.listCategories(), ]); setItems(is); setLocations(ls); setShops(ss); setCategories(cs); } catch (err) { setError(err.message); } finally { setLoading(false); } } useEffect(() => { load(); }, []); // Kategorie als Pfad + Tokens (jede Ebene), damit "Klamotten" auch die // Unterkategorien findet und die Oberkategorie im Filter wählbar ist. const catInfo = useMemo(() => categoryInfoMap(categories), [categories]); async function patch(it, body) { try { await api.updateItem(it.id, body); await load(); } catch (err) { setError(err.message); } } const preis = (it) => (it.price_cents != null ? `${(it.price_cents / 100).toFixed(2)} ${it.currency || ""}`.trim() : ""); const columns = [ { key: "thumb", header: "", label: "Bild", fixed: true, width: 56, render: (it) => }, { key: "uid", header: "UID", width: 110, filterText: (it) => it.uid, sortValue: (it) => it.uid, render: (it) => {it.uid} }, { key: "product", header: "Produkt", grow: true, min: 180, filterText: (it) => it.product_name || "", sortValue: (it) => it.product_name || "", render: (it) => it.product_name || "–" }, { key: "brand", header: "Marke", width: 140, filterText: (it) => it.product_brand || "", render: (it) => {it.product_brand || "–"} }, { key: "category", header: "Kategorie", width: 200, filterText: (it) => catInfo.get(it.category_id)?.path || it.category_name || "", filterValues: (it) => catInfo.get(it.category_id)?.tokens || (it.category_name ? [it.category_name] : [""]), filterOptionLabel: (v) => (v === "" ? "(Leere)" : ), sortValue: (it) => catInfo.get(it.category_id)?.path || it.category_name || "", render: (it) => }, { key: "location", header: "Lagerort", width: 220, filterText: (it) => locationPathById(it.location_id, locations), render: (it) => (isAdmin ? ( ) : (locationPathById(it.location_id, locations) || "–")) }, { key: "shop", header: "Gekauft bei", width: 150, filterText: (it) => it.shop_name || "", render: (it) => (isAdmin ? ( ) : (it.shop_name || "–")) }, { key: "acquired", header: "Gekauft am", width: 120, filterText: (it) => it.acquired_on || "", sortValue: (it) => it.acquired_on || "", render: (it) => it.acquired_on || "–" }, { key: "warranty", header: "Garantie bis", width: 120, filterText: (it) => it.warranty_until || "", sortValue: (it) => it.warranty_until || "", render: (it) => it.warranty_until || "–" }, { key: "price", header: "Preis", width: 110, align: "num", sortValue: (it) => it.price_cents ?? -1, filterText: preis, render: (it) => preis(it) || "–" }, { key: "note", header: "Notiz", width: 160, filterText: (it) => it.note || "", render: (it) => {it.note || "–"} }, { key: "open", header: "", label: "Produkt öffnen", fixed: true, width: 84, align: "num", render: (it) => Produkt }, ]; return (

Einzelstücke

{items.length} Exemplare · nach Lagerort, Produkt, Kategorie u.a. filterbar
{error &&
{error}
}
it.id} empty="Noch keine Einzelstücke." />
); }