summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/orders.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-14 21:29:26 +0000
committerGitHub <noreply@github.com>2026-02-14 21:29:26 +0000
commit9aadbc7958d39d181c0dd0600e2b7c30bb6c391a (patch)
treeef8bed9718c39041191b58a284ee31f5d8d32521 /makima/frontend/src/routes/orders.tsx
parentc1e55ce4fec79f9909b957f86bd7fa8b76939746 (diff)
downloadsoryu-9aadbc7958d39d181c0dd0600e2b7c30bb6c391a.tar.gz
soryu-9aadbc7958d39d181c0dd0600e2b7c30bb6c391a.zip
Makima system improvements: Orders, directive questions, PR creation fix, bug fixes (#62)
* feat: soryu-co/soryu - makima: Fix directive goal update bug - stale closure issue * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Create Orders database schema and backend API * feat: soryu-co/soryu - makima: Fix task Claude instance not receiving user inputs from input box * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Build Orders frontend page replacing the Board page * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Fix directive PR creation system
Diffstat (limited to 'makima/frontend/src/routes/orders.tsx')
-rw-r--r--makima/frontend/src/routes/orders.tsx238
1 files changed, 238 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/orders.tsx b/makima/frontend/src/routes/orders.tsx
new file mode 100644
index 0000000..735c557
--- /dev/null
+++ b/makima/frontend/src/routes/orders.tsx
@@ -0,0 +1,238 @@
+import { useState, useEffect } from "react";
+import { useParams, useNavigate } from "react-router";
+import { Masthead } from "../components/Masthead";
+import { OrderList } from "../components/orders/OrderList";
+import { OrderDetail } from "../components/orders/OrderDetail";
+import { useOrders, useOrder } from "../hooks/useOrders";
+import { useDirectives } from "../hooks/useDirectives";
+import { useAuth } from "../contexts/AuthContext";
+import type { OrderStatus, OrderType, OrderPriority } from "../lib/api";
+
+export default function OrdersPage() {
+ const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
+ const navigate = useNavigate();
+ const { id: selectedId } = useParams<{ id: string }>();
+
+ 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 { directives } = useDirectives();
+
+ const [showCreate, setShowCreate] = useState(false);
+ const [newTitle, setNewTitle] = useState("");
+ const [newDesc, setNewDesc] = useState("");
+ const [newPriority, setNewPriority] = useState<OrderPriority>("medium");
+ const [newType, setNewType] = useState<OrderType>("feature");
+
+ useEffect(() => {
+ if (!authLoading && isAuthConfigured && !isAuthenticated) {
+ navigate("/login");
+ }
+ }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
+
+ if (authLoading) {
+ return (
+ <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
+ <Masthead showNav />
+ <main className="flex-1 flex items-center justify-center">
+ <p className="text-[#7788aa] font-mono text-sm">Loading...</p>
+ </main>
+ </div>
+ );
+ }
+
+ const handleCreate = async () => {
+ if (!newTitle.trim()) return;
+ try {
+ const o = await create({
+ title: newTitle.trim(),
+ description: newDesc.trim() || undefined,
+ priority: newPriority,
+ orderType: newType,
+ });
+ setShowCreate(false);
+ setNewTitle("");
+ setNewDesc("");
+ setNewPriority("medium");
+ setNewType("feature");
+ navigate(`/orders/${o.id}`);
+ } catch (e) {
+ console.error("Failed to create order:", e);
+ }
+ };
+
+ const handleDelete = async () => {
+ if (!selectedId) return;
+ if (!window.confirm("Delete this order?")) return;
+ try {
+ await removeOrder();
+ await refreshList();
+ navigate("/orders");
+ } catch (e) {
+ console.error("Failed to delete:", e);
+ }
+ };
+
+ const handleUpdate = async (req: Parameters<typeof update>[0]) => {
+ await update(req);
+ await refreshList();
+ };
+
+ const handleLinkDirective = async (directiveId: string) => {
+ await linkDirective(directiveId);
+ await refreshList();
+ };
+
+ const handleLinkContract = async (contractId: string) => {
+ await linkContract(contractId);
+ await refreshList();
+ };
+
+ const handleConvertToStep = async (directiveId: string) => {
+ await convertToStep(directiveId);
+ await refreshList();
+ };
+
+ const priorityOptions: { value: OrderPriority; label: string }[] = [
+ { value: "critical", label: "Critical" },
+ { value: "high", label: "High" },
+ { value: "medium", label: "Medium" },
+ { value: "low", label: "Low" },
+ { value: "none", label: "None" },
+ ];
+
+ const typeOptions: { value: OrderType; label: string }[] = [
+ { value: "feature", label: "Feature" },
+ { value: "bug", label: "Bug" },
+ { value: "spike", label: "Spike" },
+ { value: "chore", label: "Chore" },
+ { value: "improvement", label: "Improvement" },
+ ];
+
+ return (
+ <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
+ <Masthead showNav />
+ <main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}>
+ {/* Left: List */}
+ <div className="w-[280px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col">
+ <OrderList
+ orders={orders}
+ selectedId={selectedId ?? null}
+ onSelect={(id) => navigate(`/orders/${id}`)}
+ onCreate={() => setShowCreate(true)}
+ statusFilter={statusFilter}
+ onStatusFilter={setStatusFilter}
+ typeFilter={typeFilter}
+ onTypeFilter={setTypeFilter}
+ />
+ </div>
+
+ {/* Right: Detail or Create */}
+ <div className="flex-1 overflow-hidden">
+ {showCreate ? (
+ <div className="p-4 max-w-lg">
+ <h2 className="text-[14px] font-mono text-white font-medium mb-4">
+ New Order
+ </h2>
+ <div className="flex flex-col gap-3">
+ <div>
+ <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
+ Title
+ </label>
+ <input
+ value={newTitle}
+ onChange={(e) => setNewTitle(e.target.value)}
+ placeholder="Order title..."
+ 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"
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && newTitle.trim()) handleCreate();
+ }}
+ />
+ </div>
+ <div>
+ <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
+ Description (optional)
+ </label>
+ <textarea
+ value={newDesc}
+ onChange={(e) => setNewDesc(e.target.value)}
+ placeholder="Describe the order..."
+ rows={4}
+ 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 className="flex gap-4">
+ <div className="flex-1">
+ <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
+ Priority
+ </label>
+ <select
+ value={newPriority}
+ onChange={(e) => setNewPriority(e.target.value as OrderPriority)}
+ 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"
+ >
+ {priorityOptions.map((p) => (
+ <option key={p.value} value={p.value}>{p.label}</option>
+ ))}
+ </select>
+ </div>
+ <div className="flex-1">
+ <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
+ Type
+ </label>
+ <select
+ value={newType}
+ onChange={(e) => setNewType(e.target.value as OrderType)}
+ 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"
+ >
+ {typeOptions.map((t) => (
+ <option key={t.value} value={t.value}>{t.label}</option>
+ ))}
+ </select>
+ </div>
+ </div>
+ <div className="flex gap-2">
+ <button
+ type="button"
+ onClick={handleCreate}
+ disabled={!newTitle.trim()}
+ 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
+ </button>
+ <button
+ type="button"
+ onClick={() => setShowCreate(false)}
+ className="text-[11px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-3 py-1"
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ </div>
+ ) : selectedId && order ? (
+ <OrderDetail
+ order={order}
+ directives={directives}
+ onUpdate={handleUpdate}
+ onDelete={handleDelete}
+ onLinkDirective={handleLinkDirective}
+ onLinkContract={handleLinkContract}
+ onConvertToStep={handleConvertToStep}
+ onRefresh={refreshDetail}
+ />
+ ) : (
+ <div className="flex-1 flex items-center justify-center h-full">
+ <p className="text-[#556677] font-mono text-[12px]">
+ {listLoading
+ ? "Loading..."
+ : "Select an order or create a new one"}
+ </p>
+ </div>
+ )}
+ </div>
+ </main>
+ </div>
+ );
+}