diff --git a/App/pelagia-portal/app/(portal)/dashboard/page.tsx b/App/pelagia-portal/app/(portal)/dashboard/page.tsx
new file mode 100644
index 0000000..77b0a7d
--- /dev/null
+++ b/App/pelagia-portal/app/(portal)/dashboard/page.tsx
@@ -0,0 +1,261 @@
+import { auth } from "@/auth";
+import { db } from "@/lib/db";
+import { StatCard } from "@/components/dashboard/stat-card";
+import { SpendCharts } from "@/components/dashboard/spend-charts";
+import { formatCurrency, formatDate, PO_STATUS_LABELS } from "@/lib/utils";
+import { FileText, Clock, CheckCircle, DollarSign } from "lucide-react";
+import Link from "next/link";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = { title: "Dashboard" };
+
+export default async function DashboardPage() {
+ const session = await auth();
+ if (!session?.user) return null;
+
+ const { role, id: userId } = session.user;
+
+ if (role === "TECHNICAL" || role === "MANNING" || role === "SUPERUSER") {
+ return ;
+ }
+ if (role === "MANAGER") {
+ return ;
+ }
+ if (role === "ACCOUNTS") {
+ return ;
+ }
+ return ;
+}
+
+async function SubmitterDashboard({ userId }: { userId: string }) {
+ const [openCount, pendingCount, closedCount, recentPos] = await Promise.all([
+ db.purchaseOrder.count({
+ where: { submitterId: userId, status: { in: ["DRAFT", "SUBMITTED", "MGR_REVIEW", "VENDOR_ID_PENDING", "EDITS_REQUESTED"] } },
+ }),
+ db.purchaseOrder.count({
+ where: { submitterId: userId, status: "MGR_REVIEW" },
+ }),
+ db.purchaseOrder.count({
+ where: { submitterId: userId, status: "CLOSED" },
+ }),
+ db.purchaseOrder.findMany({
+ where: { submitterId: userId },
+ orderBy: { updatedAt: "desc" },
+ take: 8,
+ select: { id: true, poNumber: true, title: true, status: true, totalAmount: true, updatedAt: true },
+ }),
+ ]);
+
+ return (
+
+
Dashboard
+
+
+
+
+
+
+ {recentPos.length > 0 && (
+
+
Recent Orders
+
+
+
+
+ | PO Number |
+ Title |
+ Status |
+ Amount |
+ Updated |
+
+
+
+ {recentPos.map((po) => (
+
+ |
+
+ {po.poNumber}
+
+ |
+ {po.title} |
+
+
+ {PO_STATUS_LABELS[po.status]}
+
+ |
+ {formatCurrency(Number(po.totalAmount))} |
+ {formatDate(po.updatedAt)} |
+
+ ))}
+
+
+
+
+ )}
+
+ );
+}
+
+async function ManagerDashboard() {
+ const now = new Date();
+ const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+ const twelveMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 11, 1);
+
+ const approvedStatuses = ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED", "CLOSED"] as const;
+
+ const [awaitingCount, approvedThisMonth, totalSpendResult, recentApproved, vesselBreakdown, monthlyPos] = await Promise.all([
+ db.purchaseOrder.count({ where: { status: "MGR_REVIEW" } }),
+ db.purchaseOrder.count({ where: { status: "MGR_APPROVED", approvedAt: { gte: startOfMonth } } }),
+ db.purchaseOrder.aggregate({
+ _sum: { totalAmount: true },
+ where: { status: { in: [...approvedStatuses] } },
+ }),
+ db.purchaseOrder.findMany({
+ where: { status: { in: ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED"] } },
+ orderBy: { approvedAt: "desc" },
+ take: 8,
+ select: { id: true, poNumber: true, title: true, status: true, totalAmount: true, approvedAt: true, vessel: { select: { name: true } } },
+ }),
+ db.purchaseOrder.groupBy({
+ by: ["vesselId"],
+ _sum: { totalAmount: true },
+ where: { status: { in: [...approvedStatuses] } },
+ orderBy: { _sum: { totalAmount: "desc" } },
+ take: 5,
+ }),
+ db.purchaseOrder.findMany({
+ where: { status: { in: [...approvedStatuses] }, approvedAt: { gte: twelveMonthsAgo } },
+ select: { totalAmount: true, approvedAt: true },
+ }),
+ ]);
+
+ const vesselIds = vesselBreakdown.map((r) => r.vesselId);
+ const vessels = await db.vessel.findMany({ where: { id: { in: vesselIds } }, select: { id: true, name: true } });
+ const vesselMap = Object.fromEntries(vessels.map((v) => [v.id, v.name]));
+
+ const totalSpend = Number(totalSpendResult._sum.totalAmount ?? 0);
+
+ // Build monthly series for last 12 months
+ const monthlyMap: Record = {};
+ for (let i = 11; i >= 0; i--) {
+ const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
+ const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
+ monthlyMap[key] = 0;
+ }
+ for (const po of monthlyPos) {
+ if (!po.approvedAt) continue;
+ const key = `${po.approvedAt.getFullYear()}-${String(po.approvedAt.getMonth() + 1).padStart(2, "0")}`;
+ if (key in monthlyMap) monthlyMap[key] += Number(po.totalAmount);
+ }
+ const monthData = Object.entries(monthlyMap).map(([key, amount]) => {
+ const [year, month] = key.split("-");
+ const label = new Date(Number(year), Number(month) - 1, 1).toLocaleString("en", { month: "short", year: "2-digit" });
+ return { month: label, amount };
+ });
+
+ const vesselChartData = vesselBreakdown.map((row) => ({
+ name: vesselMap[row.vesselId] ?? "Unknown",
+ amount: Number(row._sum.totalAmount ?? 0),
+ }));
+
+ return (
+
+
Dashboard
+
+
+
+
+
+
+ {/* Recent approved POs */}
+ {recentApproved.length > 0 && (
+
+
+
Recent Approved Orders
+ View all →
+
+
+
+
+
+ | PO |
+ Title |
+ Vessel |
+ Status |
+ Amount |
+ Approved |
+
+
+
+ {recentApproved.map((po) => (
+
+ |
+
+ {po.poNumber}
+
+ |
+ {po.title} |
+ {po.vessel.name} |
+
+
+ {PO_STATUS_LABELS[po.status]}
+
+ |
+ {formatCurrency(Number(po.totalAmount))} |
+ {po.approvedAt ? formatDate(po.approvedAt) : "—"} |
+
+ ))}
+
+
+
+
+ )}
+
+
+
+ );
+}
+
+async function AccountsDashboard() {
+ const [queueCount, queueValueResult] = await Promise.all([
+ db.purchaseOrder.count({ where: { status: "MGR_APPROVED" } }),
+ db.purchaseOrder.aggregate({
+ _sum: { totalAmount: true },
+ where: { status: "MGR_APPROVED" },
+ }),
+ ]);
+
+ const queueValue = Number(queueValueResult._sum.totalAmount ?? 0);
+
+ return (
+
+ );
+}
+
+async function GenericDashboard() {
+ const total = await db.purchaseOrder.count();
+ return (
+
+ );
+}
diff --git a/App/pelagia-portal/app/(portal)/layout.tsx b/App/pelagia-portal/app/(portal)/layout.tsx
new file mode 100644
index 0000000..d563085
--- /dev/null
+++ b/App/pelagia-portal/app/(portal)/layout.tsx
@@ -0,0 +1,23 @@
+import { auth } from "@/auth";
+import { redirect } from "next/navigation";
+import { Sidebar } from "@/components/layout/sidebar";
+import { Header } from "@/components/layout/header";
+
+export default async function PortalLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const session = await auth();
+ if (!session?.user) redirect("/login");
+
+ return (
+
+ );
+}