"use client" import * as React from "react" import { CalendarClockIcon, DollarSignIcon, EyeIcon, PackageIcon, PlusIcon, TrashIcon, TruckIcon } from "lucide-react" import { toast } from "sonner" import { createGrn, getGrn, getGrns, getProducts, getSuppliers } from "@/lib/api/inventory" import type { CreateGrnInput, GoodsReceivedNote, GrnItemInput } from "@/lib/models/inventory" import { formatCurrency } from "@/lib/format" import { useFetch } from "@/lib/use-fetch" import { useMutation } from "@/lib/use-mutation" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Spinner } from "@/components/ui/spinner" import { Textarea } from "@/components/ui/textarea" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { DataTable, type DataTableColumn } from "@/components/hexa/data-table" import { PageSkeleton } from "@/components/hexa/page-skeleton" import { StatCard } from "@/components/hexa/stat-card" function formatDate(iso: string) { return new Date(iso).toLocaleString(undefined, { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }) } interface GrnLine { productId: string quantity: string unitCost: string } interface CreateGrnDialogProps { open: boolean onOpenChange: (open: boolean) => void onCreated: () => void } function CreateGrnDialog({ open, onOpenChange, onCreated }: CreateGrnDialogProps) { const suppliersFetch = useFetch(() => getSuppliers(false)) const suppliers = suppliersFetch.data ?? [] const productsFetch = useFetch(() => getProducts(false)) const products = productsFetch.data ?? [] const [supplierId, setSupplierId] = React.useState("") const [notes, setNotes] = React.useState("") const [lines, setLines] = React.useState([{ productId: "", quantity: "1", unitCost: "0" }]) const { busy, run } = useMutation() const [prevOpen, setPrevOpen] = React.useState(open) if (open !== prevOpen) { setPrevOpen(open) if (open) { setSupplierId("") setNotes("") setLines([{ productId: "", quantity: "1", unitCost: "0" }]) } } function updateLine(idx: number, patch: Partial) { setLines((prev) => prev.map((l, i) => (i === idx ? { ...l, ...patch } : l))) } function addLine() { setLines((prev) => [...prev, { productId: "", quantity: "1", unitCost: "0" }]) } function removeLine(idx: number) { setLines((prev) => prev.filter((_, i) => i !== idx)) } const total = lines.reduce((sum, l) => sum + (Number(l.quantity) || 0) * (Number(l.unitCost) || 0), 0) const linesValid = lines.length > 0 && lines.every((l) => !!l.productId && Number(l.quantity) > 0 && Number(l.unitCost) >= 0) const canSubmit = linesValid && !busy async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!canSubmit) return const input: CreateGrnInput = { supplierId: supplierId || undefined, notes: notes.trim() || undefined, items: lines.map((l) => ({ productId: l.productId, quantity: Number(l.quantity), unitCost: Number(l.unitCost), })), } try { await run(async () => { await createGrn(input) toast.success("GRN recorded", { description: "Stock levels were updated." }) onCreated() }) onOpenChange(false) } catch { // run() already toasted the real error; keep the dialog open for retry. } } return (
Record Goods Received Stock coming in from a supplier — increases each product's quantity on hand.
Product Quantity Unit Cost (Rs.)
{lines.map((line, idx) => (
updateLine(idx, { quantity: e.target.value })} required /> updateLine(idx, { unitCost: e.target.value })} required />
))}
Total {formatCurrency(total)}