Olympus/frontend/athena/components/inventory/StockActionDialog.tsx
2026-07-05 11:32:06 +02:00

107 lines
3.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import type { InventoryItem, InventoryStockPayload } from "@/types/inventory";
export type StockAction = "adjust" | "reserve" | "release" | "consume";
type Props = {
open: boolean;
item: InventoryItem | null;
action: StockAction;
pending: boolean;
serverError: string;
onOpenChange: (open: boolean) => void;
onSubmit: (payload: InventoryStockPayload) => void;
};
const labels: Record<StockAction, { title: string; description: string; reason: string }> = {
adjust: {
title: "Bestand anpassen",
description: "Setzt den physischen Bestand auf den angegebenen Wert.",
reason: "Inventurkorrektur",
},
reserve: {
title: "Bestand reservieren",
description: "Reserviert verfügbaren Bestand für einen späteren Vorgang.",
reason: "Reservierung",
},
release: {
title: "Reservierung aufheben",
description: "Gibt reservierten Bestand wieder frei.",
reason: "Reservierung aufgehoben",
},
consume: {
title: "Verbrauch buchen",
description: "Bucht Bestand als verbraucht aus.",
reason: "Verbrauch",
},
};
export default function StockActionDialog({ open, item, action, pending, serverError, onOpenChange, onSubmit }: Props) {
const [quantity, setQuantity] = useState(action === "adjust" ? item?.quantity_on_hand ?? 0 : 1);
const [reason, setReason] = useState(labels[action].reason);
const [note, setNote] = useState("");
const [clientError, setClientError] = useState("");
function submit() {
if (quantity <= 0 && action !== "adjust") {
setClientError("Bitte eine Menge größer 0 angeben.");
return;
}
if (quantity < 0) {
setClientError("Bitte keine negative Menge eingeben.");
return;
}
if (!reason.trim()) {
setClientError("Bitte einen Grund angeben.");
return;
}
setClientError("");
onSubmit({
quantity,
reason: reason.trim(),
note: note.trim() || null,
reference_type: null,
reference_id: null,
});
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{labels[action].title}</DialogTitle>
<DialogDescription>{item ? `${item.sku} · ${item.name}` : labels[action].description}</DialogDescription>
</DialogHeader>
<div className="grid gap-3">
<p className="rounded-lg border bg-slate-50 p-3 text-sm text-slate-600">
Bestand {item?.quantity_on_hand ?? 0} · Reserviert {item?.quantity_reserved ?? 0} · Verfügbar {item?.quantity_available ?? 0}
</p>
<label className="grid gap-1 text-sm">Menge<Input type="number" min={action === "adjust" ? 0 : 1} value={quantity} onChange={(event) => setQuantity(Number(event.target.value))} /></label>
<label className="grid gap-1 text-sm">Grund<Input value={reason} onChange={(event) => setReason(event.target.value)} /></label>
<label className="grid gap-1 text-sm">Notiz<textarea className="min-h-20 rounded-lg border p-2 text-sm" value={note} onChange={(event) => setNote(event.target.value)} /></label>
</div>
{(clientError || serverError) && <p className="text-sm text-red-600">{clientError || serverError}</p>}
<DialogFooter>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={pending}>Abbrechen</Button>
<Button type="button" onClick={submit} disabled={pending}>{pending ? "Speichert..." : "Buchen"}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}