import { useEffect, useState, type FormEvent } from 'react' import { useNavigate } from 'react-router-dom' import { catalog, partners, salesOrders } from '@/api/client' import type { Item, Partner } from '@/types/api' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' import { useT } from '@/i18n/LocaleContext' interface LineInput { itemCode: string quantity: string unitPrice: string } export function CreateSalesOrderPage() { const navigate = useNavigate() const t = useT() const [code, setCode] = useState('') const [partnerCode, setPartnerCode] = useState('') const [currencyCode] = useState('USD') const [lines, setLines] = useState([ { itemCode: '', quantity: '', unitPrice: '' }, ]) const [items, setItems] = useState([]) const [partnerList, setPartnerList] = useState([]) const [ext, setExt] = useState>({}) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) useEffect(() => { Promise.all([catalog.listItems(), partners.list()]).then(([i, p]) => { setItems(i) const customers = p.filter((x) => x.type === 'CUSTOMER' || x.type === 'BOTH') setPartnerList(customers) if (customers.length > 0 && !partnerCode) setPartnerCode(customers[0].code) }) }, []) // eslint-disable-line react-hooks/exhaustive-deps const addLine = () => setLines([...lines, { itemCode: items[0]?.code ?? '', quantity: '1', unitPrice: '1.00' }]) const removeLine = (idx: number) => { if (lines.length <= 1) return setLines(lines.filter((_, i) => i !== idx)) } const updateLine = (idx: number, field: keyof LineInput, value: string) => { const next = [...lines] next[idx] = { ...next[idx], [field]: value } setLines(next) } const orderTotal = lines.reduce( (sum, l) => sum + Number(l.quantity || 0) * Number(l.unitPrice || 0), 0, ) const onSubmit = async (e: FormEvent) => { e.preventDefault() setError(null) setSubmitting(true) try { const extPayload = Object.keys(ext).length > 0 ? ext : undefined const created = await salesOrders.create({ code, partnerCode, orderDate: new Date().toISOString().slice(0, 10), currencyCode, lines: lines.map((l, i) => ({ lineNo: i + 1, itemCode: l.itemCode, quantity: Number(l.quantity), unitPrice: Number(l.unitPrice), currencyCode, })), ...(extPayload ? { ext: extPayload } : {}), } as Parameters[0]) navigate(`/sales-orders/${created.id}`) } catch (err: unknown) { setError(err instanceof Error ? err : new Error(String(err))) } finally { setSubmitting(false) } } /* ── label cell (light blue bg, right-aligned) ── */ const labelCls = 'bg-sky-50 border-b border-r border-slate-200 px-3 py-2 text-right text-slate-600 whitespace-nowrap text-sm' /* ── value cell ── */ const valueCls = 'border-b border-slate-200 px-2 py-1' const valueClsR = `${valueCls} border-r` // value cell with right border (cols 1 & 3) /* ── inline input ── */ const inputCls = 'w-full border-0 outline-none text-sm py-0.5 bg-transparent' return (
{/* ════════ Title bar ════════ */}

{t('page.salesOrder.create.title')}

{/* ════════ Top toolbar ════════ */}
{/* ════════ Scrollable body ════════ */}
{/* ════════ Form header — dense grid ════════ */}
{/* Row 1 */}
* {t('label.orderCode')}
setCode(e.target.value)} placeholder="SO-2026-0003" className={inputCls} />
{t('label.orderDate')}
{/* Row 2 */}
* {t('label.customer')}
{t('label.currency')}
{currencyCode}
{/* Row 3 — system / auto fields */}
{t('label.createdBy')}
{t('label.autoGenerated')}
{t('label.createdAt')}
{t('label.autoGenerated')}
{/* ════════ Tabs ════════ */}
{/* ════════ Line items — spreadsheet table ════════ */}
{lines.map((line, idx) => ( ))}
# * {t('label.item')} * {t('label.quantity')} * {t('label.unitPrice')} {t('label.lineTotal')} {t('label.actions')}
{idx + 1} updateLine(idx, 'quantity', e.target.value)} className="w-full border-0 outline-none text-sm py-1 text-right bg-transparent" /> updateLine(idx, 'unitPrice', e.target.value)} className="w-full border-0 outline-none text-sm py-1 text-right bg-transparent" /> {(Number(line.quantity || 0) * Number(line.unitPrice || 0)).toFixed(2)}
{t('label.orderTotal')} {orderTotal.toFixed(2)}
{/* ════════ Custom fields (Tier 1 ext) ════════ */} setExt((prev) => ({ ...prev, [k]: v }))} /> {/* ════════ Error ════════ */} {error && (
)}
) }