feat: site search with a dialog in the header and a /api/busca endpoint
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8323c653b
commit
7d0edf13c4
7 files changed
+1115
-5
No files matched your search
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo, useRef } from "react";
|
||||
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";
|
||||
|
||||
/*
|
||||
@@ -41,7 +42,25 @@ const type_colors = {
|
||||
nc: "#94a3b8", // muted gray
|
||||
};
|
||||
|
||||
export default function ConnectionsDiagram({ connections, serie }) {
|
||||
/**
|
||||
* 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 (
|
||||
<Suspense fallback={null}>
|
||||
<ConnectionsDiagramInner {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
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("");
|
||||
@@ -60,7 +79,8 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
searchQuery === "" ||
|
||||
connection.nome.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
connection.numero.toString().toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesFilter && matchesSearch;
|
||||
const matchesPinos = pinosFiltro.size === 0 || pinosFiltro.has(String(connection.numero));
|
||||
return matchesFilter && matchesSearch && matchesPinos;
|
||||
};
|
||||
|
||||
const getCategoryColor = (category) => {
|
||||
@@ -70,7 +90,9 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
|
||||
const nomeCategoria = (cat) => (cat === "Strapping" ? "Inicialização" : cat);
|
||||
|
||||
const visibleConnections = useMemo(() => connections.filter(isConnectionVisible), [connections, activeFilter, searchQuery]);
|
||||
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;
|
||||
@@ -137,6 +159,18 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{pinosFiltro.size > 0 && (
|
||||
<div role="status" className="callout flex flex-wrap items-center justify-between gap-3">
|
||||
<p className="text-sm text-ink">
|
||||
<strong className="font-semibold">Filtrado pela busca:</strong> {motivoFiltro || "pinos selecionados"} ({pinosFiltro.size}{" "}
|
||||
{pinosFiltro.size === 1 ? "pino" : "pinos"})
|
||||
</p>
|
||||
<button type="button" onClick={mostrarTodos} className="btn btn-ghost btn-sm">
|
||||
Mostrar todos os pinos
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<div className="relative flex-1">
|
||||
<label htmlFor="busca-pino" className="sr-only">Buscar conexão por nome ou número</label>
|
||||
@@ -191,7 +225,7 @@ export default function ConnectionsDiagram({ connections, serie }) {
|
||||
<Search className="mx-auto h-8 w-8 text-muted" aria-hidden="true" />
|
||||
<p className="mt-3 font-semibold text-ink">Nenhuma conexão encontrada</p>
|
||||
<p className="mt-1 text-sm text-muted">Ajuste a busca ou volte para todas as funções.</p>
|
||||
<button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); }} className="btn btn-primary btn-sm mt-5">
|
||||
<button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); if (pinosFiltro.size) mostrarTodos(); }} className="btn btn-primary btn-sm mt-5">
|
||||
Limpar busca e filtros
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user