- Delete /inventory/items/[id] — items expand inline in the list - Move SiteSelect from deleted [id] folder to components/inventory/site-select - Fix admin product detail page import to use new shared path - Fix items-table: Fragment key prop, restore Link import, plain text item names - Fix vendor-items-table: remove broken link to deleted item detail page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
502 lines
25 KiB
JavaScript
502 lines
25 KiB
JavaScript
/* Pelagia Portal — Approvals, PO Detail, New PO */
|
||
|
||
/* ═══════════════════ APPROVAL QUEUE ═══════════════════ */
|
||
const ApprovalsPage = ({ go }) => (
|
||
<>
|
||
<div className="page-head">
|
||
<div>
|
||
<Crumbs items={["Purchase Orders", "Approvals"]} />
|
||
<h1 className="page-title">Approval queue
|
||
<span style={{ color: "var(--muted)", fontWeight: 400, marginLeft: 10, fontSize: 16 }}>· {APPROVAL_QUEUE.length} pending</span>
|
||
</h1>
|
||
<div className="page-sub">POs awaiting manager decision · oldest first</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="filter-bar">
|
||
<div style={{ display: "flex", alignItems: "center", gap: 6, padding: "0 10px", height: 28, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 6, flex: "0 0 240px" }}>
|
||
<Icon name="search" size={12} />
|
||
<input className="mono" style={{ border: 0, outline: 0, flex: 1, background: "transparent", fontSize: 11.5 }} placeholder="Search PO #, submitter, title" />
|
||
</div>
|
||
<select className="select"><option>All vessels</option>{VESSELS.map(v => <option key={v.id}>{v.name}</option>)}</select>
|
||
<select className="select"><option>Any submitter</option>{["Rajesh Pillai","Fatima Sheikh","Dev Shah"].map(s => <option key={s}>{s}</option>)}</select>
|
||
<input className="input" type="date" defaultValue="2026-05-01" style={{ width: 140 }} />
|
||
<span className="faint" style={{ fontSize: 11.5, marginLeft: "auto" }}>Sorted by submitted date</span>
|
||
</div>
|
||
|
||
<Card flush>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th>PO Number</th><th>Title</th><th>Submitter</th><th>Vessel</th><th>Submitted</th><th className="num">Amount</th><th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{APPROVAL_QUEUE.map(o => (
|
||
<tr key={o.id} className="clickable" onClick={() => go("approval-detail", o.id)}>
|
||
<td><span className="po-num">{o.id}</span></td>
|
||
<td>{o.title}</td>
|
||
<td>{o.submitter}</td>
|
||
<td className="muted">{o.vessel}</td>
|
||
<td className="muted">{o.submitted}</td>
|
||
<td className="num">{inr(o.amount)}</td>
|
||
<td style={{ textAlign: "right" }}>
|
||
<span style={{ color: "var(--primary-ink)", fontSize: 12 }}>Review →</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</Card>
|
||
</>
|
||
);
|
||
|
||
/* ═══════════════════ PO DETAIL (shared) ═══════════════════ */
|
||
const ItemsTable = ({ items }) => {
|
||
if (!items?.length) return <div className="empty-state">No line items.</div>;
|
||
const taxable = items.reduce((s, i) => s + i.qty * i.price, 0);
|
||
const gst = items.reduce((s, i) => s + i.qty * i.price * i.gst / 100, 0);
|
||
return (
|
||
<>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Item</th><th>Description</th><th className="num">Qty</th><th>Unit</th>
|
||
<th className="num">Unit Price</th><th className="num">GST</th><th className="num">Total</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{items.map((i, idx) => (
|
||
<tr key={idx}>
|
||
<td>{i.name}</td>
|
||
<td className="muted">{i.desc}</td>
|
||
<td className="num">{i.qty}</td>
|
||
<td className="muted">{i.unit}</td>
|
||
<td className="num">{inrFull(i.price)}</td>
|
||
<td className="num muted">{i.gst}%</td>
|
||
<td className="num">{inrFull(i.qty * i.price * (1 + i.gst/100))}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
<div style={{ display: "flex", justifyContent: "flex-end", padding: "12px 16px", gap: 32, borderTop: "1px solid var(--line)", background: "var(--paper-2)" }}>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>TAXABLE</div>
|
||
<div className="mono" style={{ fontSize: 14 }}>{inrFull(taxable)}</div>
|
||
</div>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>GST</div>
|
||
<div className="mono" style={{ fontSize: 14 }}>{inrFull(gst)}</div>
|
||
</div>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>GRAND TOTAL</div>
|
||
<div className="mono" style={{ fontSize: 16, fontWeight: 500 }}>{inrFull(taxable + gst)}</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
const PODetailBase = ({ order, go, role, isApproval }) => {
|
||
const isOwner = order.submitter === "Rajesh Pillai" && role === "TECHNICAL";
|
||
const isManager = role === "MANAGER" || role === "SUPERUSER";
|
||
const showEdit = ["DRAFT", "EDITS_REQUESTED"].includes(order.status) && (isOwner || role === "SUPERUSER");
|
||
const showReceipt = order.status === "PAID_DELIVERED" && (isOwner || role === "SUPERUSER");
|
||
const showVendorPicker = order.status === "VENDOR_ID_PENDING";
|
||
|
||
return (
|
||
<>
|
||
<div className="page-head">
|
||
<div>
|
||
<Crumbs items={[isApproval ? "Approvals" : "Purchase Orders", order.id]} />
|
||
<h1 className="page-title">{order.title}</h1>
|
||
<div className="page-sub">
|
||
Created by {order.submitter} · {order.created || order.submitted}
|
||
</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button className="btn"><Icon name="download" /> Export PDF</button>
|
||
{showEdit && <button className="btn"><Icon name="edit" /> Edit</button>}
|
||
{!isApproval && order.status === "DRAFT" && <button className="btn danger"><Icon name="trash" /> Discard</button>}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="detail-band">
|
||
<div className="detail-band-left">
|
||
<div className="po-id">{order.id}</div>
|
||
<Badge status={order.status} />
|
||
{order.project && <span className="role-badge">{order.project}</span>}
|
||
</div>
|
||
<div style={{ textAlign: "right" }}>
|
||
<div className="muted" style={{ fontSize: 11 }}>GRAND TOTAL</div>
|
||
<div className="mono" style={{ fontSize: 22, letterSpacing: "-0.01em" }}>{inr(order.amount)}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{order.managerNote && (
|
||
<div className="alert">
|
||
<Icon name="bell" />
|
||
<div>
|
||
<strong>Manager note · Anjali Krishnan:</strong> {order.managerNote}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="detail-layout">
|
||
{/* MAIN */}
|
||
<div>
|
||
{isApproval && (
|
||
<Card title="Manager actions" className="action-panel" style={{ marginBottom: 16 }}>
|
||
<ApprovalActions />
|
||
</Card>
|
||
)}
|
||
|
||
{showVendorPicker && (
|
||
<Card title="Vendor selection required" style={{ marginBottom: 16 }}>
|
||
<div className="muted" style={{ marginBottom: 10, fontSize: 12.5 }}>
|
||
The manager has requested that a vendor be selected before approval.
|
||
</div>
|
||
<div className="form-row cols-2" style={{ alignItems: "end" }}>
|
||
<div className="field">
|
||
<label className="field-label">Vendor</label>
|
||
<select className="select">
|
||
<option>Select vendor…</option>
|
||
{VENDORS.filter(v => v.active).map(v => <option key={v.id}>{v.name}</option>)}
|
||
</select>
|
||
</div>
|
||
<button className="btn maritime">Submit & return to manager</button>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
|
||
<h2 className="section-title">Summary</h2>
|
||
<Card>
|
||
<dl className="kv">
|
||
<dt>Vessel</dt> <dd>{order.vessel}</dd>
|
||
<dt>Account</dt> <dd className="mono">{order.account}</dd>
|
||
<dt>Vendor</dt> <dd>{order.vendor || <em className="muted">Not yet assigned</em>}</dd>
|
||
<dt>Project code</dt> <dd>{order.project ? <span className="mono">{order.project}</span> : <em className="muted">—</em>}</dd>
|
||
<dt>Date required</dt> <dd>{order.required || "—"}</dd>
|
||
<dt>Currency</dt> <dd>INR (₹)</dd>
|
||
</dl>
|
||
</Card>
|
||
|
||
<h2 className="section-title">Line items</h2>
|
||
<Card flush>
|
||
<ItemsTable items={order.items?.length ? order.items : ORDERS[0].items} />
|
||
</Card>
|
||
|
||
<h2 className="section-title">Terms & conditions</h2>
|
||
<Card>
|
||
<dl className="kv">
|
||
<dt>Delivery</dt> <dd>{order.terms?.delivery || ORDERS[0].terms.delivery}</dd>
|
||
<dt>Dispatch</dt> <dd>{order.terms?.dispatch || ORDERS[0].terms.dispatch}</dd>
|
||
<dt>Inspection</dt> <dd>{order.terms?.inspection || ORDERS[0].terms.inspection}</dd>
|
||
<dt>Transit insurance</dt> <dd>{order.terms?.insurance || ORDERS[0].terms.insurance}</dd>
|
||
<dt>Payment terms</dt> <dd>{order.terms?.payment || ORDERS[0].terms.payment}</dd>
|
||
<dt>Other</dt> <dd>{order.terms?.others || ORDERS[0].terms.others}</dd>
|
||
</dl>
|
||
</Card>
|
||
|
||
<h2 className="section-title">Documents</h2>
|
||
<Card>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||
{(order.docs || ORDERS[0].docs).map((d, i) => (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 10px", background: "var(--paper-2)", borderRadius: 6 }}>
|
||
<Icon name="paperclip" />
|
||
<span style={{ fontSize: 12.5 }}>{d.name}</span>
|
||
<span className="muted" style={{ fontSize: 11.5 }}>{d.size}</span>
|
||
<span style={{ flex: 1 }} />
|
||
<button className="btn sm"><Icon name="download" size={11} /> Download</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
{showReceipt && (
|
||
<>
|
||
<h2 className="section-title">Confirm receipt</h2>
|
||
<Card>
|
||
<div className="muted" style={{ marginBottom: 12, fontSize: 12.5 }}>
|
||
Goods have been marked as paid and dispatched. Upload the delivery receipt to close this PO.
|
||
</div>
|
||
<div className="form-row" style={{ gap: 10 }}>
|
||
<div style={{ padding: 22, border: "1.5px dashed var(--line-2)", borderRadius: 8, textAlign: "center", color: "var(--muted)" }}>
|
||
<Icon name="upload" /> Drop delivery receipt file or <span style={{ color: "var(--primary-ink)" }}>browse</span>
|
||
</div>
|
||
<textarea className="textarea" placeholder="Notes (optional) — note any damage or discrepancy"></textarea>
|
||
<button className="btn maritime" style={{ alignSelf: "flex-end" }}>Confirm receipt</button>
|
||
</div>
|
||
</Card>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
{/* SIDEBAR */}
|
||
<div>
|
||
<Card title="Timestamps">
|
||
<dl className="kv" style={{ gridTemplateColumns: "100px 1fr", gap: "6px 8px", fontSize: 12 }}>
|
||
<dt>Created</dt> <dd className="mono">{order.created || "May 10, 2026"}</dd>
|
||
<dt>Submitted</dt> <dd className="mono">{order.submitted || "—"}</dd>
|
||
<dt>Approved</dt> <dd className="mono">{order.approved || "—"}</dd>
|
||
<dt>Paid</dt> <dd className="mono">{order.paid || "—"}</dd>
|
||
<dt>Closed</dt> <dd className="mono">{order.closed || "—"}</dd>
|
||
</dl>
|
||
</Card>
|
||
|
||
<div style={{ height: 14 }} />
|
||
|
||
<Card title="Audit trail">
|
||
<div>
|
||
{(order.audit || ORDERS[0].audit).map((a, i) => (
|
||
<div className="timeline-stop done" key={i}>
|
||
<div className="dot" />
|
||
<div>
|
||
<div className="actor">{a.who}</div>
|
||
<div className="action">{a.what}</div>
|
||
</div>
|
||
<div className="when">{a.when.split(" · ")[1] || a.when}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
const ApprovalActions = () => {
|
||
const [mode, setMode] = useS(null); // null | edits | reject | note | vendor
|
||
|
||
if (mode === null) {
|
||
return (
|
||
<div className="action-row">
|
||
<button className="btn maritime"><Icon name="check" /> Approve</button>
|
||
<button className="btn" onClick={() => setMode("note")}>Approve with Note</button>
|
||
<button className="btn" onClick={() => setMode("edits")}>Request Edits</button>
|
||
<button className="btn" onClick={() => setMode("vendor")}>Request Vendor ID</button>
|
||
<span style={{ flex: 1 }} />
|
||
<button className="btn danger" onClick={() => setMode("reject")}>Reject</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const titles = {
|
||
edits: ["Request edits", "Describe what needs to change before approval.", "var(--st-edits-fg)", "Send back to submitter"],
|
||
reject: ["Reject PO", "Provide a reason — this terminates the PO.", "var(--st-rejected-fg)", "Reject"],
|
||
note: ["Approve with note", "Optional note visible to the submitter.", "var(--primary-ink)", "Approve"],
|
||
vendor: ["Request vendor selection", "Note for submitter (optional). PO returns here once a vendor is chosen.", "var(--st-vendor-fg)", "Send back for vendor"],
|
||
};
|
||
const [title, sub, color, cta] = titles[mode];
|
||
return (
|
||
<div className="form-row" style={{ gap: 10 }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||
<div>
|
||
<div style={{ fontWeight: 600, fontSize: 13 }}>{title}</div>
|
||
<div className="muted" style={{ fontSize: 12 }}>{sub}</div>
|
||
</div>
|
||
<button className="btn sm" onClick={() => setMode(null)}>Cancel</button>
|
||
</div>
|
||
<textarea className="textarea" placeholder={mode === "vendor" ? "e.g. Please use the verified vendor in Kochi for faster delivery." : "Required reason / note…"}></textarea>
|
||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||
<button className="btn" style={{ background: color, color: "var(--paper)", borderColor: color }}>{cta}</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
/* ═══════════════════ NEW PO ═══════════════════ */
|
||
const NewPOPage = ({ go }) => {
|
||
const [rows, setRows] = useS([
|
||
{ name: "Marine Bearing 6310-2RS", desc: "50×110×27mm, sealed", qty: 24, unit: "ea", size: "", price: 4250, gst: 18, suggest: false },
|
||
{ name: "Cylinder Head Gasket Set", desc: "MAN B&W L28/32H", qty: 4, unit: "set", size: "", price: 18750, gst: 18, suggest: false },
|
||
]);
|
||
const [suggestIdx, setSuggestIdx] = useS(-1);
|
||
|
||
const taxable = rows.reduce((s, r) => s + (Number(r.qty) || 0) * (Number(r.price) || 0), 0);
|
||
const gst = rows.reduce((s, r) => s + (Number(r.qty) || 0) * (Number(r.price) || 0) * (Number(r.gst) || 0) / 100, 0);
|
||
|
||
const addRow = () => setRows([...rows, { name: "", desc: "", qty: 1, unit: "ea", size: "", price: 0, gst: 18 }]);
|
||
const updateRow = (idx, key, val) => setRows(rows.map((r, i) => i === idx ? { ...r, [key]: val } : r));
|
||
const removeRow = (idx) => setRows(rows.filter((_, i) => i !== idx));
|
||
|
||
return (
|
||
<>
|
||
<div className="page-head">
|
||
<div>
|
||
<Crumbs items={["Purchase Orders", "New PO"]} />
|
||
<h1 className="page-title">New purchase order</h1>
|
||
<div className="page-sub">Fill the four sections below. You can save as draft at any time.</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button className="btn" onClick={() => go("my-orders")}>Cancel</button>
|
||
<button className="btn">Save as Draft</button>
|
||
<button className="btn maritime"><Icon name="check" /> Submit for Approval</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 className="section-title">1 · Header</h2>
|
||
<Card>
|
||
<div className="form-row" style={{ gap: 14 }}>
|
||
<div className="field">
|
||
<label className="field-label">Title <span className="req">*</span></label>
|
||
<input className="input" defaultValue="Engine room bearings — Q2 replenishment" />
|
||
</div>
|
||
<div className="form-row cols-3">
|
||
<div className="field">
|
||
<label className="field-label">Vessel <span className="req">*</span></label>
|
||
<select className="select"><option>MV Pelagia Voyager</option>{VESSELS.slice(1).map(v => <option key={v.id}>{v.name}</option>)}</select>
|
||
</div>
|
||
<div className="field">
|
||
<label className="field-label">Account <span className="req">*</span></label>
|
||
<select className="select"><option>ENGINE — Engine Department</option>{ACCOUNTS.slice(0,4).map(a => <option key={a.id}>{a.code} — {a.name}</option>)}</select>
|
||
</div>
|
||
<div className="field">
|
||
<label className="field-label">Vendor (optional)</label>
|
||
<select className="select"><option>Mahalakshmi Marine Stores</option>{VENDORS.slice(1).map(v => <option key={v.id}>{v.name}</option>)}<option>+ Add new vendor…</option></select>
|
||
</div>
|
||
</div>
|
||
<div className="form-row cols-3">
|
||
<div className="field">
|
||
<label className="field-label">Date required</label>
|
||
<input className="input" type="date" defaultValue="2026-05-28" />
|
||
</div>
|
||
<div className="field">
|
||
<label className="field-label">Project code</label>
|
||
<input className="input mono" defaultValue="ER-MAINT-2026" />
|
||
</div>
|
||
<div className="field">
|
||
<label className="field-label">Currency</label>
|
||
<select className="select"><option>INR (₹)</option><option>USD ($)</option><option>EUR (€)</option></select>
|
||
</div>
|
||
</div>
|
||
<div className="field">
|
||
<label className="field-label">Description / remarks</label>
|
||
<textarea className="textarea" defaultValue="Second-quarter replenishment of bearings and gasket sets for the engine room. Includes spares for the L28/32H mains and auxiliaries."></textarea>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<h2 className="section-title">2 · Line items</h2>
|
||
<Card flush>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th style={{ width: "26%" }}>Name</th>
|
||
<th>Description</th>
|
||
<th className="num" style={{ width: 70 }}>Qty</th>
|
||
<th style={{ width: 60 }}>Unit</th>
|
||
<th className="num" style={{ width: 110 }}>Unit price</th>
|
||
<th className="num" style={{ width: 70 }}>GST %</th>
|
||
<th className="num" style={{ width: 110 }}>Total</th>
|
||
<th style={{ width: 30 }}></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{rows.map((r, idx) => (
|
||
<React.Fragment key={idx}>
|
||
<tr>
|
||
<td>
|
||
<input className="input" style={{ height: 28, fontSize: 12.5 }} value={r.name}
|
||
onChange={e => updateRow(idx, "name", e.target.value)}
|
||
onFocus={() => setSuggestIdx(idx)}
|
||
onBlur={() => setTimeout(() => setSuggestIdx(-1), 200)} />
|
||
</td>
|
||
<td>
|
||
<input className="input" style={{ height: 28, fontSize: 12.5 }} value={r.desc}
|
||
onChange={e => updateRow(idx, "desc", e.target.value)} />
|
||
</td>
|
||
<td><input className="input num mono" style={{ height: 28, fontSize: 12.5, textAlign: "right" }} value={r.qty} onChange={e => updateRow(idx, "qty", e.target.value)} /></td>
|
||
<td><input className="input" style={{ height: 28, fontSize: 12.5 }} value={r.unit} onChange={e => updateRow(idx, "unit", e.target.value)} /></td>
|
||
<td><input className="input mono" style={{ height: 28, fontSize: 12.5, textAlign: "right" }} value={r.price} onChange={e => updateRow(idx, "price", e.target.value)} /></td>
|
||
<td><input className="input mono" style={{ height: 28, fontSize: 12.5, textAlign: "right" }} value={r.gst} onChange={e => updateRow(idx, "gst", e.target.value)} /></td>
|
||
<td className="num mono">{inr((Number(r.qty)||0) * (Number(r.price)||0) * (1 + (Number(r.gst)||0)/100))}</td>
|
||
<td><button className="btn icon sm" onClick={() => removeRow(idx)} title="Remove"><Icon name="trash" size={11} /></button></td>
|
||
</tr>
|
||
{suggestIdx === idx && r.name && (
|
||
<tr>
|
||
<td colSpan="8" style={{ padding: "0 16px 8px", background: "var(--paper-2)" }}>
|
||
<div style={{ fontSize: 11, color: "var(--muted)", padding: "6px 0" }}>
|
||
<strong style={{ color: "var(--ink)" }}>Last seen at:</strong>
|
||
<span className="dot-sep">·</span> Mahalakshmi Marine Stores <span className="mono">₹4,250</span>
|
||
<span className="dot-sep">·</span> Coastline Engineering <span className="mono">₹4,380</span>
|
||
<span className="dot-sep">·</span> Konark Industrial Spares <span className="mono">₹4,520</span>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
)}
|
||
</React.Fragment>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
<div style={{ padding: 12, borderTop: "1px solid var(--line)", display: "flex", gap: 12, alignItems: "center" }}>
|
||
<button className="btn sm" onClick={addRow}><Icon name="plus" size={11} /> Add line item</button>
|
||
<span className="faint" style={{ fontSize: 11.5 }}>Tip: start typing a product name to see vendor price hints</span>
|
||
</div>
|
||
<div style={{ display: "flex", justifyContent: "flex-end", padding: "12px 16px", gap: 32, borderTop: "1px solid var(--line)", background: "var(--paper-2)" }}>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>TAXABLE</div>
|
||
<div className="mono" style={{ fontSize: 14 }}>{inrFull(taxable)}</div>
|
||
</div>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>GST</div>
|
||
<div className="mono" style={{ fontSize: 14 }}>{inrFull(gst)}</div>
|
||
</div>
|
||
<div>
|
||
<div className="muted" style={{ fontSize: 11 }}>GRAND TOTAL</div>
|
||
<div className="mono" style={{ fontSize: 17, fontWeight: 500 }}>{inrFull(taxable + gst)}</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<h2 className="section-title">3 · Terms & conditions</h2>
|
||
<Card>
|
||
<div className="form-row cols-2" style={{ gap: 14 }}>
|
||
{[
|
||
["Delivery", "FOB Cochin Port. Vendor responsible until cargo cleared at gate."],
|
||
["Dispatch", "Within 14 days of approval."],
|
||
["Inspection", "Joint inspection at vendor warehouse prior to dispatch."],
|
||
["Transit insurance", "Transit insurance included in vendor scope."],
|
||
["Payment terms", "30 days from receipt of goods and invoice."],
|
||
["Other", "All items to be packed with desiccant and shrink-wrapped."],
|
||
].map(([k, v]) => (
|
||
<div className="field" key={k}>
|
||
<label className="field-label">{k}</label>
|
||
<textarea className="textarea" style={{ minHeight: 56 }} defaultValue={v}></textarea>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
<h2 className="section-title">4 · Documents</h2>
|
||
<Card>
|
||
<div style={{ padding: 22, border: "1.5px dashed var(--line-2)", borderRadius: 8, textAlign: "center", color: "var(--muted)" }}>
|
||
<Icon name="upload" /> Drop files here or <span style={{ color: "var(--primary-ink)" }}>browse</span>
|
||
<div className="faint" style={{ fontSize: 11.5, marginTop: 4 }}>Quotations, technical sheets, requisitions. Max 25 MB per file.</div>
|
||
</div>
|
||
<div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 6 }}>
|
||
{[
|
||
["Vendor Quotation Q-2841.pdf", "412 KB"],
|
||
["Engine Inspection Report.pdf", "1.8 MB"],
|
||
].map(([n, s]) => (
|
||
<div key={n} style={{ display: "flex", alignItems: "center", gap: 10, padding: "7px 10px", background: "var(--paper-2)", borderRadius: 6, fontSize: 12.5 }}>
|
||
<Icon name="paperclip" />
|
||
<span>{n}</span>
|
||
<span className="muted" style={{ fontSize: 11.5 }}>{s}</span>
|
||
<span style={{ flex: 1 }} />
|
||
<button className="btn sm icon"><Icon name="trash" size={11} /></button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
<div style={{ marginTop: 22, display: "flex", justifyContent: "flex-end", gap: 8 }}>
|
||
<button className="btn">Save as Draft</button>
|
||
<button className="btn maritime"><Icon name="check" /> Submit for Approval</button>
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
Object.assign(window, { ApprovalsPage, PODetailBase, NewPOPage });
|