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." />
);
}