Files
Vorrania/web/src/components/LocationMinStock.jsx
Scarriffle c8c1686395 Amazon-Kaufdatum erkennen; Preis-Vorschlaege beim Anlegen; Lagerort-Dropdowns mit Pfad
1) Beleganalyse erkennt Datumsangaben mit Monatsnamen (DE+EN, z.B. Amazon 'Bestelldatum 29 April 2026'), nicht nur 29.04.2026 - so wird das Kaufdatum solcher Rechnungen gefunden. 2) Beim direkten Anlegen eines Einzelstuecks mit Beleg werden jetzt alle erkannten Preise als Vorschlag angeboten (Dropdown), statt stillschweigend nur den wahrscheinlichsten zu nehmen. 3) Alle Lagerort-Dropdowns zeigen den vollen Pfad (Hedingen -> Keller -> Schublade 1) statt nur den Namen, damit gleichnamige Orte unterscheidbar sind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:24:08 +02:00

87 lines
3.2 KiB
JavaScript

import { useState } from "react";
import Icon from "./Icon";
import { locationOptions } from "../locationPath";
/**
* Editor für Mindestbestände je Lagerort (Bedarfe). Eine Zeile je Ort mit Menge;
* „Bedarfe speichern" ersetzt über onSave die komplette Liste. Menge 0/leer =
* Ort fällt weg.
*/
export default function LocationMinStock({ locations, initial = [], unitLabel = "", onSave, onError }) {
const [rows, setRows] = useState(() =>
initial.map((e) => ({ location_id: String(e.location_id), min_stock: String(e.min_stock) })),
);
const [busy, setBusy] = useState(false);
const [ok, setOk] = useState(false);
const used = new Set(rows.map((r) => r.location_id));
const frei = locations.filter((l) => !used.has(String(l.id)));
function addRow() {
if (!frei.length) return;
setRows([...rows, { location_id: String(frei[0].id), min_stock: "" }]);
setOk(false);
}
function setRow(i, patch) {
setRows(rows.map((r, j) => (j === i ? { ...r, ...patch } : r)));
setOk(false);
}
function removeRow(i) {
setRows(rows.filter((_, j) => j !== i));
setOk(false);
}
async function save() {
setBusy(true);
try {
const list = rows
.filter((r) => r.location_id !== "" && Number(r.min_stock) > 0)
.map((r) => ({ location_id: r.location_id, min_stock: Number(r.min_stock) }));
await onSave(list);
setOk(true);
} catch (err) {
onError?.(err.message);
} finally {
setBusy(false);
}
}
if (!locations.length) {
return <p className="muted small mt-0">Erst Lagerorte anlegen, dann Bedarfe je Ort möglich.</p>;
}
return (
<div className="stack">
{rows.length === 0 && (
<p className="muted small mt-0">Kein Bedarf je Lagerort festgelegt.</p>
)}
{rows.map((r, i) => (
<div className="field-inline" key={i} style={{ marginBottom: 0 }}>
<label className="grow" style={{ flex: "1 1 auto", minWidth: 0, margin: 0 }}>
<select value={r.location_id} onChange={(e) => setRow(i, { location_id: e.target.value })}>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label>
<label style={{ margin: 0, width: 130 }}>
<input type="number" min="0" step="0.01" placeholder="Menge" value={r.min_stock}
onChange={(e) => setRow(i, { min_stock: e.target.value })} />
</label>
{unitLabel && <span className="muted small" style={{ alignSelf: "center" }}>{unitLabel}</span>}
<button type="button" className="btn-icon danger" onClick={() => removeRow(i)} title="Entfernen">
<Icon name="trash" size={16} />
</button>
</div>
))}
<div className="field-inline" style={{ marginBottom: 0 }}>
<button type="button" className="btn" onClick={addRow} disabled={!frei.length}>
<Icon name="plus" size={16} />Lagerort
</button>
<button type="button" className="btn primary" onClick={save} disabled={busy}>
<Icon name="check" size={16} />{busy ? "Speichern…" : "Bedarfe speichern"}
</button>
{ok && <span className="muted small" style={{ alignSelf: "center" }}>Gespeichert.</span>}
</div>
</div>
);
}