pelagia-portal/App/pelagia-portal/components/layout/sidebar.tsx
Hardik 4c1a41fe61 feat(accounts): add payment history page at /payments/history
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>
2026-05-16 15:56:07 +05:30

130 lines
4.6 KiB
TypeScript

"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
FileText,
Plus,
CheckSquare,
CreditCard,
History,
Receipt,
Users,
Ship,
Building2,
Store,
Anchor,
Package,
Upload,
MapPin,
ShoppingCart,
BarChart3,
} from "lucide-react";
import type { Role } from "@prisma/client";
interface NavItem {
href: string;
label: string;
icon: React.ElementType;
roles?: Role[];
}
const NAV_ITEMS: NavItem[] = [
{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
{ href: "/po/new", label: "New PO", icon: Plus, roles: ["TECHNICAL", "MANNING", "MANAGER", "SUPERUSER"] },
{ href: "/my-orders", label: "My Purchase Orders", icon: FileText, roles: ["TECHNICAL", "MANNING", "MANAGER", "SUPERUSER"] },
{ href: "/po/import", label: "Import PO", icon: Upload, roles: ["MANAGER", "SUPERUSER"] },
{ href: "/approvals", label: "Approvals", icon: CheckSquare, roles: ["MANAGER", "SUPERUSER"] },
{ href: "/payments", label: "Payments", icon: CreditCard, roles: ["ACCOUNTS"] },
{ href: "/payments/history", label: "Payment History", icon: Receipt, roles: ["ACCOUNTS", "SUPERUSER"] },
{ href: "/history", label: "History", icon: History, roles: ["MANAGER", "SUPERUSER", "AUDITOR", "ADMIN"] },
];
const INVENTORY_ITEMS: NavItem[] = [
{ href: "/inventory/items", label: "Items", icon: Package, roles: ["TECHNICAL", "MANNING", "SUPERUSER"] },
{ href: "/inventory/vendors", label: "Vendors", icon: Store, roles: ["TECHNICAL", "MANNING", "SUPERUSER"] },
{ href: "/inventory/cart", label: "Cart", icon: ShoppingCart, roles: ["TECHNICAL", "MANNING", "SUPERUSER", "MANAGER"] },
{ 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: "Cost Centres", icon: Ship, roles: ["MANAGER", "ADMIN"] },
{ href: "/admin/sites", label: "Sites", icon: MapPin, roles: ["MANAGER", "ADMIN"] },
];
const ADMIN_ITEMS: NavItem[] = [
{ href: "/admin/users", label: "Users", icon: Users },
{ href: "/admin/accounts", label: "Accounts", icon: Building2 },
{ href: "/reports", label: "Reports", icon: BarChart3 },
];
export function Sidebar({ userRole }: { userRole: Role }) {
const pathname = usePathname();
const isAdmin = userRole === "ADMIN";
const visible = NAV_ITEMS.filter(
(item) => !item.roles || item.roles.includes(userRole)
);
const visibleInventory = INVENTORY_ITEMS.filter(
(item) => !item.roles || item.roles.includes(userRole)
);
return (
<aside className="flex h-screen w-60 shrink-0 flex-col border-r border-neutral-200 bg-white">
<div className="flex h-16 items-center gap-2.5 border-b border-neutral-200 px-4">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-600">
<Anchor className="h-4 w-4 text-white" />
</div>
<span className="text-sm font-semibold text-neutral-900">Pelagia Portal</span>
</div>
<nav className="flex-1 overflow-y-auto px-3 py-4 space-y-0.5">
{visible.map((item) => (
<NavLink key={item.href} item={item} pathname={pathname} />
))}
{visibleInventory.length > 0 && (
<>
<div className="pt-4 pb-1 px-3">
<p className="text-xs font-semibold text-neutral-400 uppercase tracking-wider">Inventory</p>
</div>
{visibleInventory.map((item) => (
<NavLink key={item.href} item={item} pathname={pathname} />
))}
</>
)}
{isAdmin && (
<>
<div className="pt-4 pb-1 px-3">
<p className="text-xs font-semibold text-neutral-400 uppercase tracking-wider">Administration</p>
</div>
{ADMIN_ITEMS.map((item) => (
<NavLink key={item.href} item={item} pathname={pathname} />
))}
</>
)}
</nav>
</aside>
);
}
function NavLink({ item, pathname }: { item: NavItem; pathname: string }) {
const isActive = pathname === item.href || pathname.startsWith(item.href + "/");
const Icon = item.icon;
return (
<Link
href={item.href}
className={cn(
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary-50 text-primary-700"
: "text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900"
)}
>
<Icon className="h-4 w-4 shrink-0" />
{item.label}
</Link>
);
}