"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { createPo } from "./actions"; import type { Vendor } from "@prisma/client"; import { LineItemsEditor } from "@/components/po/po-line-items-editor"; import { FileUploader } from "@/components/po/file-uploader"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { VendorSelect } from "@/components/ui/vendor-select"; import { DeliveryLocationField } from "@/components/po/delivery-location-field"; import { ProjectCodeField } from "@/components/po/project-code-field"; import { PoTermsEditor } from "@/components/po/po-terms-editor"; import { UnsavedChangesGuard } from "@/components/po/unsaved-changes-guard"; import type { CatalogueCategory, PoTerm } from "@/lib/terms"; import { uploadAndLinkFiles } from "@/lib/upload-files"; import type { LineItemInput } from "@/lib/validations/po"; export type VesselOption = { id: string; code: string; name: string }; export type AccountGroup = { group: string; items: { id: string; code: string; name: string }[] }; export type CompanyOption = { id: string; name: string; code: string | null }; const INPUT_CLS = "w-full rounded-lg border border-neutral-300 px-3 py-2.5 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"; const EMPTY_LINE: LineItemInput = { name: "", description: "", quantity: 1, unit: "pc", size: "", unitPrice: 0, gstRate: 0.18 }; interface Props { vessels: VesselOption[]; accounts: AccountGroup[]; vendors: Vendor[]; companies: CompanyOption[]; deliveryOptions: string[]; projectCodeOptions: string[]; termsCatalogue: CatalogueCategory[]; defaultTerms: PoTerm[]; initialLineItems?: LineItemInput[]; initialVendorId?: string; initialVesselId?: string; initialCompanyId?: string; } export function NewPoForm({ vessels, accounts, vendors, companies, deliveryOptions, projectCodeOptions, termsCatalogue, defaultTerms, initialLineItems, initialVendorId, initialVesselId, initialCompanyId }: Props) { const router = useRouter(); const [lineItems, setLineItems] = useState( initialLineItems && initialLineItems.length > 0 ? initialLineItems : [EMPTY_LINE] ); const [files, setFiles] = useState([]); const [submitting, setSubmitting] = useState<"draft" | "submit" | null>(null); const [error, setError] = useState(""); const [multiAccount, setMultiAccount] = useState(false); const [defaultAccountId, setDefaultAccountId] = useState(""); const [terms, setTerms] = useState(defaultTerms); const [dirty, setDirty] = useState(false); const markDirty = () => setDirty(true); async function handleSubmit(intent: "draft" | "submit") { setSubmitting(intent); setError(""); const form = document.getElementById("po-form") as HTMLFormElement; const data = new FormData(form); data.set("intent", intent); data.set("termsJson", JSON.stringify(terms)); lineItems.forEach((item, i) => { data.set(`lineItems[${i}].name`, item.name); data.set(`lineItems[${i}].description`, item.description ?? ""); data.set(`lineItems[${i}].quantity`, String(item.quantity)); data.set(`lineItems[${i}].unit`, item.unit); data.set(`lineItems[${i}].size`, item.size ?? ""); data.set(`lineItems[${i}].unitPrice`, String(item.unitPrice)); data.set(`lineItems[${i}].gstRate`, String(item.gstRate ?? 0.18)); if (item.productId) data.set(`lineItems[${i}].productId`, item.productId); if (multiAccount && item.accountId) data.set(`lineItems[${i}].accountId`, item.accountId); }); const result = await createPo(data); if ("error" in result) { setError(result.error); setSubmitting(null); return; } if (files.length > 0) { const uploadErr = await uploadAndLinkFiles(result.id, files); if (uploadErr) { setError(uploadErr.error); setSubmitting(null); return; } } setDirty(false); // saved — don't warn on the redirect router.push(`/po/${result.id}`); } return (
e.preventDefault()} onInput={markDirty} onChange={markDirty}> {/* Order Information */}

Order Information

{companies.length > 0 && (
)}
0 ? "" : "sm:col-span-2"}>
{/* Cost Centre — vessels only */}
{/* Accounting Code — searchable */}
{ setDefaultAccountId(v); markDirty(); }} groups={accounts} placeholder="Search accounting code…" required />

Optional — can be back-dated or forward-dated. Defaults to the approved date if left blank.

{projectCodeOptions.length === 0 && (

No project codes configured yet — a Manager can add them under Administration → Project Codes.

)}
{/* Quotation Reference */}

Quotation Reference

{/* Requisition */}

Requisition

{/* Delivery */}

Delivery

{deliveryOptions.length === 0 && (

No delivery locations configured yet — a Manager can add them under Administration → Delivery Locations.

)}
{/* Line Items */}

Line Items

{ setLineItems(v); markDirty(); }} multiAccount={multiAccount} accounts={accounts} defaultAccountId={defaultAccountId || undefined} />
{/* Vendor */}

Vendor

{/* Terms & Conditions */}

Terms & Conditions

Add a category and pick (or type) a clause. Manage the catalogue under Administration → Terms & Conditions.

{ setTerms(v); markDirty(); }} catalogue={termsCatalogue} />
{/* Attachments */}

Attachments (optional)

{ setFiles(v); markDirty(); }} disabled={!!submitting} />
{error && (

{error}

)}
handleSubmit("draft")} saving={submitting === "draft"} /> ); }