Neue Seite "Chargen": alle Chargen ueber alle Artikel (Artikel, Menge, MHD, Lagerort, eingelagert). Lagerort je Zeile direkt aenderbar; alle Chargen ohne Ort per Klick zuweisbar (bulk-location) - gut, wenn der Lagerort erst spaeter angelegt wurde. DataTable bekommt einen loading-Prop: waehrend des Ladens steht "Wird geladen..." statt der Leer-Meldung. Auf ALLEN Tabellen angewendet (Produkte, Mindestbestaende, Kategorien, Gruppen, Einheiten, Gebinde, Einzelstuecke, Chargen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
4.3 KiB
React
124 lines
4.3 KiB
React
import { useEffect, useState } from "react";
|
|
import { api } from "../api";
|
|
import { useConfirm } from "../confirm";
|
|
import Icon from "../components/Icon";
|
|
import DataTable from "../components/DataTable";
|
|
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);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
async function load() {
|
|
setLoading(true);
|
|
try {
|
|
setUnits(await api.listUnits());
|
|
} catch (err) {
|
|
setError(err.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
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" };
|
|
|
|
const columns = [
|
|
{ key: "name", header: "Name", grow: true, min: 160,
|
|
filterText: (u) => u.name, sortValue: (u) => u.name,
|
|
render: (u) => (
|
|
<span className="cell-row">
|
|
<span className="strong">{u.name}</span>
|
|
{u.is_builtin && <span className="badge nowrap">eingebaut</span>}
|
|
</span>
|
|
) },
|
|
{ key: "kind", header: "Art", width: 230, filterText: (u) => KIND_LABEL[u.kind] || u.kind,
|
|
render: (u) => <span className="muted">{KIND_LABEL[u.kind] || u.kind}</span> },
|
|
{ key: "factor", header: "Faktor", width: 150, align: "num", sortValue: (u) => u.factor,
|
|
filterText: (u) => `${fmt(u.factor)} ${baseUnitOfKind[u.kind]}`,
|
|
render: (u) => `${fmt(u.factor)} ${baseUnitOfKind[u.kind]}` },
|
|
{ key: "actions", header: "", label: "Aktionen", fixed: true, width: 56, align: "num",
|
|
render: (u) => (!u.is_builtin && (
|
|
<button className="btn-icon danger" onClick={() => remove(u)} title="Löschen">
|
|
<Icon name="trash" size={16} />
|
|
</button>
|
|
)) },
|
|
];
|
|
|
|
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 wide-aside">
|
|
<div className="card">
|
|
<DataTable id="units" columns={columns} rows={units} loading={loading}
|
|
getRowKey={(u) => u.id} empty="Noch keine Einheiten." />
|
|
</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>
|
|
);
|
|
}
|