summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/components/directives/DOGList.tsx381
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx72
-rw-r--r--makima/frontend/src/components/orders/OrderDetail.tsx91
-rw-r--r--makima/frontend/src/routes/directives.tsx8
-rw-r--r--makima/frontend/src/routes/orders.tsx3
5 files changed, 542 insertions, 13 deletions
diff --git a/makima/frontend/src/components/directives/DOGList.tsx b/makima/frontend/src/components/directives/DOGList.tsx
new file mode 100644
index 0000000..de59d7d
--- /dev/null
+++ b/makima/frontend/src/components/directives/DOGList.tsx
@@ -0,0 +1,381 @@
+import { useState } from "react";
+import type {
+ DirectiveOrderGroup,
+ DOGStatus,
+ CreateDOGRequest,
+} from "../../lib/api";
+
+const DOG_STATUS_BADGE: Record<DOGStatus, { color: string; label: string }> = {
+ open: { color: "text-[#75aafc] border-[rgba(117,170,252,0.4)]", label: "OPEN" },
+ in_progress: { color: "text-yellow-400 border-yellow-800", label: "IN PROGRESS" },
+ done: { color: "text-emerald-400 border-emerald-800", label: "DONE" },
+ archived: { color: "text-[#556677] border-[#2a3a5a]", label: "ARCHIVED" },
+};
+
+const DOG_STATUS_OPTIONS: DOGStatus[] = ["open", "in_progress", "done", "archived"];
+
+interface DOGListProps {
+ dogs: DirectiveOrderGroup[];
+ loading: boolean;
+ onCreateDog: (req: CreateDOGRequest) => Promise<DirectiveOrderGroup | null>;
+ onUpdateDog: (dogId: string, req: { name?: string; description?: string | null; status?: DOGStatus }) => Promise<void>;
+ onDeleteDog: (dogId: string) => Promise<void>;
+ onPickUpOrders: (dogId: string) => Promise<any>;
+}
+
+export function DOGList({
+ dogs,
+ loading,
+ onCreateDog,
+ onUpdateDog,
+ onDeleteDog,
+ onPickUpOrders,
+}: DOGListProps) {
+ const [showCreate, setShowCreate] = useState(false);
+ const [newName, setNewName] = useState("");
+ const [newDesc, setNewDesc] = useState("");
+ const [creating, setCreating] = useState(false);
+
+ const handleCreate = async () => {
+ if (!newName.trim()) return;
+ setCreating(true);
+ try {
+ await onCreateDog({
+ name: newName.trim(),
+ description: newDesc.trim() || null,
+ });
+ setNewName("");
+ setNewDesc("");
+ setShowCreate(false);
+ } catch (e) {
+ console.error("Failed to create DOG:", e);
+ } finally {
+ setCreating(false);
+ }
+ };
+
+ if (loading) {
+ return (
+ <div className="flex items-center justify-center py-8">
+ <span className="text-[10px] font-mono text-[#556677]">Loading DOGs...</span>
+ </div>
+ );
+ }
+
+ return (
+ <div className="flex flex-col gap-3">
+ {/* Header + Create button */}
+ <div className="flex items-center justify-between">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Order Groups ({dogs.length})
+ </span>
+ <button
+ type="button"
+ onClick={() => setShowCreate(!showCreate)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.3)] rounded px-2 py-0.5"
+ >
+ {showCreate ? "Cancel" : "+ New DOG"}
+ </button>
+ </div>
+
+ {/* Create form */}
+ {showCreate && (
+ <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] rounded p-3 flex flex-col gap-2">
+ <div>
+ <label className="text-[9px] font-mono text-[#7788aa] uppercase tracking-wide block mb-1">
+ Name *
+ </label>
+ <input
+ value={newName}
+ onChange={(e) => setNewName(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && newName.trim()) handleCreate();
+ if (e.key === "Escape") setShowCreate(false);
+ }}
+ placeholder="Group name..."
+ autoFocus
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[11px] font-mono text-white placeholder:text-[#445566]"
+ />
+ </div>
+ <div>
+ <label className="text-[9px] font-mono text-[#7788aa] uppercase tracking-wide block mb-1">
+ Description
+ </label>
+ <textarea
+ value={newDesc}
+ onChange={(e) => setNewDesc(e.target.value)}
+ placeholder="Optional description..."
+ rows={2}
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[11px] font-mono text-white resize-y placeholder:text-[#445566]"
+ />
+ </div>
+ <div className="flex gap-1.5">
+ <button
+ type="button"
+ onClick={handleCreate}
+ disabled={!newName.trim() || creating}
+ className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-0.5 disabled:opacity-50"
+ >
+ {creating ? "Creating..." : "Create"}
+ </button>
+ <button
+ type="button"
+ onClick={() => setShowCreate(false)}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-0.5"
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ )}
+
+ {/* DOG list */}
+ {dogs.length === 0 ? (
+ <div className="flex items-center justify-center py-6">
+ <span className="text-[10px] font-mono text-[#556677] italic">
+ No order groups yet
+ </span>
+ </div>
+ ) : (
+ <div className="flex flex-col gap-2">
+ {dogs.map((dog) => (
+ <DOGCard
+ key={dog.id}
+ dog={dog}
+ onUpdate={onUpdateDog}
+ onDelete={onDeleteDog}
+ onPickUpOrders={onPickUpOrders}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+}
+
+function DOGCard({
+ dog,
+ onUpdate,
+ onDelete,
+ onPickUpOrders,
+}: {
+ dog: DirectiveOrderGroup;
+ onUpdate: (dogId: string, req: { name?: string; description?: string | null; status?: DOGStatus }) => Promise<void>;
+ onDelete: (dogId: string) => Promise<void>;
+ onPickUpOrders: (dogId: string) => Promise<any>;
+}) {
+ const [editingName, setEditingName] = useState(false);
+ const [nameText, setNameText] = useState(dog.name);
+ const [editingDesc, setEditingDesc] = useState(false);
+ const [descText, setDescText] = useState(dog.description || "");
+ const [pickingUp, setPickingUp] = useState(false);
+ const [pickUpResult, setPickUpResult] = useState<string | null>(null);
+ const [deleting, setDeleting] = useState(false);
+
+ const badge = DOG_STATUS_BADGE[dog.status] || DOG_STATUS_BADGE.open;
+
+ const handleNameSave = async () => {
+ if (nameText.trim() && nameText !== dog.name) {
+ await onUpdate(dog.id, { name: nameText.trim() });
+ }
+ setEditingName(false);
+ };
+
+ const handleDescSave = async () => {
+ const newDesc = descText.trim() || null;
+ if (newDesc !== dog.description) {
+ await onUpdate(dog.id, { description: newDesc });
+ }
+ setEditingDesc(false);
+ };
+
+ const handleStatusChange = async (status: DOGStatus) => {
+ await onUpdate(dog.id, { status });
+ };
+
+ const handlePickUpOrders = async () => {
+ setPickingUp(true);
+ setPickUpResult(null);
+ try {
+ const result = await onPickUpOrders(dog.id);
+ if (result) {
+ setPickUpResult(result.message);
+ setTimeout(() => setPickUpResult(null), 5000);
+ }
+ } catch (e) {
+ setPickUpResult(e instanceof Error ? e.message : "Failed to plan orders");
+ setTimeout(() => setPickUpResult(null), 5000);
+ } finally {
+ setPickingUp(false);
+ }
+ };
+
+ const handleDelete = async () => {
+ if (!window.confirm(`Delete DOG "${dog.name}"?`)) return;
+ setDeleting(true);
+ try {
+ await onDelete(dog.id);
+ } catch (e) {
+ console.error("Failed to delete DOG:", e);
+ setDeleting(false);
+ }
+ };
+
+ return (
+ <div className="border border-[rgba(117,170,252,0.15)] bg-[#0a1525] rounded">
+ {/* Name + Status */}
+ <div className="px-3 py-2 border-b border-[rgba(117,170,252,0.08)]">
+ <div className="flex items-center justify-between mb-1.5">
+ {editingName ? (
+ <div className="flex-1 flex items-center gap-2 pr-2">
+ <input
+ value={nameText}
+ onChange={(e) => setNameText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleNameSave();
+ if (e.key === "Escape") setEditingName(false);
+ }}
+ autoFocus
+ className="flex-1 bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-0.5 text-[12px] font-mono text-white"
+ />
+ <button
+ type="button"
+ onClick={handleNameSave}
+ className="text-[9px] font-mono text-emerald-400 hover:text-emerald-300"
+ >
+ [save]
+ </button>
+ <button
+ type="button"
+ onClick={() => setEditingName(false)}
+ className="text-[9px] font-mono text-[#556677] hover:text-white"
+ >
+ [cancel]
+ </button>
+ </div>
+ ) : (
+ <span
+ className="text-[12px] font-mono text-white font-medium cursor-pointer hover:text-[#9bc3ff] truncate pr-2"
+ onClick={() => {
+ setNameText(dog.name);
+ setEditingName(true);
+ }}
+ >
+ {dog.name}
+ </span>
+ )}
+ <span className={`text-[9px] font-mono ${badge.color} border rounded px-1.5 py-0.5 shrink-0`}>
+ {badge.label}
+ </span>
+ </div>
+
+ {/* Status selector */}
+ <div className="flex gap-1 flex-wrap">
+ {DOG_STATUS_OPTIONS.map((s) => {
+ const sBadge = DOG_STATUS_BADGE[s];
+ return (
+ <button
+ key={s}
+ type="button"
+ onClick={() => handleStatusChange(s)}
+ className={`text-[9px] font-mono border rounded px-1.5 py-0.5 transition-colors ${
+ s === dog.status
+ ? `${sBadge.color} bg-[rgba(117,170,252,0.1)]`
+ : "text-[#556677] border-[#2a3a5a] hover:text-[#7788aa]"
+ }`}
+ >
+ {sBadge.label}
+ </button>
+ );
+ })}
+ </div>
+ </div>
+
+ {/* Description */}
+ <div className="px-3 py-2 border-b border-[rgba(117,170,252,0.08)]">
+ <div className="flex items-center justify-between mb-1">
+ <span className="text-[9px] font-mono text-[#7788aa] uppercase tracking-wide">
+ Description
+ </span>
+ {!editingDesc && (
+ <button
+ type="button"
+ onClick={() => {
+ setDescText(dog.description || "");
+ setEditingDesc(true);
+ }}
+ className="text-[8px] font-mono text-[#556677] hover:text-[#75aafc]"
+ >
+ [edit]
+ </button>
+ )}
+ </div>
+ {editingDesc ? (
+ <div className="flex flex-col gap-1">
+ <textarea
+ value={descText}
+ onChange={(e) => setDescText(e.target.value)}
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1 text-[10px] font-mono text-white resize-y min-h-[40px]"
+ rows={2}
+ autoFocus
+ />
+ <div className="flex gap-1">
+ <button
+ type="button"
+ onClick={handleDescSave}
+ className="text-[9px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-1.5 py-0.5"
+ >
+ Save
+ </button>
+ <button
+ type="button"
+ onClick={() => setEditingDesc(false)}
+ className="text-[9px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-1.5 py-0.5"
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ ) : (
+ <p className="text-[10px] font-mono text-[#c0d0e0] whitespace-pre-wrap">
+ {dog.description || <span className="text-[#556677] italic">No description</span>}
+ </p>
+ )}
+ </div>
+
+ {/* Actions */}
+ <div className="px-3 py-2 flex items-center gap-2">
+ <button
+ type="button"
+ onClick={handlePickUpOrders}
+ disabled={pickingUp}
+ className="text-[10px] font-mono text-[#c084fc] hover:text-[#d8b4fe] border border-[rgba(192,132,252,0.3)] rounded px-2 py-0.5 disabled:opacity-50"
+ >
+ {pickingUp ? "Planning..." : "Plan Orders"}
+ </button>
+ <button
+ type="button"
+ onClick={handleDelete}
+ disabled={deleting}
+ className="text-[10px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-2 py-0.5 ml-auto disabled:opacity-50"
+ >
+ {deleting ? "Deleting..." : "Delete"}
+ </button>
+ </div>
+
+ {/* Pick-up result */}
+ {pickUpResult && (
+ <div className="mx-3 mb-2 px-2 py-1.5 bg-[#1a1030] border border-[rgba(192,132,252,0.2)] rounded">
+ <span className="text-[10px] font-mono text-[#c084fc]">{pickUpResult}</span>
+ </div>
+ )}
+
+ {/* Metadata */}
+ <div className="px-3 py-1.5 border-t border-[rgba(117,170,252,0.05)]">
+ <span className="text-[8px] font-mono text-[#445566]">
+ Created {new Date(dog.createdAt).toLocaleDateString()}
+ </span>
+ </div>
+ </div>
+ );
+}
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
index 5f3489a..e3302e4 100644
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx
@@ -1,9 +1,10 @@
import { useState, useMemo, useEffect, useRef } from "react";
-import type { DirectiveWithSteps, DirectiveStatus, UpdateDirectiveRequest } from "../../lib/api";
+import type { DirectiveWithSteps, DirectiveStatus, UpdateDirectiveRequest, DirectiveOrderGroup, CreateDOGRequest, UpdateDOGRequest } from "../../lib/api";
import { DirectiveDAG } from "./DirectiveDAG";
import type { SpecializedStep } from "./DirectiveDAG";
import { DirectiveLogStream } from "./DirectiveLogStream";
import { TaskSlideOutPanel } from "./TaskSlideOutPanel";
+import { DOGList } from "./DOGList";
import { useMultiTaskSubscription } from "../../hooks/useMultiTaskSubscription";
import { useSupervisorQuestions } from "../../contexts/SupervisorQuestionsContext";
@@ -30,6 +31,12 @@ interface DirectiveDetailProps {
onCleanup: () => void;
onPickUpOrders: () => Promise<{ message: string; orderCount: number; taskId: string | null } | null>;
onCreatePR: () => Promise<void>;
+ dogs: DirectiveOrderGroup[];
+ dogsLoading: boolean;
+ onCreateDog: (req: CreateDOGRequest) => Promise<DirectiveOrderGroup | null>;
+ onUpdateDog: (dogId: string, req: UpdateDOGRequest) => Promise<void>;
+ onDeleteDog: (dogId: string) => Promise<void>;
+ onPickUpDogOrders: (dogId: string) => Promise<any>;
}
export function DirectiveDetail({
@@ -47,7 +54,14 @@ export function DirectiveDetail({
onCleanup,
onPickUpOrders,
onCreatePR,
+ dogs,
+ dogsLoading,
+ onCreateDog,
+ onUpdateDog,
+ onDeleteDog,
+ onPickUpDogOrders,
}: DirectiveDetailProps) {
+ const [activeTab, setActiveTab] = useState<"steps" | "dogs">("steps");
const [editingGoal, setEditingGoal] = useState(false);
const [goalText, setGoalText] = useState(directive.goal);
const [visibleTaskIds, setVisibleTaskIds] = useState<Set<string> | null>(null);
@@ -437,21 +451,53 @@ export function DirectiveDetail({
)}
</div>
- {/* DAG */}
- <div className="px-4 py-3 flex-1">
- <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-2">
+ {/* Tab bar */}
+ <div className="flex items-center gap-0 border-b border-[rgba(117,170,252,0.1)] px-4">
+ <button
+ type="button"
+ onClick={() => setActiveTab("steps")}
+ className={`px-3 py-2 text-[10px] font-mono uppercase tracking-wide transition-colors
+ ${activeTab === "steps" ? "text-[#dbe7ff] border-b-2 border-[#75aafc]" : "text-[#556677] hover:text-[#9bc3ff]"}
+ `}
+ >
Steps ({totalSteps})
- </span>
- <DirectiveDAG
- steps={directive.steps}
- specializedSteps={specializedSteps}
- onComplete={onCompleteStep}
- onFail={onFailStep}
- onSkip={onSkipStep}
- onViewTask={handleViewTask}
- />
+ </button>
+ <button
+ type="button"
+ onClick={() => setActiveTab("dogs")}
+ className={`px-3 py-2 text-[10px] font-mono uppercase tracking-wide transition-colors
+ ${activeTab === "dogs" ? "text-[#dbe7ff] border-b-2 border-[#75aafc]" : "text-[#556677] hover:text-[#9bc3ff]"}
+ `}
+ >
+ DOGs ({dogs.length})
+ </button>
</div>
+ {/* Tab content */}
+ {activeTab === "steps" ? (
+ <div className="px-4 py-3 flex-1">
+ <DirectiveDAG
+ steps={directive.steps}
+ specializedSteps={specializedSteps}
+ onComplete={onCompleteStep}
+ onFail={onFailStep}
+ onSkip={onSkipStep}
+ onViewTask={handleViewTask}
+ />
+ </div>
+ ) : (
+ <div className="px-4 py-3 flex-1">
+ <DOGList
+ dogs={dogs}
+ loading={dogsLoading}
+ onCreateDog={onCreateDog}
+ onUpdateDog={onUpdateDog}
+ onDeleteDog={onDeleteDog}
+ onPickUpOrders={onPickUpDogOrders}
+ />
+ </div>
+ )}
+
{/* Log Stream */}
{taskMap.size > 0 && (
<div className="px-4 py-3 border-t border-[rgba(117,170,252,0.1)]">
diff --git a/makima/frontend/src/components/orders/OrderDetail.tsx b/makima/frontend/src/components/orders/OrderDetail.tsx
index 9c3ac97..4267725 100644
--- a/makima/frontend/src/components/orders/OrderDetail.tsx
+++ b/makima/frontend/src/components/orders/OrderDetail.tsx
@@ -6,6 +6,7 @@ import type {
OrderType,
UpdateOrderRequest,
DirectiveSummary,
+ DirectiveOrderGroup,
} from "../../lib/api";
const STATUS_BADGE: Record<OrderStatus, { color: string; label: string }> = {
@@ -37,6 +38,7 @@ const STATUS_OPTIONS: OrderStatus[] = ["open", "in_progress", "under_review", "d
interface OrderDetailProps {
order: Order;
directives: DirectiveSummary[];
+ dogs: DirectiveOrderGroup[];
onUpdate: (req: UpdateOrderRequest) => Promise<void>;
onDelete: () => void;
onLinkDirective: (directiveId: string) => Promise<void>;
@@ -47,6 +49,7 @@ interface OrderDetailProps {
export function OrderDetail({
order,
directives,
+ dogs,
onUpdate,
onDelete,
onLinkDirective,
@@ -61,6 +64,7 @@ export function OrderDetail({
const [labelsText, setLabelsText] = useState(order.labels.join(", "));
const [showLinkDirective, setShowLinkDirective] = useState(false);
const [directiveSearch, setDirectiveSearch] = useState("");
+ const [showDogSelector, setShowDogSelector] = useState(false);
const badge = STATUS_BADGE[order.status] || STATUS_BADGE.open;
const currentPriority = PRIORITY_OPTIONS.find((p) => p.value === order.priority) || PRIORITY_OPTIONS[4];
@@ -192,6 +196,18 @@ export function OrderDetail({
Step: <span className="text-[#7788aa]">{order.directiveStepId.slice(0, 8)}...</span>
</div>
)}
+ {order.directiveId && (
+ <div className="text-[10px] font-mono text-[#556677] mb-1">
+ DOG:{" "}
+ {order.dogId ? (
+ <span className="text-[#75aafc]">
+ {dogs.find((d) => d.id === order.dogId)?.name || order.dogId.slice(0, 8) + "..."}
+ </span>
+ ) : (
+ <span className="text-[#445566] italic">None</span>
+ )}
+ </div>
+ )}
{/* Controls */}
<div className="flex flex-wrap gap-2 mt-2">
@@ -501,6 +517,81 @@ export function OrderDetail({
)}
</div>
+ {/* Assign to DOG */}
+ {order.directiveId && (
+ <div>
+ <div className="flex items-center gap-1.5">
+ <button
+ type="button"
+ onClick={() => setShowDogSelector(!showDogSelector)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.3)] rounded px-2 py-1 flex-1 text-left"
+ >
+ {order.dogId ? "Change DOG" : "Assign to DOG"}
+ </button>
+ {order.dogId && (
+ <button
+ type="button"
+ onClick={() => onUpdate({ dogId: null })}
+ className="text-[10px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-2 py-1"
+ title="Remove DOG assignment"
+ >
+ Unlink
+ </button>
+ )}
+ </div>
+ {showDogSelector && (
+ <div className="mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] rounded">
+ <div className="max-h-32 overflow-y-auto">
+ {dogs.length === 0 ? (
+ <div className="px-3 py-2 text-[10px] font-mono text-[#556677]">
+ No DOGs available for this directive
+ </div>
+ ) : (
+ dogs.map((d) => {
+ const isAssigned = d.id === order.dogId;
+ const statusColors: Record<string, string> = {
+ open: "text-[#75aafc] border-[rgba(117,170,252,0.4)]",
+ in_progress: "text-yellow-400 border-yellow-800",
+ done: "text-emerald-400 border-emerald-800",
+ archived: "text-[#556677] border-[#2a3a5a]",
+ };
+ const sColor = statusColors[d.status] || statusColors.open;
+ return (
+ <button
+ key={d.id}
+ type="button"
+ onClick={async () => {
+ await onUpdate({ dogId: d.id });
+ setShowDogSelector(false);
+ }}
+ className={`w-full text-left px-3 py-1.5 text-[10px] font-mono hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0 ${
+ isAssigned ? "bg-[rgba(117,170,252,0.08)] text-white" : "text-[#9bc3ff]"
+ }`}
+ >
+ <div className="flex items-center gap-1.5">
+ <span className={`shrink-0 text-[8px] font-mono ${sColor} border rounded px-1 py-0.5 uppercase`}>
+ {d.status}
+ </span>
+ <span className="truncate">{d.name}</span>
+ {isAssigned && (
+ <span className="shrink-0 text-[8px] text-emerald-400">current</span>
+ )}
+ </div>
+ {d.description && (
+ <div className="text-[8px] text-[#556677] truncate mt-0.5">
+ {d.description}
+ </div>
+ )}
+ </button>
+ );
+ })
+ )}
+ </div>
+ </div>
+ )}
+ </div>
+ )}
+
{/* Convert to Directive Step */}
{!order.directiveStepId && order.directiveId && (
<button
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
index cee4920..846f52f 100644
--- a/makima/frontend/src/routes/directives.tsx
+++ b/makima/frontend/src/routes/directives.tsx
@@ -4,6 +4,7 @@ import { Masthead } from "../components/Masthead";
import { DirectiveList } from "../components/directives/DirectiveList";
import { DirectiveDetail } from "../components/directives/DirectiveDetail";
import { useDirectives, useDirective } from "../hooks/useDirectives";
+import { useDogs } from "../hooks/useDogs";
import { useAuth } from "../contexts/AuthContext";
import { getRepositorySuggestions, type RepositoryHistoryEntry } from "../lib/api";
@@ -13,6 +14,7 @@ export default function DirectivesPage() {
const { id: selectedId } = useParams<{ id: string }>();
const { directives, loading: listLoading, create, remove } = useDirectives();
const { directive, refresh: refreshDetail, update, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanup, pickUpOrders, createPR } = useDirective(selectedId);
+ const { dogs, loading: dogsLoading, create: createDog, update: updateDog, remove: removeDog, pickUpOrders: pickUpDogOrders } = useDogs(selectedId);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
@@ -213,6 +215,12 @@ export default function DirectivesPage() {
onCleanup={cleanup}
onPickUpOrders={pickUpOrders}
onCreatePR={createPR}
+ dogs={dogs}
+ dogsLoading={dogsLoading}
+ onCreateDog={createDog}
+ onUpdateDog={updateDog}
+ onDeleteDog={removeDog}
+ onPickUpDogOrders={pickUpDogOrders}
/>
) : (
<div className="flex-1 flex items-center justify-center h-full">
diff --git a/makima/frontend/src/routes/orders.tsx b/makima/frontend/src/routes/orders.tsx
index aa14e68..06e091a 100644
--- a/makima/frontend/src/routes/orders.tsx
+++ b/makima/frontend/src/routes/orders.tsx
@@ -5,6 +5,7 @@ import { OrderList } from "../components/orders/OrderList";
import { OrderDetail } from "../components/orders/OrderDetail";
import { useOrders, useOrder } from "../hooks/useOrders";
import { useDirectives } from "../hooks/useDirectives";
+import { useDogs } from "../hooks/useDogs";
import { useAuth } from "../contexts/AuthContext";
import type { OrderStatus, OrderType, OrderPriority } from "../lib/api";
@@ -18,6 +19,7 @@ export default function OrdersPage() {
const { orders, loading: listLoading, create, refresh: refreshList } = useOrders(statusFilter, typeFilter);
const { order, refresh: refreshDetail, update, remove: removeOrder, linkDirective, convertToStep } = useOrder(selectedId);
const { directives } = useDirectives();
+ const { dogs } = useDogs(order?.directiveId ?? undefined);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
@@ -228,6 +230,7 @@ export default function OrdersPage() {
<OrderDetail
order={order}
directives={directives}
+ dogs={dogs}
onUpdate={handleUpdate}
onDelete={handleDelete}
onLinkDirective={handleLinkDirective}