Lagerorte: ueberall ein aufklappbarer Baum statt flacher Pfadliste

Die Lagerort-Auswahl schrieb in jeder Zeile den ganzen Pfad aus
("Hedingen → Guido Zimmer → Kallax → Fach Oben Links → Oben"). Bei einem
tiefen Baum ist das eine Wand aus Wiederholungen, und Geschwister stehen
nur zufaellig beieinander.

Jetzt dasselbe Auswahlfeld wie bei den Kategorien: eingerueckter Baum,
Aeste ein- und ausklappbar. Geschlossen steht weiter der volle Pfad da -
"Oben" allein waere nicht eindeutig, genau dafuer war der Pfad gedacht.

CategorySelect konnte das schon und war auf Lagerort-Codes vorbereitet
(seine IDs duerfen Zeichenketten sein). Dazu kam nur LocationSelect als
duenne Huelle: Baumaufbau und die Umrechnung "" <-> null, damit die 16
Aufrufstellen weiter mit dem leeren String fuer "kein Ort" arbeiten.

Zwei Kleinigkeiten dabei: CategorySelect bekommt showRoot, weil bei den
Mindestbestaenden "nichts gewaehlt" kein gueltiger Wert ist - dort heisst
die oberste Zeile "Ueberall". Und LocationSelect sortiert Geschwister
selbst nach Namen; der Server sortiert zeichenweise, dort stuende "Fach
oben Rechts" hinter "Fach Unten Rechts".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-24 17:37:00 +02:00
co-authored by Claude Opus 5
parent 290ff9341a
commit 7cb61d226f
12 changed files with 113 additions and 91 deletions
+15 -8
View File
@@ -20,7 +20,11 @@ export default function CategorySelect({
// Zusätzliche Einträge über dem Baum, z.B. { value: 0, label: "Ohne Kategorie" } // Zusätzliche Einträge über dem Baum, z.B. { value: 0, label: "Ohne Kategorie" }
// für Filter. Ihr Wert wird unverändert an onChange gereicht. // für Filter. Ihr Wert wird unverändert an onChange gereicht.
extraOptions = [], extraOptions = [],
// Aus, wo „nichts gewaehlt" gar kein gueltiger Wert ist (Mindestbestaende:
// dort heisst die oberste Zeile „Ueberall" und steht in extraOptions).
showRoot = true,
disabled = false, disabled = false,
title,
}) { }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [collapsed, setCollapsed] = useState(() => new Set()); const [collapsed, setCollapsed] = useState(() => new Set());
@@ -123,6 +127,7 @@ export default function CategorySelect({
ref={btnRef} ref={btnRef}
className="tree-select-btn" className="tree-select-btn"
disabled={disabled} disabled={disabled}
title={title}
aria-expanded={open} aria-expanded={open}
onClick={() => setOpen((o) => !o)} onClick={() => setOpen((o) => !o)}
> >
@@ -138,14 +143,16 @@ export default function CategorySelect({
className="tree-pop" className="tree-pop"
style={{ left: pos.left, top: pos.top, minWidth: Math.max(pos.width, 220) }} style={{ left: pos.left, top: pos.top, minWidth: Math.max(pos.width, 220) }}
> >
<button {showRoot && (
type="button" <button
className={`tree-pop-opt ${isRoot ? "sel" : ""}`} type="button"
style={{ paddingLeft: 12 }} className={`tree-pop-opt ${isRoot ? "sel" : ""}`}
onClick={() => pick(null)} style={{ paddingLeft: 12 }}
> onClick={() => pick(null)}
{rootLabel} >
</button> {rootLabel}
</button>
)}
{extraOptions.map((o) => ( {extraOptions.map((o) => (
<button <button
key={String(o.value)} key={String(o.value)}
+6 -11
View File
@@ -6,7 +6,8 @@ import { useToast } from "../toast";
import Icon from "./Icon"; import Icon from "./Icon";
import Zubehoer from "./Zubehoer"; import Zubehoer from "./Zubehoer";
import { REASONS } from "./ObjektBestand"; import { REASONS } from "./ObjektBestand";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "./LocationSelect";
import { itemDeepLink } from "../qr"; import { itemDeepLink } from "../qr";
import { import {
VerleihAktion, VerleihAktion,
@@ -298,11 +299,8 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh
<td data-label="UID" className="strong nowrap">{it.uid}</td> <td data-label="UID" className="strong nowrap">{it.uid}</td>
<td data-label="Lagerort"> <td data-label="Lagerort">
{isAdmin ? ( {isAdmin ? (
<select value={it.location_id ?? ""} style={{ marginTop: 0, minWidth: 120 }} <LocationSelect value={it.location_id ?? ""} locations={locations}
onChange={(e) => patchItem(it, { location_id: e.target.value === "" ? null : e.target.value })}> onChange={(id) => patchItem(it, { location_id: id === "" ? null : id })} />
<option value="">– ohne –</option>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
) : <span>{ortName(it.location_id) || "–"}</span>} ) : <span>{ortName(it.location_id) || "–"}</span>}
</td> </td>
<td data-label="Verliehen an"><VerleihText loan={it.loan} /></td> <td data-label="Verliehen an"><VerleihText loan={it.loan} /></td>
@@ -488,11 +486,8 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh
onChange={(e) => setAddForm({ ...addForm, count: e.target.value })} /> onChange={(e) => setAddForm({ ...addForm, count: e.target.value })} />
</label> </label>
<label className="grow">Lagerort <label className="grow">Lagerort
<select value={addForm.location_id} <LocationSelect value={addForm.location_id} locations={locations}
onChange={(e) => setAddForm({ ...addForm, location_id: e.target.value })}> onChange={(id) => setAddForm({ ...addForm, location_id: id })} />
<option value="">– ohne –</option>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
<label className="grow">Gekauft bei <label className="grow">Gekauft bei
<select value={addForm.shop_id} <select value={addForm.shop_id}
+4 -3
View File
@@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import Icon from "./Icon"; import Icon from "./Icon";
import { locationOptions } from "../locationPath"; import { locationOptions } from "../locationPath";
import LocationSelect from "./LocationSelect";
// „Überall" ist ein Ort wie jeder andere – nur eben der oberste. Im Datenmodell // „Überall" ist ein Ort wie jeder andere – nur eben der oberste. Im Datenmodell
// ist er location_id = null; im <select> braucht es einen Wert, deshalb hier ein // ist er location_id = null; im <select> braucht es einen Wert, deshalb hier ein
@@ -70,9 +71,9 @@ export default function LocationMinStock({ locations, initial = [], unitLabel =
{rows.map((r, i) => ( {rows.map((r, i) => (
<div className="field-inline" key={i} style={{ marginBottom: 0 }}> <div className="field-inline" key={i} style={{ marginBottom: 0 }}>
<label className="grow" style={{ flex: "1 1 auto", minWidth: 0, margin: 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 })}> <LocationSelect value={r.location_id} locations={locations}
{alleOrte.map((o) => <option key={o.id} value={o.id}>{o.label}</option>)} showRoot={false} extraOptions={[{ value: UEBERALL, label: "Überall (egal wo)" }]}
</select> onChange={(id) => setRow(i, { location_id: id })} />
</label> </label>
<label style={{ margin: 0, width: 130 }}> <label style={{ margin: 0, width: 130 }}>
<input type="number" min="0" step="0.01" placeholder="Menge" value={r.min_stock} <input type="number" min="0" step="0.01" placeholder="Menge" value={r.min_stock}
+41
View File
@@ -0,0 +1,41 @@
import CategorySelect from "./CategorySelect";
import { asTree } from "../categoryTree";
/**
* Lagerort-Auswahl als aufklappbarer Baum – dieselbe Darstellung wie bei den
* Kategorien, denn Lagerorte sind genauso verschachtelt (Hedingen → Kallax →
* Fach Oben Links → Oben). Vorher stand überall ein flaches <select>, in dem
* jede Zeile den ganzen Pfad ausschrieb; Geschwister standen dadurch nur
* zufällig beieinander.
*
* `CategorySelect` kann beides – seine IDs dürfen Zeichenketten sein, und
* Lagerort-Codes sind welche. Hier kommt nur der Baumaufbau dazu und die
* Umrechnung "" ↔ null, damit die Aufrufstellen weiter mit dem leeren String
* für „kein Ort" arbeiten wie beim <select> zuvor.
*/
export default function LocationSelect({
value,
onChange,
locations,
rootLabel = "– ohne –",
extraOptions = [],
showRoot = true,
disabled = false,
title,
}) {
return (
<CategorySelect
value={value === "" || value == null ? null : value}
onChange={(id) => onChange(id == null ? "" : id)}
// Nach Namen sortieren, bevor der Baum entsteht: Der Server sortiert
// zeichenweise, dort stuende „Fach oben Rechts" hinter „Fach Unten
// Rechts". asTree uebernimmt die Reihenfolge der Geschwister unveraendert.
nodes={asTree([...(locations || [])].sort((a, b) => a.name.localeCompare(b.name, "de")))}
rootLabel={rootLabel}
extraOptions={extraOptions}
showRoot={showRoot}
disabled={disabled}
title={title}
/>
);
}
+14 -19
View File
@@ -4,7 +4,8 @@ import { useConfirm } from "../confirm";
import { useToast } from "../toast"; import { useToast } from "../toast";
import Icon from "./Icon"; import Icon from "./Icon";
import { fmt } from "../units"; import { fmt } from "../units";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "./LocationSelect";
// Gründe zum Entfernen (Wert = Backend-Enum, Label = Anzeige). // Gründe zum Entfernen (Wert = Backend-Enum, Label = Anzeige).
export const REASONS = [ export const REASONS = [
@@ -182,11 +183,9 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
<form onSubmit={hinzufuegen} className="row" style={{ marginTop: "var(--sp-3)" }}> <form onSubmit={hinzufuegen} className="row" style={{ marginTop: "var(--sp-3)" }}>
<label className="grow"> <label className="grow">
Lagerort Lagerort
<select value={addForm.location_id} <LocationSelect value={addForm.location_id} locations={locations}
onChange={(e) => setAddForm({ ...addForm, location_id: e.target.value })}> rootLabel="– ohne Lagerort –"
<option value="">– ohne Lagerort –</option> onChange={(id) => setAddForm({ ...addForm, location_id: id })} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
<label style={{ width: 120 }}> <label style={{ width: 120 }}>
Menge Menge
@@ -208,16 +207,14 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
</div> </div>
<div className="row"> <div className="row">
<label className="grow">Von <label className="grow">Von
<select value={move.from} onChange={(e) => setMove({ ...move, from: e.target.value })}> <LocationSelect value={move.from} locations={locations}
<option value="">– ohne Lagerort –</option> rootLabel="– ohne Lagerort –"
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)} onChange={(id) => setMove({ ...move, from: id })} />
</select>
</label> </label>
<label className="grow">Nach <label className="grow">Nach
<select value={move.to} onChange={(e) => setMove({ ...move, to: e.target.value })}> <LocationSelect value={move.to} locations={locations}
<option value="">– ohne Lagerort –</option> rootLabel="– ohne Lagerort –"
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)} onChange={(id) => setMove({ ...move, to: id })} />
</select>
</label> </label>
<label style={{ width: 110 }}>Menge <label style={{ width: 110 }}>Menge
<input type="number" step="any" min="0" value={move.quantity} <input type="number" step="any" min="0" value={move.quantity}
@@ -237,11 +234,9 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
</div> </div>
<div className="row"> <div className="row">
<label className="grow">Lagerort <label className="grow">Lagerort
<select value={remove.location_id} <LocationSelect value={remove.location_id} locations={locations}
onChange={(e) => setRemove({ ...remove, location_id: e.target.value })}> rootLabel="– ohne Lagerort –"
<option value="">– ohne Lagerort –</option> onChange={(id) => setRemove({ ...remove, location_id: id })} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
<label style={{ width: 110 }}>Menge <label style={{ width: 110 }}>Menge
<input type="number" step="any" min="0" value={remove.quantity} <input type="number" step="any" min="0" value={remove.quantity}
+4 -5
View File
@@ -2,7 +2,8 @@ import { useState } from "react";
import { api } from "../api"; import { api } from "../api";
import Icon from "./Icon"; import Icon from "./Icon";
import { fmt } from "../units"; import { fmt } from "../units";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "./LocationSelect";
/** /**
* Charge aufteilen: einen Teil der Menge (in Artikeleinheiten – Packungen/Stück/g) * Charge aufteilen: einen Teil der Menge (in Artikeleinheiten – Packungen/Stück/g)
@@ -55,10 +56,8 @@ export default function SplitLotDialog({ lot, factor, labelFor, locations, onClo
</label> </label>
<label> <label>
nach Lagerort nach Lagerort
<select value={locId} onChange={(e) => setLocId(e.target.value)}> <LocationSelect value={locId} locations={locations}
<option value="">– ohne Lagerort –</option> rootLabel="– ohne Lagerort –" onChange={setLocId} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
{rest != null && !zielGleich && ( {rest != null && !zielGleich && (
<p className="muted small mt-0"> <p className="muted small mt-0">
+4 -6
View File
@@ -1,7 +1,7 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
import { ZEITRAEUME } from "./cards"; import { ZEITRAEUME } from "./cards";
import { locationOptions } from "../locationPath"; import LocationSelect from "../components/LocationSelect";
import { artikelLabel } from "../units"; import { artikelLabel } from "../units";
/** /**
@@ -28,11 +28,9 @@ export default function KartenConfig({
return ( return (
<label key={f.key}> <label key={f.key}>
{f.label}{f.hint ? <span className="muted small"> ({f.hint})</span> : null} {f.label}{f.hint ? <span className="muted small"> ({f.hint})</span> : null}
<select value={karteProps[f.key] ?? ""} <LocationSelect value={karteProps[f.key] ?? ""} locations={locations}
onChange={(e) => onProps({ [f.key]: e.target.value || undefined })}> rootLabel="– alle Orte –"
<option value="">– alle Orte –</option> onChange={(id) => onProps({ [f.key]: id || undefined })} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
); );
} }
+7 -11
View File
@@ -10,7 +10,8 @@ import { ProduktThumb } from "../components/ProduktBild";
import { useSettings } from "../settings"; import { useSettings } from "../settings";
import { useConfirm } from "../confirm"; import { useConfirm } from "../confirm";
import { useToast } from "../toast"; import { useToast } from "../toast";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "../components/LocationSelect";
import { expiryRowClass, faelligAm, fmt, gebinde, unitShort } from "../units"; import { expiryRowClass, faelligAm, fmt, gebinde, unitShort } from "../units";
// Artikeleinheit einer Charge (Gebinde, sonst Produkteinheit). // Artikeleinheit einer Charge (Gebinde, sonst Produkteinheit).
@@ -243,11 +244,8 @@ export default function Charges() {
filterText: (l) => (l.location_id ? (locationPathById(l.location_id, locations) || "") : "(ohne)"), filterText: (l) => (l.location_id ? (locationPathById(l.location_id, locations) || "") : "(ohne)"),
filterValues: (l) => [l.location_id ? (locationPathById(l.location_id, locations) || "?") : "(ohne)"], filterValues: (l) => [l.location_id ? (locationPathById(l.location_id, locations) || "?") : "(ohne)"],
render: (l) => (isAdmin ? ( render: (l) => (isAdmin ? (
<select value={l.location_id || ""} style={{ marginTop: 0, minWidth: 150 }} <LocationSelect value={l.location_id || ""} locations={locations}
onChange={(e) => setLocation(l.id, e.target.value)}> onChange={(id) => setLocation(l.id, id)} />
<option value="">– ohne –</option>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
) : ( ) : (
l.location_id ? (locationPathById(l.location_id, locations) || "?") : <span className="muted">– ohne –</span> l.location_id ? (locationPathById(l.location_id, locations) || "?") : <span className="muted">– ohne –</span>
)) }, )) },
@@ -305,11 +303,9 @@ export default function Charges() {
: "Chargen in der Tabelle anhaken, dann hier handeln. Oder unten schnell alle sichtbaren / alle ohne Ort auswählen."} : "Chargen in der Tabelle anhaken, dann hier handeln. Oder unten schnell alle sichtbaren / alle ohne Ort auswählen."}
</p> </p>
<div className="field-inline" style={{ flexWrap: "wrap" }}> <div className="field-inline" style={{ flexWrap: "wrap" }}>
<select value={bulkLoc} onChange={(e) => setBulkLoc(e.target.value)} style={{ maxWidth: 360 }} <LocationSelect value={bulkLoc} locations={locations}
disabled={selected.size === 0}> rootLabel="– ohne Lagerort –" disabled={selected.size === 0}
<option value="">– ohne Lagerort –</option> onChange={setBulkLoc} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
<button className="btn primary" disabled={selected.size === 0} onClick={applyToSelection}> <button className="btn primary" disabled={selected.size === 0} onClick={applyToSelection}>
<Icon name="location" size={16} />Lagerort setzen <Icon name="location" size={16} />Lagerort setzen
</button> </button>
+3 -5
View File
@@ -9,7 +9,7 @@ import { useToast } from "../toast";
import { suggestionToProduct } from "../offUtils"; import { suggestionToProduct } from "../offUtils";
import { asTree } from "../categoryTree"; import { asTree } from "../categoryTree";
import CategorySelect from "../components/CategorySelect"; import CategorySelect from "../components/CategorySelect";
import { locationOptions } from "../locationPath"; import LocationSelect from "../components/LocationSelect";
import { buildUnitOptions, fmt, fromMonthInput, isExpired, monthInputToLastDay } from "../units"; import { buildUnitOptions, fmt, fromMonthInput, isExpired, monthInputToLastDay } from "../units";
import { gruppenOptionen } from "../groupGraph"; import { gruppenOptionen } from "../groupGraph";
@@ -405,10 +405,8 @@ export default function CheckIn() {
</label> </label>
<label className="grow"> <label className="grow">
Lagerort (optional) Lagerort (optional)
<select value={locationId} onChange={(e) => setLocationId(e.target.value)}> <LocationSelect value={locationId} locations={locations}
<option value="">– keiner –</option> rootLabel="– keiner –" onChange={setLocationId} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label> </label>
</div> </div>
+4 -6
View File
@@ -5,7 +5,8 @@ import { useAuth } from "../auth";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
import DataTable from "../components/DataTable"; import DataTable from "../components/DataTable";
import { ProduktThumb } from "../components/ProduktBild"; import { ProduktThumb } from "../components/ProduktBild";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "../components/LocationSelect";
import { categoryInfoMap } from "../categoryPath"; import { categoryInfoMap } from "../categoryPath";
import CategoryPathLabel, { pathParts } from "../components/CategoryPathLabel"; import CategoryPathLabel, { pathParts } from "../components/CategoryPathLabel";
import { import {
@@ -73,11 +74,8 @@ export default function ItemList() {
{ key: "location", header: "Lagerort", width: 220, { key: "location", header: "Lagerort", width: 220,
filterText: (it) => locationPathById(it.location_id, locations), filterText: (it) => locationPathById(it.location_id, locations),
render: (it) => (isAdmin ? ( render: (it) => (isAdmin ? (
<select value={it.location_id ?? ""} style={{ marginTop: 0 }} <LocationSelect value={it.location_id ?? ""} locations={locations}
onChange={(e) => patch(it, { location_id: e.target.value === "" ? null : e.target.value })}> onChange={(id) => patch(it, { location_id: id === "" ? null : id })} />
<option value="">– ohne –</option>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
) : (locationPathById(it.location_id, locations) || "–")) }, ) : (locationPathById(it.location_id, locations) || "–")) },
{ key: "loan", header: "Verliehen an", width: 200, { key: "loan", header: "Verliehen an", width: 200,
filterText: (it) => it.loan?.borrower || "", filterText: (it) => it.loan?.borrower || "",
+7 -9
View File
@@ -4,6 +4,7 @@ import { useAuth } from "../auth";
import { useConfirm } from "../confirm"; import { useConfirm } from "../confirm";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
import { locationOptions, locationPathById } from "../locationPath"; import { locationOptions, locationPathById } from "../locationPath";
import LocationSelect from "../components/LocationSelect";
import { gruppenOptionen } from "../groupGraph"; import { gruppenOptionen } from "../groupGraph";
import { anzahlWort, fmt, gebinde, grpFactor, grpPkgMode, grpUnit, kindShort } from "../units"; import { anzahlWort, fmt, gebinde, grpFactor, grpPkgMode, grpUnit, kindShort } from "../units";
@@ -308,12 +309,10 @@ export default function MinStock() {
{isAdmin && ( {isAdmin && (
<> <>
<select value={vonId(r.locId)} <LocationSelect value={vonId(r.locId)} locations={locations}
title="Ort ändern – der Mindestbestand wird verschoben" title="Ort ändern – der Mindestbestand wird verschoben"
onChange={(e) => moveRow(r, e.target.value)}> showRoot={false} extraOptions={[{ value: UEBERALL, label: "Überall" }]}
<option value={UEBERALL}>Überall</option> onChange={(id) => moveRow(r, id)} />
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
<button type="button" className="btn-icon danger" title="Mindestbestand löschen" <button type="button" className="btn-icon danger" title="Mindestbestand löschen"
onClick={() => deleteRow(r)}><Icon name="trash" size={15} /></button> onClick={() => deleteRow(r)}><Icon name="trash" size={15} /></button>
</> </>
@@ -350,10 +349,9 @@ export default function MinStock() {
<div className="row"> <div className="row">
<label className="grow" style={{ maxWidth: 360 }}> <label className="grow" style={{ maxWidth: 360 }}>
Wo Wo
<select value={draft.locId} onChange={(e) => setDraft({ ...draft, locId: e.target.value })}> <LocationSelect value={draft.locId} locations={locations}
<option value={UEBERALL}>Überall (egal wo)</option> showRoot={false} extraOptions={[{ value: UEBERALL, label: "Überall (egal wo)" }]}
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)} onChange={(id) => setDraft({ ...draft, locId: id })} />
</select>
</label> </label>
<label className="seg-label"> <label className="seg-label">
Was Was
+4 -8
View File
@@ -16,7 +16,8 @@ import { DynamicFields, FIELD_TYPES } from "../fields";
import ObjektBestand from "../components/ObjektBestand"; import ObjektBestand from "../components/ObjektBestand";
import Einzelstuecke from "../components/Einzelstuecke"; import Einzelstuecke from "../components/Einzelstuecke";
import SplitLotDialog from "../components/SplitLotDialog"; import SplitLotDialog from "../components/SplitLotDialog";
import { locationOptions, locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import LocationSelect from "../components/LocationSelect";
import { gruppenOptionen } from "../groupGraph"; import { gruppenOptionen } from "../groupGraph";
import { import {
BASE_UNITS, daysUntil, expiryRowClass, fmt, fromMonthInput, gebinde, isExpired, BASE_UNITS, daysUntil, expiryRowClass, fmt, fromMonthInput, gebinde, isExpired,
@@ -1354,13 +1355,8 @@ function LotsCard({ product, lots, locations = [], baseShort, warnDays, isAdmin,
</td> </td>
<td data-label="Lagerort"> <td data-label="Lagerort">
{editing ? ( {editing ? (
<select value={draft.location_id} style={{ marginTop: 0, minWidth: 140 }} <LocationSelect value={draft.location_id} locations={locations}
onChange={(e) => setDraft({ ...draft, location_id: e.target.value })}> onChange={(id) => setDraft({ ...draft, location_id: id })} />
<option value="">– ohne –</option>
{locationOptions(locations).map((o) => (
<option key={o.id} value={o.id}>{o.label}</option>
))}
</select>
) : ( ) : (
l.location_id l.location_id
? (locationPathById(l.location_id, locations) || "?") ? (locationPathById(l.location_id, locations) || "?")