Intelligence

Artifacts

Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.

Repository
README.md
CONSTITUTION_COMPLIANCE_AUDIT_V1.mdREADME.md
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