Web: Einzelstuecke verleihen, auch direkt nach dem Scannen

Verleihen und Zuruecknehmen in der Einzelstueck-Tabelle eines Artikels und
in der Liste aller Einzelstuecke, dort als Spalten "Verliehen an" und
"Zurueck bis" (filter- und sortierbar). Ueberfaellig ist rot, demnaechst
faellig gelb - dieselben Zustandsfarben wie beim MHD.

Die Scan-Zielseite /i/<uid> leitete vorher nur zum Artikel weiter. Fuer
"scannen und verleihen" braucht die Aktion aber genau dort ihren Platz:
Die Seite zeigt jetzt, was man in der Hand haelt - Stueck, Artikel,
Lagerort, Verleihzustand - und bietet Verleihen bzw. Zuruecknehmen an.
Gegenstueck zur Lagerort-Seite /l/<code>, die es fuers Einlagern genauso
macht.

Anzeige und Aktionen liegen einmal in components/Verleih.jsx, weil alle
drei Stellen dasselbe brauchen. Der Knopf haengt nicht an isAdmin:
Verleihen ist eine Buchung wie Ein- und Auslagern.

Dazu zwei Dashboard-Karten: alles Verliehene und, getrennt, nur das
Ueberfaellige. Stuecke ohne Rueckgabedatum sind nie faellig und tauchen in
der zweiten Karte nicht auf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-17 09:30:14 +02:00
co-authored by Claude Opus 5
parent 467ef3c804
commit 1332dd86fa
9 changed files with 472 additions and 36 deletions
+67
View File
@@ -5,6 +5,7 @@ import Icon from "../components/Icon";
import { CardSkeleton } from "../components/Skeleton";
import ShoppingNeedText from "../components/ShoppingNeedText";
import BewegungIcon from "../components/BewegungIcon";
import { FristBadge, verleihRowClass } from "../components/Verleih";
import { useSettings } from "../settings";
import {
articlePrimary, articleSecondary, fmt, relativeExpiry,
@@ -396,6 +397,60 @@ function KarteEinkaufsliste({ props: karteProps }) {
/**
* Verliehene Einzelstücke – wer hat was, und ist die Rückgabe schon fällig?
*
* „modus" trennt die beiden nützlichen Blickwinkel: alles Draußene oder nur das
* Überfällige. Stücke ohne Rückgabedatum sind nie fällig und tauchen deshalb im
* Modus „ueberfaellig" nicht auf.
*/
function KarteVerleih({ modus = "alle" }) {
const { daten, fehler } = useDaten(() => api.loans());
const zeilen = (daten || []).filter(
(v) => (modus === "ueberfaellig" ? !!v.days_overdue : true),
);
const leer = modus === "ueberfaellig"
? "Nichts überfällig."
: "Gerade ist nichts verliehen.";
return (
<Zustand fehler={fehler} daten={daten} leer={leer}>
{zeilen.length === 0 ? (
<div className="empty">{leer}</div>
) : (
<div className="table-wrap">
<table className="table">
<thead>
<tr><th>Stück</th><th>Verliehen an</th><th>Zurück</th></tr>
</thead>
<tbody>
{zeilen.map((v) => (
<tr key={v.id} className={verleihRowClass(v)}>
<td data-label="Stück">
<Link to={`/i/${encodeURIComponent(v.item_uid || "")}`}>
{v.product_name || v.item_uid}
</Link>
{v.product_name && <div className="muted small">{v.item_uid}</div>}
</td>
<td data-label="Verliehen an">
{v.borrower}
{v.reason && <div className="muted small">{v.reason}</div>}
</td>
<td data-label="Zurück">
{v.due_on
? <FristBadge loan={v} />
: <span className="muted small">offen</span>}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Zustand>
);
}
function KarteBewegungen() {
const { daten, fehler } = useDaten(() => api.listMovements(20));
return (
@@ -655,6 +710,18 @@ export const KARTEN = {
config: [{ key: "location_id", type: "location", label: "Lagerort", hint: "leer = alle Orte" }],
komponente: KarteEinkaufsliste,
},
loans: {
titel: "Verliehen", icon: "loan", vorschau: "tabelle",
beschreibung: "Welche Einzelstücke gerade draußen sind und bei wem",
standard: { w: 6, h: 5 }, min: { w: 4, h: 4 },
komponente: () => <KarteVerleih modus="alle" />,
},
"loans-overdue": {
titel: "Überfällige Verleihe", icon: "alert", vorschau: "tabelle",
beschreibung: "Nur Stücke, deren Rückgabedatum vorbei ist",
standard: { w: 6, h: 4 }, min: { w: 4, h: 3 },
komponente: () => <KarteVerleih modus="ueberfaellig" />,
},
movements: {
titel: "Letzte Bewegungen", icon: "history", vorschau: "tabelle",
beschreibung: "Wer hat zuletzt was ein- oder ausgelagert",