summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-12 16:17:26 +0000
committersoryu <soryu@soryu.co>2026-02-12 16:17:26 +0000
commitdd57e7d002d6e4f3eb10eb600916eba55b8d1626 (patch)
treee8a0e65c0f36ab0428030f26f30170c69848de56
parentaf82a845d5210214a0482c00378f967aecb0b274 (diff)
downloadsoryu-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.tsx2
-rw-r--r--makima/frontend/src/main.tsx14
-rw-r--r--makima/frontend/src/routes/orders.tsx143
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>
+ );
+}