Kategorien: - Oberkategorien lassen sich per Pfeil zuklappen; ihre Unterkategorien verschwinden dann aus der Liste. Ausgeblendet wird ueber die gesamte Vorfahrenkette, nicht nur eine Ebene, damit auch mehrstufige Baeume stimmen. - Ist eine Kategorie zugeklappt, zeigt ein Badge, wie viele Eintraege darunter liegen - sonst waere nicht erkennbar, dass dort noch etwas ist. - Zeilen ohne Unterkategorien bekommen einen Platzhalter, damit alle Namen buendig stehen. Badges in Zellen: - In der Karten-Ansicht verteilte das Raster mehrere Elemente einer Zelle auf zwei Rasterzellen - deshalb rutschte "eingebaut" unter die Beschriftung. Jetzt Flexfluss, alle Teile stehen hinter der Beschriftung. - Einheiten-Tabelle: Name und "eingebaut" liegen in einer Zeile zusammen. Geprueft: "npm run build" laeuft durch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.2 KiB
JavaScript
121 lines
4.2 KiB
JavaScript
import { useEffect, useState } from "react";
|
|
import { api } from "../api";
|
|
import { useConfirm } from "../confirm";
|
|
import Icon from "../components/Icon";
|
|
import { fmt } from "../units";
|
|
|
|
const KIND_LABEL = { count: "Anzahl (Stück)", weight: "Gewicht (Basis: Gramm)", volume: "Volumen (Basis: Milliliter)" };
|
|
|
|
export default function Units() {
|
|
const confirm = useConfirm();
|
|
const [units, setUnits] = useState([]);
|
|
const [form, setForm] = useState({ name: "", kind: "weight", factor: "" });
|
|
const [error, setError] = useState(null);
|
|
|
|
async function load() {
|
|
try {
|
|
setUnits(await api.listUnits());
|
|
} catch (err) {
|
|
setError(err.message);
|
|
}
|
|
}
|
|
|
|
useEffect(() => { load(); }, []);
|
|
|
|
async function add(e) {
|
|
e.preventDefault();
|
|
setError(null);
|
|
try {
|
|
await api.createUnit({ name: form.name, kind: form.kind, factor: Number(form.factor) });
|
|
setForm({ name: "", kind: form.kind, factor: "" });
|
|
load();
|
|
} catch (err) {
|
|
setError(err.message);
|
|
}
|
|
}
|
|
|
|
async function remove(u) {
|
|
const ok = await confirm({
|
|
title: `Einheit „${u.name}“ löschen?`,
|
|
message: "Produkte, die diese Einheit nutzen, fallen auf ihre Basiseinheit zurück.",
|
|
confirmLabel: "Löschen",
|
|
danger: true,
|
|
});
|
|
if (!ok) return;
|
|
try {
|
|
await api.deleteUnit(u.id);
|
|
load();
|
|
} catch (err) {
|
|
setError(err.message);
|
|
}
|
|
}
|
|
|
|
const baseUnitOfKind = { count: "Stück", weight: "Gramm", volume: "Milliliter" };
|
|
|
|
return (
|
|
<div>
|
|
<div className="page-head">
|
|
<div>
|
|
<h1>Einheiten</h1>
|
|
<div className="sub">Faktor = wie viele Basiseinheiten 1 dieser Einheit entsprechen</div>
|
|
</div>
|
|
</div>
|
|
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
|
|
|
<div className="grid-2">
|
|
<div className="card" style={{ padding: 0 }}>
|
|
<div className="table-wrap">
|
|
<table className="table">
|
|
<thead><tr><th>Name</th><th>Art</th><th className="num">Faktor</th><th></th></tr></thead>
|
|
<tbody>
|
|
{units.map((u) => (
|
|
<tr key={u.id}>
|
|
<td data-label="Name" className="strong">
|
|
<span className="cell-row">
|
|
<span>{u.name}</span>
|
|
{u.is_builtin && <span className="badge nowrap">eingebaut</span>}
|
|
</span>
|
|
</td>
|
|
<td data-label="Art" className="muted">{KIND_LABEL[u.kind] || u.kind}</td>
|
|
<td data-label="Faktor" className="num">{fmt(u.factor)} {baseUnitOfKind[u.kind]}</td>
|
|
<td className="num">
|
|
{!u.is_builtin && (
|
|
<button className="btn-icon danger" onClick={() => remove(u)} title="Löschen">
|
|
<Icon name="trash" size={16} />
|
|
</button>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<form className="card" onSubmit={add}>
|
|
<div className="card-head"><Icon name="settings" /><h2>Neue Einheit</h2></div>
|
|
<label>
|
|
Name
|
|
<input placeholder="z.B. Pfund" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required />
|
|
</label>
|
|
<label>
|
|
Art
|
|
<select value={form.kind} onChange={(e) => setForm({ ...form, kind: e.target.value })}>
|
|
<option value="count">Anzahl (Stück)</option>
|
|
<option value="weight">Gewicht (Basis: Gramm)</option>
|
|
<option value="volume">Volumen (Basis: Milliliter)</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Faktor zur Basiseinheit
|
|
<input type="number" step="any" min="0" value={form.factor}
|
|
onChange={(e) => setForm({ ...form, factor: e.target.value })} required
|
|
placeholder={`z.B. 500 (= 500 ${baseUnitOfKind[form.kind]})`} />
|
|
</label>
|
|
<button className="btn primary"><Icon name="plus" size={16} />Anlegen</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|