Renames the product-catalogue pages (items + vendors, incl. their [id] detail
pages) out of /inventory into /catalogue. /inventory/cart is unchanged. All
internal links, redirects, revalidatePath calls, sidebar nav, and tests are
updated; next.config redirects keep old /inventory/{items,vendors}[/...] URLs
working (permanent) so existing bookmarks don't 404.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: the five named PO T&C slots now allow a one-off custom clause as
well as picking a catalogued one. TermsField becomes a native <input list> +
<datalist> combobox (still plain FormData, no form/page changes). Any current/
custom value is preserved as the input value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the Place-of-Delivery (#19) pattern: an admin clause library that feeds
the PO T&C fields as dropdowns. (No "work order" type — POs only, per steer.)
- schema + migration: TermsCondition (category enum + text + isActive); the
migration seeds the prior TC_DEFAULTS as the starting clauses.
- permission manage_terms (Manager + SuperUser + Admin).
- admin screen /admin/terms: table + Add/Edit dialogs + activate/deactivate +
delete (mirrors /admin/delivery-locations); sidebar link under Administration.
- PO forms (new / edit / manager-edit): the five named T&C slots (Delivery /
Dispatch / Inspection / Transit Insurance / Payment Terms) become a shared
<TermsField> select sourced from active clauses of that category; "Others"
stays free text; the fixed boilerplate lines are untouched.
- tc* columns stay free-text SNAPSHOTS (export/import unchanged); a current value
not among active clauses is preserved as a "(current)" option.
- tests: terms CRUD + permission guard + grouping helper (6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an "Email to vendor" button on the PO detail (available once approved,
through CLOSED, and again after payment) that opens an Outlook draft addressed
to the vendor's primary contact with a time-limited PDF download link.
Since mailto: can't attach files, the PDF is rendered and stored, and the draft
carries a link (the approach chosen for this issue):
- PdfService/: new standalone Express + Playwright microservice (GstService/
EpfoService pattern) — POST /pdf { url } renders a page to a real PDF via
headless Chromium. SSRF-guarded (shared token + optional origin allowlist).
- export route: accepts a server-only `svc` token (PDF_SERVICE_TOKEN) so
PdfService can fetch /api/po/[id]/export?format=pdf without a user session;
`pdf=1` drops the print button + window.print() auto-trigger.
- lib/pdf-service.ts renderPoPdf(); prepareVendorEmail() server action renders →
uploads to R2 (po-pdf/…) → presigns a 7-day link → returns a mailto draft.
- po-detail: EmailVendorButton, shown when approved + vendor has a contact email.
- Gated by PDF_SERVICE_URL/PDF_SERVICE_TOKEN; friendly error if unconfigured.
- No DB model/migration. Tests: prepareVendorEmail (6, PdfService/storage mocked).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the free-text "Place of Delivery" with a dropdown sourced from a new
admin-managed Delivery Locations list (each = a Company FK + free-text address).
- schema + migration: new DeliveryLocation model (companyId, address, isActive).
- permission: manage_delivery_locations granted to Manager + SuperUser + Admin
(Manager-accessible, not admin-only, per the issue).
- admin screen /admin/delivery-locations: table + Add/Edit dialogs +
activate/deactivate + delete (mirrors /admin/sites); sidebar link under
Administration for Manager/SuperUser/Admin.
- PO forms (new / edit / manager-edit): shared <DeliveryLocationField> native
select populated from active locations, formatted "Company — address".
- PurchaseOrder.placeOfDelivery stays a free-text SNAPSHOT (no FK) — the dropdown
only changes how the value is picked, so export/import/historical POs are
unchanged, and an edit preserves a current value not in the list as a
"(current)" option. Deleting a location is therefore always safe.
- tests: delivery-location CRUD + permission guard (6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The approving Manager decides how much of the PO is paid first, via a
0–100% slider on the approval card (default 100% = full). The slider is
convenience only — the resolved ABSOLUTE amount is stored on
PurchaseOrder.suggestedAdvancePayment (Decimal(12,2), nullable).
- schema + migration: add suggestedAdvancePayment (null = no explicit
advance ⇒ full payment, preserves legacy behaviour).
- approvePo(): accepts the amount, clamps to [0, totalAmount], persists
it, records it on the APPROVED audit row. Set once at approval; never
edited afterwards.
- approval-actions.tsx: whole-percent slider showing the resolved ₹
amount + remaining balance; value sent with Approve / Approve-with-Remarks.
- Accounts surface: payment queue + PO detail show the advance; it
prefills the FIRST payment amount (only when nothing is paid yet and
it is a true partial). Balance runs the normal PARTIALLY_PAID loop.
- Not shown on the exported PO/invoice (po-export-layout untouched).
- Tests: persist + audit metadata + clamp-to-total.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Purchasing, Crewing and Administration headings are now collapsible
buttons (chevron + aria-expanded/aria-controls) that collapse by
default. Single-open accordion: opening one heading collapses any
other open one. The section containing the active route auto-expands
on mount/navigation so the user is never stranded on a hidden link.
Adds a jsdom/Testing Library unit test covering default-collapsed,
toggle, single-open accordion, and active-route auto-expand.
Fixes#96
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Office/admin crewing-management surface behind a new manage_crew permission
(Manager + SuperUser + Admin). Stacks on 4b. Behind NEXT_PUBLIC_CREWING_ENABLED.
What's in
- Permission: manage_crew added to the §6 matrix (MGR/SU/ADMIN).
- Direct placement (placeCrew): a Manager assigns a crew member to a vessel/site
WITHOUT a requisition — creates an ACTIVE CrewAssignment, promotes a candidate to
EMPLOYEE with a CRW- number (generateEmployeeId), blocked if already actively
assigned.
- Admin crew CRUD: createCrewMember / updateCrewMember / deleteCrewMember (delete
blocked when assignments/applications exist).
- Crew strength config: upsert/delete VesselRankRequirement (the minStrength that
drives R6 leave-clash detection).
- Screens under Administration (flag-gated, MGR/SU/ADMIN): /admin/crew (list + add/
edit/delete + Place modal) and /admin/crew-strength (requirement table + form).
Tests & docs
- Unit: permissions-crewing.test.ts gains a manage_crew check. Integration:
crewing-admin.test.ts (9) — CRUD, delete guard, direct placement (+promotion,
+active-assignment guard), strength upsert/delete, manage_crew gating.
type-check clean; full unit (241) + integration (192) green.
- CLAUDE.md updated with the crewing-admin surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second slice of Phase 4 (stacked on 4a crew records). Leave (site-applied,
Manager-decided) with clash auto-backfill, and the daily attendance calendar,
per Crewing-Implementation-Spec §5.3/§8.9–8.10. Behind NEXT_PUBLIC_CREWING_ENABLED.
What's in
- Schema (crewing_leave_attendance migration): LeaveRequest (LeaveType,
LeaveStatus) + Attendance (AttendanceStatus, unique per assignment+date) on
CrewAssignment; CrewActionType += LEAVE_APPLIED/LEAVE_DECIDED/ATTENDANCE_RECORDED.
- Leave (R1): site staff apply on behalf (apply_leave); Manager decides
(decide_leave) → assignment ON_LEAVE; MPO has no leave role. Leave approvals also
surface in the central /approvals queue (§8.13 Leave kind). Notification
LEAVE_FOR_APPROVAL.
- Clash auto-backfill (R6): lib/leave-clash.ts, required strength = 1 — approving a
leave that leaves the vessel with zero active same-rank cover auto-raises a LEAVE
requisition via the Phase-2 autoRaiseRequisition.
- Attendance (R5): daily month calendar; site staff record (record_attendance),
Manager views (view_attendance) but cannot edit, MPO neither. saveAttendance
bulk-upserts dirty cells.
- Screens: /crewing/leave (apply-on-behalf + Manager Approve/Decline) and
/crewing/attendance (tap-to-cycle calendar + Save). Leave + Attendance added to
the flag-gated nav (Manager + Site staff).
Tests & docs
- Integration: leave-attendance.test.ts (7) — apply/decide, clash auto-raise (and
no-raise when cover remains), MPO/Manager attendance lockout, permission gating.
type-check clean; full unit (240) + integration (182) green.
- CLAUDE.md updated with the Phase 4b surface.
Deferred: the 6-month leave-planner timeline (lightweight list for now); hours/
overtime attendance (A7).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First slice of Phase 4 (stacked on 3c onboarding). The Crew directory and tabbed
crew profile with documents, bank/EPF (role-masked), next of kin, PPE and
experience, per Crewing-Implementation-Spec §8.7–8.8. Behind
NEXT_PUBLIC_CREWING_ENABLED; production unchanged.
What's in
- Schema (crewing_crew_records migration): SeafarerDocument, NextOfKin
(isEmergency), ExperienceRecord, PpeIssue (PpeItem enum) — all on CrewMember;
CrewActionType += DOCUMENT_UPLOADED/RECORD_UPDATED/PPE_ISSUED/PPE_RETURNED/
EXPERIENCE_ADDED.
- PII masking (lib/crew-pii.ts, §6/§8.8): bank account + Aadhaar full only for
Accounts/SuperUser, masked otherwise; salary hidden from site staff. Applied
server-side before crossing to the client.
- Actions (crewing/crew/actions.ts): uploadDocument/deleteDocument, saveBankEpf,
addNextOfKin/deleteNextOfKin, issuePpe/returnPpe, addExperience — guarded by
upload_crew_records / issue_ppe, each writes a CrewAction.
- Screens: /crewing/crew (directory, search + vessel filter, ex-hands excluded)
and /crewing/crew/[id] (tabbed profile: Documents · Bank & EPF · Next of kin ·
PPE · Experience · Pay status). Crew added to the flag-gated nav (MGR/MPO/Site/
Accounts).
Tests & docs
- Unit: crew-pii.test.ts (6). Integration: crew-records.test.ts (7) — documents,
bank/EPF upsert, NoK, PPE issue/return, experience + permission gating.
type-check clean; full unit (240) + integration (175) green.
- CLAUDE.md updated with the Phase 4a surface.
Deferred: site-staff own-site scoping (needs a User↔Site link); the records verify
queue (§8.11, Phase 5); Pay-status shows the salary structure only until payroll
(Phase 6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First slice of Phase 3 (Epics B/C/D shipped as stacked sub-PRs). Adds the
CrewMember talent-pool spine and the Candidates screens. Behind
NEXT_PUBLIC_CREWING_ENABLED; production unchanged. Stacks on the requisitions
branch (Phase 2).
What's in
- Schema (crewing_candidates migration): CrewMember (spine) + CrewStatus,
CandidateType, CandidateSource enums; CrewAction gains a nullable crewMemberId;
CrewActionType += CANDIDATE_ADDED/UPDATED. employeeId is assigned at onboarding
(3c), so it's nullable here.
- Actions (crewing/candidates/actions.ts): addCandidate / updateCandidate —
guard flag + manage_candidates, write a CrewAction, optional CV upload via
buildStorageKey("cv", …) + uploadBuffer (no parsing — A2 deferred). EX_HAND
source ⇒ type/status EX_HAND; edits never downgrade an EMPLOYEE.
- Screens: /crewing/candidates (master list with search/source/rank-applied/
min-experience filters as removable chips + match count + Clear all; Add-candidate
modal) and /crewing/candidates/[id] (profile; pipeline stepper is 3b). Candidates
added to the flag-gated Crewing nav (Manager + MPO).
Tests & docs
- Integration: candidates.test.ts (7) — add/update, ex-hand derivation, employee
no-downgrade, permission gating. type-check clean; full unit (225) + integration
(153) suites green.
- CLAUDE.md "Crewing" section updated with the Phase 3a surface.
Deferred: public careers intake API (A2, §13 open question); CV parsing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second slice of the Crewing module per wiki Crewing-Implementation-Spec §12
(build order item 2). Everything stays behind NEXT_PUBLIC_CREWING_ENABLED;
production is unchanged. Schema is added incrementally — this lands the
requisition lifecycle layer.
What's in
- Schema: Requisition (OPEN→SHORTLISTING→PROPOSING→INTERVIEWING→SELECTED→FILLED,
→CANCELLED), ReliefRequest, CrewAction (the POAction mirror) + their enums.
Migration crewing_requisitions.
- State machine: lib/requisition-state-machine.ts mirrors po-state-machine
(selection Manager-only; orthogonal cancel from OPEN/SHORTLISTING by
cancel_requisition holders, §6). Codes REQ-9000… via lib/requisition-number.ts.
- Actions: raise/cancel/transition + requestReliefCover/convertReliefToRequisition,
each guarding flag+permission+state, writing a CrewAction and notifying. Shared
autoRaiseRequisition() (lib/requisition-service.ts) is the backfill entry point
for sign-off / leave-clash (later phases).
- Notifier: notifyCrew() PO-independent path + CrewNotificationEvent.
- Screens: /crewing/requisitions (list + Raise modal + relief convert) and
/crewing/requisitions/[id] (detail). Requisitions added to the flag-gated
Crewing sidebar (Manager + MPO, §7).
Tests & docs
- Unit: requisition-state-machine.test.ts (11).
- Integration: requisitions.test.ts (15) — raise/cancel/transition, relief
request + convert, auto-raise, permission gating.
- CLAUDE.md "Crewing" section updated with the Phase 2 surface.
Deferred: sign-off/experience (Epic K, §12 item 2) depends on the crew/assignment
models from Phase 3/4; autoRaiseRequisition() is ready for it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gated behind NEXT_PUBLIC_SUBMITTER_VIEW_ALL_ENABLED (opt-in, "true").
When on, submitter roles (TECHNICAL/MANNING) get read-only access to every
PO: the History page + report export, any other user's PO detail page, and
the per-PO Export PDF/XLSX buttons. No approval/payment/edit rights are added.
- lib/feature-flags.ts: SUBMITTER_VIEW_ALL_ENABLED flag
- lib/permissions.ts: isSubmitterRole / submitterCanViewAll / canViewAllPos
- po/[id] page + export route: gate via canViewAllPos
- history page + reports/export route: OR submitterCanViewAll into export_reports
- sidebar: show History to submitters when flag on
- tests: permission helpers, both flag states
- docs: .env.example, CLAUDE.md (wiki updated separately)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Managers and superusers can cancel a PO from any state via a confirmation modal
that requires typing "cancel" and a mandatory reason. A cancelled PO becomes a
terminal CANCELLED state and drops out of every spend tracker/graph (those filter
on POST_APPROVAL_STATUSES / explicit whitelists, none of which include CANCELLED).
A cancelled PO may optionally be linked to the existing PO that supersedes it
(by PO number); the replacement shows the reciprocal "supersedes" link. No
vessel/account/vendor match is enforced and the link can be added any time.
Cancelled POs remain visible (greyed in history) and exportable, with a diagonal
"CANCELLED" watermark on both the PDF and XLSX exports.
- schema: POStatus CANCELLED; cancelledAt/cancellationReason; self-referential
supersededById relation; ActionType CANCELLED/SUPERSEDED (+ migration)
- state machine canCancel(); cancel_po permission (MANAGER + SUPERUSER)
- cancelPo / supersedePo server actions + PO_CANCELLED notification
- cancel modal + supersede form; cancelled banner with reciprocal links
- exhaustive CANCELLED entries in all status label/variant maps
- diagonal CANCELLED watermark embedded for PDF (CSS) and XLSX (image)
- integration tests (cancel from any state, reason/role guards, supersede)
Inventory reversal on cancel is deferred to #55 (inventory is feature-flagged off).
Closes#53
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PO line-items Unit of Measure dropdown only offered hr/day among
time-based units. Add week, month and year so durations beyond days can
be selected, as requested. UOM_OPTIONS is the single source of truth and
`unit` is validated as a free-form string, so no schema/validation change
is needed.
Fixes#44
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renders a thin fixed banner only when NEXT_PUBLIC_ENV_LABEL is set; production
leaves it unset so nothing shows. Used to mark the staging instance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All PO attachments are stored as PODocument rows whose lifecycle stage
(submission vs delivery) is encoded in the storageKey prefix. The PO
details screen previously listed them in a single flat "Attachments"
block, giving no indication of which were submission documents (invoice,
quotation) versus delivery receipts.
Add lib/attachments.ts to derive a user-facing group from the storageKey
prefix (submission / payment / delivery / other) and render each
non-empty group as a labelled subsection on the PO details screen, in
lifecycle order. Unknown prefixes fall back to an "Other" group so
nothing is ever hidden.
Fixes#10
Portal issues now file with only the 'portal' label. The watcher runs two phases:
1. Triage — Claude reads each untriaged 'portal' issue (analysis only), posts a
requirements-breakdown comment, and routes it to 'claude-queue' (auto-fixable)
or 'interactive' (needs human steering).
2. Fix — unchanged; processes 'claude-queue' issues into PRs.
The triage breakdown is posted without the bot marker so the fix stage reads it
back as refined requirements.
PS 5.1 fixes found while validating:
- Send API bodies as UTF-8 bytes (Invoke-RestMethod mangled non-ASCII, e.g. the
em-dash in Claude's breakdown, so Forgejo rejected the JSON)
- Build the labels array body by hand (ConvertTo-Json unwraps a single-element
array to a scalar, which Forgejo rejects)
- Triage output via two plain files (label + markdown) instead of one JSON blob
(embedded-newline markdown broke ConvertFrom-Json)
- Read triage files as UTF-8; additive label POST + a guard so Set-IssueLabels
can never wipe an issue's labels
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PO date field was only displayed when a submitter explicitly set it.
For approved POs without an explicit date, the approval date is now shown.
Precedence: submitter-set poDate → approvedAt → createdAt (matches the
export route which already used this logic).
Fixes#5
Add an optional PO Date field to the create and edit PO forms.
Submitters can pick any date (back-dated or forward-dated). If left
blank, the exported PO document falls back to the approved date, then
to the creation date.
Changes:
- Prisma schema: add `poDate DateTime?` to PurchaseOrder
- Migration 20260616000000_add_po_date: ALTER TABLE to add the column
- createPoSchema: add optional `poDate` string field
- new-po-form, edit-po-form: add PO Date picker in Order Information
- create/edit actions: persist poDate to DB
- edit action resubmit snapshot: track poDate changes for manager diff
- po-detail: show PO Date in Order Details; include in resubmit diff banner
- export route: use poDate ?? approvedAt ?? createdAt as the date on
the exported PDF/XLSX document
- validations.test: fix pre-existing costCentreRef→vesselId mismatch
and add poDate test cases
Fixes#4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Report Issue button in portal header files a Forgejo issue (portal + claude-queue labels)
- Windows scheduled watcher runs headless Claude Code on queued issues and opens a PR
- .forgejo/workflows/deploy.yml deploys v* release tags via the pms1 host runner (pm2 restart ppms)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New PurchaseOrder.paymentDate field (migration 20260531000002)
- Backfill: existing POs use paidAt, else the earliest payment action date
- Accounts must enter a payment date with the payment reference
- Date input pre-selected to today, max=today (no future dates)
- Validated server-side (required + not in future) in processPaymentSchema
- paymentDate stored on both full and partial payments; paidAt set from it
- Shown on PO detail (Payment Date) and payment history (prefers paymentDate)
- Integration tests updated; added future-date rejection test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PURCHASING_MGMT roles narrowed to MANAGER only, so the entire Purchasing
section disappears for ADMIN (they never needed the /inventory/ browse links).
Cost Centres (/admin/vessels) added to the top of ADMIN_ITEMS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds scroll (capture) and resize listeners so the fixed-position dropdown
tracks its trigger as the page scrolls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sidebar:
- Inventory section renamed to Purchasing
- Manager gets separate Administration section for Vendors only
- Admin gets full Administration (Vendors + Users + Accounting Codes + Companies)
- Sites hidden from Manager when NEXT_PUBLIC_INVENTORY_ENABLED=false
- Cost Centres replaces Vessels in the Purchasing nav link
Admin vessel pages:
- All headings, titles, dialogs, breadcrumbs: Vessels -> Cost Centre
- Error messages updated accordingly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Compact dropdown is now right-anchored at fixed 380px width so it
extends leftward from the trigger and doesn't overflow the table edge
- Group headers in compact mode show only the sub-category (strip the
top-level breadcrumb before the arrow) and are single-line truncated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema:
- New Company model (name, gstNumber, address, telephone, mobile, email, invoiceAddress, isActive)
- PurchaseOrder.companyId FK (optional, SET NULL on company delete)
- Migration: 20260530000003_add_company
Admin:
- /admin/companies page with full CRUD (create, edit, deactivate, delete)
- Companies table shows name, GST, contact details, status
- Companies link added to Admin section of sidebar (Briefcase icon)
PO forms (new / edit / import / manager-edit):
- Company dropdown appears at the top of Order Information when companies exist
- Pre-populated with first active company; selection persisted to DB via companyId
Import form:
- parseSheet() now extracts companyName from Excel row 1 (col A)
- Import preview auto-matches detected company name against known companies
- Shows detected name as a hint; user can override before saving
Export (PDF + XLSX):
- Company constants (CO_NAME, CO_ADDR, CO_TEL, INV_ADDR, INV_GST) are now
derived from the linked Company record when present, falling back to the
original Pelagia Marine hardcoded defaults when no company is set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cost Centre on PO forms now shows only Vessels (plain vesselId field).
Sites are a separate concept and not selectable as cost centres.
- PurchaseOrder.vesselId is required again (NOT NULL restored)
- Vessel.siteId and vessel->site relation removed from schema
- DB migration: drops Vessel.siteId column, restores PO.vesselId NOT NULL
- All PO forms (new/edit/import/manager-edit): plain vessel <select> with
code-prefixed labels (e.g. "HNR1 — HNR 1")
- History, approvals, dashboard, my-orders, payments: back to vesselId
filter params and po.vessel.name display
- Admin vessels: removed Site column and site-assignment dropdown
- Admin sites detail page: removed "Assigned Vessels" section
- Sites table: removed Vessels count column (no longer linked)
- seed-prod.ts and seed.ts: vessels created without siteId
- SearchableSelect accounting code picker retained from previous commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accounting Code search (new/edit/import/manager-edit PO forms):
- New SearchableSelect component (components/ui/searchable-select.tsx):
type-to-filter by code or name, results grouped by sub-category with
sticky headers, highlighted selected item, clear button, Escape/outside-click
to dismiss
- Replaces the plain <select> for the main Accounting Code field on all PO forms
- LineItemsEditor per-row account column also uses SearchableSelect (compact size)
when multi-account mode is active
Cost Centre dropdown reorganised by site:
- New type CostCentreGroup replaces flat CostCentreOption
- Each site becomes an <optgroup> label (unselectable); the site itself is the
first selectable option inside ("Haldia (Site)"), followed by its vessels
- Vessels with no site assigned appear under an "Unassigned Vessels" group
- Shared helpers buildCostCentreGroups() and buildAccountGroups() in
lib/cost-centre-groups.ts — used by all four PO form pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Undo Vessel→Cost Centre rename in admin (admin shows "Vessel Management" again)
- Sidebar: "Cost Centres"→"Vessels", "Accounts"→"Accounting Codes"
- PO forms (new/edit/import/manager-edit) now show both Vessels (with code) and Sites in the
Cost Centre dropdown, encoded as v:<id> / s:<id> via a costCentreRef field
- vesselId on PurchaseOrder is now nullable; siteId is set when a site is the cost centre
- History, approvals, dashboard, my-orders, payments display vessel.name ?? site.name as Cost Centre
- History and approvals cost centre filters use costCentreRef URL param supporting both types
- Admin vessel form: adds Site assignment dropdown
- Admin accounts: renamed to "Accounting Code" throughout (pages, forms, sidebar)
- PO detail and exports: "Account" label renamed to "Accounting Code"
- Site detail: "Assigned Vessels (Cost Centres)" heading; vessel detail breadcrumb fixed
- Create PO links from vessel/site detail use ?costCentreRef= param
- Export routes handle costCentreRef filter param (with legacy vesselId fallback)
- DB migration: ALTER TABLE PurchaseOrder ALTER COLUMN vesselId DROP NOT NULL
- CLAUDE.md updated with Cost Centre Model documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace immediate server action calls with ConfirmDialog modals for
activate/deactivate on all 6 admin tables (users, vendors, vessels,
sites, accounts, products). Delete already used DeleteConfirmDialog;
this adds the same pattern for reversible toggle actions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace per-row inline action buttons (Edit, Activate/Deactivate, Delete,
Grant SuperUser) across all six admin tables with a Radix DropdownMenu
triggered by a ⋯ button. Introduces RowActionsMenu/Item/DestructiveItem/
Separator primitives and a DeleteConfirmDialog modal. Each Edit*Button
gains controlled open/onOpenChange props so the dialog can be driven from
the table's per-row ActionsMenu sub-component. Toggle and delete actions
use useTransition + router.refresh() directly in the table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a reusable useTableControls hook and TableControls/SortableTh
components, then wires them into all six admin table pages (users,
vendors, vessels, sites, accounts, products). Each page now supports
a global search bar, clickable sortable column headers with ↑/↓/⇅
indicators, and role/status filter chips — all purely client-side with
no URL params or server round-trips. Server pages continue to fetch the
full list and pass it as props to a new *-table.tsx Client Component.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>