import config from "@payload-config"; import { getPayload } from "payload"; import { notFound } from "next/navigation"; import Link from "next/link"; import { headers as nextHeaders } from "next/headers"; import type { Mission, Campaign, Map, User, Faction, Qualification, Rank, } from "@/payload-types"; import { ArrowLeftIcon, ArrowRightIcon, CalendarDaysIcon, ChevronRightIcon, ClockIcon, MapPinIcon, CrosshairIcon, ServerIcon, FlagIcon, } from "lucide-react"; import { Item, ItemContent, ItemDescription, ItemHeader, ItemMedia, ItemTitle, } from "@/components/ui/item"; import { MissionHeader } from "@/components/frontend/intelligence/MissionHeader"; import { MissionBriefingSection } from "@/components/frontend/intelligence/MissionBriefingSection"; import { MissionObjectives } from "@/components/frontend/intelligence/MissionObjectives"; import { MissionFactions } from "@/components/frontend/intelligence/MissionFactions"; import { MissionRoles } from "@/components/frontend/intelligence/MissionRoles"; import { MissionLogistics } from "@/components/frontend/intelligence/MissionLogistics"; import { MissionComms } from "@/components/frontend/intelligence/MissionComms"; import { MissionAttendance } from "@/components/frontend/intelligence/MissionAttendance"; import { getMissionAttendance, getUserAttendance, isMissionPast, type AttendanceResponse, } from "@/lib/attendance"; interface MissionDetailPageProps { params: Promise<{ id: string }>; } const statusColors: Record = { Concept: "bg-muted text-muted-foreground", Planning: "bg-muted text-muted-foreground", Ready: "bg-blue-500/20 text-blue-400", Scheduled: "bg-amber-500/20 text-amber-400", Active: "bg-green-500/20 text-green-400", Completed: "bg-emerald-500/20 text-emerald-400", Cancelled: "bg-red-500/20 text-red-400", }; export default async function MissionDetailPage({ params, }: MissionDetailPageProps) { const { id } = await params; const payload = await getPayload({ config }); const mission = await payload .findByID({ collection: "missions", id: Number(id), depth: 2, }) .catch(() => null); if (!mission) { notFound(); } const map = typeof mission.classification.map === "object" ? (mission.classification.map as Map) : null; const campaign = typeof mission.classification.campaign === "object" ? (mission.classification.campaign as Campaign) : null; const startDate = new Date(mission.classification.startDateTime); const dateStr = startDate.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", }); const timeStr = startDate.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", }); const durationHours = Math.floor( mission.classification.estimatedDuration / 60, ); const durationMins = mission.classification.estimatedDuration % 60; const durationStr = durationMins > 0 ? `${durationHours}h ${durationMins}m` : `${durationHours}h`; // Fetch sibling missions in the same campaign const campaignId = typeof mission.classification.campaign === "object" ? (mission.classification.campaign as Campaign).id : mission.classification.campaign; const siblingMissionsRes = await payload .find({ collection: "missions", where: { and: [ { "classification.campaign": { equals: campaignId } }, { id: { not_equals: mission.id } }, ], }, sort: "-classification.startDateTime", limit: 5, depth: 0, select: { name: true, codeName: true, ownershipAndStatus: { status: true }, classification: { startDateTime: true }, }, }) .catch(() => ({ docs: [] })); const siblingMissions = siblingMissionsRes.docs; const { user: currentUser } = await payload.auth({ headers: await nextHeaders(), canSetHeaders: false, }); const [attendance, userAttendance] = await Promise.all([ getMissionAttendance(payload, mission.id), currentUser ? getUserAttendance(payload, currentUser.id as number, [mission.id]) : Promise.resolve>({}), ]); const currentResponse = userAttendance[mission.id] ?? null; const attendanceClosed = isMissionPast(mission.classification.startDateTime); const attendanceProps = { yes: attendance.yes.map((u) => ({ id: u.id, name: u.displayName || u.username })), no: attendance.no.map((u) => ({ id: u.id, name: u.displayName || u.username })), tentative: attendance.tentative.map((u) => ({ id: u.id, name: u.displayName || u.username, })), }; return (
Back to Missions
{/* ── Main Content (2/3) ──────────────────────────── */}
Date {dateStr} Time & Duration {timeStr} · {durationStr} {map && ( Map {map.name} )} {campaign && ( Campaign {campaign.name} )}

{mission.summary}

Mission Details
Type

{mission.classification.missionType}

Operation Type

{mission.operationType}

Max Players

{mission.missionRoles?.maxPlayers ?? "—"}

{/* ── Right Sidebar (1/3) ─────────────────────────── */}
{/* Campaign Card */} {campaign && (
Campaign
View

{campaign.name}

{campaign.status === "inProgress" ? "In Progress" : campaign.status}

{campaign.summary}

)} {/* Sibling Missions */} {siblingMissions.length > 0 && (
Other Missions in Campaign
{siblingMissions.map((m) => { const mDate = new Date( m.classification.startDateTime, ); return (
{m.codeName} {m.name}
{m.ownershipAndStatus.status}
); })}
)} {/* Server Details */} {mission.gameDetails?.serverDetails?.serverIp && (
Server
Address {mission.gameDetails.serverDetails.serverIp}
Port {mission.gameDetails.serverDetails.serverPort}
)}
); }