- 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>
23 KiB
Handoff: Pelagia Portal — Internal Purchase-Order Management
Overview
Pelagia Portal is an internal web application for a maritime company that digitises the full purchase-order lifecycle: requisition aboard a vessel → manager approval → finance payment → receipt confirmation on delivery. It is a role-gated, traceable workflow replacing paper and email-based processes.
This handoff covers the design system and 16 primary screens spanning the seven user roles defined in the spec (TECHNICAL, MANNING, ACCOUNTS, MANAGER, SUPERUSER, AUDITOR, ADMIN).
The full functional specification lives in DESIGN.md (included). The HTML prototype demonstrates layout, look, and key interactions; this README documents what to build.
About the Design Files
The files in this bundle are design references created in HTML/JSX — prototypes showing intended look and behaviour, not production code to ship. They use React via Babel-in-browser for fast iteration, with no build step, no API layer, and mock data hard-coded in data.jsx.
The task is to recreate these designs in the target codebase's existing environment (likely Next.js / React + a real backend) using its established patterns, component library, and conventions. If no environment exists yet, pick the most appropriate stack — Next.js App Router with TypeScript + Tailwind/shadcn or Radix is a natural fit for this product.
A role-switcher dropdown appears in the prototype header. It is a prototype-only affordance to preview how the sidebar adapts per role. In production, role is determined by the authenticated user's record.
Fidelity
High-fidelity. Final typography, colour, spacing, status-badge palette, table density, and form layout are intended to ship as-is. The chrome (header, sidebar, page-head, card, table, badge) is a complete design system — recreate it pixel-faithfully with the codebase's chosen primitives.
Charts are illustrative — the line and bar charts in the prototype are hand-rolled CSS/SVG. In production use a charting library (Recharts, Visx, Chart.js) but keep the visual style: low chroma fills, monospaced numerals, minimal axes, no gradients.
Design Tokens
Colour
All colours are defined in oklch and pinned to a consistent chroma/lightness band per role (foreground vs background, status hue, etc). Convert to hex/RGB for the target environment if needed.
/* Surface */
--paper: oklch(98% 0.005 240); /* page background — warm-cool off-white */
--paper-2: oklch(96.5% 0.006 240); /* recessed surfaces, table headers */
--surface: #ffffff; /* cards, inputs */
/* Ink */
--ink: oklch(22% 0.02 245); /* primary text */
--ink-2: oklch(35% 0.02 245); /* secondary text */
--muted: oklch(55% 0.015 245); /* labels, meta */
--faint: oklch(72% 0.012 245); /* placeholder, separator copy */
/* Lines */
--line: oklch(91% 0.006 245); /* default borders */
--line-2: oklch(87% 0.008 245); /* hover/active borders */
/* Primary maritime */
--primary: oklch(38% 0.06 230); /* brand mark, primary CTA */
--primary-ink: oklch(28% 0.06 230); /* primary CTA hover, active nav text */
--primary-soft: oklch(95% 0.02 230); /* active nav background, soft fills */
Status Badge Palette
Nine PO statuses, each with a paired bg/fg keeping chroma and lightness consistent across the set:
| Status | Background hue | Foreground hue |
|---|---|---|
| DRAFT | oklch(94% 0.005 245) |
oklch(40% 0.015 245) |
| SUBMITTED / MGR_REVIEW | oklch(94% 0.03 245) |
oklch(38% 0.09 245) |
| VENDOR_ID_PENDING | oklch(94% 0.04 60) |
oklch(42% 0.12 60) |
| EDITS_REQUESTED | oklch(95% 0.05 95) |
oklch(42% 0.1 80) |
| MGR_APPROVED | oklch(94% 0.04 190) |
oklch(38% 0.08 195) |
| SENT_FOR_PAYMENT | oklch(94% 0.04 300) |
oklch(40% 0.1 300) |
| PAID_DELIVERED | oklch(94% 0.04 215) |
oklch(38% 0.08 220) |
| CLOSED | oklch(94% 0.04 150) |
oklch(38% 0.08 150) |
| REJECTED | oklch(94% 0.04 25) |
oklch(45% 0.13 28) |
Badge style: pill (100px radius), 2×8 padding, 10.5px uppercase, 5px round dot prefix using currentColor.
Typography
- UI sans: Geist, 400 / 500 / 600 / 700 (Google Fonts).
- Mono: Geist Mono, 400 / 500 / 600 — used for PO numbers, amounts, codes, timestamps, GSTINs, geocoded coordinates. Always with
font-variant-numeric: tabular-nums. - Base body size 13px with line-height 1.5.
- Page titles 22px / 600 / -0.01em tracking.
- Section titles 11px uppercase / 500 / 0.08em tracking, colour
--muted. - Field labels 11px uppercase / 500 / 0.05em.
- Stat values 26px monospaced / 500 / -0.02em.
Spacing & Radii
- Sidebar width 232px, header height 52px, main padding
22px 32px 60px, max content width 1280px. - Border radii:
4px(chips),6px(buttons, inputs),10px(cards, large surfaces). - Form input height 32px, secondary/button height 30px, small-button 26px.
- Card head padding
13px 16px, body16px. Tables: header10px 16px, cell12px 16px. - Grid gap 12–16px for stat grids / dashboard layouts; 22px between page sections.
Iconography
Custom 16×16 monoline SVG icons with stroke-width: 1.4, linecap: round, linejoin: round. Recreate in your icon library of choice (Lucide is a near-perfect match — Home, FileText, List, Check, ShoppingCart, Package, Truck, Users, Ship, Map, BarChart3, User, Settings, Plus, Download, Upload, Search, ChevronRight, Star, Pencil, Trash, Bell, Eye, RotateCw, Paperclip, ArrowRight).
Shadow / Elevation
No drop shadows anywhere in the chrome. Depth comes from hairline borders (--line) and the recessed --paper-2 background. The brand mark sits on solid --primary.
Navigation Structure
The left sidebar is role-aware (groups and items hide/show per role). Group titles are 10.5px uppercase, --faint.
| Role | Groups visible |
|---|---|
| TECHNICAL / MANNING | Dashboard · Purchase Orders (New PO, My Orders) · Inventory (Items, Cart) |
| MANAGER | Dashboard · Purchase Orders (New PO, My Orders, Approvals, Import PO, History) · Inventory (Vendors, Items, Vessels, Sites, Cart) |
| ACCOUNTS | Dashboard · Purchase Orders (Payments, History) · Inventory (Vendors) |
| SUPERUSER | Full submitter + manager + accounts surface |
| AUDITOR | Dashboard · Purchase Orders (History) |
| ADMIN | Dashboard · Purchase Orders (Import PO, History) · Inventory (Vendors, Items, Vessels, Sites) · Administration (Users, Accounts) |
Nav item: 6×10 padding, 6px radius, 12.5px label, leading 14px icon. Active state: --primary-soft background, --primary-ink text, weight 500. Optional count pill on the right (10.5px monospace).
Screen Catalogue
1. Login (/login)
- Centred 360px-wide card on
--paperbackground. - Brand mark + "Pelagia Portal" wordmark + 11.5px subtitle "Internal · Purchase Order Management".
- Email + password inputs, then a full-width maritime-primary "Sign in" button (height 36).
- Footnote: "Contact an administrator to request access." No self-registration.
2. Dashboard (/dashboard)
Content adapts per role. Page-head left: crumb "Dashboard", title "Good afternoon, <name>", sub-line with date/time and a "3 vessels active at sea" indicator. Page-head right: Export + New PO buttons.
Manager view:
- 4 stat tiles in
auto-fit minmax(190px, 1fr)grid: Awaiting approval (clickable → approvals queue, shows trend "↑ 2 vs last week"), Approved this month, Approved spend, Avg cycle (submit → approve). - Two side-by-side cards (1fr 1fr): "Monthly spend — last 8 months" (vertical bar chart, ₹ in thousands, current month highlighted in
--primary); "Spend by vessel — YTD" (horizontal bar rows with--primaryfills). - "Recently approved" card-flush table with "View history →" link.
Submitter (TECH/MANNING) view:
- 4 stat tiles: Open orders, Pending approval, Completed, Total spend YTD.
- "Open orders" mini-table.
Accounts view:
- 4 stat tiles: Ready for payment, Awaiting confirmation, Value awaiting payment, Paid this month.
- "Payments queue" link.
Auditor/Admin view:
- 4 stat tiles + quick-access button row (Order history, Vendor registry, Export PDF, Export CSV).
3. My Purchase Orders (/my-orders)
- Page-head with status counts:
<n> open · <n> past. - Two stacked sections: "Open orders" and "Past orders", each a flush card with table.
- Columns: PO Number (monospace,
--primary-ink), Title, Vessel (muted), Status badge, Updated, Amount (right-aligned monospace). - For EDITS_REQUESTED rows, a tinted sub-row below the data row shows
Manager note · <name>: <text>with the inline badge. - Whole row clickable →
/po/[id]. Hover row:--paper-2.
4. Approval Queue (/approvals)
- Title "Approval queue" + appended count "· N pending" in muted regular weight.
- Filter bar (recessed
--paper-2, 1px border, 6px radius): search field with icon, vessel dropdown, submitter dropdown, date-from picker, right-aligned "Sorted by submitted date" note. - Table columns: PO Number, Title, Submitter, Vessel, Submitted, Amount, "Review →" link on right.
5. PO Detail (/po/[id])
Two-column layout: main content (1fr) + sidebar (280px).
- Detail band (full width, above the two-col): card with PO id (18px monospace), status badge, project code chip (mono pill), and right-aligned "GRAND TOTAL · ₹X,XX,XXX" (22px monospace).
- Manager-note alert if
EDITS_REQUESTED(yellow-warning alert above main content). - Main column sections (each preceded by an 11px uppercase section title):
- Summary — key/value
dl(130px 1frcolumns). - Line items — flush card with table; footer row inside card has Taxable / GST / Grand Total summary (right-aligned, monospace).
- Terms & conditions — key/value
dlof the 6 T&C fields. - Documents — list of attached files with
paperclipicon + size + Download button. - Confirm receipt — visible only when
PAID_DELIVERED: dashed-border upload zone + notes textarea + maritime CTA.
- Summary — key/value
- Sidebar:
- Timestamps card — Created / Submitted / Approved / Paid / Closed (monospace dates).
- Audit trail card — list of
actor · action · monospaced timestamprows with green dot per row (1px dashed separator between).
Action buttons in page-head are contextual per status & role (see DESIGN.md §5.4 table).
6. Approval Detail (/approvals/[id])
Same as PO Detail, plus a Manager actions card at the top of the main column:
- Default state: row of buttons —
Approve(maritime primary),Approve with Note,Request Edits,Request Vendor ID, withReject(danger) pushed right via a flex spacer. - Clicking any action other than plain Approve swaps the row for an inline form with title, subtitle, textarea, Cancel + tinted-CTA buttons. CTA colour matches the action's intent (edits =
--st-edits-fg, reject =--st-rejected-fg, etc). - Manager line-item editing happens inline on the line-items card (not in a modal) — per spec §9.
7. New PO (/po/new)
Four numbered sections, each prefixed by a section title:
- Header — Title (full row) + 3-col (Vessel, Account, Vendor) + 3-col (Date Required, Project Code, Currency) + Description textarea.
- Line items — flush card with table of editable rows. Per-row inputs: Name, Description, Qty, Unit, Unit price, GST%, Total (computed, monospace), trash icon. Footer "Add line item" button + tip text. Card footer with live Taxable / GST / Grand Total summary identical to the PO Detail footer.
- As-you-type product autocomplete: when a name input is focused with text, a tinted sub-row appears showing "Last seen at: Vendor A ₹X · Vendor B ₹X · Vendor C ₹X" hints (per spec §9).
- Terms & conditions — 6 textareas in a 2-col grid (Delivery, Dispatch, Inspection, Transit Insurance, Payment Terms, Other).
- Documents — dashed drop zone with paperclip-list of attached files.
Footer: Cancel · Save as Draft · Submit for Approval (maritime primary).
8. Payment Queue (/payments)
ACCOUNTS only. Two sections:
- "Ready for payment" — card grid
auto-fill minmax(320px, 1fr). - "Processing — awaiting confirmation" — same grid layout, different action button.
Each card: PO number (mono header) + title, status badge, kv table (Vessel, Vendor, Submitter, Approved/Sent on), prominent amount (22px monospace), then a row of two buttons — View (neutral) and the action button (maritime primary, larger flex weight 1.6).
9. History / Export (/history)
- Page-head right: Export PDF · Export CSV buttons.
- Filter bar: From + To date pickers (with prefix labels), vessel dropdown, status dropdown. Right side: "N matching · export uses current filters".
- Full PO table, columns: PO Number, Title, Vessel, Submitter, Status, Created, Amount.
10. Vendor Registry (/admin/vendors)
- Page-head right: "Add vendor" maritime button.
- Add-vendor opens an inline panel (not a modal) above the table — a
.action-panelCard with the GSTIN lookup wizard:- Step 1: GSTIN input + "Look up" button.
- Step 2: a hand-drawn captcha image placeholder (45° striped background, line-through wavy effect on the 6-char code) + input + Refresh icon button + "Verify" button.
- Step 3: green confirmation alert + auto-filled form fields (Legal name, Trade name, Address textarea, Pincode, Geocoded location read-only, Contact name, Email, Mobile) + Cancel / Save.
- Vendor table columns: Vendor ID (mono or "Pending" badge), Name + city + GSTIN (two-line cell), Contact (name + email two-line), Items count, Verified (check + "Verified" or em-dash), Status badge, edit icon.
11. Vendor Detail (/admin/vendors/[id])
Two-column layout (1fr + 280px).
- Page-head sub: vendor ID (mono) · "GSTIN verified" check · Active badge.
- Main: Items supplied table (Code, Product, Last quoted, Last updated) + Recent POs table.
- Sidebar: Vendor info kv (GSTIN, Pincode, City, Contact, Mobile, Email); Address card with a striped placeholder "map" graphic + geocoded coordinates label.
12. Item Catalogue (/admin/products)
- Info alert at top (per spec §5.15 footer note): "Items are added automatically when a PO is marked as paid. Manual entry is reserved for ADMIN."
- Table columns: Code (mono), Name (medium weight), Description (muted), Vendors, Last price, Last vendor, Updated, Status badge.
13. Item Detail (/admin/products/[id])
- Stat row: Vendors supplying, Lowest price, Highest price, Sites with stock.
- 2-col card row: "Price comparison" vertical bar chart (lowest-priced vendor bar in
--primary) + "Stock by site" chip list. - Vendor pricing table with site-proximity filter (top-right "Sort by distance from " dropdown). When a site is chosen: a Distance column appears, rows sort by distance ascending, and the closest vendor row gets a star ★ marker. Per-row "Add to Cart" small button.
14. Sites (/admin/sites) & Site Detail (/admin/sites/[id])
- List: Name, Code (mono), Address (muted), Vessels, Items, Location (mono coords), Status, edit icon.
- Detail: 4 stat tiles (Vessels at site, Items tracked, Inventory value, 30-day consumption). 2-col card row: Current stock horizontal bars + Consumption SVG line chart (5 colored series for 5 products) with a flat legend below. Then a 2/3 + 1/3 split: Inventory table & Recent POs on the left, Log-consumption form & assigned vessels chips on the right.
15. Cart (/inventory/cart)
- 2/3 + 1/3 split.
- Left card: header row + per-item rows (5-col grid: name+vendor / price / qty input / subtotal / delete). Inline qty editing.
- Right column: "Order summary" card (Taxable / GST / Grand Total) + "Delivery site" dropdown card.
- Page-head right: Clear cart · Create PO from cart (maritime primary).
16. Import PO (/po/import)
- Step 1 card (1.6fr): drop zone + parsed-file confirmation row (check icon + "5 line items detected").
- Step 2 card (1fr): vessel & account selectors.
- Step 3: extracted line-items table (read-only-style) for review.
- Footer: Cancel · Save as Draft.
17. Vessels / Accounts / Users (admin tables)
Straightforward dense tables matching the design system's table primitive. Per-row edit icon, status badge. Add buttons in page-head.
Interactions & Behaviour
Routing
- Hash-based in the prototype (
#dashboard,#po-detail/PO-2026-00481). In production, use real URL routing. - Browser back/forward should restore exact screen state.
Click handlers
- Every table row with a PO is row-clickable → opens PO Detail. Action buttons within rows must
stopPropagation. - Status badges are non-clickable (decorative only).
- Stat tiles with an underline cursor are clickable to drill into the relevant list.
Hover / focus
- Buttons:
--paper-2background and--line-2border on hover. Inputs/selects:--primaryborder + 3px--primary-softfocus ring. - Nav items:
--paper-2hover (when not active). - Table rows with
.clickable:--paper-2background.
Forms
- All required fields marked with a red
*after the label (.req). - Live totals on the New PO line-items table — recompute Taxable / GST / Grand Total on every change.
- Save as Draft is always available; Submit requires required fields.
Confirmation
- Per spec §9: destructive actions use inline two-step confirm (Delete → "Delete ? Confirm / Cancel"), not a modal.
Notifications / emails
- Every PO status transition fires an email (spec §9). Outside scope of UI but worth wiring on the backend.
Approval action flow
- Clicking Approve immediately moves the PO to MGR_APPROVED (toast confirm in production).
- Clicking Approve-with-Note / Edits / Reject / Vendor-ID swaps the action panel for an inline form with mandatory note (Edits/Reject) or optional note (Vendor ID, Approve w/ note).
Cart persistence
- LocalStorage under a fixed key. Fire a
cart-updatedcustom event on mutation so header counters can react.
GSTIN lookup
- Three-step inline wizard inside the Add Vendor form. Captcha image and verify response should be proxied via the company's microservice to the GST portal.
Charts
- Use Recharts/Visx. Bar charts: rounded top corners, no axis lines, monospaced tick labels in
--muted, the highlighted bar in--primary, the rest in--primary-soft. Line charts: 1.5px stroke, rounded caps, 5 distinct hues at moderate chroma (sampled from the consumption chart series).
Responsive
- Desktop-first per spec §10. Minimum supported width 1280px. The header has fixed-width sidebar (232px); cards collapse to single column under 1024px if you want to graceful-degrade.
State Management
State boundaries that matter:
- Auth / current user / role — global (Context or Zustand). Drives sidebar nav and action visibility.
- PO list / detail — server state via TanStack Query / RTK Query. Cache invalidation on status transition.
- Approval inline form — local component state (
modeenum: null | edits | reject | note | vendor). - New PO form — react-hook-form with
useFieldArrayfor line items; live totals are derived fromwatch. - Cart —
localStorage+ an in-memory store synced via acart-updatedevent. - Vendor add wizard — local component state (
step1 → 3); GSTIN captcha and verify are async mutations.
PO lifecycle state machine (per spec §6)
DRAFT → SUBMITTED → MGR_REVIEW
↓ (approve)
MGR_APPROVED → SENT_FOR_PAYMENT → PAID_DELIVERED → CLOSED
Re-entry branches from MGR_REVIEW:
→ REJECTED (terminal)
→ EDITS_REQUESTED → submitter edits → MGR_REVIEW
→ VENDOR_ID_PENDING → submitter picks vendor → MGR_REVIEW
Terminal states: REJECTED, CLOSED.
Data Entities
See DESIGN.md §8 for the full field list. Tables you'll need:
purchase_order— id, title, status, total_amount, currency, date_required, project_code, manager_note, payment_ref, quotation_no/date, requisition_no/date, place_of_delivery, terms.{delivery,dispatch,inspection,insurance,payment,others}, all timestamps.po_line_item— fk po_id, name, description, qty, unit, size, unit_price, gst_rate (default 18), total (computed), sort_order, optional product_id.vendor— name, vendor_id (nullable unique), address, pincode, gstin, contact, latitude, longitude, verified, active.product— code, name, description, last_price, last_vendor_id, active.product_vendor_price— composite (product_id, vendor_id) → price, updated_at.vessel— name, imo, active, assigned_site_id.site— name, code, address, pincode, lat, lng, active.account— code, name, description, active.user— emp_id, email, name, role, active, password_hash.item_inventory— (product_id, site_id) → qty.item_consumption— (product_id, site_id, date) → qty.po_audit_log— fk po_id, actor_id, action, note, timestamp.
Auto-sync of product / product_vendor_price on PO mark_paid per spec §7.9.
Files in this Bundle
| File | Role |
|---|---|
Pelagia Portal.html |
Entry point — references all scripts and styles |
styles.css |
Complete design system: tokens, components, layout, charts |
components.jsx |
Shared primitives — Icon, Badge, Card, Stat, Crumbs, format helpers |
data.jsx |
Mock data — vessels, accounts, vendors, products, sites, users, orders |
pages-1.jsx |
LoginPage, Dashboard, MyOrders |
pages-2.jsx |
ApprovalsPage, PODetailBase (shared by detail + approval), NewPOPage, ApprovalActions |
pages-3.jsx |
PaymentsPage, HistoryPage, VendorsPage + AddVendorPanel, VendorDetailPage, ItemsPage, ItemDetailPage |
pages-4.jsx |
SitesPage, SiteDetailPage, VesselsPage, AccountsPage, UsersPage, CartPage, ImportPOPage |
app.jsx |
Router, role-aware Sidebar, Header (with prototype role-switcher), App entrypoint |
DESIGN.md |
Original product specification — authoritative for behaviour, copy, and role matrix |
The fastest way to absorb the design: open Pelagia Portal.html in a browser, switch roles via the header dropdown, and click through the sidebar. Then port screen-by-screen using DESIGN.md as the source of truth for any behavioural ambiguity.
Assets
- Fonts: Geist + Geist Mono via Google Fonts. Both free and OFL-licensed.
- Icons: custom 16×16 monoline set; reproduce with Lucide React.
- Logo / brand mark: generated in CSS (a navy square with a clipped semicircle representing a wave/anchor abstract). Replace with the real Pelagia mark when available.
- Map/photographic imagery: none used. Vendor-detail "map" is an intentionally schematic placeholder.
Out of Scope (per spec §10)
- Mobile app — web-only, desktop-first.
- Public-facing pages — entirely internal.
- Self-registration / OAuth — admin-created accounts only.
- Vendor portal — vendors do not log in.
- Automated bank/payment-gateway integration — payment is marked manually.