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 Link from "next/link"; import { ArrowLeft } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { SOURCE_LABEL, STATUS_LABEL, STATUS_VARIANT, experienceLabel } from "../candidate-ui"; import type { Metadata } from "next"; export const metadata: Metadata = { title: "Candidate" }; export default async function CandidateDetailPage({ params }: { params: Promise<{ id: string }> }) { if (!CREWING_ENABLED) notFound(); const session = await auth(); if (!session?.user) redirect("/login"); if (!hasPermission(session.user.role, "manage_candidates")) redirect("/dashboard"); const { id } = await params; const c = await db.crewMember.findUnique({ where: { id }, include: { appliedRank: { select: { name: true } }, currentRank: { select: { name: true } }, // B3 AC3 — pull the returning hand's history so the callout shows real records. experienceRecords: { orderBy: { fromDate: "desc" }, include: { rank: { select: { name: true } } } }, documents: { orderBy: { createdAt: "desc" }, select: { id: true, docType: true, expiryDate: true } }, }, }); if (!c) notFound(); const profile: [string, string][] = [ ["Rank applied", c.appliedRank?.name ?? "—"], ["Last rank held", c.currentRank?.name ?? "—"], ["Experience", experienceLabel(c.experienceMonths)], ["Vessel type", c.vesselTypeExperience ?? "—"], ["Source", SOURCE_LABEL[c.source]], ["Email", c.email ?? "—"], ["Phone", c.phone ?? "—"], ]; return (
Candidates

{c.name}

{STATUS_LABEL[c.status]} {c.source === "EX_HAND" && ( Returning crew )}
{c.source === "EX_HAND" && (
Returning crew. The interview may be waived with Manager approval.{" "} {c.experienceRecords.length === 0 && c.documents.length === 0 ? ( No prior records are on file yet. ) : ( Prior records on file from earlier assignments: )} {c.experienceRecords.length > 0 && (

Tour history

    {c.experienceRecords.map((e) => (
  • {e.rank?.name ?? "—"} {e.vesselType ? ` · ${e.vesselType}` : ""} {e.durationMonths != null ? ` · ${experienceLabel(e.durationMonths)}` : ""} {e.fromDate ? ` (${e.fromDate.getFullYear()}${e.toDate ? `–${e.toDate.getFullYear()}` : ""})` : ""}
  • ))}
)} {c.documents.length > 0 && (

Documents on file

{c.documents.map((doc) => ( {doc.docType} {doc.expiryDate ? ` · exp ${doc.expiryDate.getFullYear()}` : ""} ))}
)}
)}
{/* Profile */}

Profile

{profile.map(([k, v]) => (
{k}
{v}
))}
{c.notes && (

Notes

{c.notes}

)}
{/* Recruitment pipeline — Phase 3b */}

Recruitment

The 7-stage recruitment pipeline (shortlist → competency & references → docs → salary → proposed → interview → selected) arrives in the next phase. Applications against requisitions will appear here.

); }