"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 FILTER_CATEGORIES_EN = [
{ id: "All", name: "All", color: "#8b5cf6" },
{ id: "GND", name: "Ground (GND)", color: "#64748b" },
{ id: "Power", name: "Power", color: "#ef4444" },
{ id: "GPIO", name: "GPIO", color: "#22c55e" },
{ id: "Low Power", name: "LP GPIO", color: "#f59e0b" },
{ id: "ADC", name: "ADC", color: "#ec4899" },
{ id: "DAC", name: "DAC", color: "#a855f7" },
{ id: "Touch", name: "Touch", color: "#06b6d4" },
{ id: "UART", name: "UART", color: "#3b82f6" },
{ id: "SPI", name: "SPI", color: "#f43f5e" },
{ id: "USB", name: "USB", color: "#a855f7" },
{ id: "Clock", name: "Clock", color: "#eab308" },
{ id: "JTAG", name: "JTAG", color: "#6366f1" },
{ id: "Strapping", name: "Boot mode", color: "#dc2626" },
{ id: "DSI", name: "Display (DSI)", color: "#f97316" },
{ id: "CSI", name: "Camera (CSI)", color: "#14b8a6" },
{ id: "Ethernet", name: "Ethernet", color: "#0284c7" },
{ id: "SD", name: "SD Card", color: "#10b981" },
{ id: "I2C", name: "I2C", color: "#f59e0b" },
{ id: "Antena", name: "Antenna", color: "#06b6d4" },
];
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 isEn = pathname?.startsWith("/en");
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);
const filterCategories = isEn ? FILTER_CATEGORIES_EN : FILTER_CATEGORIES;
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 = filterCategories.find((f) => f.id === category);
return filter?.color || "#64748b";
};
const nomeCategoria = (cat) => {
if (cat === "Strapping") return isEn ? "Boot mode" : "Inicialização";
if (isEn) {
const found = FILTER_CATEGORIES_EN.find((f) => f.id === cat);
if (found) return found.name;
}
return 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 (
{isEn ? "Pinout diagram unavailable" : "Diagrama de conexões indisponível"}
{isEn ? "This model does not yet have a detailed pinout and bus map." : "Este modelo ainda não tem um mapa detalhado de conexões e barramentos."}
);
}
const numeroCurto = (c) => (typeof c.numero === "number" ? c.numero : String(c.numero).substring(0, 4));
return (
{isEn ? "Interactive pinout diagram" : "Diagrama de conexões interativo"}
{isEn ? (
`All connections and multiplexing for the ${serie.nome_completo}. Search by name or number, filter by function, and open a pin to see details.`
) : (
`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 && (
{isEn ? "Filtered by search:" : "Filtrado pela busca:"} {motivoFiltro || (isEn ? "selected pins" : "pinos selecionados")} ({pinosFiltro.size}{" "}
{pinosFiltro.size === 1 ? (isEn ? "pin" : "pino") : (isEn ? "pins" : "pinos")})
{isEn ? "Show all pins" : "Mostrar todos os pinos"}
)}
{isEn ? "Pin function" : "Função do pino"}
{isEn ? (
`${visibleConnections.length} of ${connections.length} pins`
) : (
`${visibleConnections.length} de ${connections.length} pinos`
)}
{filterCategories.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 (
setActiveFilter(filter.id)} aria-pressed={isActive} className="chip-btn">
{filter.id !== "All" && }
{filter.name}
{count}
);
})}
{visibleConnections.length === 0 ? (
{isEn ? "No connections found" : "Nenhuma conexão encontrada"}
{isEn ? "Adjust your search or return to all functions." : "Ajuste a busca ou volte para todas as funções."}
{ setActiveFilter("All"); setSearchQuery(""); if (pinosFiltro.size) mostrarTodos(); }} className="btn btn-primary btn-sm mt-5">
Limpar busca e filtros
) : viewMode === "grid" ? (
{visibleConnections.map((connection) => {
const baseColor = type_colors[connection.tipo] || "#94a3b8";
return (
abrir(connection, e)}
aria-haspopup="dialog"
aria-label={`Pino ${connection.nome}, número ${connection.numero}${connection.avisos ? ", com restrições de uso" : ""}`}
className="flex w-full flex-col items-center rounded-xl border border-line bg-surface p-3 transition-colors hover:bg-surface-2"
>
{numeroCurto(connection)}
{connection.nome}
{connection.categorias.slice(0, 5).map((cat) => (
))}
{connection.avisos && (
)}
);
})}
) : (
{isEn ? `Connections of ${serie.nome_completo}` : `Conexões do ${serie.nome_completo}`}
{isEn ? "Pin" : "Pino"}
{isEn ? "Name" : "Nome"}
{isEn ? "Functions" : "Funções"}
{isEn ? "Usage" : "Uso"}
{visibleConnections.map((connection) => {
const baseColor = type_colors[connection.tipo] || "#94a3b8";
return (
abrir(connection, e)}
aria-haspopup="dialog"
aria-label={isEn ? `Open pin details for ${connection.nome}, number ${connection.numero}` : `Abrir detalhes do pino ${connection.nome}, número ${connection.numero}`}
className="grid h-11 w-11 place-items-center rounded-full border-2 bg-surface text-sm font-semibold tabular-nums text-ink hover:bg-surface-2"
style={{ borderColor: baseColor }}
>
{numeroCurto(connection)}
{connection.nome}
{connection.funcoes.join(", ")}
{connection.avisos ? (
{isEn ? "Restricted" : "Restrito"}
) : (
{isEn ? "Free" : "Livre"}
)}
);
})}
)}
{connections.some((c) => c.avisos) && (
{isEn ? "Pinout notes" : "Notas sobre a pinagem"}
{isEn ? (
<>
Pins with a warning icon have special boot roles or analog limitations. Open the pin to see details.
Isolate capacitive touch traces from coupled RF noise.
>
) : (
<>
Pinos com o ícone de alerta têm papel especial no boot ou limitações analógicas. Abra o pino para ver os detalhes.
Isole os circuitos de toque capacitivo de pinos com ruído de RF acoplado.
>
)}
)}
{selectedConnection && mounted && createPortal(
e.stopPropagation()}
>
{numeroCurto(selectedConnection)}
{selectedConnection.nome}
{isEn ? `Physical pin ${selectedConnection.numero}` : `Pino físico ${selectedConnection.numero}`}
{selectedConnection.categorias.length > 0 && (
{isEn ? "Categories" : "Categorias"}
{selectedConnection.categorias.map((cat) => (
{nomeCategoria(cat)}
))}
)}
{selectedConnection.alimentacao && (
{isEn ? "Power domain" : "Domínio de alimentação"}
{selectedConnection.alimentacao.map((source) => (
{source}
))}
)}
{isEn ? "Available functions" : "Funções disponíveis"}
{selectedConnection.funcoes.map((func) => (
{func}
))}
{selectedConnection.avisos && (
{isEn ? "Engineering notes" : "Avisos de engenharia"}
{selectedConnection.avisos.map((aviso) => (
{aviso}
))}
)}
{isEn ? "Close" : "Fechar"}
,
document.body
)}
);
}