import { useEffect, useMemo, useState } from "react"; import SecaoHeader from "../components/SecaoHeader.jsx"; import { consultaTipos, consultaBuscar, consultaDetalhar } from "../api.js"; // Tela de Consulta: busca e detalhe somente-leitura sobre Salesforce, com // cache Turso como camada rápida (modo híbrido). Nenhum cálculo é feito // aqui — apenas exibição do que o backend Python já retornou. const ROTULOS_TIPO = { conta: { icone: "🏢", cor: "#0018FF" }, oportunidade: { icone: "💼", cor: "#16C79A" }, contrato: { icone: "📄", cor: "#0004DD" }, item: { icone: "🧾", cor: "#FFC453" }, }; export default function Search({ notificar }) { const [tiposInfo, setTiposInfo] = useState(null); const [tiposAtivos, setTiposAtivos] = useState([]); const [termo, setTermo] = useState(""); const [buscando, setBuscando] = useState(false); const [resultados, setResultados] = useState(null); const [erroBusca, setErroBusca] = useState(null); const [selecionado, setSelecionado] = useState(null); // { tipo, id, name } const [detalhe, setDetalhe] = useState(null); const [carregandoDetalhe, setCarregandoDetalhe] = useState(false); const [erroDetalhe, setErroDetalhe] = useState(null); useEffect(() => { consultaTipos() .then((d) => { setTiposInfo(d); setTiposAtivos((d.tipos || []).map((t) => t.tipo)); }) .catch(() => setTiposInfo(null)); }, []); const tiposDisponiveis = useMemo(() => tiposInfo?.tipos || [], [tiposInfo]); function alternarTipo(tipo) { setTiposAtivos((atuais) => atuais.includes(tipo) ? atuais.filter((t) => t !== tipo) : [...atuais, tipo] ); } async function executarBusca(e) { e?.preventDefault(); const t = termo.trim(); if (t.length < 2) { setErroBusca("Digite ao menos 2 caracteres."); return; } setBuscando(true); setErroBusca(null); setResultados(null); try { const d = await consultaBuscar(t, tiposAtivos, 20); setResultados(d.resultados || {}); } catch (err) { setErroBusca(err.message); notificar?.("Falha na busca: " + err.message, "bad"); } finally { setBuscando(false); } } async function abrirDetalhe(tipo, item) { setSelecionado({ tipo, id: item.id, name: item.name }); setDetalhe(null); setErroDetalhe(null); setCarregandoDetalhe(true); try { const d = await consultaDetalhar(tipo, item.id); setDetalhe(d); } catch (err) { setErroDetalhe(err.message); } finally { setCarregandoDetalhe(false); } } const totalResultados = resultados ? Object.values(resultados).reduce((acc, arr) => acc + arr.length, 0) : 0; return ( <>
Fonte: cache Turso + Salesforce (somente leitura) } />
setTermo(e.target.value)} autoFocus />
{tiposDisponiveis.map((t) => ( alternarTipo(t.tipo)} title={t.objeto} > {ROTULOS_TIPO[t.tipo]?.icone || "•"} {t.rotulo} ))}
{tiposInfo && !tiposInfo.itens_do_contrato_configurados && (
⚙️ Vínculo Contrato → Itens não configurado (defina{" "} SF_CONTRACT_ITEM_PARENT_FIELD no .env). O contrato ainda pode ser consultado, mas sem a lista de itens relacionados.
)} {erroBusca &&
{erroBusca}
}
{buscando &&
Buscando…
} {!buscando && !resultados && (
Digite um termo e clique em Buscar.
)} {!buscando && resultados && totalResultados === 0 && (
Nenhum resultado para "{termo}".
)} {!buscando && resultados && Object.entries(resultados).map(([tipo, itens]) => (
{ROTULOS_TIPO[tipo]?.icone} {tiposInfo?.tipos?.find((t) => t.tipo === tipo)?.rotulo || tipo}
{itens.map((item) => (
abrirDetalhe(tipo, item)} >
{item.name}
{item.subtitle && (
{item.subtitle}
)}
))}
))}
{!selecionado &&
Selecione um resultado para ver os detalhes.
} {selecionado && carregandoDetalhe &&
Carregando detalhe…
} {selecionado && erroDetalhe &&
{erroDetalhe}
} {selecionado && detalhe && ( <>
Origem: {detalhe.origem}
{(detalhe.campos || []).map((c) => ( ))}
{c.campo} {String(c.valor)}
{detalhe.itens_do_contrato != null && (
{detalhe.itens_do_contrato.length === 0 ? (
Nenhum item vinculado.
) : (
{detalhe.itens_do_contrato.map((it, idx) => (
{it.Name || it.Id}
    {Object.entries(it) .filter(([k, v]) => k !== "attributes" && v != null && k !== "Name") .slice(0, 6) .map(([k, v]) => (
  • {k}: {String(v)}
  • ))}
))}
)}
)} )}
); }