diff options
| author | soryu <soryu@soryu.co> | 2026-02-12 16:17:26 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-12 16:17:26 +0000 |
| commit | dd57e7d002d6e4f3eb10eb600916eba55b8d1626 (patch) | |
| tree | e8a0e65c0f36ab0428030f26f30170c69848de56 | |
| parent | af82a845d5210214a0482c00378f967aecb0b274 (diff) | |
| download | soryu-makima/makima-jp--build-the-orders-page-frontend-replacin-cb5dbe68.tar.gz soryu-makima/makima-jp--build-the-orders-page-frontend-replacin-cb5dbe68.zip | |
feat: makima.jp: Build the Orders page frontend replacing the Boardmakima/makima-jp--build-the-orders-page-frontend-replacin-cb5dbe68
| -rw-r--r-- | makima/frontend/src/components/NavStrip.tsx | 2 | ||||
| -rw-r--r-- | makima/frontend/src/main.tsx | 14 | ||||
| -rw-r--r-- | makima/frontend/src/routes/orders.tsx | 143 |
3 files changed, 155 insertions, 4 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 9bb7777..5aba6a3 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -12,7 +12,7 @@ const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, { label: "Directives", href: "/directives", requiresAuth: true }, { label: "Contracts", href: "/contracts", requiresAuth: true }, - { label: "Board", href: "/workflow", requiresAuth: true }, + { label: "Orders", href: "/orders", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, ]; diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index 3dc68f5..acc9afc 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -12,7 +12,7 @@ import HomePage from "./routes/_index"; import ListenPage from "./routes/listen"; import FilesPage from "./routes/files"; import ContractsPage from "./routes/contracts"; -import WorkflowPage from "./routes/workflow"; +import OrdersPage from "./routes/orders"; import MeshPage from "./routes/mesh"; import HistoryPage from "./routes/history"; import LoginPage from "./routes/login"; @@ -81,10 +81,18 @@ createRoot(document.getElementById("root")!).render( } /> <Route - path="/workflow" + path="/orders" element={ <ProtectedRoute> - <WorkflowPage /> + <OrdersPage /> + </ProtectedRoute> + } + /> + <Route + path="/orders/:id" + element={ + <ProtectedRoute> + <OrdersPage /> </ProtectedRoute> } /> diff --git a/makima/frontend/src/routes/orders.tsx b/makima/frontend/src/routes/orders.tsx new file mode 100644 index 0000000..be9a908 --- /dev/null +++ b/makima/frontend/src/routes/orders.tsx @@ -0,0 +1,143 @@ +import { useState, useEffect, useCallback } 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 { CreateOrderModal } from "../components/orders/CreateOrderModal"; +import { useOrders, useOrder, useOrderLabels } from "../hooks/useOrders"; +import { useAuth } from "../contexts/AuthContext"; +import type { OrderStatus, OrderType, CreateOrderRequest } from "../lib/api"; + +export default function OrdersPage() { + const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); + const navigate = useNavigate(); + const { id: selectedId } = useParams<{ id: string }>(); + + // Filter state + const [statusFilter, setStatusFilter] = useState<OrderStatus | "all">("all"); + const [typeFilter, setTypeFilter] = useState<OrderType | "all">("all"); + const [showCreate, setShowCreate] = useState(false); + + // Hooks + const { orders, loading: listLoading, refresh: refreshList, create, remove } = useOrders(); + const { order, loading: detailLoading, refresh: refreshDetail, update } = useOrder(selectedId); + const { addLabel, removeLabel } = useOrderLabels(selectedId); + + // Auth redirect + 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 = useCallback(async (req: CreateOrderRequest) => { + try { + const o = await create(req); + setShowCreate(false); + navigate(`/orders/${o.id}`); + } catch (e) { + console.error("Failed to create order:", e); + } + }, [create, navigate]); + + const handleDelete = useCallback(async () => { + if (!selectedId) return; + try { + await remove(selectedId); + navigate("/orders"); + } catch (e) { + console.error("Failed to delete:", e); + } + }, [selectedId, remove, navigate]); + + const handleUpdate = useCallback(async (fields: Parameters<typeof update>[0]) => { + await update(fields); + refreshList(); + }, [update, refreshList]); + + const handleAddLabel = useCallback(async (label: { name: string; color: string }) => { + await addLabel(label); + refreshDetail(); + refreshList(); + }, [addLabel, refreshDetail, refreshList]); + + const handleRemoveLabel = useCallback(async (name: string) => { + await removeLabel(name); + refreshDetail(); + refreshList(); + }, [removeLabel, refreshDetail, refreshList]); + + 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 Panel */} + <div className="w-[320px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col"> + <OrderList + orders={orders} + selectedId={selectedId ?? null} + statusFilter={statusFilter} + typeFilter={typeFilter} + onStatusFilterChange={setStatusFilter} + onTypeFilterChange={setTypeFilter} + onSelect={(id) => { + setShowCreate(false); + navigate(`/orders/${id}`); + }} + onCreate={() => { + setShowCreate(true); + navigate("/orders"); + }} + loading={listLoading} + /> + </div> + + {/* Right: Detail / Create / Empty */} + <div className="flex-1 overflow-hidden"> + {showCreate ? ( + <CreateOrderModal + onCreate={handleCreate} + onCancel={() => setShowCreate(false)} + /> + ) : selectedId && order ? ( + <OrderDetail + order={order} + onUpdate={handleUpdate} + onDelete={handleDelete} + onRefresh={() => { + refreshDetail(); + refreshList(); + }} + onAddLabel={handleAddLabel} + onRemoveLabel={handleRemoveLabel} + /> + ) : ( + <div className="flex-1 flex items-center justify-center h-full"> + <div className="text-center"> + <p className="text-[#445566] font-mono text-[11px] uppercase tracking-wider mb-1"> + {listLoading ? "Loading..." : detailLoading ? "Loading order..." : "命令を選択"} + </p> + <p className="text-[#556677] font-mono text-[12px]"> + {listLoading + ? "" + : "Select an order or create a new one"} + </p> + </div> + </div> + )} + </div> + </main> + </div> + ); +} |
