Intelligence
Artifacts
Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.
Repository
repositories/aaf-holdings/hq01/app/mission-control/[id]/page.tsx
11.8 KB
import Link from "next/link";
import { notFound } from "next/navigation";
import {
Building2,
GitBranch,
Package,
User,
Clock,
FileText,
History as HistoryIcon,
} from "lucide-react";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { MetaItem } from "@/components/shared/field-list";
import { PriorityBadge } from "@/components/shared/status-badge";
import { MissionStatusBadge } from "@/components/missions/mission-status-badge";
import { MissionEdit } from "@/components/missions/mission-edit";
import { MissionStateActions } from "@/components/missions/mission-state-actions";
import { ObjectivesPanel } from "@/components/missions/objectives-panel";
import { WorkOrdersPanel } from "@/components/missions/work-orders-panel";
import { AssignmentsPanel } from "@/components/missions/assignments-panel";
import { AutoRefresh } from "@/components/sessions/auto-refresh";
import { getMissionView } from "@/lib/missions/manager";
import { listObjectives, missionProgress } from "@/lib/missions/objectives";
import { listWorkOrders } from "@/lib/missions/work-orders";
import { listAssignments, collectAssignmentReports } from "@/lib/missions/assignments";
import { listReportsForMission } from "@/lib/missions/reports";
import { listAssetsForMission } from "@/lib/assets/ledger";
import { ReportList } from "@/components/missions/report-list";
import { AssetList } from "@/components/assets/asset-list";
import {
RisksPanel,
DecisionsPanel,
DependenciesPanel,
KpisPanel,
} from "@/components/missions/governance-panels";
import {
listRisksWithHistory,
listDecisionsWithHistory,
listDependenciesWithHistory,
listKpisWithHistory,
missionBlockReasons,
} from "@/lib/missions/governance";
import { isExecutable } from "@/lib/missions/lifecycle";
import { loadRegistry } from "@/lib/executives/registry";
import type { MissionEventType } from "@/lib/missions/types";
export const dynamic = "force-dynamic";
const EVENT_LABEL: Record<MissionEventType, string> = {
mission_created: "Mission created",
mission_updated: "Mission updated",
executive_assigned: "Executive assigned",
state_changed: "State changed",
dispatch_started: "Dispatch started",
dispatch_completed: "Dispatch completed",
report_added: "Report added",
};
export default function MissionDetailPage({
params,
}: {
params: { id: string };
}) {
const view = getMissionView(params.id);
if (!view) notFound();
const { mission, history } = view;
const owners = loadRegistry().map((e) => ({
id: e.id,
label: `${e.displayName} — ${e.office}`,
active: e.status === "active",
}));
const executable = isExecutable(mission.status);
const objectives = listObjectives(mission.id);
const progress = missionProgress(mission.id);
// Reconcile finished assignment sessions into assignment status/history.
collectAssignmentReports(mission.id);
const workOrders = listWorkOrders(mission.id);
const assignments = listAssignments(mission.id);
const missionReports = listReportsForMission(mission.id);
const missionAssets = listAssetsForMission(mission.id);
const risks = listRisksWithHistory(mission.id);
const decisions = listDecisionsWithHistory(mission.id);
const dependencies = listDependenciesWithHistory(mission.id);
const kpis = listKpisWithHistory(mission.id);
const blockReasons = missionBlockReasons(mission.id);
const reportCountsByWorkOrder: Record<string, number> = {};
for (const r of missionReports) {
reportCountsByWorkOrder[r.work_order_id] =
(reportCountsByWorkOrder[r.work_order_id] ?? 0) + 1;
}
return (
<div>
<AutoRefresh intervalMs={5000} />
<PageHeader
eyebrow={mission.id}
title={mission.title}
description={mission.description}
back={{ label: "Mission Control", href: "/mission-control" }}
actions={
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2">
<PriorityBadge priority={mission.priority} />
<MissionStatusBadge status={mission.status} />
</div>
<MissionEdit mission={mission} owners={owners} />
</div>
}
/>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
<div className="space-y-6 lg:col-span-2">
<Card>
<CardContent className="p-6">
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
Lifecycle
</div>
<div className="mb-4 flex flex-wrap items-center gap-3">
<MissionStatusBadge status={mission.status} />
<span
className={
executable
? "text-[12px] font-medium text-emerald-700"
: "text-[12px] font-medium text-muted-foreground"
}
>
Dispatch available: {executable ? "yes" : "no"}
</span>
</div>
<MissionStateActions missionId={mission.id} status={mission.status} />
{blockReasons.length > 0 && (
<div className="mt-4 rounded-md bg-red-50 px-3 py-2.5">
<div className="text-[11px] font-semibold uppercase tracking-[0.08em] text-red-700">
Eligible to block — {blockReasons.length} reason
{blockReasons.length === 1 ? "" : "s"}
</div>
<ul className="mt-1 space-y-0.5">
{blockReasons.map((b) => (
<li key={b.id} className="text-[12px] text-red-800">
• {b.detail}
</li>
))}
</ul>
<p className="mt-1 text-[11px] text-red-700/70">
Blocking is manual — use the lifecycle actions above.
</p>
</div>
)}
</CardContent>
</Card>
<ObjectivesPanel
missionId={mission.id}
objectives={objectives}
progress={progress}
owners={owners}
/>
<WorkOrdersPanel
missionId={mission.id}
workOrders={workOrders}
objectives={objectives}
owners={owners}
reportCounts={reportCountsByWorkOrder}
/>
<AssignmentsPanel
missionId={mission.id}
assignments={assignments}
workOrders={workOrders}
owners={owners}
/>
{!executable && (
<p className="text-[12px] text-muted-foreground">
Mission is in <span className="font-medium">{mission.status}</span> —
reach Approved/Planning/Active before dispatching assignments.
</p>
)}
<Card>
<CardContent className="p-6">
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<FileText className="h-3.5 w-3.5" /> Reports ({missionReports.length})
</div>
<ReportList
reports={missionReports}
emptyText="No reports yet. Dispatch an assignment; completed sessions produce an immutable report."
/>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<Package className="h-3.5 w-3.5" /> Produced Assets ({missionAssets.length})
</div>
<AssetList
assets={missionAssets}
showMission={false}
emptyText="No assets yet. Register a report's produced artifacts into the ledger."
/>
</CardContent>
</Card>
<RisksPanel missionId={mission.id} risks={risks} owners={owners} />
<DecisionsPanel missionId={mission.id} decisions={decisions} owners={owners} />
<DependenciesPanel missionId={mission.id} dependencies={dependencies} />
<KpisPanel missionId={mission.id} kpis={kpis} />
<Card>
<CardContent className="p-6">
<div className="mb-4 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
<HistoryIcon className="h-3.5 w-3.5" /> History
</div>
<ol className="space-y-3">
{history.map((e, i) => (
<li key={i} className="flex gap-3">
<div className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-accent" />
<div className="min-w-0">
<div className="text-[13px] font-medium text-foreground">
{EVENT_LABEL[e.type] ?? e.type}
{e.detail && (
<span className="font-normal text-muted-foreground"> — {e.detail}</span>
)}
</div>
<div className="text-[11px] text-muted-foreground">
{new Date(e.at).toLocaleString()}
{e.session_id && (
<>
{" · "}
<Link
href={`/sessions/${e.session_id}`}
className="font-mono underline underline-offset-2"
>
{e.session_id.slice(0, 24)}…
</Link>
</>
)}
</div>
</div>
</li>
))}
</ol>
</CardContent>
</Card>
</div>
<aside className="space-y-6">
<Card>
<CardContent className="space-y-5 p-6">
<MetaItem label="Executive Owner">
<span className="inline-flex items-center gap-1.5 font-mono">
<User className="h-4 w-4 text-muted-foreground" />
{mission.executive_owner ?? "—"}
</span>
</MetaItem>
<Separator />
<MetaItem label="Product">
<span className="inline-flex items-center gap-1.5">
<Package className="h-4 w-4 text-muted-foreground" />
{mission.product}
</span>
</MetaItem>
<Separator />
<MetaItem label="Repository">
<span className="inline-flex items-center gap-1.5 font-mono text-[13px]">
<GitBranch className="h-4 w-4 text-muted-foreground" />
{mission.repository}
</span>
</MetaItem>
<Separator />
<MetaItem label="Organization">
<span className="inline-flex items-center gap-1.5">
<Building2 className="h-4 w-4 text-muted-foreground" />
{mission.organization}
{mission.company ? ` · ${mission.company}` : ""}
</span>
</MetaItem>
<Separator />
<MetaItem label="Timestamps">
<span className="inline-flex items-center gap-1.5 text-[12px] text-muted-foreground">
<Clock className="h-4 w-4" />
updated {new Date(mission.updated_at).toLocaleString()}
</span>
</MetaItem>
</CardContent>
</Card>
<div className="font-mono text-[11px] text-muted-foreground">
/srv/aaf/missions/{mission.id}/
</div>
</aside>
</div>
</div>
);
}
root · /srv/aaf