The theme only defines danger/warning/success as flat colors plus -50/-100/-700
shades. bg-danger-600 and bg-warning-600 don't exist so those buttons rendered
with a transparent background, making white text invisible until hover revealed
bg-danger-700. Replaced with bg-danger / bg-warning which are defined.
Also fixed border-danger-200/400 and text-danger-600 (undefined) on the
Delete and Confirm Delete buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Derives the author from the most recent EDITS_REQUESTED / REJECTED /
APPROVED action that carries a note. PO detail banner now shows 'Note from
[name]', edit-page banner shows 'Edits requested by [name]', and the
closed-orders list prefixes the truncated note with the author's name.
No schema changes required - uses the already-fetched actions with actor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parent button components (EditVendorButton, EditAccountButton, etc.) stay
mounted even when their AdminDialog closes — so pending was never cleared
on success, causing buttons to show 'Saving...' on the next open. The
payment confirm button (no dialog) was stuck in 'Confirming...' until the
page re-render eventually unmounted it. Added setPending(false) before
setOpen/router.refresh in every success path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the action was logged unconditionally for every line item on
every payment confirmation, even when the price was identical to what was
already on the product. Now we compare unitPrice to the stored lastPrice
before deciding to record the change. New products being registered for
the first time are also excluded since that is not a price update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Open POs are already visible on the dashboard. The /my-orders page now
fetches and displays only completed/closed statuses (MGR_APPROVED through
REJECTED) and is labelled 'Closed Purchase Orders' throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allow accounts to record partial/advance payments against a PO before
full delivery. A new PARTIALLY_PAID status tracks in-progress payment;
paidAmount accumulates across multiple markPaid calls. PO only closes
when both paidAmount >= totalAmount AND all line items are delivered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PO_SUBMITTED: managers only, submitter no longer copied
PAYMENT_SENT: submitter only (it is a receipt prompt, not a manager action)
PARTIAL_RECEIPT_CONFIRMED: managers now notified via new event type
RECEIPT_CONFIRMED: unchanged (managers + accounts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subject line now reads 'Purchase Order XXXX' instead of 'PO XXXX'.
Each email includes a recipient-specific deep-link action button,
a PO summary card (number, title, submitter, vendor, vessel, cost centre, total),
and a line items table with qty/unit price/GST/line total.
notify() fetches enriched data internally so no call sites change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
qtyChanged/priceChanged/nameChanged were tracked for the diff view but
gstChanged was missing, so an edited GST % showed only the new value
with no strikethrough of the old one. Added gstChanged detection and
two-line rendering (old % struck through, new % in amber) to match the
other diff columns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dropdown was w-96 (384 px) anchored right-0 to the bell button.
On phones the bell sits left of the logout icon so the panel bled off
the left viewport edge. Changed to w-80/sm:w-96 with a 100vw safety cap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PARTIALLY_CLOSED was missing from the open-filter so affected POs
disappeared from the submitter''s My Orders view entirely, making it
impossible to confirm remaining deliveries.
Also hardens confirmReceipt() against negative delivery quantities
and extends partial-receipt.spec.ts with US-8c/8d/8e covering the
full PARTIALLY_CLOSED revisit flow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parseFloat('0') is falsy in JS so `|| 0.18` silently discarded the user's
explicit 0% selection. Replaced with an explicit empty-string guard.
Adds e2e spec gst-rate.spec.ts covering all five GST rates (0/5/12/18/28%).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increase the Prisma interactive transaction timeout from the default 5s
to 30s so that the four sequential nullification + delete queries complete
reliably on a seeded database (P2028 timeout was the root cause).
Wrap the transaction in a try/catch so that if a timeout does still occur
the user sees "Delete timed out — please try again." instead of an
unhandled 500 that previously manifested as the misleading "referenced in
submitted or active purchase orders" error message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers 21 user story groups extracted from the last 15+ feature commits:
- rebrand.spec.ts — PPMS branding on login page, sidebar, and tab title
- dashboard/po-status-badges.js — color-coded status badges for submitter & manager
- po-submit-button.spec.ts — Submit for Approval button visibility on DRAFT POs
- notification-bell.spec.ts — in-app bell icon, unread badge, and panel open
- export-gate.spec.ts — export buttons gated on MGR_APPROVED+ status; 403 on pre-approval
- payment-history.spec.ts — /payments/history accessible to ACCOUNTS; redirects others
- partial-receipt.spec.ts — per-item delivery tracking UI on paid POs
- vendor-auto-verify.spec.ts — vendor verification status visible in admin
- admin-bordered-buttons.spec.ts — Edit/Deactivate/Delete have border classes on admin pages
- profile.spec.ts — profile page loads for all roles; signature section for MANAGER/SUPERUSER
- inventory/items-tags.spec.ts — Cheapest/Closest tags and auto-sort by distance
- inventory/cart-icon.spec.ts — cart header icon with badge; item/vendor detail pages
- mobile/desktop-required.spec.ts — Desktop Required overlay for non-mobile roles + sign-out
- mobile/manager-approvals.spec.ts — mobile card layout; edit form hidden; action buttons visible
- mobile/accounts-payments.spec.ts — ACCOUNTS payments queue and buttons on mobile
- mobile/bottom-nav.spec.ts — Home/Approvals/Profile tabs for MANAGER; Home/Payments/Profile for ACCOUNTS
- approvals-edit-highlight.spec.ts — diff indicators on resubmitted POs
Also adds shared helpers/login.ts with USERS constants, login(), createDraftPo(),
and submitPo() — all using name-attribute selectors since PO form labels have no
htmlFor binding. Playwright config updated: workers capped at 2 locally (was
unlimited) to prevent auth concurrency failures, and retries set to 1 locally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prepend a Home tab (linking to /dashboard) to the bottom navigation bar
for both Manager/SuperUser and Accounts roles, giving one-tap access to
the dashboard from any mobile screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users on mobile who see the "Desktop Required" wall had no way to log out.
Added a Sign out button using next-auth signOut (requires "use client").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ACCOUNTS to MOBILE_ROLES so the desktop-required wall no longer
blocks them on small screens.
- MobileBottomNav is now role-aware: ACCOUNTS gets Payments + Profile tabs;
MANAGER/SUPERUSER keep Approvals + Profile. Role prop threaded from
layout → MobileBottomNav.
- PaymentActions (both MGR_APPROVED and SENT_FOR_PAYMENT states) stacks
vertically on small screens — input takes full width, button below it —
then reverts to the horizontal inline layout at sm+ breakpoint.
- Payments page card bottom row (status badge + action) stacks on mobile
(flex-col sm:flex-row) so the reference input isn't squashed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a responsive mobile experience scoped to MANAGER and SUPERUSER roles:
Layout:
- Sidebar hidden on small screens (md: breakpoint)
- New MobileHeader (logo + notification bell + sign out) visible on mobile
- New MobileBottomNav (Approvals + Profile tabs) pinned to bottom on mobile
- New DesktopRequired overlay shown to all other roles on small screens —
a fixed full-screen message directing them to use a desktop browser
Approvals queue:
- Desktop: existing table layout (hidden md:block)
- Mobile: tap-to-review card stack (md:hidden) — shows PO number, title,
submitter, cost centre, amount, and a full-width Review button
PO approval detail:
- ManagerEditPoForm (direct field editing) hidden on mobile; still available
on desktop (direct edits not required per spec)
- ApprovalActions buttons stack full-width on mobile, row on sm+
- Paddings reduced on small screens throughout
PO detail component:
- Order Details and Vendor grids switch from grid-cols-2 → grid-cols-1
on small screens (sm:grid-cols-2 restores two columns at 640px+)
- Section padding reduced on mobile (p-3 md:p-6, p-4 md:p-6)
- Line items table already had overflow-x-auto — no change needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Edit, Deactivate/Activate, and Delete actions in all admin table rows
were styled as plain text links (coloured text, no border or background).
Replaced with small pill-shaped bordered buttons that have a clear visual
affordance as interactive controls:
- Edit → blue tinted border/bg (primary-50 / primary-200)
- Deactivate → red tinted border/bg (danger-50 / danger-200)
- Activate → green tinted border/bg (success-50 / success-200)
- Delete → white bg, red border; confirm state = solid red
- Cancel → white bg, neutral border
Applies to: accounts, cost centres (vessels), users, vendors, products,
and the shared ConfirmDeleteButton component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When accounts confirms a payment (SENT_FOR_PAYMENT → PAID_DELIVERED),
set Vendor.isVerified = true for the PO's vendor. The field already
exists in the schema (default false); this closes the loop so vendors
who have transacted at least once are marked verified automatically
without manual admin intervention.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename the application to PPMS across all user-facing surfaces:
- Browser title / metadata
- Login page (with tagline: "PMS — it runs the ship.")
- Sidebar logo text
- Email header, footer, and body copy in all transactional emails
- PDF/XLSX workbook creator field
- Reports export page title and heading
- Notifier FROM display name and default email domain
Legal company name ("Pelagia Marine Services Pvt. Ltd.") on PO documents
is unchanged — that is the issuing entity, not the application brand.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export API returns 403 for any PO not yet approved (DRAFT, SUBMITTED,
MGR_REVIEW, EDITS_REQUESTED, VENDOR_ID_PENDING, REJECTED) — only
MGR_APPROVED, SENT_FOR_PAYMENT, PAID_DELIVERED, PARTIALLY_CLOSED and
CLOSED are exportable.
- The submitter's name is no longer used as a signatory fallback; since
export is blocked until after manager approval an approver always exists.
- Export PDF / Export XLSX buttons are hidden in po-detail for pre-approval
statuses, so users never encounter the 403 through normal UI flows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Logging (GstService):
- JSON-structured log lines: { ts, level, msg, ...ctx } — one per line,
machine-parseable by any log aggregator (datadog, loki, etc.)
- LOG_LEVEL env var (DEBUG|INFO|WARN|ERROR, default INFO) — set DEBUG to
see every captcha fetch, raw GST response body, and page console event
- WARN and ERROR lines go to stderr; INFO/DEBUG go to stdout so process
supervisors can separate them
- Every log line carries relevant context: reqId, sessionId, gstin, ms, etc.
- errCtx() helper extracts errName, errMsg, and first 6 stack frames from
any thrown value — no more bare String(e)
- elapsed() helper records wall-clock ms for every expensive step:
browser launch, page navigation, captcha fetch, GST API call
- Request/response middleware: every HTTP request logs method, path,
reqId, status, and duration; status >= 500 logs at ERROR, >= 400 at WARN
- Playwright page listeners: console errors/warnings, pageerror,
requestfailed, and HTTP 4xx/5xx on GST portal endpoints
- process.on(uncaughtException) and process.on(unhandledRejection) so
unexpected crashes surface in logs instead of silently dying
- Browser "disconnected" event logged; _browser reset so next request
auto-relaunches without manual restart
- SESSION_TTL_MS configurable via env (default 3 min)
- closeSession() logs the reason (success / errorCode / exception / etc.)
- GET /health now returns browserConnected, per-session captchaCount,
expiresInMs, and lastUsedMsAgo for operational visibility
Multiple captchas per session:
- Session now holds captchas: CaptchaEntry[] (ordered oldest→newest)
so every image fetched in a session is kept for traceability
- GET /captcha/:sessionId — new endpoint that calls /services/captcha
again within the SAME browser context (no page reload, ~200ms vs ~5s)
and appends a new CaptchaEntry; resets TTL; returns totalCaptchas
- POST /search on SWEB_9034 (wrong captcha) no longer closes the session —
returns { canRefresh: true, sessionId } so the caller can hit
GET /captcha/:sessionId for a fresh image and retry immediately
- All other error paths (SWEB_9000, network error, no data) still close
the session as before
Next.js proxy (app/api/gst/captcha/route.ts):
- GET /api/gst/captcha?refresh=<sessionId> proxies to the new
GET /captcha/:sessionId endpoint on GstService
- Plain GET /api/gst/captcha still creates a new session as before
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Notification links now route each recipient to the page where they
need to take action, not just the PO detail:
Manager / SuperUser:
PO_SUBMITTED, VENDOR_ID_PROVIDED → /approvals/[id] (approval queue)
Accounts:
PO_APPROVED / PO_APPROVED_WITH_NOTE → /payments (payment queue)
Submitter:
EDITS_REQUESTED → /po/[id]/edit (open the edit form directly)
PAYMENT_SENT → /po/[id]/receipt (open the receipt confirmation)
All other events / recipients → /po/[id]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A draft PO had no way to be submitted without creating a brand new PO.
Two surfaces are now fixed:
Edit page (/po/[id]/edit):
- updatePo action now accepts intent "submit" (alongside "save" and
"resubmit"), which transitions a DRAFT PO to MGR_REVIEW, creates a
SUBMITTED action, and fires notifications — identical behaviour to
the new-PO submit flow
- EditPoForm shows a primary "Submit for Approval" button when the PO
is in DRAFT status; "Resubmit for Approval" remains for EDITS_REQUESTED
PO detail page (/po/[id]):
- New submitDraftPo server action in po/[id]/actions.ts handles the
same DRAFT → MGR_REVIEW transition without requiring the full edit form
- New SubmitDraftButton client component renders next to the Edit link
in the detail header for DRAFT POs owned by the current user
- Button order: Edit · Submit for Approval · Discard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In-app notification bodies now lead directly with the PO number
(e.g. "PO-2024-12345 approved" → "PO-2024-12345 approved") without the
word "PO" repeated before the identifier, since the ID already makes
the context obvious.
All notification links now route to /po/[id] so clicking any
notification takes the user straight to the relevant PO detail page,
regardless of their role or the event type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema (migration: 20260516104351_notification_isread_link):
- Notification.isRead Boolean @default(false) — tracks unread state
- Notification.link String? — deep-link URL for each notification
lib/notifier.ts:
- buildInAppBody(): per-recipient message text, context-aware
e.g. managers see "Maria Santos submitted PO-2024-12345 for your review"
submitters see "Your PO-2024-12345 has been approved"
accounts see "PO-2024-12345 approved — ready for payment"
- buildInAppLink(): routes to the correct page per recipient role
(submitter → /po/[id] or /po/[id]/receipt; manager → /approvals/[id];
accounts → /payments; etc.)
- Notifications written with both body and link on every event
API:
- GET /api/notifications — returns { unreadCount, notifications[] } for
the session user; last 20 ordered by sentAt desc
- PATCH /api/notifications/read — marks all (or specific ids) as read
NotificationBell (components/layout/notification-bell.tsx):
- Bell icon in header with red unread count badge
- Polls /api/notifications every 30 seconds
- When unread count increases vs previous tick: bounces the bell icon
and pulses the badge for 3 seconds to signal a new arrival
- Click opens dropdown panel:
- Unread items highlighted with blue-dot indicator and bolder text
- Each item is a Link to the notification's deep-link URL
- "Mark all read" button in panel header
- Auto-marks all as read when panel opens (optimistic update + API call)
- Closes on outside click
- "Showing 20 most recent" footer when list is at limit
Header: receives initialUnreadCount and initialNotifications as props
Portal layout: fetches initial notification data server-side to avoid
a loading flash on first render; dates serialised to ISO strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema (migration: 20260516103515_user_profile_signature):
- User.signatureKey String? — storage key for the manager's approval signature
- New RequestStatus enum (PENDING / APPROVED / DENIED)
- New SuperUserRequest model — tracks access-escalation requests from users
Profile page (/profile) — all roles:
- Account info panel (name, email, employee ID, role — read-only)
- Change password form (validates current password, bcrypt hash on save)
- Signature uploader (MANAGER / SUPERUSER only) — PNG/JPG/WebP up to 2 MB;
previews before save; can remove existing signature
- SuperUser access request form — textarea for reason, shows current request
status (pending / approved / denied) after submission
Signature gate on approval page (/approvals/[id]):
- Server checks if the current manager has uploaded a signatureKey
- If missing: shows an amber warning banner with a deep-link to /profile
instead of rendering the approval action buttons; managers cannot
approve, reject, or request edits without a signature on file
PDF and XLSX exports:
- Fetches the approver's signature image from storage after identifying
the approval action
- PDF: embeds as base64 data URI <img> above the approver name in the
left signature block
- XLSX: inserts the image into the sig-row cells via ExcelJS addImage;
adds a name row below the image for legibility
SuperUser requests admin page (/admin/superuser-requests):
- Pending requests listed with user info, role, reason, and Approve/Deny buttons
- Approve: sets user.role = SUPERUSER and closes the request
- Deny: marks request DENIED, user role unchanged
- Resolved history table below
Admin user management updates (/admin/users):
- "SuperUser" button (ShieldCheck icon) on every non-superuser, non-admin row
- Directly grants SUPERUSER role and auto-closes any open request for that user
lib/storage.ts:
- buildSignatureKey(userId, ext) helper
- uploadBuffer(key, buffer, contentType) — server-side write to dev-uploads or R2
- downloadBuffer(key) — server-side read from dev-uploads or R2 presigned URL
Sidebar:
- "My Profile" link (UserCircle) visible to all roles
- "SuperUser Requests" link (ShieldCheck) in admin section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Submitters can now mark individual item quantities as received when
confirming delivery, rather than treating a PO as all-or-nothing.
Schema (migration: 20260516103013_partial_receipt):
- POStatus: new PARTIALLY_CLOSED value between PAID_DELIVERED and CLOSED
- ActionType: new PARTIAL_RECEIPT_CONFIRMED value
- POLineItem: new deliveredQuantity Decimal? field — accumulates delivered qty
across multiple receipt events
State machine:
- PAID_DELIVERED → confirm_partial_receipt → PARTIALLY_CLOSED (new)
- PARTIALLY_CLOSED → confirm_receipt → CLOSED (all delivered)
- PARTIALLY_CLOSED → confirm_partial_receipt → PARTIALLY_CLOSED (more partial)
Receipt page / form:
- Loads line items with ordered qty, previously delivered qty, and remaining qty
- Per-row numeric input for "receiving now" defaulting to all remaining
- "Mark all remaining" shortcut
- Dynamic button: "Confirm Partial Receipt" vs "Confirm Receipt & Close PO"
- Info banner telling user if the PO will stay open or close
Receipt action:
- Accumulates deliveredQuantity per line item
- If all lines fully delivered → CLOSED + fires notifications + updates inventory
- If any line still outstanding → PARTIALLY_CLOSED (no notifications yet)
- Inventory auto-update runs per-event for the delivered quantities only
Dashboard & PO detail:
- Open Orders count now includes PARTIALLY_CLOSED
- "Confirm Receipt" CTA in po-detail handles PARTIALLY_CLOSED with
distinct amber styling and "Confirm Remaining" label
- Activity log shows PARTIAL_RECEIPT_CONFIRMED with appropriate label
- PARTIALLY_CLOSED gets warning (amber) badge variant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The left signature block on generated PO documents (PDF and XLSX) was
showing the submitter's name as the authorized signatory. The signatory
should be the manager who approved the PO, since they are the one
authorizing the expenditure on behalf of the company.
The approvedBy name was already extracted from the APPROVED / APPROVED_WITH_NOTE
action at the top of the export route — it just wasn't being used in the
signature block. Now both the PDF and XLSX templates use approvedBy for
the left sig block, falling back to the submitter only if the PO has not
yet been approved (e.g. when exporting a draft).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accounts users had no way to review POs that had already been processed
through the payment pipeline. The existing /history page requires the
export_reports permission which accounts does not hold.
New page at /payments/history:
- Scoped to PAID_DELIVERED and CLOSED statuses only
- Gated on view_all_pos permission (held by ACCOUNTS, MANAGER, SUPERUSER, etc.)
- Filterable by paid-date range (paidAt) and cost centre
- Columns: PO number, title, cost centre, vendor, submitter, status badge,
payment ref, amount, paid date
- Summary bar at top showing total paid amount and order count
- 200-row soft limit with prompt to refine filters
Sidebar:
- Added "Payment History" link (Receipt icon) visible to ACCOUNTS and SUPERUSER
- Removed ACCOUNTS from the /history nav item since that page requires
export_reports which accounts does not have (fixes the dead sidebar link)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a submitter edits and resubmits a PO after the manager requested edits
(EDITS_REQUESTED → MGR_REVIEW), the manager now sees exactly what changed.
Changes:
- edit/actions.ts: before mutating the PO, snapshot the current state
(line items + header fields incl. vessel/account/vendor names) into the
SUBMITTED action's metadata as { editSnapshot: { lineItems, fields } }.
- po-line-items-editor: add `originalItemsLabel` prop so the diff banner
message can be context-specific (manager edit vs. submitter resubmit).
- po-detail: detect the resubmit snapshot from the most recent SUBMITTED
action with editSnapshot metadata; show a "Changes from last review"
amber table listing every header field the submitter changed (title,
cost centre, account, vendor, project code, date required, place of
delivery); pass the resubmit snapshot as originalItems to LineItemsEditor
so changed line items are highlighted with strikethrough on prior values.
Resubmit snapshot takes priority over manager-line-edit diff.
Panel is only visible to MANAGER / SUPERUSER when status is MGR_REVIEW.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Technical/Manning dashboard's Recent Orders table was rendering all PO
statuses as a hardcoded gray pill span. The Manager dashboard's Recent
Approved Orders table was similarly hardcoded to success-green for every row,
regardless of actual state (Approved vs Sent for Payment vs Paid).
Replace both with the existing PoStatusBadge component which maps each
PO status to the correct CVA variant (success/warning/danger/default/outline)
per the design-system colour palette defined in PO_STATUS_VARIANTS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously tags were tied to the active sort (only Closest showed when
sorting by distance, only Cheapest when sorting by price). Now both are
computed independently — Cheapest = vendor with min price, Closest =
vendor with min distanceKm — and shown simultaneously when a site is
selected, regardless of which sort is active.
Verified with Playwright: both tags present under distance sort, price
sort, and absent when no site is selected.
useEffect keyed on currentSiteId resets sortBy to 'distance' whenever
the site selection changes, overcoming useState's one-time initialisation
which didn't fire on soft navigations (state preserved across router.push).
Verified with Playwright: no-site→price, select-site→distance auto,
manual price switch works, switch-site→distance resets.
- 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>
Cart icon:
- CartIcon component in header: listens to cart-updated events, shows
item count badge, navigates to /inventory/cart on click
- Visible for TECHNICAL, MANNING, SUPERUSER, MANAGER roles only
PO pre-population:
- /po/new page reads ?cart= searchParam, parses CartItem[] into
LineItemInput[], passes as initialLineItems prop to NewPoForm
- NewPoForm accepts initialLineItems and seeds useState from them
instead of always starting with one blank row
- Cart is cleared immediately when "Create Purchase Order" is clicked
so re-visiting cart doesn't re-submit the same items
Cart fixes:
- Empty state and "Add more items" links corrected from /admin/products
to /inventory/items and /inventory/vendors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /inventory/items/[id]: vendor price table with distance sorting via
SiteSelect client component (?siteId= URL param), price chart, stock
by site, Add to Cart per vendor
- New /inventory/vendors/[id]: contacts panel + searchable items table
with Add to Cart, links back to /inventory/items/[id]
- SiteSelect: reusable client component (useRouter.push, configurable
param key) used by both inventory and admin detail pages
- items-table: item names link to /inventory/items/[id]; vendor names
in expanded rows link to /inventory/vendors/[id]
- vendors-table: vendor names link to /inventory/vendors/[id]
- Fix admin product detail page: replace illegal Server Component
onChange handler with SiteSelect client component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Vendors: remove deprecated contactName/Email/Mobile fields; use
VendorContact nested creates with primary flag, role, mobile, email
- Add pincode + lat/lng to all vendors for distance calculations
- Sites: 12 Indian port locations with lat/lng
- Vessels: 11, linked to sites
- Accounts: 12 covering all spend categories
- Users: 12 across all roles
- ProductVendorPrice: 51 entries linking vendors to products
- ItemInventory: 15 stock records across sites
- ItemConsumption: 14 daily consumption records
- PurchaseOrders: 12 spanning full status lifecycle
- Seed is idempotent (all upserts, safe to re-run)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /inventory/vendors with distance-sorted vendor list and URL-param
site selector (?siteId=). Wires Items + Vendors + Cart into sidebar for
TECH/MANNING/SUPERUSER roles; MANAGER keeps admin management views.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items page now fetches all active sites and passes them alongside
preferredSiteId to ItemsTable. A "Working Site" row appears at the
top of the table — selecting a site calls setPreferredSite, revalidates
the page, and shows distances in the vendor sub-rows. A status hint
("Distances shown from selected site") appears when a site is active;
"No site selected — distances hidden" is the empty-state label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>