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/objective-edit.tsx
5.8 KB
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Pencil, X, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card } from "@/components/ui/card";
import type { OwnerOption } from "./create-mission-form";
import type { MissionPriority, Objective } from "@/lib/missions/types";
const PRIORITIES: MissionPriority[] = ["P0", "P1", "P2", "P3"];
/** Edit an objective's fields (not its status — that is a state_changed action). */
export function ObjectiveEdit({
objective,
owners,
}: {
objective: Objective;
owners: OwnerOption[];
}) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [form, setForm] = useState({
title: objective.title,
description: objective.description,
owner_executive: objective.owner_executive ?? "",
priority: objective.priority,
success_criteria: objective.success_criteria.join("\n"),
completion_percentage: objective.completion_percentage,
});
async function save(e: React.FormEvent) {
e.preventDefault();
setBusy(true);
setError(null);
try {
const res = await fetch(
`/api/missions/${objective.mission_id}/objectives/${objective.id}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...form,
success_criteria: form.success_criteria
.split("\n")
.map((s) => s.trim())
.filter(Boolean),
completion_percentage: Number(form.completion_percentage),
}),
},
);
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error ?? "Update failed.");
setOpen(false);
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Update failed.");
} finally {
setBusy(false);
}
}
return (
<div className="flex flex-col items-end gap-2">
<Button type="button" variant="outline" size="sm" onClick={() => setOpen((v) => !v)}>
{open ? <X className="h-3.5 w-3.5" /> : <Pencil className="h-3.5 w-3.5" />}
{open ? "Close" : "Edit"}
</Button>
{error && <span className="text-[11px] text-destructive">{error}</span>}
{open && (
<Card className="w-full max-w-md p-5 text-left">
<form className="space-y-3" onSubmit={save}>
<Input value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value })} />
<textarea
value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })}
rows={2}
className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/>
<label className="block">
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Success criteria (one per line)
</span>
<textarea
value={form.success_criteria}
onChange={(e) => setForm({ ...form, success_criteria: e.target.value })}
rows={3}
className="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/>
</label>
<div className="grid grid-cols-3 gap-3">
<label>
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Owner
</span>
<select
value={form.owner_executive}
onChange={(e) => setForm({ ...form, owner_executive: e.target.value })}
className="h-9 w-full rounded-md border border-input bg-background px-2 text-sm"
>
{owners.map((o) => (
<option key={o.id} value={o.id}>
{o.label}
</option>
))}
</select>
</label>
<label>
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Priority
</span>
<select
value={form.priority}
onChange={(e) => setForm({ ...form, priority: e.target.value as MissionPriority })}
className="h-9 w-full rounded-md border border-input bg-background px-2 text-sm"
>
{PRIORITIES.map((p) => (
<option key={p}>{p}</option>
))}
</select>
</label>
<label>
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
Progress %
</span>
<Input
type="number"
min={0}
max={100}
value={form.completion_percentage}
onChange={(e) =>
setForm({ ...form, completion_percentage: Number(e.target.value) })
}
className="h-9"
/>
</label>
</div>
<div className="flex justify-end">
<Button type="submit" size="sm" disabled={busy}>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "Save"}
</Button>
</div>
</form>
</Card>
)}
</div>
);
}
root · /srv/aaf