import { useMemo, useState } from "react" import { Link } from "react-router-dom" import { ArrowDown, ArrowUp, ArrowUpDown, Download, Play, RefreshCw, Square, Trash2 } from "lucide-react" import type { ComponentSummary, HealthStatus } from "@/types" import { useServiceAction, useToolAction } from "@/services/api/hooks" import { runnerLabel } from "@/lib/labels" import { HealthBadge } from "./HealthBadge" import { RoleBadge } from "./RoleBadge" interface ComponentTableProps { components: ComponentSummary[] statuses: HealthStatus[] } type SortKey = "id" | "category" | "runner" | "schedule" | "port" | "status" type SortDir = "asc" | "desc" function statusRank(s: HealthStatus | undefined, installed: boolean | null): number { // Health takes priority for services if (s) { if (s.status === "down") return 0 if (s.status === "up") return 3 return 2 } // Installed state for tools if (installed === false) return 1 if (installed === true) return 3 return 2 } export function ComponentTable({ components, statuses }: ComponentTableProps) { const statusMap = useMemo(() => new Map(statuses.map((s) => [s.id, s])), [statuses]) const allCategories = useMemo(() => { const set = new Set() for (const c of components) set.add(c.category) return Array.from(set).sort() }, [components]) const [categoryFilter, setCategoryFilter] = useState(null) const [search, setSearch] = useState("") const [sortKey, setSortKey] = useState("id") const [sortDir, setSortDir] = useState("asc") const toggleSort = (key: SortKey) => { if (sortKey === key) { setSortDir((d) => (d === "asc" ? "desc" : "asc")) } else { setSortKey(key) setSortDir("asc") } } const filtered = useMemo(() => { let list = components if (categoryFilter) { list = list.filter((c) => c.category === categoryFilter) } if (search) { const q = search.toLowerCase() list = list.filter( (c) => c.id.toLowerCase().includes(q) || (c.description?.toLowerCase().includes(q) ?? false), ) } return list }, [components, categoryFilter, search]) const sorted = useMemo(() => { const dir = sortDir === "asc" ? 1 : -1 return [...filtered].sort((a, b) => { switch (sortKey) { case "id": return dir * a.id.localeCompare(b.id) case "category": return dir * a.category.localeCompare(b.category) case "runner": return dir * (a.runner ?? "").localeCompare(b.runner ?? "") case "schedule": return dir * (a.schedule ?? "").localeCompare(b.schedule ?? "") case "port": return dir * ((a.port ?? 0) - (b.port ?? 0)) case "status": return dir * (statusRank(statusMap.get(a.id), a.installed) - statusRank(statusMap.get(b.id), b.installed)) default: return 0 } }) }, [filtered, sortKey, sortDir, statusMap]) return (
{/* Filters */}
setSearch(e.target.value)} placeholder="Filter components..." className="bg-black/30 border border-[var(--border)] rounded px-3 py-1.5 text-sm focus:outline-none focus:border-[var(--primary)] w-56" />
{allCategories.map((cat) => ( ))}
{/* Table */}
{sorted.map((comp) => ( ))} {sorted.length === 0 && ( )}
Actions
No components match.
) } function SortHeader({ label, sortKey, current, dir, onSort, }: { label: string sortKey: SortKey current: SortKey dir: SortDir onSort: (key: SortKey) => void }) { const active = current === sortKey const Icon = active ? (dir === "asc" ? ArrowUp : ArrowDown) : ArrowUpDown return ( ) } function InstalledBadge({ installed }: { installed: boolean }) { return installed ? ( installed ) : ( not installed ) } function ComponentRow({ component, health, }: { component: ComponentSummary health?: HealthStatus }) { const hasHttp = component.port != null const isTool = component.installed !== null const { mutate: serviceAction, isPending: servicePending } = useServiceAction() const { mutate: toolAction, isPending: toolPending } = useToolAction() const isDown = health?.status === "down" return ( {component.id} {component.description && (

{component.description}

)} {component.runner ? runnerLabel(component.runner) : "—"} {component.schedule ?? "—"} {component.port ?? "—"} {health ? ( ) : hasHttp ? ( ) : isTool ? ( ) : ( )} {component.managed ? (
{isDown && ( )} {!isDown && ( )}
) : isTool ? (
{component.installed ? ( ) : ( )}
) : ( )} ) }