Web: Produkte und Kategorien nach Typ filtern (Lebensmittel/Gegenstand)
Zum leichteren Unterscheiden gibt es in der Produkte-Liste und in der Kategorien-Verwaltung einen Umschalter Alle/Lebensmittel/Gegenstaende. Er filtert die jeweilige Liste und in der Produktsuche auch die Kategorieauswahl auf den Typ. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ export default function Categories() {
|
|||||||
const [collapsed, setCollapsed] = useState(() => new Set());
|
const [collapsed, setCollapsed] = useState(() => new Set());
|
||||||
// Kategorie, deren Felder gerade verwaltet werden.
|
// Kategorie, deren Felder gerade verwaltet werden.
|
||||||
const [feldKat, setFeldKat] = useState(null);
|
const [feldKat, setFeldKat] = useState(null);
|
||||||
|
// "" = alle, sonst "food"/"object": trennt Lebensmittel und Gegenstände.
|
||||||
|
const [typFilter, setTypFilter] = useState("");
|
||||||
|
|
||||||
function toggle(id) {
|
function toggle(id) {
|
||||||
setCollapsed((alt) => {
|
setCollapsed((alt) => {
|
||||||
@@ -101,7 +103,9 @@ export default function Categories() {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const sichtbar = tree.filter((c) => !versteckt(c.id));
|
const sichtbar = tree.filter(
|
||||||
|
(c) => !versteckt(c.id) && (!typFilter || c.tracking === typFilter),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -117,6 +121,12 @@ export default function Categories() {
|
|||||||
</div>
|
</div>
|
||||||
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
||||||
|
|
||||||
|
<div className="segmented" style={{ marginBottom: "var(--sp-3)" }}>
|
||||||
|
<button type="button" className={typFilter === "" ? "active" : ""} onClick={() => setTypFilter("")}>Alle</button>
|
||||||
|
<button type="button" className={typFilter === "food" ? "active" : ""} onClick={() => setTypFilter("food")}>Lebensmittel</button>
|
||||||
|
<button type="button" className={typFilter === "object" ? "active" : ""} onClick={() => setTypFilter("object")}>Gegenstände</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid-2">
|
<div className="grid-2">
|
||||||
<div className="card" style={{ padding: 0 }}>
|
<div className="card" style={{ padding: 0 }}>
|
||||||
<div className="table-wrap">
|
<div className="table-wrap">
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export default function Products() {
|
|||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
// "" = alle, "0" = ohne Kategorie, sonst die ID (Unterkategorien zaehlen mit).
|
// "" = alle, "0" = ohne Kategorie, sonst die ID (Unterkategorien zaehlen mit).
|
||||||
const [categoryId, setCategoryId] = useState("");
|
const [categoryId, setCategoryId] = useState("");
|
||||||
|
// "" = alle, sonst "food"/"object": trennt Lebensmittel und Gegenstände.
|
||||||
|
const [typ, setTyp] = useState("");
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
async function load(cat = categoryId) {
|
async function load(cat = categoryId) {
|
||||||
@@ -46,12 +48,23 @@ export default function Products() {
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Typ umschalten: passt der Kategoriefilter nicht mehr, zurücksetzen.
|
||||||
|
function changeTyp(neu) {
|
||||||
|
setTyp(neu);
|
||||||
|
if (neu && categoryId && categoryId !== "0") {
|
||||||
|
const cat = categories.find((c) => String(c.id) === String(categoryId));
|
||||||
|
if (cat && cat.tracking !== neu) { setCategoryId(""); load(""); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sichtbar = products.filter((p) => !typ || p.tracking === typ);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-head">
|
<div className="page-head">
|
||||||
<div>
|
<div>
|
||||||
<h1>Produkte</h1>
|
<h1>Produkte</h1>
|
||||||
<div className="sub">{products.length} Produkte im Katalog</div>
|
<div className="sub">{sichtbar.length} Produkte{typ ? (typ === "food" ? " · Lebensmittel" : " · Gegenstände") : " im Katalog"}</div>
|
||||||
</div>
|
</div>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Link className="btn primary" to="/products/new"><Icon name="plus" size={16} />Neues Produkt</Link>
|
<Link className="btn primary" to="/products/new"><Icon name="plus" size={16} />Neues Produkt</Link>
|
||||||
@@ -60,6 +73,12 @@ export default function Products() {
|
|||||||
|
|
||||||
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
||||||
|
|
||||||
|
<div className="segmented" style={{ marginBottom: "var(--sp-3)" }}>
|
||||||
|
<button type="button" className={typ === "" ? "active" : ""} onClick={() => changeTyp("")}>Alle</button>
|
||||||
|
<button type="button" className={typ === "food" ? "active" : ""} onClick={() => changeTyp("food")}>Lebensmittel</button>
|
||||||
|
<button type="button" className={typ === "object" ? "active" : ""} onClick={() => changeTyp("object")}>Gegenstände</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form className="field-inline" style={{ marginBottom: "var(--sp-4)", maxWidth: 640 }} onSubmit={onSearch}>
|
<form className="field-inline" style={{ marginBottom: "var(--sp-4)", maxWidth: 640 }} onSubmit={onSearch}>
|
||||||
<label className="grow" style={{ margin: 0 }}>
|
<label className="grow" style={{ margin: 0 }}>
|
||||||
<input placeholder="Produkt suchen…" value={q} onChange={(e) => setQ(e.target.value)} />
|
<input placeholder="Produkt suchen…" value={q} onChange={(e) => setQ(e.target.value)} />
|
||||||
@@ -68,7 +87,7 @@ export default function Products() {
|
|||||||
<select value={categoryId} onChange={(e) => { setCategoryId(e.target.value); load(e.target.value); }}>
|
<select value={categoryId} onChange={(e) => { setCategoryId(e.target.value); load(e.target.value); }}>
|
||||||
<option value="">Alle Kategorien</option>
|
<option value="">Alle Kategorien</option>
|
||||||
<option value="0">Ohne Kategorie</option>
|
<option value="0">Ohne Kategorie</option>
|
||||||
{asTree(categories).map((c) => (
|
{asTree(categories).filter((c) => !typ || c.tracking === typ).map((c) => (
|
||||||
<option key={c.id} value={c.id}>{"— ".repeat(c.depth)}{c.name}</option>
|
<option key={c.id} value={c.id}>{"— ".repeat(c.depth)}{c.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -92,7 +111,7 @@ export default function Products() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{products.map((p) => {
|
{sichtbar.map((p) => {
|
||||||
const low = p.min_stock != null && p.stock < p.min_stock;
|
const low = p.min_stock != null && p.stock < p.min_stock;
|
||||||
return (
|
return (
|
||||||
<tr key={p.id}>
|
<tr key={p.id}>
|
||||||
@@ -134,7 +153,7 @@ export default function Products() {
|
|||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{products.length === 0 && (
|
{sichtbar.length === 0 && (
|
||||||
<tr><td colSpan={8} className="empty">Keine Produkte gefunden.</td></tr>
|
<tr><td colSpan={8} className="empty">Keine Produkte gefunden.</td></tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user