Files
inven/components/sales-order-form.tsx

125 lines
4.5 KiB
TypeScript
Raw Permalink Normal View History

2026-03-23 16:59:34 -05:00
"use client";
import { useState } from "react";
import { createSalesOrder } from "@/lib/actions";
import type { ContactRow, OrderItemOption } from "@/lib/types";
type SalesOrderFormProps = {
customers: ContactRow[];
items: OrderItemOption[];
};
type DraftLine = {
rowId: number;
partId: number | "";
quantity: number;
amount: number;
};
function createEmptyLine(rowId: number): DraftLine {
return { rowId, partId: "", quantity: 1, amount: 0 };
}
export function SalesOrderForm({ customers, items }: SalesOrderFormProps) {
const [lines, setLines] = useState<DraftLine[]>([createEmptyLine(1)]);
function updateLine(rowId: number, patch: Partial<DraftLine>) {
setLines((current) =>
current.map((line) => {
if (line.rowId !== rowId) {
return line;
}
const nextLine = { ...line, ...patch };
if (patch.partId && typeof patch.partId === "number") {
const item = items.find((candidate) => candidate.id === patch.partId);
if (item) {
nextLine.amount = item.salePrice;
}
}
return nextLine;
})
);
}
function addLine() {
setLines((current) => [...current, createEmptyLine(Date.now())]);
}
function removeLine(rowId: number) {
setLines((current) => (current.length === 1 ? current : current.filter((line) => line.rowId !== rowId)));
}
const payload = JSON.stringify(
lines
.filter((line) => typeof line.partId === "number")
.map((line) => ({ partId: line.partId, quantity: line.quantity, amount: line.amount }))
);
return (
<form action={createSalesOrder} className="form-grid">
<div className="form-row">
<label htmlFor="customerId">Customer</label>
<select className="select" id="customerId" name="customerId">
{customers.map((customer) => (
<option key={customer.id} value={customer.id}>
{customer.code} - {customer.name}
</option>
))}
</select>
</div>
<div className="form-row">
<label>Line Items</label>
<div className="grid">
{lines.map((line, index) => (
<div className="panel" key={line.rowId}>
<div className="form-grid">
<div className="form-row">
<label htmlFor={`so-part-${line.rowId}`}>Item {index + 1}</label>
<select
className="select"
id={`so-part-${line.rowId}`}
value={line.partId}
onChange={(event) => updateLine(line.rowId, { partId: event.target.value ? Number(event.target.value) : "" })}
>
<option value="">Select inventory item</option>
{items.map((item) => (
<option key={item.id} value={item.id}>
{item.sku} - {item.name} ({item.quantityOnHand} {item.unitOfMeasure} on hand)
</option>
))}
</select>
</div>
<div className="form-row">
<label htmlFor={`so-qty-${line.rowId}`}>Quantity</label>
<input className="input" id={`so-qty-${line.rowId}`} type="number" min="0.01" step="0.01" value={line.quantity} onChange={(event) => updateLine(line.rowId, { quantity: Number(event.target.value) || 0 })} />
</div>
<div className="form-row">
<label htmlFor={`so-price-${line.rowId}`}>Unit Price</label>
<input className="input" id={`so-price-${line.rowId}`} type="number" min="0" step="0.01" value={line.amount} onChange={(event) => updateLine(line.rowId, { amount: Number(event.target.value) || 0 })} />
</div>
<button className="button secondary" type="button" onClick={() => removeLine(line.rowId)}>
Remove Line
</button>
</div>
</div>
))}
</div>
</div>
<div className="inline-actions">
<button className="button secondary" type="button" onClick={addLine}>
Add Line
</button>
</div>
<input type="hidden" name="lineItems" value={payload} />
<div className="form-row">
<label htmlFor="sales-notes">Notes</label>
<textarea className="textarea" id="sales-notes" name="notes" />
</div>
<button className="button" type="submit">
Save Sales Order
</button>
</form>
);
}