Files
Order-Loop/components/OrderForm.tsx
2026-06-02 10:23:09 +03:00

258 lines
8.1 KiB
TypeScript

"use client";
import { useState, useTransition, type FormEvent } from "react";
import { CustomerChips } from "./CustomerChips";
import { ItemTable } from "./ItemTable";
import { SummaryPanel } from "./SummaryPanel";
import type {
OrderItemData,
OrderWithDetails,
OrderStatus,
FormState,
} from "@/lib/types";
interface OrderFormProps {
mode: "create" | "edit";
initialData?: OrderWithDetails;
serverAction: (
prevState: FormState | undefined,
formData: FormData,
) => Promise<FormState>;
disabled?: boolean;
}
export function OrderForm({
mode,
initialData,
serverAction,
disabled = false,
}: OrderFormProps) {
const isClosed = initialData?.status === "closed";
const isDisabled = disabled || isClosed;
const [title, setTitle] = useState(initialData?.title ?? "");
const [status, setStatus] = useState<OrderStatus>(
initialData?.status ?? "pending",
);
const [notes, setNotes] = useState(initialData?.notes ?? "");
const [customers, setCustomers] = useState<{ id: string; name: string }[]>(
initialData?.customers.map((c) => ({
id: c.customer.id,
name: c.customer.name,
})) ?? [],
);
const [items, setItems] = useState<OrderItemData[]>(
initialData?.items ?? [],
);
const [customerInput, setCustomerInput] = useState("");
const [actionMessage, setActionMessage] = useState<string | null>(null);
const [actionSuccess, setActionSuccess] = useState(false);
const [isPending, startTransition] = useTransition();
function handleAddCustomer() {
const name = customerInput.trim();
if (!name) return;
if (customers.some((c) => c.name.toLowerCase() === name.toLowerCase()))
return;
setCustomers((prev) => [...prev, { id: crypto.randomUUID(), name }]);
setCustomerInput("");
}
function handleRemoveCustomer(id: string) {
setCustomers((prev) => prev.filter((c) => c.id !== id));
setItems((prev) =>
prev.map((item) =>
item.customerId === id
? { ...item, customerId: null, customerName: null }
: item,
),
);
}
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
formData.set("title", title);
formData.set("status", status);
formData.set("notes", notes);
formData.set(
"customers",
JSON.stringify(customers.map((c) => ({ id: c.id, name: c.name }))),
);
formData.set(
"items",
JSON.stringify(
items.map((i) => ({
id: i.id,
customerId: i.customerId,
itemName: i.itemName,
initPrice: i.initPrice,
myPrice: i.myPrice,
taxRatio: i.taxRatio,
quantity: i.quantity,
})),
),
);
startTransition(async () => {
const result = await serverAction(undefined, formData);
if (result?.message) {
setActionMessage(result.message);
setActionSuccess(result.success ?? false);
}
});
}
const inputClass =
"border-2 border-border rounded-xl px-3 py-2 text-sm bg-bg text-fg focus:outline-none focus:border-accent disabled:opacity-50 disabled:cursor-not-allowed";
const labelClass = "text-sm font-medium text-fg mb-1";
const submitClass =
"bg-accent text-accent-on font-bold px-6 py-3 rounded-xl border-b-4 border-accent-active hover:bg-accent-hover active:translate-y-0.5 active:border-b-0 transition-all disabled:opacity-50 disabled:cursor-not-allowed";
return (
<form onSubmit={handleSubmit} className="w-full max-w-[1080px] mx-auto">
{/* Closed order lock banner */}
{isClosed && (
<div className="mb-6 p-4 rounded-xl border-2 border-border bg-surface text-fg font-medium">
This order is closed and read-only. Editing is disabled.
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content — left 2/3 on desktop */}
<div className="lg:col-span-2 flex flex-col gap-6">
{/* Title + Status row */}
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1 flex flex-col">
<label htmlFor="order-title" className={labelClass}>
Order Title
</label>
<input
id="order-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Order title"
className={inputClass}
disabled={isDisabled}
/>
</div>
<div className="flex flex-col sm:w-48">
<label htmlFor="order-status" className={labelClass}>
Status
</label>
<select
id="order-status"
value={status}
onChange={(e) => setStatus(e.target.value as OrderStatus)}
className={inputClass}
disabled={isDisabled}
>
<option value="pending">Pending</option>
<option value="purchased">Purchased</option>
<option value="delivered">Delivered</option>
<option value="closed">Closed</option>
</select>
</div>
</div>
{/* Customer section */}
<div className="flex flex-col gap-3">
<label className={labelClass}>Customers</label>
<div className="flex flex-col sm:flex-row gap-2">
<input
type="text"
value={customerInput}
onChange={(e) => setCustomerInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleAddCustomer();
}
}}
placeholder="Customer name"
className={`flex-1 ${inputClass}`}
disabled={isDisabled}
/>
<button
type="button"
onClick={handleAddCustomer}
disabled={isDisabled}
className={submitClass}
>
Add Customer
</button>
</div>
<CustomerChips
customers={customers}
onRemove={handleRemoveCustomer}
disabled={isDisabled}
/>
</div>
{/* Items section */}
<div className="flex flex-col gap-3">
<label className={labelClass}>Items ({items.length})</label>
<ItemTable
items={items}
customers={customers}
onItemsChange={setItems}
disabled={isDisabled}
/>
</div>
{/* Notes */}
<div className="flex flex-col">
<label htmlFor="order-notes" className={labelClass}>
Notes
</label>
<textarea
id="order-notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Order notes"
rows={4}
className={`${inputClass} resize-y`}
disabled={isDisabled}
/>
</div>
{/* Submit button */}
<div>
<button
type="submit"
disabled={isDisabled || isPending}
className={submitClass}
>
{isPending
? "Saving..."
: mode === "create"
? "Create Order"
: "Save Order"}
</button>
</div>
{/* Error/success message */}
{actionMessage && (
<p
aria-live="polite"
className={`text-sm font-medium ${actionSuccess ? "text-success" : "text-danger"}`}
>
{actionMessage}
</p>
)}
</div>
{/* Sidebar — right 1/3 on desktop, below on mobile */}
<div className="lg:col-span-1">
<SummaryPanel
items={items}
status={status}
customerCount={customers.length}
/>
</div>
</div>
</form>
);
}