pelagia-portal/App/app/(portal)/crewing/requisitions/page.tsx
Hardik 93d13a415c feat(crewing): complete requisition list A3 (candidate count + filters)
- A3 AC2: each requisition row shows its candidate count (sourced via
  _count.applications in the list query) alongside the existing days-open age.
- A3 AC1: add rank and reason filters (derived from the visible data, like the
  existing vessel/site filter) on top of search + status + location.

requisitions.test.ts asserts the per-row candidateCount (2 vs 0) the page exposes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:52:05 +05:30

80 lines
2.7 KiB
TypeScript

import { auth } from "@/auth";
import { db } from "@/lib/db";
import { hasPermission } from "@/lib/permissions";
import { CREWING_ENABLED } from "@/lib/feature-flags";
import { redirect, notFound } from "next/navigation";
import { RequisitionsManager } from "./requisitions-manager";
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Requisitions" };
export default async function RequisitionsPage() {
// Dark unless the crewing module is switched on.
if (!CREWING_ENABLED) notFound();
const session = await auth();
if (!session?.user) redirect("/login");
if (!hasPermission(session.user.role, "view_requisitions")) redirect("/dashboard");
const role = session.user.role;
const [requisitions, reliefRequests, ranks, vessels, sites] = await Promise.all([
db.requisition.findMany({
orderBy: { createdAt: "desc" },
include: {
rank: { select: { name: true } },
vessel: { select: { name: true } },
site: { select: { name: true } },
raisedBy: { select: { name: true } },
_count: { select: { applications: true } },
},
}),
db.reliefRequest.findMany({
where: { status: "OPEN" },
orderBy: { createdAt: "desc" },
include: {
rank: { select: { name: true } },
vessel: { select: { name: true } },
site: { select: { name: true } },
requestedBy: { select: { name: true } },
},
}),
db.rank.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, code: true, name: true } }),
db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }),
db.site.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }),
]);
// Flatten to plain props — no Date/Decimal crosses the server→client boundary.
const rows = requisitions.map((r) => ({
id: r.id,
code: r.code,
status: r.status,
reason: r.reason,
autoRaised: r.autoRaised,
rankName: r.rank.name,
location: r.vessel?.name ?? r.site?.name ?? "—",
raisedBy: r.raisedBy?.name ?? "System",
candidateCount: r._count.applications,
createdAt: r.createdAt.toISOString(),
}));
const relief = reliefRequests.map((r) => ({
id: r.id,
rankName: r.rank.name,
location: r.vessel?.name ?? r.site?.name ?? "—",
note: r.note,
requestedBy: r.requestedBy.name,
createdAt: r.createdAt.toISOString(),
}));
return (
<RequisitionsManager
requisitions={rows}
reliefRequests={relief}
ranks={ranks}
vessels={vessels}
sites={sites}
canRaise={hasPermission(role, "raise_requisition")}
canConvert={hasPermission(role, "convert_relief_to_requisition")}
/>
);
}