Intelligence
Artifacts
Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.
Repository
assignment-dispatch-button.tsxassignment-status-badge.tsxassignments-panel.tsxcreate-mission-form.tsxgovernance-panels.tsxmission-dispatch.tsxmission-edit.tsxmission-state-actions.tsxmission-status-badge.tsxobjective-edit.tsxobjective-status-badge.tsxobjective-status-select.tsxobjectives-panel.tsxreport-list.tsxwork-orders-panel.tsx
README.md
CONSTITUTION_COMPLIANCE_AUDIT_V1.mdREADME.md
repositories/aaf-holdings/hq01/components/missions/mission-dispatch.tsx
4.1 KB
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { Send, Loader2, AlertTriangle, CheckCircle2, ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
/**
* Dispatch panel on a mission. The instruction is dispatched to the mission's
* executive owner through the Executive Router, bound to this mission. Dispatch
* is only possible when the mission has an owner.
*/
export function MissionDispatch({
missionId,
owner,
executable,
status,
}: {
missionId: string;
owner: string | null;
executable: boolean;
status: string;
}) {
const router = useRouter();
const [instruction, setInstruction] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [launched, setLaunched] = useState<string | null>(null);
async function dispatch() {
if (!instruction.trim() || !owner) return;
setBusy(true);
setError(null);
setLaunched(null);
try {
const res = await fetch("/api/router/dispatch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
missionId,
executiveId: owner,
instruction,
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error ?? "Dispatch failed.");
setLaunched(data.session.id as string);
setInstruction("");
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Dispatch failed.");
} finally {
setBusy(false);
}
}
return (
<Card className="p-5">
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground">
Dispatch
</div>
{!executable ? (
<p className="flex items-center gap-2 text-[13px] text-amber-700">
<AlertTriangle className="h-4 w-4 shrink-0" />
Mission is not executable in state <span className="font-medium">{status}</span>.
Reach Approved, Planning, or Active to dispatch.
</p>
) : !owner ? (
<p className="text-[13px] text-muted-foreground">
Assign an executive owner before dispatching.
</p>
) : (
<>
<p className="mb-2 text-[12px] text-muted-foreground">
Routes to the mission owner (<span className="font-mono">{owner}</span>)
through the Executive Router and launches a briefed session bound to
this mission.
</p>
<textarea
value={instruction}
onChange={(e) => setInstruction(e.target.value)}
rows={3}
placeholder="What should the executive do for this mission?"
className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
/>
{error && (
<div className="mt-2 flex items-center gap-2 text-[12px] text-destructive">
<AlertTriangle className="h-3.5 w-3.5" /> {error}
</div>
)}
{launched && (
<div className="mt-2 flex items-center gap-2 text-[12px] text-emerald-700">
<CheckCircle2 className="h-3.5 w-3.5" /> Dispatched.
<Link
href={`/sessions/${launched}`}
className="inline-flex items-center gap-0.5 font-medium underline underline-offset-2"
>
Watch session <ArrowRight className="h-3 w-3" />
</Link>
</div>
)}
<div className="mt-3 flex justify-end">
<Button type="button" size="sm" onClick={dispatch} disabled={busy || !instruction.trim()}>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />}
Dispatch
</Button>
</div>
</>
)}
</Card>
);
}
root · /srv/aaf