- 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>
80 lines
2.7 KiB
TypeScript
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")}
|
|
/>
|
|
);
|
|
}
|