diff options
Diffstat (limited to 'makima/frontend/src')
| -rw-r--r-- | makima/frontend/src/components/orders/OrderDetail.tsx | 89 | ||||
| -rw-r--r-- | makima/frontend/src/components/orders/OrderList.tsx | 8 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useOrders.ts | 14 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 24 | ||||
| -rw-r--r-- | makima/frontend/src/routes/contracts.tsx | 23 | ||||
| -rw-r--r-- | makima/frontend/src/routes/orders.tsx | 34 |
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} /> |
