"use client"; import { useState, useEffect, useMemo, useRef, Suspense } from "react"; import { createPortal } from "react-dom"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { AlertTriangle, CheckCircle, List, Grid, Search, Sliders, X, Zap } from "lucide-react"; /* * As cores abaixo são dado de visualização: identificam o tipo de função do * pino no diagrama. O texto nunca fica sobre elas; elas aparecem só como borda * ou ponto, e o rótulo usa as cores do tema (contraste AAA). */ const FILTER_CATEGORIES = [ { id: "All", name: "Todos", color: "#8b5cf6" }, // purple { id: "GND", name: "Terra (GND)", color: "#64748b" }, // slate { id: "Power", name: "Alimentação", color: "#ef4444" }, // red { id: "GPIO", name: "GPIO", color: "#22c55e" }, // green { id: "Low Power", name: "GPIO LP", color: "#f59e0b" }, // amber { id: "ADC", name: "ADC", color: "#ec4899" }, // pink { id: "DAC", name: "DAC", color: "#a855f7" }, // purple { id: "Touch", name: "Touch", color: "#06b6d4" }, // cyan { id: "UART", name: "UART", color: "#3b82f6" }, // blue { id: "SPI", name: "SPI", color: "#f43f5e" }, // rose { id: "USB", name: "USB", color: "#a855f7" }, // purple { id: "Clock", name: "Clock", color: "#eab308" }, // yellow { id: "JTAG", name: "JTAG", color: "#6366f1" }, // indigo { id: "Strapping", name: "Inicialização", color: "#dc2626" }, // red { id: "DSI", name: "Display (DSI)", color: "#f97316" }, // orange { id: "CSI", name: "Câmera (CSI)", color: "#14b8a6" }, // teal { id: "Ethernet", name: "Ethernet", color: "#0284c7" }, // sky { id: "SD", name: "Cartão SD", color: "#10b981" }, // emerald { id: "I2C", name: "I2C", color: "#f59e0b" }, // amber { id: "Antena", name: "Antena", color: "#06b6d4" }, // cyan ]; const type_colors = { power: "#ef4444", // high-voltage red ground: "#64748b", // slate gray io: "#22c55e", // laser green analog: "#ec4899", // dynamic pink dedicated: "#3b82f6", // electric blue nc: "#94a3b8", // muted gray }; /** * Aceita ?pinos=28,30,31&motivo=... (gerado pela busca do site): mostra só * esses pinos, pelo número físico, com um aviso para voltar à lista completa. * O Suspense é exigido por useSearchParams em páginas estáticas. */ export default function ConnectionsDiagram(props) { return ( ); } function ConnectionsDiagramInner({ connections, serie }) { const params = useSearchParams(); const pathname = usePathname(); const router = useRouter(); const pinosFiltro = useMemo(() => new Set((params.get("pinos") ?? "").split(",").filter(Boolean)), [params]); const motivoFiltro = params.get("motivo") ?? ""; const [activeFilter, setActiveFilter] = useState("All"); const [selectedConnection, setSelectedConnection] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [viewMode, setViewMode] = useState("grid"); const [mounted, setMounted] = useState(false); const dialogRef = useRef(null); const gatilhoRef = useRef(null); useEffect(() => { setMounted(true); }, []); const isConnectionVisible = (connection) => { const matchesFilter = activeFilter === "All" || connection.categorias.includes(activeFilter); const matchesSearch = searchQuery === "" || connection.nome.toLowerCase().includes(searchQuery.toLowerCase()) || connection.numero.toString().toLowerCase().includes(searchQuery.toLowerCase()); const matchesPinos = pinosFiltro.size === 0 || pinosFiltro.has(String(connection.numero)); return matchesFilter && matchesSearch && matchesPinos; }; const getCategoryColor = (category) => { const filter = FILTER_CATEGORIES.find((f) => f.id === category); return filter?.color || "#64748b"; }; const nomeCategoria = (cat) => (cat === "Strapping" ? "Inicialização" : cat); const visibleConnections = useMemo(() => connections.filter(isConnectionVisible), [connections, activeFilter, searchQuery, pinosFiltro]); const mostrarTodos = () => router.replace(`${pathname}#connections`, { scroll: false }); const abrir = (connection, evento) => { gatilhoRef.current = evento.currentTarget; setSelectedConnection(connection); }; const fechar = () => { setSelectedConnection(null); gatilhoRef.current?.focus?.(); }; // Diálogo: Esc fecha, o foco entra nele, fica preso enquanto aberto e volta ao pino de origem. useEffect(() => { if (!selectedConnection) return; const dialogo = dialogRef.current; const focaveis = () => dialogo?.querySelectorAll('button, [href], input, [tabindex]:not([tabindex="-1"])') ?? []; focaveis()[0]?.focus(); const aoTeclar = (e) => { if (e.key === "Escape") { fechar(); return; } if (e.key !== "Tab") return; const itens = [...focaveis()]; if (itens.length === 0) return; const primeiro = itens[0]; const ultimo = itens[itens.length - 1]; if (e.shiftKey && document.activeElement === primeiro) { e.preventDefault(); ultimo.focus(); } else if (!e.shiftKey && document.activeElement === ultimo) { e.preventDefault(); primeiro.focus(); } }; document.addEventListener("keydown", aoTeclar); document.body.style.overflow = "hidden"; return () => { document.removeEventListener("keydown", aoTeclar); document.body.style.overflow = ""; }; }, [selectedConnection]); if (!connections || connections.length === 0) { return (
); } const numeroCurto = (c) => (typeof c.numero === "number" ? c.numero : String(c.numero).substring(0, 4)); return (

Diagrama de conexões interativo

Todas as conexões e a multiplexação do {serie.nome_completo}. Busque por nome ou número, filtre por função e abra um pino para ver os detalhes.

{pinosFiltro.size > 0 && (

Filtrado pela busca: {motivoFiltro || "pinos selecionados"} ({pinosFiltro.size}{" "} {pinosFiltro.size === 1 ? "pino" : "pinos"})

)}

Função do pino

{visibleConnections.length} de {connections.length} pinos

{FILTER_CATEGORIES.map((filter) => { const isActive = activeFilter === filter.id; const count = connections.filter((c) => (filter.id === "All" ? true : c.categorias.includes(filter.id))).length; if (count === 0 && !isActive) return null; // filtros que não se aplicam a este chip return ( ); })}
{visibleConnections.length === 0 ? (
) : viewMode === "grid" ? ( ) : (
{visibleConnections.map((connection) => { const baseColor = type_colors[connection.tipo] || "#94a3b8"; return ( ); })}
Conexões do {serie.nome_completo}
Pino Nome Funções Uso
{connection.nome} {connection.funcoes.join(", ")} {connection.avisos ? ( ) : ( )}
)} {connections.some((c) => c.avisos) && (
)} {selectedConnection && mounted && createPortal(
e.stopPropagation()} >
{numeroCurto(selectedConnection)}

Pino físico {selectedConnection.numero}

{selectedConnection.categorias.length > 0 && (

Categorias

    {selectedConnection.categorias.map((cat) => (
  • ))}
)} {selectedConnection.alimentacao && (

    {selectedConnection.alimentacao.map((source) => (
  • {source}
  • ))}
)}

Funções disponíveis

    {selectedConnection.funcoes.map((func) => (
  • ))}
{selectedConnection.avisos && (

    {selectedConnection.avisos.map((aviso) => (
  • {aviso}
  • ))}
)}
, document.body )}
); }