summaryrefslogtreecommitdiff
path: root/makima/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend')
-rw-r--r--makima/frontend/src/components/orders/OrderDetail.tsx89
-rw-r--r--makima/frontend/src/components/orders/OrderList.tsx8
-rw-r--r--makima/frontend/src/hooks/useOrders.ts14
-rw-r--r--makima/frontend/src/lib/api.ts24
-rw-r--r--makima/frontend/src/routes/contracts.tsx23
-rw-r--r--makima/frontend/src/routes/orders.tsx34
6 files changed, 61 insertions, 131 deletions
diff --git a/makima/frontend/src/components/orders/OrderDetail.tsx b/makima/frontend/src/components/orders/OrderDetail.tsx
index 7f8a95d..9d4c00c 100644
--- a/makima/frontend/src/components/orders/OrderDetail.tsx
+++ b/makima/frontend/src/components/orders/OrderDetail.tsx
@@ -39,8 +39,7 @@ interface OrderDetailProps {
onUpdate: (req: UpdateOrderRequest) => Promise<void>;
onDelete: () => void;
onLinkDirective: (directiveId: string) => Promise<void>;
- onLinkContract: (contractId: string) => Promise<void>;
- onConvertToStep: (directiveId: string) => Promise<void>;
+ onConvertToStep: () => Promise<void>;
onRefresh: () => void;
}
@@ -50,7 +49,6 @@ export function OrderDetail({
onUpdate,
onDelete,
onLinkDirective,
- onLinkContract,
onConvertToStep,
onRefresh,
}: OrderDetailProps) {
@@ -61,9 +59,6 @@ export function OrderDetail({
const [editingLabels, setEditingLabels] = useState(false);
const [labelsText, setLabelsText] = useState(order.labels.join(", "));
const [showLinkDirective, setShowLinkDirective] = useState(false);
- const [showLinkContract, setShowLinkContract] = useState(false);
- const [contractIdInput, setContractIdInput] = useState("");
- const [showConvertToStep, setShowConvertToStep] = useState(false);
const badge = STATUS_BADGE[order.status] || STATUS_BADGE.open;
const currentPriority = PRIORITY_OPTIONS.find((p) => p.value === order.priority) || PRIORITY_OPTIONS[4];
@@ -110,17 +105,6 @@ export function OrderDetail({
setShowLinkDirective(false);
};
- const handleLinkContract = async () => {
- if (!contractIdInput.trim()) return;
- await onLinkContract(contractIdInput.trim());
- setContractIdInput("");
- setShowLinkContract(false);
- };
-
- const handleConvertToStep = async (directiveId: string) => {
- await onConvertToStep(directiveId);
- setShowConvertToStep(false);
- };
return (
<div className="flex flex-col h-full overflow-y-auto">
@@ -196,12 +180,9 @@ export function OrderDetail({
{/* Linked entities */}
{order.directiveId && (
<div className="text-[10px] font-mono text-[#556677] mb-1 truncate">
- Directive: <a href={`/directives/${order.directiveId}`} className="text-[#75aafc] hover:text-white underline">{order.directiveId.slice(0, 8)}...</a>
- </div>
- )}
- {order.contractId && (
- <div className="text-[10px] font-mono text-[#556677] mb-1 truncate">
- Contract: <a href={`/contracts/${order.contractId}`} className="text-[#75aafc] hover:text-white underline">{order.contractId.slice(0, 8)}...</a>
+ Directive: <a href={`/directives/${order.directiveId}`} className="text-[#75aafc] hover:text-white underline">
+ {order.directiveName || order.directiveId.slice(0, 8) + "..."}
+ </a>
</div>
)}
{order.directiveStepId && (
@@ -453,67 +434,15 @@ export function OrderDetail({
)}
</div>
- {/* Link to Contract */}
- <div>
+ {/* Convert to Directive Step */}
+ {!order.directiveStepId && order.directiveId && (
<button
type="button"
- onClick={() => setShowLinkContract(!showLinkContract)}
- className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.3)] rounded px-2 py-1 w-full text-left"
+ onClick={() => onConvertToStep()}
+ className="text-[10px] font-mono text-yellow-400 hover:text-yellow-300 border border-yellow-800 rounded px-2 py-1 w-full text-left"
>
- Link to Contract
+ Convert to Directive Step
</button>
- {showLinkContract && (
- <div className="mt-1 flex gap-1.5">
- <input
- value={contractIdInput}
- onChange={(e) => setContractIdInput(e.target.value)}
- placeholder="Contract ID..."
- className="flex-1 bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1 text-[10px] font-mono text-white"
- autoFocus
- />
- <button
- type="button"
- onClick={handleLinkContract}
- disabled={!contractIdInput.trim()}
- className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-1 disabled:opacity-50"
- >
- Link
- </button>
- </div>
- )}
- </div>
-
- {/* Convert to Directive Step */}
- {!order.directiveStepId && (
- <div>
- <button
- type="button"
- onClick={() => setShowConvertToStep(!showConvertToStep)}
- className="text-[10px] font-mono text-yellow-400 hover:text-yellow-300 border border-yellow-800 rounded px-2 py-1 w-full text-left"
- >
- Convert to Directive Step
- </button>
- {showConvertToStep && (
- <div className="mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto rounded">
- {directives.length === 0 ? (
- <div className="px-3 py-2 text-[10px] font-mono text-[#556677]">
- No directives available
- </div>
- ) : (
- directives.map((d) => (
- <button
- key={d.id}
- type="button"
- onClick={() => handleConvertToStep(d.id)}
- className="w-full text-left px-3 py-1.5 text-[10px] font-mono text-yellow-400 hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
- >
- {d.title}
- </button>
- ))
- )}
- </div>
- )}
- </div>
)}
</div>
</div>
diff --git a/makima/frontend/src/components/orders/OrderList.tsx b/makima/frontend/src/components/orders/OrderList.tsx
index 76ac7a7..1d279f7 100644
--- a/makima/frontend/src/components/orders/OrderList.tsx
+++ b/makima/frontend/src/components/orders/OrderList.tsx
@@ -57,7 +57,8 @@ export function OrderList({
(o) =>
o.title.toLowerCase().includes(q) ||
(o.description && o.description.toLowerCase().includes(q)) ||
- o.labels.some((l) => l.toLowerCase().includes(q)),
+ o.labels.some((l) => l.toLowerCase().includes(q)) ||
+ (o.directiveName && o.directiveName.toLowerCase().includes(q)),
);
}, [orders, search]);
@@ -158,6 +159,11 @@ export function OrderList({
/>
<span className="text-[12px] font-mono text-white truncate flex-1">
{o.title}
+ {o.directiveName && (
+ <span className="text-[9px] font-mono text-[#556677] truncate block">
+ {o.directiveName}
+ </span>
+ )}
</span>
</div>
<div className="flex items-center gap-1.5 pl-4">
diff --git a/makima/frontend/src/hooks/useOrders.ts b/makima/frontend/src/hooks/useOrders.ts
index 2dd20bb..9380080 100644
--- a/makima/frontend/src/hooks/useOrders.ts
+++ b/makima/frontend/src/hooks/useOrders.ts
@@ -12,7 +12,6 @@ import {
updateOrder,
deleteOrder,
linkOrderToDirective,
- linkOrderToContract,
convertOrderToStep,
} from "../lib/api";
@@ -101,16 +100,9 @@ export function useOrder(id: string | undefined) {
return o;
}, [id]);
- const linkContract = useCallback(async (contractId: string) => {
+ const convertToStep = useCallback(async () => {
if (!id) return;
- const o = await linkOrderToContract(id, contractId);
- setOrder(o);
- return o;
- }, [id]);
-
- const convertToStep = useCallback(async (directiveId: string) => {
- if (!id) return;
- const step = await convertOrderToStep(id, directiveId);
+ const step = await convertOrderToStep(id);
await refresh();
return step;
}, [id, refresh]);
@@ -118,6 +110,6 @@ export function useOrder(id: string | undefined) {
return {
order, loading, error, refresh,
update, remove,
- linkDirective, linkContract, convertToStep,
+ linkDirective, convertToStep,
};
}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index a496412..17bc20f 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3295,7 +3295,7 @@ export interface Order {
labels: string[];
directiveId: string | null;
directiveStepId: string | null;
- contractId: string | null;
+ directiveName: string | null;
repositoryUrl: string | null;
createdAt: string;
updatedAt: string;
@@ -3313,8 +3313,7 @@ export interface CreateOrderRequest {
status?: OrderStatus;
orderType?: OrderType;
labels?: string[];
- directiveId?: string | null;
- contractId?: string | null;
+ directiveId: string;
repositoryUrl?: string | null;
}
@@ -3327,7 +3326,6 @@ export interface UpdateOrderRequest {
labels?: string[];
directiveId?: string | null;
directiveStepId?: string | null;
- contractId?: string | null;
repositoryUrl?: string | null;
}
@@ -3336,14 +3334,14 @@ export async function listOrders(
type?: OrderType,
priority?: OrderPriority,
directiveId?: string,
- contractId?: string,
+ search?: string,
): Promise<OrderListResponse> {
const params = new URLSearchParams();
if (status) params.set("status", status);
if (type) params.set("type", type);
if (priority) params.set("priority", priority);
if (directiveId) params.set("directiveId", directiveId);
- if (contractId) params.set("contractId", contractId);
+ if (search) params.set("search", search);
const qs = params.toString();
const res = await authFetch(`${API_BASE}/api/v1/orders${qs ? `?${qs}` : ""}`);
if (!res.ok) throw new Error(`Failed to list orders: ${res.statusText}`);
@@ -3391,21 +3389,9 @@ export async function linkOrderToDirective(orderId: string, directiveId: string)
return res.json();
}
-export async function linkOrderToContract(orderId: string, contractId: string): Promise<Order> {
- const res = await authFetch(`${API_BASE}/api/v1/orders/${orderId}/link-contract`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ contractId }),
- });
- if (!res.ok) throw new Error(`Failed to link order to contract: ${res.statusText}`);
- return res.json();
-}
-
-export async function convertOrderToStep(orderId: string, directiveId: string): Promise<DirectiveStep> {
+export async function convertOrderToStep(orderId: string): Promise<DirectiveStep> {
const res = await authFetch(`${API_BASE}/api/v1/orders/${orderId}/convert-to-step`, {
method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ directiveId }),
});
if (!res.ok) throw new Error(`Failed to convert order to step: ${res.statusText}`);
return res.json();
diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
index acb6789..6d838ab 100644
--- a/makima/frontend/src/routes/contracts.tsx
+++ b/makima/frontend/src/routes/contracts.tsx
@@ -524,15 +524,9 @@ function ContractsPageContent() {
return (
<div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
<Masthead showNav />
- <main className="flex-1 flex flex-col p-4 pt-2 gap-4 overflow-hidden">
- {error && (
- <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
- {error}
- </div>
- )}
-
- <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0">
- {/* Contract list */}
+ <main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}>
+ {/* Left: Contract list */}
+ <div className="w-[350px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col">
<ContractList
contracts={contracts}
loading={loading}
@@ -545,8 +539,18 @@ function ContractsPageContent() {
onDelete={handleContextDelete}
onGoToSupervisor={handleContextGoToSupervisor}
/>
+ </div>
+
+ {/* Right: Detail or Create */}
+ <div className="flex-1 overflow-hidden flex flex-col">
+ {error && (
+ <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
+ {error}
+ </div>
+ )}
{/* Contract detail, creation form, or empty state */}
+ <div className="flex-1 min-h-0 overflow-hidden">
{isCreating ? (
<div className="p-4 max-w-lg overflow-y-auto h-full bg-[#0a1628]">
<h3 className="font-mono text-[10px] text-[#9bc3ff] uppercase tracking-wide mb-4">
@@ -873,6 +877,7 @@ function ContractsPageContent() {
</div>
</div>
)}
+ </div>
</div>
</main>
</div>
diff --git a/makima/frontend/src/routes/orders.tsx b/makima/frontend/src/routes/orders.tsx
index 735c557..deca77f 100644
--- a/makima/frontend/src/routes/orders.tsx
+++ b/makima/frontend/src/routes/orders.tsx
@@ -16,7 +16,7 @@ export default function OrdersPage() {
const [statusFilter, setStatusFilter] = useState<OrderStatus | undefined>(undefined);
const [typeFilter, setTypeFilter] = useState<OrderType | undefined>(undefined);
const { orders, loading: listLoading, create, refresh: refreshList } = useOrders(statusFilter, typeFilter);
- const { order, refresh: refreshDetail, update, remove: removeOrder, linkDirective, linkContract, convertToStep } = useOrder(selectedId);
+ const { order, refresh: refreshDetail, update, remove: removeOrder, linkDirective, convertToStep } = useOrder(selectedId);
const { directives } = useDirectives();
const [showCreate, setShowCreate] = useState(false);
@@ -24,6 +24,7 @@ export default function OrdersPage() {
const [newDesc, setNewDesc] = useState("");
const [newPriority, setNewPriority] = useState<OrderPriority>("medium");
const [newType, setNewType] = useState<OrderType>("feature");
+ const [newDirectiveId, setNewDirectiveId] = useState<string>("");
useEffect(() => {
if (!authLoading && isAuthConfigured && !isAuthenticated) {
@@ -43,19 +44,21 @@ export default function OrdersPage() {
}
const handleCreate = async () => {
- if (!newTitle.trim()) return;
+ if (!newTitle.trim() || !newDirectiveId) return;
try {
const o = await create({
title: newTitle.trim(),
description: newDesc.trim() || undefined,
priority: newPriority,
orderType: newType,
+ directiveId: newDirectiveId,
});
setShowCreate(false);
setNewTitle("");
setNewDesc("");
setNewPriority("medium");
setNewType("feature");
+ setNewDirectiveId("");
navigate(`/orders/${o.id}`);
} catch (e) {
console.error("Failed to create order:", e);
@@ -84,13 +87,8 @@ export default function OrdersPage() {
await refreshList();
};
- const handleLinkContract = async (contractId: string) => {
- await linkContract(contractId);
- await refreshList();
- };
-
- const handleConvertToStep = async (directiveId: string) => {
- await convertToStep(directiveId);
+ const handleConvertToStep = async () => {
+ await convertToStep();
await refreshList();
};
@@ -162,6 +160,21 @@ export default function OrdersPage() {
className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white resize-y"
/>
</div>
+ <div>
+ <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
+ Directive *
+ </label>
+ <select
+ value={newDirectiveId}
+ onChange={(e) => setNewDirectiveId(e.target.value)}
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
+ >
+ <option value="">Select directive...</option>
+ {directives.map((d) => (
+ <option key={d.id} value={d.id}>{d.title}</option>
+ ))}
+ </select>
+ </div>
<div className="flex gap-4">
<div className="flex-1">
<label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
@@ -196,7 +209,7 @@ export default function OrdersPage() {
<button
type="button"
onClick={handleCreate}
- disabled={!newTitle.trim()}
+ disabled={!newTitle.trim() || !newDirectiveId}
className="text-[11px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-3 py-1 disabled:opacity-50"
>
Create
@@ -218,7 +231,6 @@ export default function OrdersPage() {
onUpdate={handleUpdate}
onDelete={handleDelete}
onLinkDirective={handleLinkDirective}
- onLinkContract={handleLinkContract}
onConvertToStep={handleConvertToStep}
onRefresh={refreshDetail}
/>