diff options
Diffstat (limited to 'makima')
| -rw-r--r-- | makima/frontend/src/components/NavStrip.tsx | 2 | ||||
| -rw-r--r-- | makima/frontend/src/components/chains/ChainEditor.tsx | 22 | ||||
| -rw-r--r-- | makima/frontend/src/components/chains/ChainList.tsx | 187 |
3 files changed, 97 insertions, 114 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 5937982..4f6cf32 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -10,8 +10,8 @@ interface NavLink { const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, - { label: "Contracts", href: "/contracts", requiresAuth: true }, { label: "Chains", href: "/chains", requiresAuth: true }, + { label: "Contracts", href: "/contracts", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, diff --git a/makima/frontend/src/components/chains/ChainEditor.tsx b/makima/frontend/src/components/chains/ChainEditor.tsx index 9077d19..92bd496 100644 --- a/makima/frontend/src/components/chains/ChainEditor.tsx +++ b/makima/frontend/src/components/chains/ChainEditor.tsx @@ -5,6 +5,12 @@ import type { ChainContractDetail, } from "../../lib/api"; +const statusColors: Record<string, string> = { + active: "text-green-400", + completed: "text-blue-400", + archived: "text-[#555]", +}; + interface ChainEditorProps { chain: ChainWithContracts; graph: ChainGraphResponse | null; @@ -116,12 +122,8 @@ export function ChainEditor({ </div> <div className="flex items-center gap-2"> <span - className={`px-2 py-1 font-mono text-[10px] uppercase rounded ${ - chain.chain.status === "active" - ? "text-[#4ade80] bg-[#4ade80]/10" - : chain.chain.status === "completed" - ? "text-[#60a5fa] bg-[#60a5fa]/10" - : "text-[#6b7280] bg-[#6b7280]/10" + className={`font-mono text-[10px] uppercase ${ + statusColors[chain.chain.status] || "text-[#555]" }`} > {chain.chain.status} @@ -371,12 +373,8 @@ function ContractDetailPanel({ Status </label> <span - className={`inline-block px-2 py-1 font-mono text-xs uppercase rounded ${ - contract.contractStatus === "active" - ? "text-[#4ade80] bg-[#4ade80]/10" - : contract.contractStatus === "completed" - ? "text-[#60a5fa] bg-[#60a5fa]/10" - : "text-[#6b7280] bg-[#6b7280]/10" + className={`font-mono text-xs uppercase ${ + statusColors[contract.contractStatus] || "text-[#555]" }`} > {contract.contractStatus} diff --git a/makima/frontend/src/components/chains/ChainList.tsx b/makima/frontend/src/components/chains/ChainList.tsx index eda79d7..befccd2 100644 --- a/makima/frontend/src/components/chains/ChainList.tsx +++ b/makima/frontend/src/components/chains/ChainList.tsx @@ -10,6 +10,12 @@ interface ChainListProps { onArchive: (chain: ChainSummary) => void; } +const statusColors: Record<ChainStatus, string> = { + active: "text-green-400", + completed: "text-blue-400", + archived: "text-[#555]", +}; + export function ChainList({ chains, loading, @@ -18,101 +24,74 @@ export function ChainList({ selectedId, onArchive, }: ChainListProps) { - const [statusFilter, setStatusFilter] = useState<ChainStatus | "all">("all"); - const [contextMenu, setContextMenu] = useState<{ - chain: ChainSummary; - x: number; - y: number; - } | null>(null); + const [filter, setFilter] = useState<ChainStatus | "all">("all"); + const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null); + const [contextMenuChain, setContextMenuChain] = useState<ChainSummary | null>(null); - const filteredChains = chains.filter((chain) => - statusFilter === "all" ? true : chain.status === statusFilter - ); + const filteredChains = + filter === "all" + ? chains + : chains.filter((c) => c.status === filter); const handleContextMenu = useCallback( (e: React.MouseEvent, chain: ChainSummary) => { e.preventDefault(); - setContextMenu({ chain, x: e.clientX, y: e.clientY }); + setContextMenuPosition({ x: e.clientX, y: e.clientY }); + setContextMenuChain(chain); }, [] ); const closeContextMenu = useCallback(() => { - setContextMenu(null); + setContextMenuPosition(null); + setContextMenuChain(null); }, []); const handleArchive = useCallback(() => { - if (contextMenu) { - onArchive(contextMenu.chain); - setContextMenu(null); - } - }, [contextMenu, onArchive]); - - const getStatusColor = (status: ChainStatus) => { - switch (status) { - case "active": - return "text-[#4ade80] bg-[#4ade80]/10"; - case "completed": - return "text-[#60a5fa] bg-[#60a5fa]/10"; - case "archived": - return "text-[#6b7280] bg-[#6b7280]/10"; - default: - return "text-[#8b949e] bg-[#8b949e]/10"; + if (contextMenuChain) { + onArchive(contextMenuChain); + closeContextMenu(); } - }; + }, [contextMenuChain, onArchive, closeContextMenu]); - const getStatusIcon = (status: ChainStatus) => { - switch (status) { - case "active": - return ( - <svg className="w-3 h-3" viewBox="0 0 24 24" fill="currentColor"> - <circle cx="12" cy="12" r="4" /> - </svg> - ); - case "completed": - return ( - <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"> - <polyline points="20 6 9 17 4 12" /> - </svg> - ); - case "archived": - return ( - <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> - <path d="M21 8v13H3V8" /> - <path d="M1 3h22v5H1z" /> - <path d="M10 12h4" /> - </svg> - ); - default: - return null; - } - }; + if (loading) { + return ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> + </div> + ); + } return ( <div className="panel h-full flex flex-col" onClick={closeContextMenu}> {/* Header */} - <div className="p-3 border-b border-[rgba(117,170,252,0.2)]"> + <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> <div className="flex items-center justify-between mb-3"> - <h2 className="font-mono text-sm text-[#75aafc] uppercase">Chains</h2> + <h2 className="font-mono text-sm text-[#75aafc] uppercase tracking-wider"> + Chains + </h2> <button onClick={onCreate} - className="px-3 py-1 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors" + className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" > + New </button> </div> - {/* Status filter */} + {/* Filter tabs */} <div className="flex gap-1"> {(["all", "active", "completed", "archived"] as const).map((status) => ( <button key={status} - onClick={() => setStatusFilter(status)} - className={`px-2 py-1 font-mono text-[10px] uppercase transition-colors ${ - statusFilter === status - ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]" - : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]" - }`} + onClick={() => setFilter(status)} + className={` + px-2 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors + ${ + filter === status + ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]" + : "text-[#555] hover:text-[#75aafc]" + } + `} > {status} </button> @@ -122,78 +101,84 @@ export function ChainList({ {/* Chain list */} <div className="flex-1 overflow-y-auto"> - {loading ? ( - <div className="flex items-center justify-center h-32"> - <p className="font-mono text-xs text-[#8b949e]">Loading chains...</p> - </div> - ) : filteredChains.length === 0 ? ( - <div className="flex items-center justify-center h-32"> - <p className="font-mono text-xs text-[#8b949e]"> - {statusFilter === "all" ? "No chains yet" : `No ${statusFilter} chains`} + {filteredChains.length === 0 ? ( + <div className="p-4 text-center"> + <p className="font-mono text-sm text-[#555]"> + {filter === "all" + ? "No chains yet" + : `No ${filter} chains`} </p> </div> ) : ( - <div className="divide-y divide-[rgba(117,170,252,0.1)]"> + <div className="divide-y divide-[rgba(117,170,252,0.15)]"> {filteredChains.map((chain) => ( - <div + <button key={chain.id} onClick={() => onSelect(chain.id)} onContextMenu={(e) => handleContextMenu(e, chain)} - className={`p-3 cursor-pointer transition-colors ${ - selectedId === chain.id - ? "bg-[rgba(117,170,252,0.15)]" - : "hover:bg-[rgba(117,170,252,0.05)]" - }`} + className={` + w-full text-left p-4 transition-colors + ${ + selectedId === chain.id + ? "bg-[rgba(117,170,252,0.1)]" + : "hover:bg-[rgba(117,170,252,0.05)]" + } + `} > - <div className="flex items-center justify-between mb-1"> - <span className="font-mono text-sm text-[#dbe7ff] truncate"> + <div className="flex items-start justify-between gap-2 mb-2"> + <h3 className="font-mono text-sm text-[#dbe7ff] truncate"> {chain.name} - </span> + </h3> <span - className={`flex items-center gap-1 px-1.5 py-0.5 font-mono text-[10px] uppercase rounded ${getStatusColor( - chain.status - )}`} + className={`text-[10px] font-mono uppercase shrink-0 ${ + statusColors[chain.status] + }`} > - {getStatusIcon(chain.status)} {chain.status} </span> </div> + {chain.description && ( - <p className="font-mono text-xs text-[#8b949e] truncate mb-1"> + <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2"> {chain.description} </p> )} - <div className="flex items-center gap-3 font-mono text-[10px] text-[#556677]"> + + <div className="flex items-center gap-4 text-[10px] font-mono text-[#555]"> <span>{chain.contractCount} contracts</span> - <span> - {new Date(chain.updatedAt).toLocaleDateString()} - </span> + <span>{chain.completedContractCount} completed</span> + {chain.loopEnabled && ( + <span className="text-amber-400"> + loop {chain.loopCurrentIteration || 0}/{chain.loopMaxIterations || "∞"} + </span> + )} </div> - </div> + </button> ))} </div> )} </div> - {/* Context menu */} - {contextMenu && ( + {/* Context Menu */} + {contextMenuPosition && contextMenuChain && ( <div - className="fixed z-50 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] shadow-lg py-1" - style={{ top: contextMenu.y, left: contextMenu.x }} + className="fixed z-50 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] shadow-lg py-1 min-w-[150px]" + style={{ top: contextMenuPosition.y, left: contextMenuPosition.x }} + onClick={(e) => e.stopPropagation()} > <button onClick={() => { - onSelect(contextMenu.chain.id); - setContextMenu(null); + onSelect(contextMenuChain.id); + closeContextMenu(); }} - className="w-full px-4 py-2 text-left font-mono text-xs text-[#dbe7ff] hover:bg-[rgba(117,170,252,0.1)]" + className="w-full px-4 py-2 text-left font-mono text-xs text-[#dbe7ff] hover:bg-[rgba(117,170,252,0.1)] transition-colors" > View Details </button> - {contextMenu.chain.status !== "archived" && ( + {contextMenuChain.status !== "archived" && ( <button onClick={handleArchive} - className="w-full px-4 py-2 text-left font-mono text-xs text-red-400 hover:bg-red-400/10" + className="w-full px-4 py-2 text-left font-mono text-xs text-red-400 hover:bg-red-400/10 transition-colors" > Archive </button> |
