summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/chains/ChainList.tsx
blob: e185efcfe4e32758931886ad22cb878e369850b2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                               
                                                   
                             




                             







                           


                                                                                                        
 



                                                  



                                                   

                                                             




                                              

                                 


                                           


                                  
     
                                                      
 






                                                                          



                                                                           
                                                                                  
                                                                


                                                                                    

                              
                                                                                                                                                        




                   
                           



                                                                                  








                                                                                                       








                                              





                                                         


                
                                                                    
                                            
                     


                                                                  







                                                           
               

                                                                             
                                
                       
                       


                                                                           
                   


                                  
 
                                       
                                                                                 


                                       

                                                                                           
                                                              





                                                                                               
                      
                       




                

                                                   
            


                                                                                                                


                            

                                            
              
                                                                                                                                      


                        
                                                      

                                     
                                                                                                                         








                     
import { useState, useCallback } from "react";
import type { ChainSummary, ChainStatus } from "../../lib/api";

interface ChainListProps {
  chains: ChainSummary[];
  loading: boolean;
  onSelect: (chainId: string) => void;
  onCreate: () => void;
  selectedId?: string;
  onArchive: (chain: ChainSummary) => void;
}

const statusColors: Record<ChainStatus, string> = {
  pending: "text-yellow-400",
  active: "text-green-400",
  completed: "text-blue-400",
  archived: "text-[#555]",
};

export function ChainList({
  chains,
  loading,
  onSelect,
  onCreate,
  selectedId,
  onArchive,
}: ChainListProps) {
  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 =
    filter === "all"
      ? chains
      : chains.filter((c) => c.status === filter);

  const handleContextMenu = useCallback(
    (e: React.MouseEvent, chain: ChainSummary) => {
      e.preventDefault();
      setContextMenuPosition({ x: e.clientX, y: e.clientY });
      setContextMenuChain(chain);
    },
    []
  );

  const closeContextMenu = useCallback(() => {
    setContextMenuPosition(null);
    setContextMenuChain(null);
  }, []);

  const handleArchive = useCallback(() => {
    if (contextMenuChain) {
      onArchive(contextMenuChain);
      closeContextMenu();
    }
  }, [contextMenuChain, onArchive, closeContextMenu]);

  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-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 tracking-wider">
            Chains
          </h2>
          <button
            onClick={onCreate}
            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>

        {/* Filter tabs */}
        <div className="flex gap-1">
          {(["all", "active", "completed", "archived"] as const).map((status) => (
            <button
              key={status}
              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>
          ))}
        </div>
      </div>

      {/* Chain list */}
      <div className="flex-1 overflow-y-auto">
        {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.15)]">
            {filteredChains.map((chain) => (
              <button
                key={chain.id}
                onClick={() => onSelect(chain.id)}
                onContextMenu={(e) => handleContextMenu(e, chain)}
                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-start justify-between gap-2 mb-2">
                  <h3 className="font-mono text-sm text-[#dbe7ff] truncate">
                    {chain.name}
                  </h3>
                  <span
                    className={`text-[10px] font-mono uppercase shrink-0 ${
                      statusColors[chain.status]
                    }`}
                  >
                    {chain.status}
                  </span>
                </div>

                {chain.description && (
                  <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2">
                    {chain.description}
                  </p>
                )}

                <div className="flex items-center gap-4 text-[10px] font-mono text-[#555]">
                  <span>{chain.contractCount} contracts</span>
                  <span>{chain.completedContractCount} completed</span>
                  {chain.loopEnabled && (
                    <span className="text-amber-400">
                      loop {chain.loopCurrentIteration || 0}/{chain.loopMaxIterations || "∞"}
                    </span>
                  )}
                </div>
              </button>
            ))}
          </div>
        )}
      </div>

      {/* Context Menu */}
      {contextMenuPosition && contextMenuChain && (
        <div
          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(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)] transition-colors"
          >
            View Details
          </button>
          {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 transition-colors"
            >
              Archive
            </button>
          )}
        </div>
      )}
    </div>
  );
}