107 lines
3.8 KiB
TypeScript
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>
|
|
);
|
|
}
|