pelagia-portal/design_handoff_pelagia_portal/pages-1.jsx
Hardik d769cae71e chore(inventory): remove item detail page; move SiteSelect to shared components
- 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>
2026-05-16 00:07:04 +05:30

263 lines
12 KiB
JavaScript

/* Pelagia Portal — page components */
const { useState: useS, useEffect: useE, useMemo: useM } = React;
/* ═══════════════════ LOGIN ═══════════════════ */
const LoginPage = ({ onLogin }) => (
<div className="login-shell">
<div className="login-card">
<div className="login-brand">
<div className="brand-mark" />
<div>
<div className="brand-name" style={{ fontWeight: 600 }}>Pelagia Portal</div>
<div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>Internal · Purchase Order Management</div>
</div>
</div>
<div className="form-row" style={{ gap: 12 }}>
<div className="field">
<label className="field-label">Email</label>
<input className="input" defaultValue="anjali.k@pelagia.co" />
</div>
<div className="field">
<label className="field-label">Password</label>
<input className="input" type="password" defaultValue="••••••••••" />
</div>
<button className="btn maritime" style={{ height: 36, marginTop: 6, justifyContent: "center" }} onClick={onLogin}>
Sign in
</button>
<div className="faint" style={{ fontSize: 11.5, textAlign: "center" }}>
Contact an administrator to request access.
</div>
</div>
</div>
</div>
);
/* ═══════════════════ DASHBOARD (Manager view) ═══════════════════ */
const Dashboard = ({ role, go }) => {
// Manager-rich view; switch headline content by role
const isMgr = role === "MANAGER" || role === "SUPERUSER";
const isAcct = role === "ACCOUNTS";
const isAuditor = role === "AUDITOR" || role === "ADMIN";
const isSubmitter = role === "TECHNICAL" || role === "MANNING";
const maxSpend = Math.max(...SPEND_TREND.map(s => s.v));
return (
<>
<div className="page-head">
<div>
<Crumbs items={["Dashboard"]} />
<h1 className="page-title">Good afternoon, Anjali</h1>
<div className="page-sub">Tuesday, May 12, 2026 · 14:38 IST · 3 vessels active at sea</div>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button className="btn" onClick={() => go("history")}><Icon name="download" /> Export</button>
<button className="btn maritime" onClick={() => go("po-new")}><Icon name="plus" /> New PO</button>
</div>
</div>
{isMgr && (
<>
<div className="stat-grid" style={{ marginBottom: 22 }}>
<Stat label="Awaiting approval" value="6" sub={<><span className="trend-up"> 2</span> vs last week</>} onClick={() => go("approvals")} />
<Stat label="Approved · May" value="42" sub={<span className="muted">across 5 vessels</span>} />
<Stat label="Approved spend" value="₹14.2L" sub={<><span className="trend-dn"> 8%</span> vs Apr</>} />
<Stat label="Avg cycle" value="3.4d" sub={<span className="muted">submit approve</span>} />
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
<Card title="Monthly spend — last 8 months" action={<span className="muted" style={{ fontSize: 11.5 }}> in thousands</span>}>
<div className="bar-chart" style={{ marginBottom: 22 }}>
{SPEND_TREND.map((s, i) => (
<div key={i} className="bar"
data-label={s.m}
style={{ height: (s.v / maxSpend * 130) + "px", background: i === SPEND_TREND.length - 1 ? "var(--primary)" : "var(--primary-soft)" }}>
<span className="bar-val">{s.v}</span>
</div>
))}
</div>
</Card>
<Card title="Spend by vessel — YTD" action={<span className="muted" style={{ fontSize: 11.5 }}> in thousands</span>}>
<div style={{ paddingTop: 4 }}>
{VESSEL_SPEND.map((v, i) => {
const max = Math.max(...VESSEL_SPEND.map(x => x.v));
return (
<div className="hbar-row" key={i}>
<div className="name">{v.name}</div>
<div className="track"><div className="fill" style={{ width: (v.v / max * 100) + "%" }} /></div>
<div className="v">{v.v}</div>
</div>
);
})}
</div>
</Card>
</div>
<Card title="Recently approved" action={<a className="muted" style={{ fontSize: 12 }} onClick={() => go("history")} href="#">View history </a>} flush>
<table className="table">
<thead>
<tr>
<th>PO Number</th><th>Title</th><th>Vessel</th>
<th>Submitter</th><th>Approved</th><th className="num">Amount</th>
</tr>
</thead>
<tbody>
{ORDERS.filter(o => ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED", "CLOSED"].includes(o.status)).slice(0, 5).map(o => (
<tr key={o.id} className="clickable" onClick={() => go("po-detail", o.id)}>
<td><span className="po-num">{o.id}</span></td>
<td>{o.title}</td>
<td className="muted">{o.vessel}</td>
<td className="muted">{o.submitter}</td>
<td className="muted">{o.approved || "—"}</td>
<td className="num">{inr(o.amount)}</td>
</tr>
))}
</tbody>
</table>
</Card>
</>
)}
{isAcct && (
<>
<div className="stat-grid" style={{ marginBottom: 22 }}>
<Stat label="Ready for payment" value="4" onClick={() => go("payments")} />
<Stat label="Awaiting confirmation" value="2" />
<Stat label="Value awaiting payment" value="₹3.84L" />
<Stat label="Paid this month" value="₹11.6L" />
</div>
<Card title="Payments queue" action={<a onClick={() => go("payments")} className="muted" style={{ fontSize: 12 }}>Open queue </a>}>
<div className="muted">4 POs ready to send for payment. Open the queue to process.</div>
</Card>
</>
)}
{isSubmitter && (
<>
<div className="stat-grid" style={{ marginBottom: 22 }}>
<Stat label="Open orders" value="3" onClick={() => go("my-orders")} />
<Stat label="Pending approval" value="1" />
<Stat label="Completed" value="28" />
<Stat label="Total spend YTD" value="₹4.6L" />
</div>
<Card title="Open orders" flush>
<table className="table">
<thead><tr><th>PO Number</th><th>Title</th><th>Vessel</th><th>Status</th><th className="num">Amount</th></tr></thead>
<tbody>
{ORDERS.filter(o => o.submitter === "Rajesh Pillai").slice(0, 4).map(o => (
<tr key={o.id} className="clickable" onClick={() => go("po-detail", o.id)}>
<td><span className="po-num">{o.id}</span></td>
<td>{o.title}</td>
<td className="muted">{o.vessel}</td>
<td><Badge status={o.status} /></td>
<td className="num">{inr(o.amount)}</td>
</tr>
))}
</tbody>
</table>
</Card>
</>
)}
{isAuditor && (
<>
<div className="stat-grid" style={{ marginBottom: 22 }}>
<Stat label="Total POs YTD" value="284" onClick={() => go("history")} />
<Stat label="Total spend YTD" value="₹98.4L" />
<Stat label="Avg PO value" value="₹34.6K" />
<Stat label="Vessels" value="5" />
</div>
<Card title="Quick access">
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button className="btn" onClick={() => go("history")}><Icon name="file" /> Order history</button>
<button className="btn" onClick={() => go("vendors")}><Icon name="users" /> Vendor registry</button>
<button className="btn"><Icon name="download" /> Export PDF</button>
<button className="btn"><Icon name="download" /> Export CSV</button>
</div>
</Card>
</>
)}
</>
);
};
/* ═══════════════════ MY ORDERS ═══════════════════ */
const MyOrders = ({ go }) => {
const mine = ORDERS.filter(o => o.submitter === "Rajesh Pillai");
const open = mine.filter(o => ["DRAFT","SUBMITTED","MGR_REVIEW","VENDOR_ID_PENDING","EDITS_REQUESTED"].includes(o.status));
const past = mine.filter(o => !["DRAFT","SUBMITTED","MGR_REVIEW","VENDOR_ID_PENDING","EDITS_REQUESTED"].includes(o.status));
// Add a couple of synthetic past orders to make it feel real
const pastFull = [...past, ...ORDERS.filter(o => ["CLOSED","PAID_DELIVERED","SENT_FOR_PAYMENT","MGR_APPROVED","REJECTED"].includes(o.status)).slice(0,3)];
return (
<>
<div className="page-head">
<div>
<Crumbs items={["Purchase Orders", "My Orders"]} />
<h1 className="page-title">My Purchase Orders</h1>
<div className="page-sub">{open.length} open · {pastFull.length} past</div>
</div>
<button className="btn maritime" onClick={() => go("po-new")}><Icon name="plus" /> New PO</button>
</div>
<h2 className="section-title">Open orders</h2>
<Card flush>
{open.length === 0 ? <div className="empty-state">No open orders.</div> : (
<table className="table">
<thead>
<tr><th>PO Number</th><th>Title</th><th>Vessel</th><th>Status</th><th>Updated</th><th className="num">Amount</th></tr>
</thead>
<tbody>
{open.map(o => (
<React.Fragment key={o.id}>
<tr className="clickable" onClick={() => go("po-detail", o.id)}>
<td><span className="po-num">{o.id}</span></td>
<td>{o.title}</td>
<td className="muted">{o.vessel}</td>
<td><Badge status={o.status} /></td>
<td className="muted">{o.submitted || o.created || "—"}</td>
<td className="num">{inr(o.amount)}</td>
</tr>
{o.managerNote && (
<tr><td colSpan="6" style={{ background: "oklch(98% 0.015 95)", borderTop: 0 }}>
<div style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "2px 0" }}>
<Badge status="EDITS_REQUESTED" />
<div style={{ fontSize: 12 }}>
<strong>Manager note · Anjali Krishnan:</strong> <span className="muted">{o.managerNote}</span>
</div>
</div>
</td></tr>
)}
</React.Fragment>
))}
</tbody>
</table>
)}
</Card>
<h2 className="section-title">Past orders</h2>
<Card flush>
<table className="table">
<thead>
<tr><th>PO Number</th><th>Title</th><th>Vessel</th><th>Status</th><th>Closed/Completed</th><th className="num">Amount</th></tr>
</thead>
<tbody>
{pastFull.map(o => (
<tr key={o.id} className="clickable" onClick={() => go("po-detail", o.id)}>
<td><span className="po-num">{o.id}</span></td>
<td>{o.title}</td>
<td className="muted">{o.vessel}</td>
<td><Badge status={o.status} /></td>
<td className="muted">{o.closed || o.paid || o.approved || o.rejected || "—"}</td>
<td className="num">{inr(o.amount)}</td>
</tr>
))}
</tbody>
</table>
</Card>
</>
);
};
Object.assign(window, { LoginPage, Dashboard, MyOrders });