From dd57e7d002d6e4f3eb10eb600916eba55b8d1626 Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 12 Feb 2026 16:17:26 +0000 Subject: feat: makima.jp: Build the Orders page frontend replacing the Board --- makima/frontend/src/components/NavStrip.tsx | 2 +- makima/frontend/src/main.tsx | 14 ++- makima/frontend/src/routes/orders.tsx | 143 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 makima/frontend/src/routes/orders.tsx 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( } /> - + + + } + /> + + } /> 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("all"); + const [typeFilter, setTypeFilter] = useState("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 ( +
+ +
+

Loading...

+
+
+ ); + } + + 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[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 ( +
+ +
+ {/* Left: List Panel */} +
+ { + setShowCreate(false); + navigate(`/orders/${id}`); + }} + onCreate={() => { + setShowCreate(true); + navigate("/orders"); + }} + loading={listLoading} + /> +
+ + {/* Right: Detail / Create / Empty */} +
+ {showCreate ? ( + setShowCreate(false)} + /> + ) : selectedId && order ? ( + { + refreshDetail(); + refreshList(); + }} + onAddLabel={handleAddLabel} + onRemoveLabel={handleRemoveLabel} + /> + ) : ( +
+
+

+ {listLoading ? "Loading..." : detailLoading ? "Loading order..." : "命令を選択"} +

+

+ {listLoading + ? "" + : "Select an order or create a new one"} +

+
+
+ )} +
+
+
+ ); +} -- cgit v1.2.3