import React, { useEffect, useMemo, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { api, downloadBlob } from '../api' import { buildPesquisaLink, buildPesquisaRoutePayload, getPesquisaFilterDefaults, hasPesquisaRoutePayload } from '../deepLinks' import DataTable from './DataTable' import EquationFormatsPanel from './EquationFormatsPanel' import LoadingOverlay from './LoadingOverlay' import MapFrame from './MapFrame' import ModeloObservacaoCard from './ModeloObservacaoCard' import ModeloTrabalhosTecnicosPanel from './ModeloTrabalhosTecnicosPanel' import PlotFigure from './PlotFigure' import PesquisaAdminConfigPanel from './PesquisaAdminConfigPanel' import SectionBlock from './SectionBlock' import ShareLinkButton from './ShareLinkButton' import SinglePillAutocomplete from './SinglePillAutocomplete' import { getFaixaDataRecencyInfo } from '../modelRecency' const EMPTY_FILTERS = { nomeModelo: '', tipoModelo: '', negociacaoModelo: '', dataMin: '', dataMax: '', versionamentoModelos: 'incluir_antigos', avalFinalidade: '', avalZona: '', avalBairro: '', avalArea: '', avalRh: '', } const EMPTY_LOCATION_INPUTS = { latitude: '', longitude: '', logradouro: '', numero: '', cdlog: '', } const CRITERIO_ESPACIAL_OPTIONS = [ { value: 'maior_distancia', label: 'Maior distância' }, { value: 'media_distancia', label: 'Média distância' }, { value: 'menor_distancia', label: 'Menor distância' }, ] const RESULT_INITIAL = { modelos: [], sugestoes: {}, total_filtrado: 0, total_geral: 0, } const PESQUISA_INNER_TABS = [ { key: 'mapa', label: 'Mapa' }, { key: 'trabalhos_tecnicos', label: 'Trabalhos Técnicos' }, { key: 'dados_mercado', label: 'Dados de Mercado' }, { key: 'metricas', label: 'Métricas' }, { key: 'transformacoes', label: 'Transformações' }, { key: 'resumo', label: 'Resumo' }, { key: 'coeficientes', label: 'Coeficientes' }, { key: 'obs_calc', label: 'Obs x Calc' }, { key: 'graficos', label: 'Gráficos' }, ] const MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO = 'selecionados_e_outras_versoes' const TIPO_SIGLAS = { RECOND: 'Residencia em condominio', RCOMD: 'Residencia em condominio', TCOND: 'Terreno em condominio', SALA: 'Salas comerciais', APTO: 'Apartamentos', APART: 'Apartamentos', AP: 'Apartamentos', TERRENO: 'Terrenos', TER: 'Terrenos', EDIF: 'Edificio', RES: 'Residencias isoladas / casas', CASA: 'Residencias isoladas / casas', LOJA: 'Loja', LCOM: 'Loja', DEP: 'Deposito', DEPOS: 'Deposito', CCOM: 'Casa comercial', } const TIPOS_MODELO_GENERICOS = [ 'Apartamentos', 'Casa comercial', 'Deposito', 'Edificio', 'Loja', 'Salas comerciais', 'Terrenos', ] function formatRange(range) { if (!range) return '-' const min = range.min ?? null const max = range.max ?? null if (min === null && max === null) return '-' if (min !== null && max !== null) return `${formatDateBrIfIso(min)} a ${formatDateBrIfIso(max)}` if (min !== null) return `a partir de ${formatDateBrIfIso(min)}` return `ate ${formatDateBrIfIso(max)}` } function formatCount(value) { if (value === null || value === undefined || value === '') return '-' if (typeof value === 'number') { return new Intl.NumberFormat('pt-BR').format(value) } return String(value) } function formatDistanceKm(value) { const number = Number(value) if (!Number.isFinite(number)) return '-' return `${number.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} km` } function formatAvaliandoGeoLabel(index) { return `A${index + 1}` } function parseCoordinateValue(value) { if (value === null || value === undefined) return null const text = String(value).trim().replace(',', '.') if (!text) return null const parsed = Number(text) return Number.isFinite(parsed) ? parsed : null } function normalizeLogradouroOption(item) { if (typeof item === 'string') { const text = String(item || '').trim() return text ? { value: text, label: text } : null } if (!item || typeof item !== 'object') return null const value = String(item.value ?? item.logradouro ?? '').trim() if (!value) return null const label = String(item.label ?? item.display_label ?? value).trim() || value return { value, label, secondary: String(item.secondary ?? '').trim(), } } function buildAvaliandosGeoPayload(entries = []) { return (entries || []).map((item, index) => ({ id: String(item?.id || `avaliando-${index + 1}`), label: formatAvaliandoGeoLabel(index), lat: parseCoordinateValue(item?.lat), lon: parseCoordinateValue(item?.lon), logradouro: item?.logradouro || null, numero_usado: item?.numero_usado || null, cdlog: item?.cdlog ?? null, origem: item?.origem || null, })).filter((item) => item.lat !== null && item.lon !== null) } function getResumoEspacialValor(resumo, criterio) { if (!resumo || typeof resumo !== 'object') return null if (criterio === 'menor_distancia') return Number(resumo.menor_distancia_km) if (criterio === 'media_distancia') return Number(resumo.media_distancia_km) return Number(resumo.maior_distancia_km) } function sortModelosBySpatialCriterion(modelos = [], criterio = 'maior_distancia') { if (!Array.isArray(modelos)) return [] return [...modelos].sort((a, b) => { const aValor = getResumoEspacialValor(a?.distancia_resumo, criterio) const bValor = getResumoEspacialValor(b?.distancia_resumo, criterio) const aSem = !Number.isFinite(aValor) const bSem = !Number.isFinite(bValor) if (aSem !== bSem) return aSem ? 1 : -1 if (!aSem && !bSem && aValor !== bValor) return aValor - bValor const aNome = String(a?.nome_modelo || a?.arquivo || a?.id || '').toLowerCase() const bNome = String(b?.nome_modelo || b?.arquivo || b?.id || '').toLowerCase() return aNome.localeCompare(bNome, 'pt-BR') }) } function formatResumoEspacial(resumo) { if (!resumo || typeof resumo !== 'object') return '-' return `Maior ${String(resumo.maior_distancia_label || '-')} • Média ${String(resumo.media_distancia_label || '-')} • Menor ${String(resumo.menor_distancia_label || '-')}` } function formatResumoEspacialCobertura(resumo) { if (!resumo || typeof resumo !== 'object') return '' const total = Number(resumo.total_avaliandos || 0) const comDistancia = Number(resumo.total_com_distancia || 0) if (!total) return '' return `${comDistancia}/${total} com distância calculada` } function formatDistanciasPorAvaliando(distancias = []) { if (!Array.isArray(distancias) || !distancias.length) return '-' return distancias.map((item, index) => { const label = String(item?.label || formatAvaliandoGeoLabel(index)).trim() || formatAvaliandoGeoLabel(index) const distancia = String(item?.distancia_label || '').trim() || formatDistanceKm(item?.distancia_km) return `${label}: ${distancia}` }).join(' • ') } function formatLocalizacaoOrigemLabel(localizacao) { return localizacao?.origem === 'eixos' ? 'Eixos de logradouro' : 'Coordenadas informadas' } function createLocalizacaoEntry(resolvida, id) { return { ...resolvida, id, lat: parseCoordinateValue(resolvida?.lat), lon: parseCoordinateValue(resolvida?.lon), } } function formatDateBrIfIso(value) { const text = String(value ?? '').trim() if (!text) return '-' const isoMatch = text.match(/^(\d{4})-(\d{2})-(\d{2})$/) if (!isoMatch) return text return `${isoMatch[3]}/${isoMatch[2]}/${isoMatch[1]}` } function normalizeTokenText(value) { return String(value || '') .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toUpperCase() } function normalizeSearchText(value) { return String(value || '') .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .trim() } function splitMultiTerms(value) { const text = String(value || '').trim() const parts = text.includes('||') ? text.split(/\s*\|\|\s*/) : text.split(/[;|]/) const raw = parts .map((item) => String(item || '').trim()) .filter(Boolean) const seen = new Set() const unique = [] raw.forEach((item) => { const key = normalizeSearchText(item) if (!key || seen.has(key)) return seen.add(key) unique.push(item) }) return unique } function joinMultiTerms(values) { return (values || []).map((item) => String(item || '').trim()).filter(Boolean).join(' || ') } function sanitizeCompactList(values) { const seen = new Set() const unique = [] ;(values || []).forEach((item) => { const text = String(item || '').trim() const key = normalizeSearchText(text) if (!text || !key || seen.has(key)) return seen.add(key) unique.push(text) }) return unique } function uppercaseListText(values) { const items = sanitizeCompactList(values) return items.map((item) => item.toLocaleUpperCase('pt-BR')).join(', ') } function inferDependentVariable(modelo) { const equacao = String(modelo?.equacao || '').trim() if (!equacao || !equacao.includes('=')) return '' return equacao.split('=', 1)[0].trim() } function buildVariablesDisplay(modelo) { const dependente = inferDependentVariable(modelo) const independentes = sanitizeCompactList((modelo?.variaveis_resumo || []).map((item) => item?.variavel)) .filter((item) => normalizeSearchText(item) !== normalizeSearchText(dependente)) .filter((item) => normalizeSearchText(item) !== 'const') const partes = [] if (dependente) { partes.push(`DEPENDENTE: ${dependente.toLocaleUpperCase('pt-BR')}`) } if (independentes.length) { partes.push(`INDEPENDENTES: ${independentes.map((item) => item.toLocaleUpperCase('pt-BR')).join(', ')}`) } return partes.join('\n') } function buildVariablesContent(text) { const lines = String(text || '').split('\n').map((item) => item.trim()).filter(Boolean) if (!lines.length) return null return ( <> {lines.map((line, index) => { const [head, ...rest] = line.split(':') const tail = rest.join(':').trim() return ( {head}:{tail ? ` ${tail}` : ''} ) })} ) } function LocalizacaoResumoCard({ title, localizacao = null, actions = null, className = '', }) { if (!localizacao) return null const endereco = String(localizacao?.logradouro || '').trim() const numeroUsado = String(localizacao?.numero_usado || '').trim() const enderecoTexto = endereco ? `${endereco}${numeroUsado ? `, ${numeroUsado}` : ''}` : '' const badges = [ enderecoTexto ? { label: 'Logradouro', value: enderecoTexto } : null, localizacao?.cdlog ? { label: 'CDLOG', value: String(localizacao.cdlog) } : null, { label: 'Lat', value: Number(localizacao.lat).toFixed(6) }, { label: 'Lon', value: Number(localizacao.lon).toFixed(6) }, { label: 'Origem', value: formatLocalizacaoOrigemLabel(localizacao) }, ].filter(Boolean) const classes = ['pesquisa-localizacao-registered', className].filter(Boolean).join(' ') return (
{title}
{badges.map((item) => ( {item.label}: {item.value} ))}
{actions}
) } function CompactHoverList({ label, buttonLabel, previewText, modalText, previewContent = null, modalContent = null, }) { const rootRef = useRef(null) const closePreviewTimeoutRef = useRef(null) const panelRef = useRef(null) const [previewOpen, setPreviewOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false) const [previewStyle, setPreviewStyle] = useState({}) const inlinePreviewText = String(previewText || '').trim() const inlineModalText = String(modalText || '').trim() function clearPreviewCloseTimeout() { if (closePreviewTimeoutRef.current) { window.clearTimeout(closePreviewTimeoutRef.current) closePreviewTimeoutRef.current = null } } function schedulePreviewClose() { clearPreviewCloseTimeout() closePreviewTimeoutRef.current = window.setTimeout(() => { setPreviewOpen(false) }, 80) } function openPreview() { clearPreviewCloseTimeout() setPreviewOpen(true) } function updatePreviewLayout() { const bounds = rootRef.current?.getBoundingClientRect() const panel = panelRef.current if (!bounds || !panel || typeof window === 'undefined') return const viewportWidth = window.innerWidth const viewportHeight = window.innerHeight const gap = 10 const padding = 16 const triggerCenter = bounds.left + (bounds.width / 2) const towardCenterSide = triggerCenter >= (viewportWidth / 2) ? 'left' : 'right' const oppositeSide = towardCenterSide === 'left' ? 'right' : 'left' const downwardSpace = viewportHeight - bounds.bottom - gap - padding const upwardSpace = bounds.top - gap - padding const preferredVertical = downwardSpace >= upwardSpace ? 'down' : 'up' const oppositeVertical = preferredVertical === 'down' ? 'up' : 'down' const desiredWidth = Math.min(panel.scrollWidth || 560, 560) const desiredHeight = Math.min(panel.scrollHeight || 220, 420) function clamp(value, min, max) { if (max < min) return min return Math.min(Math.max(value, min), max) } function makeVerticalCandidate(side, vertical) { const maxWidth = Math.max(160, viewportWidth - (padding * 2)) const width = Math.min(desiredWidth, maxWidth) const maxHeight = Math.max(96, vertical === 'down' ? downwardSpace : upwardSpace) const height = Math.min(desiredHeight, maxHeight) const leftBase = side === 'left' ? bounds.right - width : bounds.left const left = clamp(leftBase, padding, viewportWidth - padding - width) const topBase = vertical === 'down' ? bounds.bottom + gap : bounds.top - gap - height const top = clamp(topBase, padding, viewportHeight - padding - height) return { left, top, maxWidth, maxHeight, score: (Math.min(maxWidth, desiredWidth) / desiredWidth) + (Math.min(maxHeight, desiredHeight) / desiredHeight), } } function makeSideCandidate(side) { const maxWidth = Math.max(180, side === 'right' ? viewportWidth - bounds.right - gap - padding : bounds.left - gap - padding) const width = Math.min(desiredWidth, maxWidth) const maxHeight = Math.max(120, viewportHeight - (padding * 2)) const height = Math.min(desiredHeight, maxHeight) const leftBase = side === 'right' ? bounds.right + gap : bounds.left - gap - width const left = clamp(leftBase, padding, viewportWidth - padding - width) const top = clamp(bounds.top, padding, viewportHeight - padding - height) return { left, top, maxWidth, maxHeight, score: (Math.min(maxWidth, desiredWidth) / desiredWidth) + (Math.min(maxHeight, desiredHeight) / desiredHeight), } } const candidates = [ makeVerticalCandidate(towardCenterSide, preferredVertical), makeVerticalCandidate(towardCenterSide, oppositeVertical), makeSideCandidate(towardCenterSide), makeSideCandidate(oppositeSide), makeVerticalCandidate(oppositeSide, preferredVertical), makeVerticalCandidate(oppositeSide, oppositeVertical), ] const best = candidates.reduce((current, candidate) => { if (!current || candidate.score > current.score) return candidate return current }, null) if (!best) return setPreviewStyle({ left: `${best.left}px`, top: `${best.top}px`, maxWidth: `${Math.max(160, best.maxWidth)}px`, maxHeight: `${Math.max(96, best.maxHeight)}px`, }) } useEffect(() => { if (!previewOpen) return undefined const rafId = window.requestAnimationFrame(() => { updatePreviewLayout() }) function onWindowResize() { updatePreviewLayout() } window.addEventListener('resize', onWindowResize) return () => { window.cancelAnimationFrame(rafId) window.removeEventListener('resize', onWindowResize) } }, [previewOpen]) useEffect(() => () => { clearPreviewCloseTimeout() }, []) useEffect(() => { if (!modalOpen) return undefined function onDocumentKeyDown(event) { if (event.key === 'Escape') { setModalOpen(false) } } document.addEventListener('keydown', onDocumentKeyDown) return () => { document.removeEventListener('keydown', onDocumentKeyDown) } }, [modalOpen]) if (!inlineModalText) { return ( ) } return ( <> openPreview()} onMouseLeave={() => schedulePreviewClose()} onFocus={() => openPreview()} onBlur={(event) => { if (!rootRef.current?.contains(event.relatedTarget)) { setPreviewOpen(false) } }} > {previewOpen && typeof document !== 'undefined' ? createPortal( openPreview()} onMouseLeave={() => schedulePreviewClose()} > {previewContent || inlinePreviewText || inlineModalText} , document.body, ) : null} {modalOpen && typeof document !== 'undefined' ? createPortal(
{ setModalOpen(false) }} >
event.stopPropagation()} >

{label}

{modalContent || inlineModalText}
, document.body, ) : null} ) } function inferTipoPorNomeModelo(...nomes) { const tokens = Object.keys(TIPO_SIGLAS).sort((a, b) => b.length - a.length) for (const nome of nomes) { const source = normalizeTokenText(nome) if (!source) continue for (const token of tokens) { const re = new RegExp(`(^|[^A-Z])${token}([^A-Z]|$)`) if (re.test(source)) { return TIPO_SIGLAS[token] } } } return '' } function formatTipoImovel(modelo) { const tipoPorNome = inferTipoPorNomeModelo(modelo?.nome_modelo, modelo?.arquivo) if (tipoPorNome) return tipoPorNome const text = String(modelo?.tipo_imovel || '').trim() if (!text) return '-' const mapped = TIPO_SIGLAS[normalizeTokenText(text)] return mapped || text } function buildApiFilters(filters, avaliandosGeolocalizados = []) { const payload = { otica: 'avaliando', nome: filters.nomeModelo, tipo_modelo: filters.tipoModelo, negociacao_modelo: filters.negociacaoModelo, aval_finalidade: filters.avalFinalidade, aval_zona: filters.avalZona, aval_bairro: filters.avalBairro, data_min: filters.dataMin, data_max: filters.dataMax, somente_versoes_atuais: filters.versionamentoModelos !== 'incluir_antigos', aval_area: filters.avalArea, aval_rh: filters.avalRh, } const avaliandosPayload = buildAvaliandosGeoPayload(avaliandosGeolocalizados) if (avaliandosPayload.length > 1) { payload.avaliandos_geo_json = JSON.stringify(avaliandosPayload) } else if (avaliandosPayload.length === 1) { payload.aval_lat = avaliandosPayload[0].lat payload.aval_lon = avaliandosPayload[0].lon } return payload } function toInputName(field) { let hash = 0 for (let i = 0; i < field.length; i += 1) { hash = (hash * 31 + field.charCodeAt(i)) % 1000000007 } return `mesa_${Math.abs(hash)}` } function buildInputAutofillProps(field) { return { name: toInputName(field), autoComplete: 'new-password', autoCorrect: 'off', autoCapitalize: 'none', spellCheck: false, 'data-lpignore': 'true', 'data-1p-ignore': 'true', } } function buildSelectAutofillProps(field) { return { name: toInputName(field), autoComplete: 'new-password', 'data-lpignore': 'true', 'data-1p-ignore': 'true', } } function TextFieldInput({ field, ...props }) { return ( ) } function NumberFieldInput({ field, ...props }) { return ( ) } function DateFieldInput({ field, ...props }) { return ( ) } function ChipAutocompleteInput({ field, value, onChange, placeholder, suggestions = [], panelTitle = 'Sugestoes', loading = false, emptyMessage = 'Nenhuma sugestao encontrada.', loadingMessage = 'Carregando sugestoes...', }) { const rootRef = useRef(null) const [query, setQuery] = useState('') const [open, setOpen] = useState(false) const [activeIndex, setActiveIndex] = useState(-1) const selectedValues = useMemo(() => splitMultiTerms(value), [value]) const selectedKeys = useMemo(() => new Set(selectedValues.map((item) => normalizeSearchText(item))), [selectedValues]) const queryNormalized = normalizeSearchText(query) useEffect(() => { if (!value) setQuery('') }, [value]) const filteredSuggestions = useMemo(() => { const unique = [] const seen = new Set() ;(suggestions || []).forEach((item) => { const text = String(item || '').trim() if (!text) return const key = normalizeSearchText(text) if (!key || seen.has(key) || selectedKeys.has(key)) return seen.add(key) unique.push(text) }) if (!queryNormalized) return unique.slice(0, 120) return unique .filter((item) => normalizeSearchText(item).includes(queryNormalized)) .slice(0, 120) }, [suggestions, queryNormalized, selectedKeys]) useEffect(() => { if (!open) return undefined function onDocumentMouseDown(event) { if (!rootRef.current) return if (!rootRef.current.contains(event.target)) { setOpen(false) } } document.addEventListener('mousedown', onDocumentMouseDown) return () => document.removeEventListener('mousedown', onDocumentMouseDown) }, [open]) useEffect(() => { if (!open || !filteredSuggestions.length) { setActiveIndex(-1) return } setActiveIndex(-1) }, [filteredSuggestions, open]) function emitValue(nextValue) { onChange({ target: { value: nextValue, dataset: { field }, name: toInputName(field), }, }) } function setSelectedValues(nextSelected) { emitValue(joinMultiTerms(nextSelected)) } function addValue(nextValue) { const text = String(nextValue || '').trim() if (!text) return const key = normalizeSearchText(text) if (!key || selectedKeys.has(key)) { setQuery('') return } setSelectedValues([...selectedValues, text]) setQuery('') setOpen(true) setActiveIndex(-1) } function removeValue(nextValue) { const target = String(nextValue || '').trim() if (!target) return const exactIndex = selectedValues.findIndex((item) => String(item || '').trim() === target) if (exactIndex >= 0) { const nextSelected = [...selectedValues] nextSelected.splice(exactIndex, 1) setSelectedValues(nextSelected) return } const key = normalizeSearchText(target) const normalizedIndex = selectedValues.findIndex((item) => normalizeSearchText(item) === key) if (normalizedIndex < 0) return const nextSelected = [...selectedValues] nextSelected.splice(normalizedIndex, 1) setSelectedValues(nextSelected) } function onInputChange(event) { setQuery(event.target.value) setOpen(true) setActiveIndex(-1) } function onInputKeyDown(event) { if (event.key === 'Escape') { setOpen(false) return } if (!filteredSuggestions.length) return if (event.key === 'ArrowDown') { event.preventDefault() setOpen(true) setActiveIndex((prev) => (prev < 0 ? 0 : (prev + 1) % filteredSuggestions.length)) return } if (event.key === 'ArrowUp') { event.preventDefault() setOpen(true) setActiveIndex((prev) => { if (prev < 0) return filteredSuggestions.length - 1 return (prev - 1 + filteredSuggestions.length) % filteredSuggestions.length }) return } if (event.key === 'Enter' && open && activeIndex >= 0) { event.preventDefault() addValue(filteredSuggestions[activeIndex]) return } if (event.key === 'Enter') { const next = String(query || '').trim() if (next) { event.preventDefault() addValue(next) } return } if (event.key === ',' || event.key === ';' || event.key === '|') { const next = String(query || '').trim() if (next) { event.preventDefault() addValue(next) } return } if (event.key === 'Backspace' && !query && selectedValues.length) { const nextSelected = selectedValues.slice(0, -1) setSelectedValues(nextSelected) } } return (
{selectedValues.length ? (
{selectedValues.map((item) => ( {item} ))}
) : null} { setOpen(true) setActiveIndex(-1) }} onKeyDown={onInputKeyDown} placeholder={placeholder} /> {open ? (
{panelTitle}
{filteredSuggestions.length ? (
{filteredSuggestions.map((item, idx) => ( ))}
) : loading ? (
{loadingMessage}
) : (
{emptyMessage}
)}
) : null}
) } export default function PesquisaTab({ sessionId, onUsarModeloEmAvaliacao = null, onAbrirModeloNoRepositorio = null, routeRequest = null, scrollToMapaRequest = null, onRouteChange = null, }) { const [searchLoading, setSearchLoading] = useState(false) const [contextLoading, setContextLoading] = useState(false) const [error, setError] = useState('') const [pesquisaInicializada, setPesquisaInicializada] = useState(false) const [sugestoesInicializadas, setSugestoesInicializadas] = useState(false) const [mostrarAdminConfig, setMostrarAdminConfig] = useState(false) const [logradouroOptions, setLogradouroOptions] = useState([]) const [logradouroOptionsLoading, setLogradouroOptionsLoading] = useState(false) const [logradouroOptionsLoaded, setLogradouroOptionsLoaded] = useState(false) const [filters, setFilters] = useState(EMPTY_FILTERS) const [result, setResult] = useState(RESULT_INITIAL) const [localizacaoModo, setLocalizacaoModo] = useState('endereco') const [localizacaoInputs, setLocalizacaoInputs] = useState(EMPTY_LOCATION_INPUTS) const [avaliandosGeolocalizados, setAvaliandosGeolocalizados] = useState([]) const [localizacaoLoading, setLocalizacaoLoading] = useState(false) const [localizacaoError, setLocalizacaoError] = useState('') const [localizacaoStatus, setLocalizacaoStatus] = useState('') const [criterioEspacial, setCriterioEspacial] = useState('maior_distancia') const [selectedIds, setSelectedIds] = useState([]) const selectAllRef = useRef(null) const localizacaoIdCounterRef = useRef(1) const [mapaLoading, setMapaLoading] = useState(false) const [mapaError, setMapaError] = useState('') const [mapaStatus, setMapaStatus] = useState('') const [mapaHtmls, setMapaHtmls] = useState({ pontos: '', cobertura: '' }) const [mapaPayloads, setMapaPayloads] = useState({ pontos: null, cobertura: null }) const [mapaModoExibicao, setMapaModoExibicao] = useState('pontos') const [mapaTrabalhosTecnicosModelosModo, setMapaTrabalhosTecnicosModelosModo] = useState('selecionados_e_outras_versoes') const [mapaTrabalhosTecnicosProximidadeModo, setMapaTrabalhosTecnicosProximidadeModo] = useState('sem_proximidade') const [mapaTrabalhosTecnicosRaio, setMapaTrabalhosTecnicosRaio] = useState(1000) const mapaTrabalhosTecnicosConfigRef = useRef('') const [modeloAbertoMeta, setModeloAbertoMeta] = useState(null) const [modeloAbertoLoading, setModeloAbertoLoading] = useState(false) const [modeloAbertoError, setModeloAbertoError] = useState('') const [modeloAbertoActiveTab, setModeloAbertoActiveTab] = useState('mapa') const [modeloAbertoDados, setModeloAbertoDados] = useState(null) const [modeloAbertoEstatisticas, setModeloAbertoEstatisticas] = useState(null) const [modeloAbertoEscalasHtml, setModeloAbertoEscalasHtml] = useState('') const [modeloAbertoDadosTransformados, setModeloAbertoDadosTransformados] = useState(null) const [modeloAbertoResumoHtml, setModeloAbertoResumoHtml] = useState('') const [modeloAbertoEquacoes, setModeloAbertoEquacoes] = useState(null) const [modeloAbertoCoeficientes, setModeloAbertoCoeficientes] = useState(null) const [modeloAbertoObsCalc, setModeloAbertoObsCalc] = useState(null) const [modeloAbertoMapaHtml, setModeloAbertoMapaHtml] = useState('') const [modeloAbertoMapaPayload, setModeloAbertoMapaPayload] = useState(null) const [modeloAbertoMapaChoices, setModeloAbertoMapaChoices] = useState(['Visualização Padrão']) const [modeloAbertoMapaVar, setModeloAbertoMapaVar] = useState('Visualização Padrão') const [modeloAbertoTrabalhosTecnicosModelosModo, setModeloAbertoTrabalhosTecnicosModelosModo] = useState(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO) const [modeloAbertoTrabalhosTecnicos, setModeloAbertoTrabalhosTecnicos] = useState([]) const [modeloAbertoPlotObsCalc, setModeloAbertoPlotObsCalc] = useState(null) const [modeloAbertoPlotResiduos, setModeloAbertoPlotResiduos] = useState(null) const [modeloAbertoPlotHistograma, setModeloAbertoPlotHistograma] = useState(null) const [modeloAbertoPlotCook, setModeloAbertoPlotCook] = useState(null) const [modeloAbertoPlotCorr, setModeloAbertoPlotCorr] = useState(null) const [modeloAbertoLoadedTabs, setModeloAbertoLoadedTabs] = useState({}) const [modeloAbertoLoadingTabs, setModeloAbertoLoadingTabs] = useState({}) const sectionResultadosRef = useRef(null) const sectionMapaRef = useRef(null) const scrollMapaHandledRef = useRef('') const routeRequestHandledRef = useRef('') const resultRequestSeqRef = useRef(0) const searchRequestSeqRef = useRef(0) const contextRequestSeqRef = useRef(0) const modeloAbertoPendingRequestsRef = useRef({}) const modeloAbertoOpenVersionRef = useRef(0) const sugestoes = result.sugestoes || {} const opcoesTipoModelo = useMemo( () => { const atual = String(filters.tipoModelo || '').trim() if (!atual || TIPOS_MODELO_GENERICOS.includes(atual)) return TIPOS_MODELO_GENERICOS return [...TIPOS_MODELO_GENERICOS, atual] }, [filters.tipoModelo], ) const modoModeloAberto = Boolean(modeloAbertoMeta) const avaliandosGeoPayload = useMemo( () => buildAvaliandosGeoPayload(avaliandosGeolocalizados), [avaliandosGeolocalizados], ) const localizacaoAtiva = avaliandosGeoPayload.length > 0 const localizacaoMultipla = avaliandosGeoPayload.length > 1 const avaliandoUnicoAtivo = avaliandosGeoPayload.length === 1 const modelosOrdenados = useMemo( () => (localizacaoMultipla ? sortModelosBySpatialCriterion(result.modelos || [], criterioEspacial) : (result.modelos || [])), [criterioEspacial, localizacaoMultipla, result.modelos], ) const resultIds = useMemo(() => modelosOrdenados.map((modelo) => modelo.id), [modelosOrdenados]) const todosSelecionados = resultIds.length > 0 && resultIds.every((id) => selectedIds.includes(id)) const algunsSelecionados = resultIds.some((id) => selectedIds.includes(id)) const mapaHtmlAtual = mapaHtmls[mapaModoExibicao] || '' const mapaPayloadAtual = mapaPayloads[mapaModoExibicao] || null const mapaFoiGerado = Boolean(mapaHtmls.pontos || mapaHtmls.cobertura || mapaPayloads.pontos || mapaPayloads.cobertura) const pesquisaShareAvaliando = !localizacaoMultipla ? (avaliandosGeoPayload[0] || null) : null const pesquisaShareHref = buildPesquisaLink(filters, pesquisaShareAvaliando) const pesquisaShareDisabled = localizacaoMultipla const isPesquisaBusy = searchLoading function buildPesquisaReturnIntent() { return { ...buildPesquisaRoutePayload(filters, pesquisaShareAvaliando), pesquisaExecutada: true, avaliandos: avaliandosGeolocalizados.map((item, index) => ({ id: String(item?.id || `avaliando-${index + 1}`), lat: parseCoordinateValue(item?.lat), lon: parseCoordinateValue(item?.lon), logradouro: String(item?.logradouro || ''), numero_usado: String(item?.numero_usado || ''), cdlog: item?.cdlog ?? null, origem: String(item?.origem || 'coords'), })).filter((item) => item.lat !== null && item.lon !== null), } } function emitPesquisaRoute(nextFilters = filters, nextAvaliandos = avaliandosGeolocalizados) { if (typeof onRouteChange !== 'function') return const avaliandosPayload = buildAvaliandosGeoPayload(nextAvaliandos) const avaliando = avaliandosPayload.length === 1 ? avaliandosPayload[0] : null onRouteChange(buildPesquisaRoutePayload(nextFilters, avaliando)) } function scrollToElementTop(el, behavior = 'smooth', offsetPx = 0) { if (!el || typeof window === 'undefined') return const rect = el.getBoundingClientRect() const targetTop = Math.max(0, window.scrollY + rect.top - offsetPx) window.scrollTo({ top: targetTop, behavior }) } function scrollParaTopoDaPagina() { if (typeof window === 'undefined') return window.scrollTo({ top: 0, behavior: 'smooth' }) } function scrollParaResultadosNoTopo() { window.requestAnimationFrame(() => { window.requestAnimationFrame(() => { scrollToElementTop(sectionResultadosRef.current, 'smooth', 0) }) }) } function resetMapaPesquisa() { setMapaHtmls({ pontos: '', cobertura: '' }) setMapaPayloads({ pontos: null, cobertura: null }) setMapaStatus('') setMapaError('') setMapaModoExibicao('pontos') mapaTrabalhosTecnicosConfigRef.current = '' } function getMapaTrabalhosTecnicosRequestConfig(overrides = {}) { const modelosModoBruto = overrides.trabalhosTecnicosModelosModo ?? mapaTrabalhosTecnicosModelosModo const modelosModo = String(modelosModoBruto || 'selecionados') === 'selecionados_e_anteriores' ? 'selecionados_e_outras_versoes' : String(modelosModoBruto || 'selecionados') const proximidadeModoBruto = overrides.trabalhosTecnicosProximidadeModo ?? mapaTrabalhosTecnicosProximidadeModo const proximidadeModo = localizacaoAtiva ? String(proximidadeModoBruto || 'sem_proximidade') : 'sem_proximidade' const raioNumero = Number(overrides.trabalhosTecnicosRaio ?? mapaTrabalhosTecnicosRaio) const raio = Number.isFinite(raioNumero) ? Math.max(0, Math.min(5000, Math.round(raioNumero))) : 1000 return { modelosModo, proximidadeModo, raio } } function buildMapaTrabalhosTecnicosConfigKey(config) { return JSON.stringify({ totalAvaliandos: avaliandosGeoPayload.length, modelosModo: config.modelosModo, proximidadeModo: config.proximidadeModo, criterioEspacial: localizacaoMultipla ? criterioEspacial : null, raio: config.proximidadeModo === 'proximos_ao_avaliando' ? config.raio : null, }) } async function carregarMapaPesquisa(ids, overrides = {}) { const idsValidos = (ids || []).map((item) => String(item || '').trim()).filter(Boolean) const modoExibicaoSolicitado = String(overrides.modoExibicao || mapaModoExibicao || 'pontos') const trabalhosTecnicosConfig = getMapaTrabalhosTecnicosRequestConfig(overrides) if (!idsValidos.length) { setMapaHtmls({ pontos: '', cobertura: '' }) setMapaPayloads({ pontos: null, cobertura: null }) setMapaStatus('Nenhum modelo marcado para exibição no mapa.') setMapaError('') mapaTrabalhosTecnicosConfigRef.current = '' return } setMapaLoading(true) setMapaError('') try { const response = await api.pesquisarMapaModelos( idsValidos, localizacaoMultipla ? avaliandosGeoPayload : (avaliandosGeoPayload[0] || null), modoExibicaoSolicitado, criterioEspacial, trabalhosTecnicosConfig.modelosModo, trabalhosTecnicosConfig.proximidadeModo, trabalhosTecnicosConfig.raio, ) const mapaHtmlSolicitado = String( response.mapa_html || (modoExibicaoSolicitado === 'cobertura' ? response.mapa_html_cobertura : response.mapa_html_pontos) || '', ) const mapaPayloadSolicitado = response.mapa_payload || (modoExibicaoSolicitado === 'cobertura' ? response.mapa_payload_cobertura : response.mapa_payload_pontos) || null setMapaHtmls({ pontos: modoExibicaoSolicitado === 'pontos' ? mapaHtmlSolicitado : '', cobertura: modoExibicaoSolicitado === 'cobertura' ? mapaHtmlSolicitado : '', }) setMapaPayloads({ pontos: modoExibicaoSolicitado === 'pontos' ? mapaPayloadSolicitado : null, cobertura: modoExibicaoSolicitado === 'cobertura' ? mapaPayloadSolicitado : null, }) setMapaStatus(response.status || '') mapaTrabalhosTecnicosConfigRef.current = buildMapaTrabalhosTecnicosConfigKey(trabalhosTecnicosConfig) } catch (err) { setMapaError(err.message) } finally { setMapaLoading(false) } } async function buscarModelos(nextFilters = filters, nextAvaliandos = avaliandosGeolocalizados, options = {}) { const requestId = resultRequestSeqRef.current + 1 const searchRequestId = searchRequestSeqRef.current + 1 resultRequestSeqRef.current = requestId searchRequestSeqRef.current = searchRequestId setSearchLoading(true) setError('') try { const response = await api.pesquisarModelos(buildApiFilters(nextFilters, nextAvaliandos)) if (requestId !== resultRequestSeqRef.current) return const modelos = response.modelos || [] const idsNovos = new Set(modelos.map((item) => item.id)) setResult({ ...RESULT_INITIAL, ...response, modelos, sugestoes: response.sugestoes || {}, }) setSelectedIds((current) => current.filter((id) => idsNovos.has(id))) resetMapaPesquisa() setPesquisaInicializada(true) setSugestoesInicializadas(true) if (options.syncRoute !== false) { emitPesquisaRoute(nextFilters, nextAvaliandos) } } catch (err) { if (requestId !== resultRequestSeqRef.current) return setError(err.message) } finally { if (searchRequestId === searchRequestSeqRef.current) { setSearchLoading(false) } } } async function carregarContextoInicial(options = {}) { const requestId = resultRequestSeqRef.current + 1 const contextRequestId = contextRequestSeqRef.current + 1 resultRequestSeqRef.current = requestId contextRequestSeqRef.current = contextRequestId setContextLoading(true) setError('') try { const response = await api.pesquisarModelos({ somente_contexto: true }) if (requestId !== resultRequestSeqRef.current) return setResult({ ...RESULT_INITIAL, ...response, modelos: [], sugestoes: response.sugestoes || {}, }) setSelectedIds([]) resetMapaPesquisa() setPesquisaInicializada(false) setSugestoesInicializadas(true) if (options.syncRoute) { emitPesquisaRoute(options.filters || getPesquisaFilterDefaults(), options.avaliandos || avaliandosGeolocalizados) } } catch (err) { if (requestId !== resultRequestSeqRef.current) return setError(err.message) setSugestoesInicializadas(true) } finally { if (contextRequestId === contextRequestSeqRef.current) { setContextLoading(false) } } } async function carregarSugestoesLogradouro() { if (logradouroOptionsLoading || logradouroOptionsLoaded) return setLogradouroOptionsLoading(true) try { const response = await api.pesquisarLogradourosEixos() const opcoes = Array.isArray(response?.logradouros_eixos) ? response.logradouros_eixos.map(normalizeLogradouroOption).filter(Boolean) : [] setLogradouroOptions(opcoes) setLogradouroOptionsLoaded(true) } catch (_err) { setLogradouroOptions([]) } finally { setLogradouroOptionsLoading(false) } } useEffect(() => { const requestKey = String(routeRequest?.requestKey || '').trim() if (requestKey) return undefined void carregarContextoInicial() return undefined }, [routeRequest]) useEffect(() => { const requestKey = String(routeRequest?.requestKey || '').trim() if (!requestKey) return if (routeRequestHandledRef.current === requestKey) return routeRequestHandledRef.current = requestKey const nextFilters = { ...getPesquisaFilterDefaults(), ...(routeRequest?.filters || {}), } const rawAvaliandos = Array.isArray(routeRequest?.avaliandos) ? routeRequest.avaliandos : [] let nextEntries = rawAvaliandos .map((item, index) => { const lat = parseCoordinateValue(item?.lat) const lon = parseCoordinateValue(item?.lon) if (lat === null || lon === null) return null return createLocalizacaoEntry({ lat, lon, origem: String(item?.origem || 'coords'), logradouro: String(item?.logradouro || ''), numero_usado: String(item?.numero_usado || ''), cdlog: item?.cdlog ?? null, }, String(item?.id || `avaliando-${localizacaoIdCounterRef.current + index}`)) }) .filter(Boolean) const lat = parseCoordinateValue(routeRequest?.avaliando?.lat) const lon = parseCoordinateValue(routeRequest?.avaliando?.lon) const possuiAvaliando = lat !== null && lon !== null if (!nextEntries.length && possuiAvaliando) { nextEntries = [createLocalizacaoEntry({ lat, lon, origem: 'coords', logradouro: '', numero_usado: '', cdlog: null, }, `avaliando-${localizacaoIdCounterRef.current}`)] localizacaoIdCounterRef.current += 1 } else if (nextEntries.length) { localizacaoIdCounterRef.current += nextEntries.length } setFilters(nextFilters) setAvaliandosGeolocalizados(nextEntries) setLocalizacaoModo(possuiAvaliando ? 'coords' : 'endereco') setLocalizacaoInputs(EMPTY_LOCATION_INPUTS) setLocalizacaoError('') setLocalizacaoStatus('') resetMapaPesquisa() if (routeRequest?.pesquisaExecutada || hasPesquisaRoutePayload(nextFilters, routeRequest?.avaliando) || nextEntries.length > 1) { void buscarModelos(nextFilters, nextEntries, { syncRoute: false }) return } void carregarContextoInicial({ syncRoute: false, filters: nextFilters, avaliandos: nextEntries }) }, [routeRequest]) useEffect(() => { if (!selectAllRef.current) return selectAllRef.current.indeterminate = algunsSelecionados && !todosSelecionados }, [algunsSelecionados, todosSelecionados]) useEffect(() => { const requestKey = String(scrollToMapaRequest?.requestKey || '').trim() if (!requestKey) return if (scrollMapaHandledRef.current === requestKey) return scrollMapaHandledRef.current = requestKey window.requestAnimationFrame(() => { window.requestAnimationFrame(() => { scrollToElementTop(sectionMapaRef.current, 'smooth', 0) }) }) }, [scrollToMapaRequest]) useEffect(() => { if (!mapaFoiGerado || !localizacaoAtiva || mapaLoading) return undefined const trabalhosTecnicosConfig = getMapaTrabalhosTecnicosRequestConfig() const nextKey = buildMapaTrabalhosTecnicosConfigKey(trabalhosTecnicosConfig) if (nextKey === mapaTrabalhosTecnicosConfigRef.current) return undefined const timeoutId = window.setTimeout(() => { void carregarMapaPesquisa(selectedIds) }, 220) return () => { window.clearTimeout(timeoutId) } }, [ localizacaoAtiva, localizacaoMultipla, mapaFoiGerado, mapaLoading, mapaTrabalhosTecnicosModelosModo, mapaTrabalhosTecnicosProximidadeModo, mapaTrabalhosTecnicosRaio, avaliandosGeoPayload.length, criterioEspacial, ]) function onFieldChange(event) { const { value, checked, type, dataset, name } = event.target const field = dataset.field || name if (!field) return setFilters((prev) => ({ ...prev, [field]: type === 'checkbox' ? checked : value })) } function onLocalizacaoFieldChange(event) { const { value, dataset, name } = event.target const field = dataset.field || name if (!field) return atualizarCampoLocalizacao(field, value) } function atualizarCampoLocalizacao(field, value) { if (!field) return setLocalizacaoInputs((prev) => ({ ...prev, [field]: value })) setLocalizacaoError('') } async function onLimparFiltros() { const nextFilters = getPesquisaFilterDefaults() setFilters(nextFilters) await carregarContextoInicial({ syncRoute: true, filters: nextFilters, avaliandos: avaliandosGeolocalizados, }) } function onToggleSelecionado(modelId) { setSelectedIds((current) => { if (current.includes(modelId)) { return current.filter((id) => id !== modelId) } return [...current, modelId] }) } function onToggleSelecionarTodos() { setSelectedIds((current) => { if (todosSelecionados) { const idsAtuais = new Set(resultIds) return current.filter((id) => !idsAtuais.has(id)) } const next = new Set(current) resultIds.forEach((id) => next.add(id)) return Array.from(next) }) } function onUsarEmAvaliacao(modelo) { if (!sessionId) { setError('Sessao indisponivel no momento. Aguarde e tente novamente.') return } if (typeof onUsarModeloEmAvaliacao === 'function') { onUsarModeloEmAvaliacao(modelo) } } function limparConteudoModeloAberto() { modeloAbertoPendingRequestsRef.current = {} setModeloAbertoDados(null) setModeloAbertoEstatisticas(null) setModeloAbertoEscalasHtml('') setModeloAbertoDadosTransformados(null) setModeloAbertoResumoHtml('') setModeloAbertoEquacoes(null) setModeloAbertoCoeficientes(null) setModeloAbertoObsCalc(null) setModeloAbertoMapaHtml('') setModeloAbertoMapaPayload(null) setModeloAbertoMapaChoices(['Visualização Padrão']) setModeloAbertoMapaVar('Visualização Padrão') setModeloAbertoTrabalhosTecnicosModelosModo(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO) setModeloAbertoTrabalhosTecnicos([]) setModeloAbertoPlotObsCalc(null) setModeloAbertoPlotResiduos(null) setModeloAbertoPlotHistograma(null) setModeloAbertoPlotCook(null) setModeloAbertoPlotCorr(null) setModeloAbertoLoadedTabs({}) setModeloAbertoLoadingTabs({}) } function aplicarSecaoModeloAberto(secao, resp) { const key = String(secao || '').trim() if (key === 'dados_mercado') { setModeloAbertoDados(resp?.dados || null) return } if (key === 'metricas') { setModeloAbertoEstatisticas(resp?.estatisticas || null) return } if (key === 'transformacoes') { setModeloAbertoEscalasHtml(resp?.escalas_html || '') setModeloAbertoDadosTransformados(resp?.dados_transformados || null) return } if (key === 'resumo') { setModeloAbertoResumoHtml(resp?.resumo_html || '') setModeloAbertoEquacoes(resp?.equacoes || null) return } if (key === 'coeficientes') { setModeloAbertoCoeficientes(resp?.coeficientes || null) return } if (key === 'obs_calc') { setModeloAbertoObsCalc(resp?.obs_calc || null) return } if (key === 'graficos') { setModeloAbertoPlotObsCalc(resp?.grafico_obs_calc || null) setModeloAbertoPlotResiduos(resp?.grafico_residuos || null) setModeloAbertoPlotHistograma(resp?.grafico_histograma || null) setModeloAbertoPlotCook(resp?.grafico_cook || null) setModeloAbertoPlotCorr(resp?.grafico_correlacao || null) return } if (key === 'trabalhos_tecnicos') { setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : []) setModeloAbertoTrabalhosTecnicosModelosModo(resp?.trabalhos_tecnicos_modelos_modo || MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO) return } if (key === 'mapa') { const nextChoices = Array.isArray(resp?.mapa_choices) && resp.mapa_choices.length ? resp.mapa_choices : ['Visualização Padrão'] setModeloAbertoMapaHtml(resp?.mapa_html || '') setModeloAbertoMapaPayload(resp?.mapa_payload || null) setModeloAbertoMapaChoices(nextChoices) setModeloAbertoMapaVar((current) => (nextChoices.includes(current) ? current : 'Visualização Padrão')) setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : []) setModeloAbertoTrabalhosTecnicosModelosModo(resp?.trabalhos_tecnicos_modelos_modo || MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO) } } async function garantirSecaoModeloAberto(secao, options = {}) { const secaoNormalizada = String(secao || '').trim() if (!sessionId || !secaoNormalizada) return if (!options.force && modeloAbertoLoadedTabs[secaoNormalizada]) return if (modeloAbertoPendingRequestsRef.current[secaoNormalizada]) { await modeloAbertoPendingRequestsRef.current[secaoNormalizada] return } const expectedVersion = options.expectedVersion ?? modeloAbertoOpenVersionRef.current const trabalhosTecnicosModo = options.trabalhosTecnicosModelosModo || modeloAbertoTrabalhosTecnicosModelosModo setModeloAbertoLoadingTabs((prev) => ({ ...prev, [secaoNormalizada]: true })) const request = (async () => { try { const resp = await api.visualizacaoSection(sessionId, secaoNormalizada, trabalhosTecnicosModo) if (modeloAbertoOpenVersionRef.current !== expectedVersion) return aplicarSecaoModeloAberto(secaoNormalizada, resp) setModeloAbertoLoadedTabs((prev) => ({ ...prev, [secaoNormalizada]: true, ...(secaoNormalizada === 'mapa' ? { trabalhos_tecnicos: true } : {}), })) } catch (err) { if (modeloAbertoOpenVersionRef.current !== expectedVersion) return setModeloAbertoError(err.message || 'Falha ao abrir modelo.') } finally { if (modeloAbertoOpenVersionRef.current !== expectedVersion) return setModeloAbertoLoadingTabs((prev) => ({ ...prev, [secaoNormalizada]: false })) } })() modeloAbertoPendingRequestsRef.current[secaoNormalizada] = request try { await request } finally { if (modeloAbertoPendingRequestsRef.current[secaoNormalizada] === request) { delete modeloAbertoPendingRequestsRef.current[secaoNormalizada] } } } async function onAbrirModelo(modelo) { if (typeof onAbrirModeloNoRepositorio === 'function') { onAbrirModeloNoRepositorio({ ...modelo, returnIntent: buildPesquisaReturnIntent(), }) return } if (!sessionId) { setError('Sessao indisponivel no momento. Aguarde e tente novamente.') return } modeloAbertoOpenVersionRef.current += 1 const openVersion = modeloAbertoOpenVersionRef.current limparConteudoModeloAberto() setModeloAbertoLoading(true) setModeloAbertoError('') setModeloAbertoMeta({ id: modelo.id, nome: modelo.nome_modelo || modelo.arquivo || modelo.id, observacao: '', }) try { const resp = await api.visualizacaoRepositorioCarregar(sessionId, modelo.id) if (modeloAbertoOpenVersionRef.current !== openVersion) return setModeloAbertoActiveTab('mapa') setModeloAbertoMeta({ id: modelo.id, nome: modelo.nome_modelo || modelo.arquivo || modelo.id, observacao: String(resp?.observacao_modelo || '').trim(), }) await garantirSecaoModeloAberto('mapa', { force: true, expectedVersion: openVersion, trabalhosTecnicosModelosModo: MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO, }) window.requestAnimationFrame(() => { scrollParaTopoDaPagina() }) } catch (err) { setModeloAbertoError(err.message || 'Falha ao abrir modelo.') } finally { setModeloAbertoLoading(false) } } function onVoltarPesquisa() { modeloAbertoOpenVersionRef.current += 1 setModeloAbertoMeta(null) setModeloAbertoError('') setModeloAbertoActiveTab('mapa') setModeloAbertoLoading(false) limparConteudoModeloAberto() scrollParaResultadosNoTopo() } async function atualizarMapaModeloAberto( nextVar = modeloAbertoMapaVar, nextTrabalhosTecnicosModo = modeloAbertoTrabalhosTecnicosModelosModo, ) { if (!sessionId) return setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: true })) try { const resp = await api.updateVisualizacaoMap(sessionId, nextVar, nextTrabalhosTecnicosModo) setModeloAbertoMapaHtml(resp?.mapa_html || '') setModeloAbertoMapaPayload(resp?.mapa_payload || null) setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : []) setModeloAbertoTrabalhosTecnicosModelosModo(resp?.trabalhos_tecnicos_modelos_modo || nextTrabalhosTecnicosModo) setModeloAbertoLoadedTabs((prev) => ({ ...prev, mapa: true, trabalhos_tecnicos: true })) } finally { setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: false })) } } async function onModeloAbertoMapChange(nextVar) { setModeloAbertoMapaVar(nextVar) try { await atualizarMapaModeloAberto(nextVar, modeloAbertoTrabalhosTecnicosModelosModo) } catch (err) { setModeloAbertoError(err.message || 'Falha ao atualizar mapa do modelo.') } } async function onModeloAbertoTrabalhosTecnicosModeChange(nextMode) { setModeloAbertoTrabalhosTecnicosModelosModo(nextMode) try { await atualizarMapaModeloAberto(modeloAbertoMapaVar, nextMode) } catch (err) { setModeloAbertoError(err.message || 'Falha ao atualizar mapa do modelo.') } } async function onDownloadEquacaoModeloAberto(mode) { if (!sessionId || !mode) return setModeloAbertoLoading(true) try { const blob = await api.exportEquationViz(sessionId, mode) const sufixo = String(mode) === 'excel_sab' ? 'estilo_sab' : 'excel' downloadBlob(blob, `equacao_modelo_${sufixo}.xlsx`) } catch (err) { setModeloAbertoError(err.message || 'Falha ao exportar equação.') } finally { setModeloAbertoLoading(false) } } function onModeloAbertoTabSelect(nextTab) { setModeloAbertoActiveTab(nextTab) void garantirSecaoModeloAberto(nextTab) } async function onGerarMapaSelecionados() { if (!selectedIds.length) { setMapaError('Selecione ao menos um modelo para plotar no mapa.') return } await carregarMapaPesquisa(selectedIds) } function onMapaModoExibicaoChange(event) { const nextModo = String(event?.target?.value || 'pontos') setMapaModoExibicao(nextModo) if (!mapaFoiGerado || mapaLoading || !selectedIds.length || mapaHtmls[nextModo] || mapaPayloads[nextModo]) return void carregarMapaPesquisa(selectedIds, { modoExibicao: nextModo }) } async function onAdminConfigSalva() { if (pesquisaInicializada) { await buscarModelos(filters, avaliandosGeolocalizados) return } await carregarContextoInicial() } function onLimparFormularioLocalizacao() { setLocalizacaoInputs(EMPTY_LOCATION_INPUTS) setLocalizacaoModo('endereco') setLocalizacaoError('') setLocalizacaoStatus('') } async function atualizarPesquisaAposGeolocalizacao(nextEntries) { resetMapaPesquisa() if (pesquisaInicializada) { await buscarModelos(filters, nextEntries) } } async function onResolverLocalizacao() { setLocalizacaoError('') setLocalizacaoStatus('') if (localizacaoModo === 'coords') { const lat = Number(localizacaoInputs.latitude) const lon = Number(localizacaoInputs.longitude) if (!Number.isFinite(lat) || !Number.isFinite(lon)) { setLocalizacaoError('Informe latitude e longitude válidas para localizar o avaliando.') return } } else { const logradouro = String(localizacaoInputs.logradouro || '').trim() const cdlogTexto = String(localizacaoInputs.cdlog || '').trim() const cdlog = cdlogTexto ? Number(cdlogTexto) : null if (!logradouro && !(Number.isFinite(cdlog) && cdlog > 0)) { setLocalizacaoError('Informe o logradouro ou um CDLOG válido para localizar o avaliando.') return } if (cdlogTexto && !(Number.isFinite(cdlog) && cdlog > 0)) { setLocalizacaoError('Informe um CDLOG válido para localizar o avaliando.') return } const numero = Number(localizacaoInputs.numero) if (!Number.isFinite(numero) || numero <= 0) { setLocalizacaoError('Informe um número válido para localizar o avaliando.') return } } setLocalizacaoLoading(true) try { const response = await api.pesquisarLocalizacaoAvaliando({ latitude: localizacaoModo === 'coords' ? Number(localizacaoInputs.latitude) : null, longitude: localizacaoModo === 'coords' ? Number(localizacaoInputs.longitude) : null, logradouro: localizacaoModo === 'endereco' ? String(localizacaoInputs.logradouro || '').trim() : null, numero: localizacaoModo === 'endereco' ? Number(localizacaoInputs.numero) : null, cdlog: localizacaoModo === 'endereco' && String(localizacaoInputs.cdlog || '').trim() ? Number(localizacaoInputs.cdlog) : null, }) const resolvida = { ...response, lat: parseCoordinateValue(response?.lat), lon: parseCoordinateValue(response?.lon), } const nextEntry = createLocalizacaoEntry( resolvida, `avaliando-${localizacaoIdCounterRef.current}`, ) localizacaoIdCounterRef.current += 1 const nextEntries = [...avaliandosGeolocalizados, nextEntry] setAvaliandosGeolocalizados(nextEntries) setLocalizacaoInputs(EMPTY_LOCATION_INPUTS) setLocalizacaoStatus( nextEntries.length > 1 ? `${nextEntries.length} avaliandos geolocalizados com sucesso.` : (response?.status || 'Geolocalização do avaliando registrada.') ) await atualizarPesquisaAposGeolocalizacao(nextEntries) } catch (err) { setLocalizacaoError(err.message || 'Falha ao localizar o avaliando.') } finally { setLocalizacaoLoading(false) } } async function onRemoverAvaliandoLocalizacao(id) { const nextEntries = avaliandosGeolocalizados.filter((item) => item.id !== id) setAvaliandosGeolocalizados(nextEntries) setLocalizacaoError('') setLocalizacaoStatus('') await atualizarPesquisaAposGeolocalizacao(nextEntries) } if (modoModeloAberto) { return (

{modeloAbertoMeta?.nome || 'Modelo'}

{PESQUISA_INNER_TABS.map((tab) => ( ))}
{modeloAbertoActiveTab === 'mapa' ? ( !modeloAbertoLoadedTabs.mapa ? (
Carregando mapa do modelo...
) : ( <>
) ) : null} {modeloAbertoActiveTab === 'trabalhos_tecnicos' ? ( modeloAbertoLoadedTabs.trabalhos_tecnicos ? :
Carregando trabalhos técnicos do modelo...
) : null} {modeloAbertoActiveTab === 'dados_mercado' ? (modeloAbertoLoadedTabs.dados_mercado ? :
Carregando dados de mercado...
) : null} {modeloAbertoActiveTab === 'metricas' ? (modeloAbertoLoadedTabs.metricas ? :
Carregando métricas do modelo...
) : null} {modeloAbertoActiveTab === 'transformacoes' ? ( modeloAbertoLoadedTabs.transformacoes ? ( <>

Dados com variáveis transformadas

) : (
Carregando transformações do modelo...
) ) : null} {modeloAbertoActiveTab === 'resumo' ? ( modeloAbertoLoadedTabs.resumo ? ( <>

Equações do Modelo

void onDownloadEquacaoModeloAberto(mode)} disabled={modeloAbertoLoading} />
) : (
Carregando resumo do modelo...
) ) : null} {modeloAbertoActiveTab === 'coeficientes' ? (modeloAbertoLoadedTabs.coeficientes ? :
Carregando coeficientes do modelo...
) : null} {modeloAbertoActiveTab === 'obs_calc' ? (modeloAbertoLoadedTabs.obs_calc ? :
Carregando observados x calculados...
) : null} {modeloAbertoActiveTab === 'graficos' ? ( modeloAbertoLoadedTabs.graficos ? ( <>
) : (
Carregando gráficos do modelo...
) ) : null}
{modeloAbertoError ?
{modeloAbertoError}
: null}
) } return (
{avaliandosGeolocalizados.length ? (
{avaliandosGeolocalizados.map((item, index) => ( void onRemoverAvaliandoLocalizacao(item.id)} disabled={localizacaoLoading || isPesquisaBusy} > Excluir avaliando )} /> ))}
) : null} {localizacaoModo === 'coords' ? (
) : (
)} {localizacaoStatus && !localizacaoAtiva ?
{localizacaoStatus}
: null} {localizacaoError ?
{localizacaoError}
: null}
setMostrarAdminConfig((current) => !current)} > Configure as fontes dos campos )} > {mostrarAdminConfig ? ( void onAdminConfigSalva()} /> ) : null}
{error ?
{error}
: null}
{formatCount(result.total_filtrado)}{' '} modelo(s) aceito(s) de {formatCount(result.total_geral)}.
{localizacaoMultipla ? ( ) : null}
{resultIds.length ? ( ) : null}
{!modelosOrdenados.length ? (
{!pesquisaInicializada ? 'Defina os filtros desejados e clique em Pesquisar.' : 'Nenhum modelo aceitou os parametros do avaliando informado.'}
) : (
{modelosOrdenados.map((modelo) => { const selecionado = selectedIds.includes(modelo.id) const faixaDataRecency = getFaixaDataRecencyInfo(modelo.faixa_data) const finalidadesText = uppercaseListText(modelo.finalidades || []) const bairrosText = uppercaseListText(modelo.bairros || []) const variaveisText = buildVariablesDisplay(modelo) const observacaoText = String(modelo.observacao_modelo || '').trim() const resumoEspacial = modelo?.distancia_resumo || null const coberturaEspacial = formatResumoEspacialCobertura(resumoEspacial) const cardClassName = [ 'pesquisa-card', selecionado ? 'is-selected' : '', ].filter(Boolean).join(' ') return (

{modelo.nome_modelo || modelo.arquivo}

{modelo.status !== 'ok' ?
{modelo.erro_leitura || 'Falha ao ler modelo.'}
: null}
) })}
)}
{formatCount(selectedIds.length)} modelo(s) selecionado(s) para plotagem.
{localizacaoMultipla ? 'Avaliandos geolocalizados:' : 'Localização do avaliando:'}{' '} {localizacaoAtiva ? (localizacaoMultipla ? formatCount(avaliandosGeoPayload.length) : 'ativa') : 'não definida'}
{mapaError ?
{mapaError}
: null} {mapaHtmlAtual ? (
{localizacaoAtiva ? ( ) : null} {localizacaoAtiva && mapaTrabalhosTecnicosProximidadeModo === 'proximos_ao_avaliando' ? ( ) : null}
) : null} {(mapaHtmlAtual || mapaPayloadAtual) ? :
Nenhum mapa gerado ainda.
}
) }