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:
co-authored by
Claude Opus 5
parent
290ff9341a
commit
7cb61d226f
@@ -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,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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 || "",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) || "?")
|
||||||
|
|||||||
Reference in New Issue
Block a user