Web: Chargen auswaehlen und Lagerort der Auswahl setzen
Chargenliste bekommt Auswahl-Checkboxen. Das obere Panel wirkt jetzt auf die Auswahl: "N ausgewaehlt -> Lagerort setzen" (leer = Ort entfernen, z.B. beim Aufloesen eines Orts). Schnellwahl "alle sichtbaren" (respektiert Filter) und "alle ohne Lagerort". DataTable meldet dafuer die sichtbaren Zeilen (onVisibleRows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ function savePersisted(id, data) {
|
||||
export default function DataTable({
|
||||
id, columns, rows, getRowKey, rowClassName,
|
||||
empty = "Nichts gefunden.", loading = false, toolbarExtra = null, tree = null,
|
||||
onVisibleRows = null,
|
||||
}) {
|
||||
const colByKey = useMemo(() => Object.fromEntries(columns.map((c) => [c.key, c])), [columns]);
|
||||
|
||||
@@ -218,6 +219,15 @@ export default function DataTable({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [treeActive, treeData, flat, collapsed]);
|
||||
|
||||
// Sichtbare (gefilterte/sortierte) Zeilen nach außen melden – z.B. damit eine
|
||||
// Seite „alle sichtbaren auswählen" anbieten kann. Ref, damit eine inline
|
||||
// übergebene Funktion keine Endlosschleife auslöst.
|
||||
const onVisibleRef = useRef(onVisibleRows);
|
||||
onVisibleRef.current = onVisibleRows;
|
||||
useEffect(() => {
|
||||
onVisibleRef.current?.(displayRows.map((d) => d.row));
|
||||
}, [displayRows]);
|
||||
|
||||
function toggleSort(c) {
|
||||
if (!c.sortValue && !c.filterText) return;
|
||||
setSort((s) => {
|
||||
|
||||
@@ -32,6 +32,8 @@ export default function Charges() {
|
||||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [bulkLoc, setBulkLoc] = useState("");
|
||||
const [selected, setSelected] = useState(() => new Set());
|
||||
const [visible, setVisible] = useState([]); // aktuell gefiltert sichtbare Chargen
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
@@ -49,6 +51,17 @@ export default function Charges() {
|
||||
|
||||
const ohneOrt = useMemo(() => lots.filter((l) => !l.location_id), [lots]);
|
||||
|
||||
function toggle(id) {
|
||||
setSelected((alt) => {
|
||||
const neu = new Set(alt);
|
||||
if (neu.has(id)) neu.delete(id); else neu.add(id);
|
||||
return neu;
|
||||
});
|
||||
}
|
||||
// Menge von IDs zur Auswahl hinzufügen (für „alle sichtbaren/ohne Ort").
|
||||
function selectAll(ids) { setSelected(new Set(ids)); }
|
||||
function clearSelection() { setSelected(new Set()); }
|
||||
|
||||
async function setLocation(lotId, locId) {
|
||||
setError(null);
|
||||
try {
|
||||
@@ -59,12 +72,14 @@ export default function Charges() {
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBulk() {
|
||||
if (!bulkLoc || ohneOrt.length === 0) return;
|
||||
// Lagerort für die aktuell ausgewählten Chargen setzen (leer = Ort entfernen).
|
||||
async function applyToSelection() {
|
||||
if (selected.size === 0) return;
|
||||
setError(null);
|
||||
try {
|
||||
await api.bulkLotLocation(ohneOrt.map((l) => l.id), bulkLoc);
|
||||
await api.bulkLotLocation([...selected], bulkLoc);
|
||||
setBulkLoc("");
|
||||
clearSelection();
|
||||
await load();
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
@@ -72,6 +87,12 @@ export default function Charges() {
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ key: "select", header: "", label: "Auswahl", fixed: true, width: 44,
|
||||
render: (l) => (
|
||||
<input type="checkbox" style={{ width: "auto", margin: 0, cursor: "pointer" }}
|
||||
checked={selected.has(l.id)} onChange={() => toggle(l.id)}
|
||||
aria-label={`${l.product_name} auswählen`} />
|
||||
) },
|
||||
{ key: "thumb", header: "", label: "Bild", fixed: true, width: 56,
|
||||
render: (l) => <ProduktThumb productId={l.product_id} alt={l.product_name} /> },
|
||||
{ key: "product", header: "Artikel", grow: true, min: 180,
|
||||
@@ -120,26 +141,43 @@ export default function Charges() {
|
||||
|
||||
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
||||
|
||||
{isAdmin && ohneOrt.length > 0 && (
|
||||
{isAdmin && (
|
||||
<div className="card" style={{ marginBottom: "var(--sp-3)" }}>
|
||||
<div className="card-head"><Icon name="location" /><h2>Lagerort für alle ohne Ort setzen</h2></div>
|
||||
<div className="card-head"><Icon name="location" /><h2>Lagerort für ausgewählte Chargen setzen</h2></div>
|
||||
<p className="muted small mt-0">
|
||||
{ohneOrt.length} Charge(n) haben noch keinen Lagerort. Ort wählen und in einem Klick allen zuweisen.
|
||||
{selected.size > 0
|
||||
? `${selected.size} Charge(n) ausgewählt – diese bekommen den gewählten Lagerort (leer = Ort entfernen).`
|
||||
: "Chargen in der Tabelle anhaken, dann hier den Lagerort setzen. Oder unten schnell alle sichtbaren / alle ohne Ort auswählen."}
|
||||
</p>
|
||||
<div className="field-inline">
|
||||
<select value={bulkLoc} onChange={(e) => setBulkLoc(e.target.value)} style={{ maxWidth: 360 }}>
|
||||
<option value="">– Lagerort wählen –</option>
|
||||
<div className="field-inline" style={{ flexWrap: "wrap" }}>
|
||||
<select value={bulkLoc} onChange={(e) => setBulkLoc(e.target.value)} style={{ maxWidth: 360 }}
|
||||
disabled={selected.size === 0}>
|
||||
<option value="">– ohne Lagerort –</option>
|
||||
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
<button className="btn primary" disabled={!bulkLoc} onClick={applyBulk}>
|
||||
<Icon name="check" size={16} />Auf alle {ohneOrt.length} anwenden
|
||||
<button className="btn primary" disabled={selected.size === 0} onClick={applyToSelection}>
|
||||
<Icon name="check" size={16} />Auf {selected.size} Auswahl anwenden
|
||||
</button>
|
||||
</div>
|
||||
<div className="field-inline" style={{ flexWrap: "wrap", marginTop: "var(--sp-2)", marginBottom: 0 }}>
|
||||
<button type="button" className="btn sm ghost" onClick={() => selectAll(visible.map((l) => l.id))}
|
||||
disabled={visible.length === 0}>
|
||||
Alle {visible.length} sichtbaren auswählen
|
||||
</button>
|
||||
<button type="button" className="btn sm ghost" onClick={() => selectAll(ohneOrt.map((l) => l.id))}
|
||||
disabled={ohneOrt.length === 0}>
|
||||
Alle {ohneOrt.length} ohne Lagerort auswählen
|
||||
</button>
|
||||
{selected.size > 0 && (
|
||||
<button type="button" className="btn sm ghost" onClick={clearSelection}>Auswahl aufheben</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card">
|
||||
<DataTable id="charges" columns={columns} rows={lots} loading={loading}
|
||||
onVisibleRows={setVisible}
|
||||
getRowKey={(l) => l.id} empty="Keine Chargen im Bestand." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user