import React, { useEffect, useMemo, useRef, useState } from 'react' import { api, downloadBlob } from '../api' import DataTable from './DataTable' import EquationFormatsPanel from './EquationFormatsPanel' import LoadingOverlay from './LoadingOverlay' import MapFrame from './MapFrame' import PlotFigure from './PlotFigure' import SectionBlock from './SectionBlock' import SinglePillAutocomplete from './SinglePillAutocomplete' const INNER_TABS = [ { key: 'mapa', label: 'Mapa' }, { 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' }, { key: 'avaliacao', label: 'Avaliação' }, { key: 'avaliacao_massa', label: 'Avaliação em Massa' }, ] const BASE_COMPARACAO_SEM_BASE = '__none__' export default function VisualizacaoTab({ sessionId }) { const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [status, setStatus] = useState('') const [badgeHtml, setBadgeHtml] = useState('') const [uploadedFile, setUploadedFile] = useState(null) const [uploadDragOver, setUploadDragOver] = useState(false) const [modeloLoadSource, setModeloLoadSource] = useState('') const [repoModelos, setRepoModelos] = useState([]) const [repoModeloSelecionado, setRepoModeloSelecionado] = useState('') const [repoModelosLoading, setRepoModelosLoading] = useState(false) const [repoFonteModelos, setRepoFonteModelos] = useState('') const [dados, setDados] = useState(null) const [estatisticas, setEstatisticas] = useState(null) const [escalasHtml, setEscalasHtml] = useState('') const [dadosTransformados, setDadosTransformados] = useState(null) const [resumoHtml, setResumoHtml] = useState('') const [equacoes, setEquacoes] = useState(null) const [coeficientes, setCoeficientes] = useState(null) const [obsCalc, setObsCalc] = useState(null) const [plotObsCalc, setPlotObsCalc] = useState(null) const [plotResiduos, setPlotResiduos] = useState(null) const [plotHistograma, setPlotHistograma] = useState(null) const [plotCook, setPlotCook] = useState(null) const [plotCorr, setPlotCorr] = useState(null) const [mapaHtml, setMapaHtml] = useState('') const [mapaChoices, setMapaChoices] = useState(['Visualização Padrão']) const [mapaVar, setMapaVar] = useState('Visualização Padrão') const [camposAvaliacao, setCamposAvaliacao] = useState([]) const valoresAvaliacaoRef = useRef({}) const [avaliacaoFormVersion, setAvaliacaoFormVersion] = useState(0) const [confirmarLimpezaAvaliacoes, setConfirmarLimpezaAvaliacoes] = useState(false) const [resultadoAvaliacaoHtml, setResultadoAvaliacaoHtml] = useState('') const [baseChoices, setBaseChoices] = useState([]) const [baseValue, setBaseValue] = useState('') const [activeInnerTab, setActiveInnerTab] = useState('mapa') const deleteConfirmTimersRef = useRef({}) const uploadInputRef = useRef(null) const temAvaliacoes = Array.isArray(baseChoices) && baseChoices.length > 0 const repoModeloOptions = useMemo( () => (repoModelos || []).map((item) => ({ value: String(item?.id || ''), label: String(item?.nome_modelo || item?.arquivo || item?.id || ''), secondary: String(item?.arquivo || ''), })).filter((item) => item.value && item.label), [repoModelos], ) function resetConteudoVisualizacao() { setDados(null) setEstatisticas(null) setEscalasHtml('') setDadosTransformados(null) setResumoHtml('') setEquacoes(null) setCoeficientes(null) setObsCalc(null) setPlotObsCalc(null) setPlotResiduos(null) setPlotHistograma(null) setPlotCook(null) setPlotCorr(null) setMapaHtml('') setMapaChoices(['Visualização Padrão']) setMapaVar('Visualização Padrão') setCamposAvaliacao([]) valoresAvaliacaoRef.current = {} setAvaliacaoFormVersion((prev) => prev + 1) setConfirmarLimpezaAvaliacoes(false) setResultadoAvaliacaoHtml('') setBaseChoices([]) setBaseValue('') setActiveInnerTab('mapa') } function applyExibicaoResponse(resp) { setDados(resp.dados || null) setEstatisticas(resp.estatisticas || null) setEscalasHtml(resp.escalas_html || '') setDadosTransformados(resp.dados_transformados || null) setResumoHtml(resp.resumo_html || '') setEquacoes(resp.equacoes || null) setCoeficientes(resp.coeficientes || null) setObsCalc(resp.obs_calc || null) setPlotObsCalc(resp.grafico_obs_calc || null) setPlotResiduos(resp.grafico_residuos || null) setPlotHistograma(resp.grafico_histograma || null) setPlotCook(resp.grafico_cook || null) setPlotCorr(resp.grafico_correlacao || null) setMapaHtml(resp.mapa_html || '') setMapaChoices(resp.mapa_choices || ['Visualização Padrão']) setMapaVar('Visualização Padrão') setCamposAvaliacao(resp.campos_avaliacao || []) const values = {} ;(resp.campos_avaliacao || []).forEach((campo) => { values[campo.coluna] = '' }) valoresAvaliacaoRef.current = values setAvaliacaoFormVersion((prev) => prev + 1) setConfirmarLimpezaAvaliacoes(false) setResultadoAvaliacaoHtml('') setBaseChoices([]) setBaseValue('') } async function withBusy(fn) { setLoading(true) setError('') try { await fn() } catch (err) { setError(err.message) } finally { setLoading(false) } } function formatarFonteRepositorio(fonte) { if (!fonte || typeof fonte !== 'object') return '' const provider = String(fonte.provider || '').toLowerCase() if (provider === 'hf_dataset') { const repo = String(fonte.repo_id || '').trim() const revision = String(fonte.revision || '').trim() const degradado = Boolean(fonte.degraded) const sufixo = degradado ? ' (modo contingência)' : '' return `Fonte: HF Dataset${repo ? ` (${repo})` : ''}${revision ? ` | revisão ${revision.slice(0, 8)}` : ''}${sufixo}` } return 'Fonte: pasta local' } function aplicarRespostaModelosRepositorio(resp) { const modelos = Array.isArray(resp?.modelos) ? resp.modelos : [] setRepoModelos(modelos) setRepoFonteModelos(formatarFonteRepositorio(resp?.fonte || null)) setRepoModeloSelecionado((prev) => { const atual = String(prev || '') if (atual && modelos.some((item) => String(item.id) === atual)) return atual return '' }) } async function carregarModelosRepositorio() { setRepoModelosLoading(true) try { const resp = await api.visualizacaoRepositorioModelos() aplicarRespostaModelosRepositorio(resp) } catch (err) { setError(err.message || 'Falha ao carregar modelos do repositório.') setRepoModelos([]) setRepoModeloSelecionado('') setRepoFonteModelos('') } finally { setRepoModelosLoading(false) } } useEffect(() => { let ativo = true if (!sessionId) return () => { ativo = false } setRepoModelosLoading(true) api.visualizacaoRepositorioModelos() .then((resp) => { if (!ativo) return aplicarRespostaModelosRepositorio(resp) }) .catch(() => { if (!ativo) return setRepoModelos([]) setRepoModeloSelecionado('') setRepoFonteModelos('') }) .finally(() => { if (!ativo) return setRepoModelosLoading(false) }) return () => { ativo = false } }, [sessionId]) async function onUploadModel(arquivo = null) { const arquivoUpload = arquivo || uploadedFile if (!sessionId || !arquivoUpload) return setModeloLoadSource('upload') await withBusy(async () => { resetConteudoVisualizacao() const uploadResp = await api.uploadVisualizacaoFile(sessionId, arquivoUpload) setStatus(uploadResp.status || '') setBadgeHtml(uploadResp.badge_html || '') const exibirResp = await api.exibirVisualizacao(sessionId) applyExibicaoResponse(exibirResp) }) } async function onCarregarModeloRepositorio() { if (!sessionId || !repoModeloSelecionado) return setModeloLoadSource('repo') await withBusy(async () => { resetConteudoVisualizacao() const uploadResp = await api.visualizacaoRepositorioCarregar(sessionId, repoModeloSelecionado) setStatus(uploadResp.status || '') setBadgeHtml(uploadResp.badge_html || '') const exibirResp = await api.exibirVisualizacao(sessionId) applyExibicaoResponse(exibirResp) setUploadedFile(null) }) } function onUploadInputChange(event) { const input = event.target const file = input.files?.[0] ?? null setModeloLoadSource('upload') setUploadedFile(file) input.value = '' if (!file || loading) return void onUploadModel(file) } function onUploadDropZoneDragOver(event) { event.preventDefault() event.dataTransfer.dropEffect = 'copy' setUploadDragOver(true) } function onUploadDropZoneDragLeave(event) { event.preventDefault() if (!event.currentTarget.contains(event.relatedTarget)) { setUploadDragOver(false) } } function onUploadDropZoneDrop(event) { event.preventDefault() setUploadDragOver(false) const file = event.dataTransfer?.files?.[0] if (!file || loading) return setModeloLoadSource('upload') setUploadedFile(file) void onUploadModel(file) } async function onMapChange(value) { setMapaVar(value) if (!sessionId) return await withBusy(async () => { const resp = await api.updateVisualizacaoMap(sessionId, value) setMapaHtml(resp.mapa_html || '') }) } async function onCalcularAvaliacao() { if (!sessionId) return await withBusy(async () => { const resp = await api.evaluationCalculateViz(sessionId, valoresAvaliacaoRef.current, baseValue || null) setResultadoAvaliacaoHtml(resp.resultado_html || '') setBaseChoices(resp.base_choices || []) setBaseValue(resp.base_value || '') setConfirmarLimpezaAvaliacoes(false) }) } function onResetCamposAvaliacao() { const limpo = {} camposAvaliacao.forEach((campo) => { limpo[campo.coluna] = '' }) valoresAvaliacaoRef.current = limpo setAvaliacaoFormVersion((prev) => prev + 1) } async function onClearAvaliacao() { if (!sessionId) return await withBusy(async () => { const resp = await api.evaluationClearViz(sessionId) setResultadoAvaliacaoHtml(resp.resultado_html || '') setBaseChoices(resp.base_choices || []) setBaseValue(resp.base_value || '') setConfirmarLimpezaAvaliacoes(false) }) } async function onDeleteAvaliacao(indice) { if (!sessionId) return await withBusy(async () => { const resp = await api.evaluationDeleteViz(sessionId, indice ? String(indice) : null, baseValue || null) setResultadoAvaliacaoHtml(resp.resultado_html || '') setBaseChoices(resp.base_choices || []) setBaseValue(resp.base_value || '') setConfirmarLimpezaAvaliacoes(false) }) } function onAvaliacaoResultadoClick(event) { const ativarExclusao = event.target.closest('[data-avaliacao-delete-arm]') if (ativarExclusao) { const indice = ativarExclusao.getAttribute('data-avaliacao-delete-index') if (!indice) return const cell = ativarExclusao.closest('td') const botaoConfirmar = cell?.querySelector(`[data-avaliacao-delete-confirm="${indice}"]`) if (!botaoConfirmar) return ativarExclusao.style.display = 'none' botaoConfirmar.style.display = 'inline-block' const timerKey = String(indice) if (deleteConfirmTimersRef.current[timerKey]) { clearTimeout(deleteConfirmTimersRef.current[timerKey]) } deleteConfirmTimersRef.current[timerKey] = window.setTimeout(() => { botaoConfirmar.style.display = 'none' ativarExclusao.style.display = 'inline' delete deleteConfirmTimersRef.current[timerKey] }, 10000) return } const confirmarExclusao = event.target.closest('[data-avaliacao-delete-confirm]') if (!confirmarExclusao) return const indice = confirmarExclusao.getAttribute('data-avaliacao-delete-confirm') if (!indice) return const timerKey = String(indice) if (deleteConfirmTimersRef.current[timerKey]) { clearTimeout(deleteConfirmTimersRef.current[timerKey]) delete deleteConfirmTimersRef.current[timerKey] } onDeleteAvaliacao(indice) } async function onBaseChange(value) { setBaseValue(value) if (!sessionId) return await withBusy(async () => { const resp = await api.evaluationBaseViz(sessionId, value) setResultadoAvaliacaoHtml(resp.resultado_html || '') }) } async function onExportAvaliacoes() { if (!sessionId) return await withBusy(async () => { const blob = await api.exportEvaluationViz(sessionId) downloadBlob(blob, 'avaliacoes_visualizacao.xlsx') }) } async function onDownloadEquacao(mode) { if (!sessionId || !mode) return await withBusy(async () => { const blob = await api.exportEquationViz(sessionId, mode) const sufixo = String(mode) === 'excel_sab' ? 'estilo_sab' : 'excel' downloadBlob(blob, `equacao_modelo_${sufixo}.xlsx`) }) } return (