125 lines
4.6 KiB
TypeScript
125 lines
4.6 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { useActionState, useEffect, useRef, useState } from "react";
|
|||
|
|
import { useRouter } from "next/navigation";
|
|||
|
|
import { Button } from "@/components/ui/button";
|
|||
|
|
import { Input } from "@/components/ui/input";
|
|||
|
|
import { Label } from "@/components/ui/label";
|
|||
|
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|||
|
|
import { Select } from "@/components/ui/select";
|
|||
|
|
import { createFuelPurchase, updateFuelPurchase, type ActionResult } from "@/actions/fuel-purchases";
|
|||
|
|
import { toast } from "sonner";
|
|||
|
|
import type { FuelPurchase, Supplier } from "@/lib/types";
|
|||
|
|
import { formatCurrency, formatNumber } from "@/lib/utils";
|
|||
|
|
|
|||
|
|
const initial: ActionResult = { ok: false, error: "" };
|
|||
|
|
|
|||
|
|
function todayISO() {
|
|||
|
|
return new Date().toISOString().slice(0, 10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function FuelPurchaseForm({ suppliers, purchase }: { suppliers: Supplier[]; purchase?: FuelPurchase }) {
|
|||
|
|
const router = useRouter();
|
|||
|
|
const isEdit = !!purchase;
|
|||
|
|
const bound = isEdit ? updateFuelPurchase.bind(null, purchase!.id) : createFuelPurchase;
|
|||
|
|
const [state, action, pending] = useActionState(bound, initial);
|
|||
|
|
const ref = useRef<HTMLFormElement>(null);
|
|||
|
|
const [supplier, setSupplier] = useState(purchase?.supplier || "");
|
|||
|
|
const [qty, setQty] = useState<number | "">(purchase?.purchase_qty ?? "");
|
|||
|
|
const [price, setPrice] = useState<number | "">(purchase?.purchase_price ?? "");
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (state.ok) {
|
|||
|
|
toast.success(isEdit ? "Purchase updated" : "Purchase created");
|
|||
|
|
if (!isEdit) {
|
|||
|
|
ref.current?.reset();
|
|||
|
|
setQty("");
|
|||
|
|
setPrice("");
|
|||
|
|
} else {
|
|||
|
|
router.push("/fuel-purchases");
|
|||
|
|
}
|
|||
|
|
} else if (state.error) { toast.error(state.error); }
|
|||
|
|
}, [state, isEdit, router]);
|
|||
|
|
|
|||
|
|
const total = qty && price ? Number(qty) * Number(price) : 0;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Card className="max-w-2xl">
|
|||
|
|
<CardHeader>
|
|||
|
|
<CardTitle>{isEdit ? "Edit fuel purchase" : "Add fuel purchase"}</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<form ref={ref} action={action}>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="supplier">Supplier *</Label>
|
|||
|
|
<Select
|
|||
|
|
id="supplier"
|
|||
|
|
name="supplier"
|
|||
|
|
value={supplier}
|
|||
|
|
onChange={(e) => setSupplier(e.target.value)}
|
|||
|
|
required
|
|||
|
|
options={suppliers.map((s) => ({ value: s.id, label: s.name }))}
|
|||
|
|
placeholder="Select a supplier"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="grid gap-4 sm:grid-cols-3">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="purchase_qty">Qty (litres) *</Label>
|
|||
|
|
<Input
|
|||
|
|
id="purchase_qty"
|
|||
|
|
name="purchase_qty"
|
|||
|
|
type="number"
|
|||
|
|
step="0.01"
|
|||
|
|
min="0.01"
|
|||
|
|
value={qty}
|
|||
|
|
onChange={(e) => setQty(e.target.value === "" ? "" : Number(e.target.value))}
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="purchase_price">Price / L *</Label>
|
|||
|
|
<Input
|
|||
|
|
id="purchase_price"
|
|||
|
|
name="purchase_price"
|
|||
|
|
type="number"
|
|||
|
|
step="0.01"
|
|||
|
|
min="0.01"
|
|||
|
|
value={price}
|
|||
|
|
onChange={(e) => setPrice(e.target.value === "" ? "" : Number(e.target.value))}
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label>Total</Label>
|
|||
|
|
<div className="h-9 rounded-md border bg-muted/50 px-3 py-1.5 text-sm font-medium">
|
|||
|
|
{formatCurrency(total)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="purchase_date">Purchase date *</Label>
|
|||
|
|
<Input
|
|||
|
|
id="purchase_date"
|
|||
|
|
name="purchase_date"
|
|||
|
|
type="date"
|
|||
|
|
defaultValue={(purchase?.purchase_date || "").slice(0, 10) || todayISO()}
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
{qty && price && (
|
|||
|
|
<p className="text-xs text-muted-foreground">
|
|||
|
|
{formatNumber(Number(qty))} L × {formatCurrency(Number(price))}/L = {formatCurrency(total)}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
</CardContent>
|
|||
|
|
<CardFooter className="flex justify-between">
|
|||
|
|
<Button type="button" variant="ghost" onClick={() => router.back()}>Cancel</Button>
|
|||
|
|
<Button type="submit" disabled={pending}>
|
|||
|
|
{pending ? "Saving…" : isEdit ? "Update purchase" : "Create purchase"}
|
|||
|
|
</Button>
|
|||
|
|
</CardFooter>
|
|||
|
|
</form>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|