refactor(labels): rename Vessel→Cost Centre and Account/Cost Centre→Account
All user-facing strings updated across 22 files. Backend field names
(vesselId, vessel relation, Vessel model) unchanged.
Vessel → Cost Centre
- Page titles: Vessel Management, Vessel Detail
- Sidebar nav item
- Form labels in New PO, Edit PO, Import PO, Manager Edit, Approvals Search
- Table column headers in Approvals, Dashboard, History, My Orders
- Filter dropdowns ("All vessels" → "All cost centres")
- vessel-form dialogs: Add/Edit/Create
- sites/[id] stat card label
- sites/page column header
- XLSX export row 7 label; PDF HTML label; CSV header
- "Vessel/Office Requisition Number" → "Cost Centre/Office Requisition Number"
- Breadcrumb in vessel detail; "Home port" → "Home site"
- spend-charts heading
- Validation error message
Account / Cost Centre → Account
- po-detail "Account / Budget Head"
- manager-edit-po-form "Account / Cost Centre"
- import-form "Account / Cost Centre"
- accounts/page heading "Account / Cost Centre Management"
- XLSX export "Budget head"; PDF HTML "Budget head"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e9ed0f8eb0
commit
902bd5f048
22 changed files with 47 additions and 47 deletions
|
|
@ -20,7 +20,7 @@ export default async function AdminAccountsPage() {
|
|||
return (
|
||||
<div>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold text-neutral-900">Account / Cost Centre Management</h1>
|
||||
<h1 className="text-2xl font-semibold text-neutral-900">Account Management</h1>
|
||||
<AddAccountButton />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export default async function SiteDetailPage({ params }: Props) {
|
|||
{/* Summary cards */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="rounded-lg border border-neutral-200 bg-white px-5 py-4">
|
||||
<p className="text-xs text-neutral-500 mb-1">Assigned Vessels</p>
|
||||
<p className="text-xs text-neutral-500 mb-1">Assigned Cost Centres</p>
|
||||
<p className="text-2xl font-semibold text-neutral-900">{site.vessels.length}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-neutral-200 bg-white px-5 py-4">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default async function SitesPage() {
|
|||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Name</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Code</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Address</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Vessels</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Cost Centres</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Items tracked</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Location</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Status</th>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ interface Props { params: Promise<{ id: string }> }
|
|||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
const v = await db.vessel.findUnique({ where: { id }, select: { name: true } });
|
||||
return { title: v?.name ?? "Vessel Detail" };
|
||||
return { title: v?.name ?? "Cost Centre Detail" };
|
||||
}
|
||||
|
||||
export default async function VesselDetailPage({ params }: Props) {
|
||||
|
|
@ -47,7 +47,7 @@ export default async function VesselDetailPage({ params }: Props) {
|
|||
return (
|
||||
<div className="max-w-5xl space-y-6">
|
||||
<div className="flex items-center gap-2 text-sm text-neutral-500">
|
||||
<Link href="/admin/vessels" className="hover:text-neutral-700">Vessels</Link>
|
||||
<Link href="/admin/vessels" className="hover:text-neutral-700">Cost Centres</Link>
|
||||
<span>/</span>
|
||||
<span className="text-neutral-900 font-medium">{vessel.name}</span>
|
||||
</div>
|
||||
|
|
@ -63,7 +63,7 @@ export default async function VesselDetailPage({ params }: Props) {
|
|||
<h1 className="text-2xl font-semibold text-neutral-900">{vessel.name}</h1>
|
||||
{vessel.site && (
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
Home port: <Link href={`/admin/sites/${vessel.site.id}`} className="text-primary-600 hover:underline">{vessel.site.name}</Link>
|
||||
Home site: <Link href={`/admin/sites/${vessel.site.id}`} className="text-primary-600 hover:underline">{vessel.site.name}</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { ConfirmDeleteButton } from "@/components/ui/confirm-delete-button";
|
|||
import { deleteVessel } from "./actions";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = { title: "Vessel Management" };
|
||||
export const metadata: Metadata = { title: "Cost Centre Management" };
|
||||
|
||||
export default async function AdminVesselsPage() {
|
||||
const session = await auth();
|
||||
|
|
@ -20,7 +20,7 @@ export default async function AdminVesselsPage() {
|
|||
return (
|
||||
<div>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold text-neutral-900">Vessel Management</h1>
|
||||
<h1 className="text-2xl font-semibold text-neutral-900">Cost Centre Management</h1>
|
||||
<AddVesselButton />
|
||||
</div>
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ export default async function AdminVesselsPage() {
|
|||
))}
|
||||
{vessels.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={4} className="px-4 py-8 text-center text-neutral-400">No vessels yet.</td>
|
||||
<td colSpan={4} className="px-4 py-8 text-center text-neutral-400">No cost centres yet.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function VesselFormFields({ vessel }: { vessel?: VesselRow }) {
|
|||
return (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-neutral-700 mb-1">Vessel Name *</label>
|
||||
<label className="block text-xs font-medium text-neutral-700 mb-1">Cost Centre Name *</label>
|
||||
<input name="name" defaultValue={vessel?.name} required
|
||||
className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" />
|
||||
</div>
|
||||
|
|
@ -49,9 +49,9 @@ export function AddVesselButton() {
|
|||
<>
|
||||
<button onClick={() => setOpen(true)}
|
||||
className="rounded-lg bg-primary-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-primary-700 transition-colors">
|
||||
+ Add Vessel
|
||||
+ Add Cost Centre
|
||||
</button>
|
||||
<AdminDialog title="Add Vessel" open={open} onClose={() => setOpen(false)}>
|
||||
<AdminDialog title="Add Cost Centre" open={open} onClose={() => setOpen(false)}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<VesselFormFields />
|
||||
{error && <p className="text-sm text-danger-700 bg-danger-50 rounded-lg px-3 py-2">{error}</p>}
|
||||
|
|
@ -62,7 +62,7 @@ export function AddVesselButton() {
|
|||
</button>
|
||||
<button type="submit" disabled={pending}
|
||||
className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60">
|
||||
{pending ? "Creating…" : "Create Vessel"}
|
||||
{pending ? "Creating…" : "Create Cost Centre"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -108,7 +108,7 @@ export function EditVesselButton({ vessel }: { vessel: VesselRow }) {
|
|||
{toggling ? "…" : vessel.isActive ? "Deactivate" : "Activate"}
|
||||
</button>
|
||||
</div>
|
||||
<AdminDialog title="Edit Vessel" open={open} onClose={() => setOpen(false)}>
|
||||
<AdminDialog title="Edit Cost Centre" open={open} onClose={() => setOpen(false)}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<VesselFormFields vessel={vessel} />
|
||||
{error && <p className="text-sm text-danger-700 bg-danger-50 rounded-lg px-3 py-2">{error}</p>}
|
||||
|
|
|
|||
|
|
@ -150,13 +150,13 @@ export function ManagerEditPoForm({ po, vessels, accounts, vendors }: Props) {
|
|||
<input name="title" required defaultValue={po.title} className={INPUT} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL}>Vessel <span className="text-danger">*</span></label>
|
||||
<label className={LABEL}>Cost Centre <span className="text-danger">*</span></label>
|
||||
<select name="vesselId" required defaultValue={po.vesselId} className={INPUT}>
|
||||
{vessels.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL}>Account / Cost Centre <span className="text-danger">*</span></label>
|
||||
<label className={LABEL}>Account <span className="text-danger">*</span></label>
|
||||
<select name="accountId" required defaultValue={po.accountId} className={INPUT}>
|
||||
{accounts.map((a) => <option key={a.id} value={a.id}>{a.name} ({a.code})</option>)}
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ export function ApprovalsSearch({ vessels }: Props) {
|
|||
className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-neutral-600 mb-1">Vessel</label>
|
||||
<label className="block text-xs font-medium text-neutral-600 mb-1">Cost Centre</label>
|
||||
<select value={vesselId} onChange={(e) => setVesselId(e.target.value)}
|
||||
className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20">
|
||||
<option value="">All vessels</option>
|
||||
<option value="">All cost centres</option>
|
||||
{vessels.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default async function ApprovalsPage({ searchParams }: Props) {
|
|||
<th className="px-4 py-3 text-left font-medium text-neutral-600">PO Number</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Title</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Submitter</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Vessel</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Cost Centre</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Amount</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Submitted</th>
|
||||
<th className="px-4 py-3"></th>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ async function ManagerDashboard() {
|
|||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">PO</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Title</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Vessel</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Cost Centre</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Status</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Amount</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Approved</th>
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@ export function HistoryFilters({ vessels }: Props) {
|
|||
className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-neutral-600 mb-1">Vessel</label>
|
||||
<label className="block text-xs font-medium text-neutral-600 mb-1">Cost Centre</label>
|
||||
<select value={vesselId} onChange={(e) => setVesselId(e.target.value)}
|
||||
className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20">
|
||||
<option value="">All vessels</option>
|
||||
<option value="">All cost centres</option>
|
||||
{vessels.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export default async function HistoryPage({ searchParams }: Props) {
|
|||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">PO Number</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Title</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Vessel</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Cost Centre</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Submitter</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Status</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Amount</th>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function PoTable({ title, rows, className = "" }: { title: string; rows: PoRow[]
|
|||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">PO Number</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Title</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Vessel</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Cost Centre</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Status</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-neutral-600">Amount</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-neutral-600">Updated</th>
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
|
|||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">
|
||||
Vessel <span className="text-danger">*</span>
|
||||
Cost Centre <span className="text-danger">*</span>
|
||||
</label>
|
||||
<select name="vesselId" required defaultValue={po.vesselId} className={INPUT_CLS}>
|
||||
<option value="">Select vessel…</option>
|
||||
|
|
@ -186,7 +186,7 @@ export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
|
|||
<h2 className="text-base font-semibold text-neutral-900 mb-4">Requisition</h2>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">Vessel / Office Requisition No.</label>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">Cost Centre / Office Requisition No.</label>
|
||||
<input name="requisitionNo" defaultValue={extPo.requisitionNo ?? ""} className={INPUT_CLS} placeholder="Optional" />
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ export function ImportForm({ vessels, accounts, vendors }: Props) {
|
|||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">
|
||||
Vessel <span className="text-danger">*</span>
|
||||
Cost Centre <span className="text-danger">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={preview.vesselId}
|
||||
|
|
@ -187,13 +187,13 @@ export function ImportForm({ vessels, accounts, vendors }: Props) {
|
|||
required
|
||||
className={INPUT_CLS}
|
||||
>
|
||||
<option value="">Select vessel…</option>
|
||||
<option value="">Select cost centre…</option>
|
||||
{vessels.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">
|
||||
Account / Cost Centre <span className="text-danger">*</span>
|
||||
Account <span className="text-danger">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={preview.accountId}
|
||||
|
|
|
|||
|
|
@ -79,10 +79,10 @@ export function NewPoForm({ vessels, accounts, vendors }: Props) {
|
|||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">
|
||||
Vessel <span className="text-danger">*</span>
|
||||
Cost Centre <span className="text-danger">*</span>
|
||||
</label>
|
||||
<select name="vesselId" required className={INPUT_CLS}>
|
||||
<option value="">Select vessel…</option>
|
||||
<option value="">Select cost centre…</option>
|
||||
{vessels.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -143,7 +143,7 @@ export function NewPoForm({ vessels, accounts, vendors }: Props) {
|
|||
<h2 className="text-base font-semibold text-neutral-900 mb-4">Requisition</h2>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">Vessel / Office Requisition No.</label>
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-1.5">Cost Centre / Office Requisition No.</label>
|
||||
<input name="requisitionNo" className={INPUT_CLS} placeholder="Optional" />
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -217,12 +217,12 @@ export async function GET(request: NextRequest, { params }: Props) {
|
|||
ws.mergeCells("G6:H6");
|
||||
sc(6, 9, piDate, { font: fBase, border: bordAll, align: alignL });
|
||||
|
||||
// ══ ROW 7: Vessel Owner / Budget / Requested By ═══════════════════════
|
||||
// ══ ROW 7: Cost Centre / Account / Requested By ═══════════════════════
|
||||
ws.getRow(7).height = 14;
|
||||
sc(7, 1, "Vessel Owner Name", { font: fBold, fill: fillLbl, border: bordAll, align: alignL });
|
||||
sc(7, 1, "Cost Centre", { font: fBold, fill: fillLbl, border: bordAll, align: alignL });
|
||||
ws.mergeCells("A7:B7");
|
||||
sc(7, 3, "Pelagia Marine Services Pvt. Ltd.", { font: fBase, border: bordAll, align: alignL });
|
||||
sc(7, 4, "Budget head", { font: fBold, fill: fillLbl, border: bordAll, align: alignC });
|
||||
sc(7, 4, "Account", { font: fBold, fill: fillLbl, border: bordAll, align: alignC });
|
||||
ws.mergeCells("D7:E7");
|
||||
sc(7, 6, po.account.code, { font: fBase, border: bordAll, align: alignC });
|
||||
sc(7, 7, "Requested By", { font: fBold, fill: fillLbl, border: bordAll, align: alignC });
|
||||
|
|
@ -231,7 +231,7 @@ export async function GET(request: NextRequest, { params }: Props) {
|
|||
|
||||
// ══ ROW 8: Requisition / Approved By ═════════════════════════════════
|
||||
ws.getRow(8).height = 14;
|
||||
sc(8, 1, "Vessel/Office Requisition Number", { font: fBold, fill: fillLbl, border: bordAll, align: alignL });
|
||||
sc(8, 1, "Cost Centre/Office Requisition Number", { font: fBold, fill: fillLbl, border: bordAll, align: alignL });
|
||||
ws.mergeCells("A8:B8");
|
||||
sc(8, 3, reqNo, { font: fBase, border: bordAll, align: alignL });
|
||||
sc(8, 4, "Reqn. Date", { font: fBold, fill: fillLbl, border: bordAll, align: alignC });
|
||||
|
|
@ -537,18 +537,18 @@ export async function GET(request: NextRequest, { params }: Props) {
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- ── Vessel / Budget / Requested By ───────────────────────── -->
|
||||
<!-- ── Cost Centre / Account / Requested By ──────────────────── -->
|
||||
<table class="meta" style="margin-bottom:0">
|
||||
<tr>
|
||||
<td class="lbl" style="width:22%">Vessel Owner Name</td>
|
||||
<td class="lbl" style="width:22%">Cost Centre</td>
|
||||
<td style="width:24%">Pelagia Marine Services Pvt. Ltd.</td>
|
||||
<td class="lbl" style="width:12%;text-align:center">Budget head</td>
|
||||
<td class="lbl" style="width:12%;text-align:center">Account</td>
|
||||
<td style="width:8%;text-align:center">${po.account.code}</td>
|
||||
<td class="lbl" style="width:14%">Requested By</td>
|
||||
<td style="width:20%">${po.submitter.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl">Vessel/Office Requisition Number</td>
|
||||
<td class="lbl">Cost Centre/Office Requisition Number</td>
|
||||
<td>${reqNo}</td>
|
||||
<td class="lbl" style="text-align:center">Reqn. Date</td>
|
||||
<td style="text-align:center">${reqDate}</td>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export async function GET(request: NextRequest) {
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PO Number</th><th>Title</th><th>Status</th><th>Vessel</th>
|
||||
<th>PO Number</th><th>Title</th><th>Status</th><th>Cost Centre</th>
|
||||
<th>Submitter</th><th>Vendor</th><th style="text-align:right">Amount</th><th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -98,7 +98,7 @@ export async function GET(request: NextRequest) {
|
|||
}
|
||||
|
||||
// Default: CSV
|
||||
const headers = ["PO Number", "Title", "Status", "Vessel", "Account", "Vendor", "Submitter", "Amount", "Currency", "Created"];
|
||||
const headers = ["PO Number", "Title", "Status", "Cost Centre", "Account", "Vendor", "Submitter", "Amount", "Currency", "Created"];
|
||||
const csvRows = orders.map((po) => [
|
||||
po.poNumber,
|
||||
`"${po.title.replace(/"/g, '""')}"`,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function SpendCharts({ vesselData, monthData }: Props) {
|
|||
<div className="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{vesselData.length > 0 && (
|
||||
<div className="rounded-lg border border-neutral-200 bg-white p-5">
|
||||
<h2 className="text-sm font-semibold text-neutral-700 mb-4">Approved Spend by Vessel (Top 5)</h2>
|
||||
<h2 className="text-sm font-semibold text-neutral-700 mb-4">Approved Spend by Cost Centre (Top 5)</h2>
|
||||
<ResponsiveContainer width="100%" height={220}>
|
||||
<BarChart data={vesselData} margin={{ top: 4, right: 8, bottom: 4, left: 8 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" vertical={false} />
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const NAV_ITEMS: NavItem[] = [
|
|||
const INVENTORY_ITEMS: NavItem[] = [
|
||||
{ href: "/admin/vendors", label: "Vendors", icon: Store, roles: ["MANAGER", "ACCOUNTS", "ADMIN"] },
|
||||
{ href: "/admin/products", label: "Items", icon: Package, roles: ["MANAGER", "ADMIN"] },
|
||||
{ href: "/admin/vessels", label: "Vessels", icon: Ship, roles: ["MANAGER", "ADMIN"] },
|
||||
{ href: "/admin/vessels", label: "Cost Centres", icon: Ship, roles: ["MANAGER", "ADMIN"] },
|
||||
{ href: "/admin/sites", label: "Sites", icon: MapPin, roles: ["MANAGER", "ADMIN"] },
|
||||
{ href: "/inventory/cart", label: "Cart", icon: ShoppingCart, roles: ["MANAGER", "SUPERUSER", "TECHNICAL", "MANNING"] },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -174,8 +174,8 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
|
|||
<div className="rounded-lg border border-neutral-200 bg-white p-6">
|
||||
<h3 className="text-sm font-semibold text-neutral-900 mb-4">Order Details</h3>
|
||||
<dl className="grid grid-cols-2 gap-x-6 gap-y-3 text-sm">
|
||||
<div><dt className="text-neutral-500">Vessel</dt><dd className="font-medium text-neutral-900">{po.vessel.name}</dd></div>
|
||||
<div><dt className="text-neutral-500">Account / Budget Head</dt><dd className="font-medium text-neutral-900">{po.account.name} ({po.account.code})</dd></div>
|
||||
<div><dt className="text-neutral-500">Cost Centre</dt><dd className="font-medium text-neutral-900">{po.vessel?.name ?? "—"}</dd></div>
|
||||
<div><dt className="text-neutral-500">Account</dt><dd className="font-medium text-neutral-900">{po.account.name} ({po.account.code})</dd></div>
|
||||
<div><dt className="text-neutral-500">Requested By</dt><dd className="font-medium text-neutral-900">{po.submitter.name}</dd></div>
|
||||
{approvalAction && (
|
||||
<div><dt className="text-neutral-500">Approved By</dt><dd className="font-medium text-neutral-900">{approvalAction.actor.name}</dd></div>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const TC_DEFAULTS = {
|
|||
|
||||
export const createPoSchema = z.object({
|
||||
title: z.string().min(1, "Title is required").max(200),
|
||||
vesselId: z.string().min(1, "Vessel is required"),
|
||||
vesselId: z.string().min(1, "Cost Centre is required"),
|
||||
accountId: z.string().min(1, "Account is required"),
|
||||
projectCode: z.string().optional(),
|
||||
dateRequired: z.string().optional(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue