mesa-react / frontend /src /components /VisualizacaoTab.jsx
Guilherme Silberfarb Costa
Refina mapas Leaflet e prepara nova release Windows
3ee46e7
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__'
const TRABALHOS_TECNICOS_MODELOS_PADRAO = 'selecionados_e_outras_versoes'
const SECTION_TABS = new Set([
'mapa',
'dados_mercado',
'metricas',
'transformacoes',
'resumo',
'coeficientes',
'obs_calc',
'graficos',
])
function getVisualizacaoLoadingLabel(tabKey) {
switch (String(tabKey || '').trim()) {
case 'mapa':
return 'Carregando mapa do modelo...'
case 'dados_mercado':
return 'Carregando dados de mercado...'
case 'metricas':
return 'Carregando métricas do modelo...'
case 'transformacoes':
return 'Carregando transformações do modelo...'
case 'resumo':
return 'Carregando resumo do modelo...'
case 'coeficientes':
return 'Carregando coeficientes do modelo...'
case 'obs_calc':
return 'Carregando observados x calculados...'
case 'graficos':
return 'Carregando gráficos do modelo...'
default:
return 'Processando dados...'
}
}
export default function VisualizacaoTab({ sessionId }) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [status, setStatus] = useState('')
const [badgeHtml, setBadgeHtml] = useState('')
const [modeloCarregado, setModeloCarregado] = useState(false)
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 [mapaPayload, setMapaPayload] = useState(null)
const [mapaChoices, setMapaChoices] = useState(['Visualização Padrão'])
const [mapaVar, setMapaVar] = useState('Visualização Padrão')
const [mapaTrabalhosTecnicosModelosModo, setMapaTrabalhosTecnicosModelosModo] = useState(TRABALHOS_TECNICOS_MODELOS_PADRAO)
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 [loadedTabs, setLoadedTabs] = useState({})
const [loadingTabs, setLoadingTabs] = useState({})
const deleteConfirmTimersRef = useRef({})
const uploadInputRef = useRef(null)
const pendingTabRequestsRef = useRef({})
const modeloLoadVersionRef = useRef(0)
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() {
pendingTabRequestsRef.current = {}
setModeloCarregado(false)
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('')
setMapaPayload(null)
setMapaChoices(['Visualização Padrão'])
setMapaVar('Visualização Padrão')
setMapaTrabalhosTecnicosModelosModo(TRABALHOS_TECNICOS_MODELOS_PADRAO)
setCamposAvaliacao([])
valoresAvaliacaoRef.current = {}
setAvaliacaoFormVersion((prev) => prev + 1)
setConfirmarLimpezaAvaliacoes(false)
setResultadoAvaliacaoHtml('')
setBaseChoices([])
setBaseValue('')
setActiveInnerTab('mapa')
setLoadedTabs({})
setLoadingTabs({})
}
function applyEvaluationContext(resp) {
setCamposAvaliacao(resp?.campos_avaliacao || [])
setEquacoes(resp?.equacoes || null)
const values = {}
;(resp?.campos_avaliacao || []).forEach((campo) => {
values[campo.coluna] = ''
})
valoresAvaliacaoRef.current = values
setAvaliacaoFormVersion((prev) => prev + 1)
setConfirmarLimpezaAvaliacoes(false)
setResultadoAvaliacaoHtml('')
setBaseChoices([])
setBaseValue('')
setModeloCarregado(true)
}
function applyVisualizacaoSection(secao, resp) {
const key = String(secao || '').trim()
if (key === 'dados_mercado') {
setDados(resp?.dados || null)
return
}
if (key === 'metricas') {
setEstatisticas(resp?.estatisticas || null)
return
}
if (key === 'transformacoes') {
setEscalasHtml(resp?.escalas_html || '')
setDadosTransformados(resp?.dados_transformados || null)
return
}
if (key === 'resumo') {
setResumoHtml(resp?.resumo_html || '')
setEquacoes(resp?.equacoes || null)
return
}
if (key === 'coeficientes') {
setCoeficientes(resp?.coeficientes || null)
return
}
if (key === 'obs_calc') {
setObsCalc(resp?.obs_calc || null)
return
}
if (key === 'graficos') {
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)
return
}
if (key === 'mapa') {
const nextChoices = Array.isArray(resp?.mapa_choices) && resp.mapa_choices.length
? resp.mapa_choices
: ['Visualização Padrão']
setMapaHtml(resp?.mapa_html || '')
setMapaPayload(resp?.mapa_payload || null)
setMapaChoices(nextChoices)
setMapaVar((current) => (nextChoices.includes(current) ? current : 'Visualização Padrão'))
setMapaTrabalhosTecnicosModelosModo(resp?.trabalhos_tecnicos_modelos_modo || TRABALHOS_TECNICOS_MODELOS_PADRAO)
}
}
async function ensureVisualizacaoSection(secao, options = {}) {
const secaoNormalizada = String(secao || '').trim()
if (!sessionId || !SECTION_TABS.has(secaoNormalizada)) return
if (!options.force && loadedTabs[secaoNormalizada]) return
if (pendingTabRequestsRef.current[secaoNormalizada]) {
await pendingTabRequestsRef.current[secaoNormalizada]
return
}
const expectedVersion = options.expectedVersion ?? modeloLoadVersionRef.current
const trabalhosTecnicosModo = options.trabalhosTecnicosModelosModo || mapaTrabalhosTecnicosModelosModo
setLoadingTabs((prev) => ({ ...prev, [secaoNormalizada]: true }))
const request = (async () => {
try {
const resp = await api.visualizacaoSection(sessionId, secaoNormalizada, trabalhosTecnicosModo)
if (modeloLoadVersionRef.current !== expectedVersion) return
applyVisualizacaoSection(secaoNormalizada, resp)
setLoadedTabs((prev) => ({ ...prev, [secaoNormalizada]: true }))
} catch (err) {
if (modeloLoadVersionRef.current !== expectedVersion) return
setError(err.message || 'Falha ao carregar dados do modelo.')
} finally {
if (modeloLoadVersionRef.current !== expectedVersion) return
setLoadingTabs((prev) => ({ ...prev, [secaoNormalizada]: false }))
}
})()
pendingTabRequestsRef.current[secaoNormalizada] = request
try {
await request
} finally {
if (pendingTabRequestsRef.current[secaoNormalizada] === request) {
delete pendingTabRequestsRef.current[secaoNormalizada]
}
}
}
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()
modeloLoadVersionRef.current += 1
const openVersion = modeloLoadVersionRef.current
const uploadResp = await api.uploadVisualizacaoFile(sessionId, arquivoUpload)
if (modeloLoadVersionRef.current !== openVersion) return
setStatus(uploadResp.status || '')
setBadgeHtml(uploadResp.badge_html || '')
const contextoResp = await api.evaluationContextViz(sessionId)
if (modeloLoadVersionRef.current !== openVersion) return
applyEvaluationContext(contextoResp)
await ensureVisualizacaoSection('mapa', {
force: true,
expectedVersion: openVersion,
trabalhosTecnicosModelosModo: TRABALHOS_TECNICOS_MODELOS_PADRAO,
})
})
}
async function onCarregarModeloRepositorio() {
if (!sessionId || !repoModeloSelecionado) return
setModeloLoadSource('repo')
await withBusy(async () => {
resetConteudoVisualizacao()
modeloLoadVersionRef.current += 1
const openVersion = modeloLoadVersionRef.current
const uploadResp = await api.visualizacaoRepositorioCarregar(sessionId, repoModeloSelecionado)
if (modeloLoadVersionRef.current !== openVersion) return
setStatus(uploadResp.status || '')
setBadgeHtml(uploadResp.badge_html || '')
const contextoResp = await api.evaluationContextViz(sessionId)
if (modeloLoadVersionRef.current !== openVersion) return
applyEvaluationContext(contextoResp)
await ensureVisualizacaoSection('mapa', {
force: true,
expectedVersion: openVersion,
trabalhosTecnicosModelosModo: TRABALHOS_TECNICOS_MODELOS_PADRAO,
})
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 atualizarMapa(
variavelMapa = mapaVar,
trabalhosTecnicosModelosModo = mapaTrabalhosTecnicosModelosModo,
) {
if (!sessionId) return
setLoadingTabs((prev) => ({ ...prev, mapa: true }))
try {
const resp = await api.updateVisualizacaoMap(sessionId, variavelMapa, trabalhosTecnicosModelosModo)
setMapaHtml(resp?.mapa_html || '')
setMapaPayload(resp?.mapa_payload || null)
setMapaTrabalhosTecnicosModelosModo(resp?.trabalhos_tecnicos_modelos_modo || trabalhosTecnicosModelosModo)
setLoadedTabs((prev) => ({ ...prev, mapa: true }))
} finally {
setLoadingTabs((prev) => ({ ...prev, mapa: false }))
}
}
async function onMapChange(value) {
setMapaVar(value)
await withBusy(async () => {
await atualizarMapa(value, mapaTrabalhosTecnicosModelosModo)
})
}
async function onMapTrabalhosTecnicosModeChange(value) {
setMapaTrabalhosTecnicosModelosModo(value)
await withBusy(async () => {
await atualizarMapa(mapaVar, value)
})
}
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`)
})
}
function onInnerTabSelect(nextTab) {
setActiveInnerTab(nextTab)
if (SECTION_TABS.has(nextTab)) {
void ensureVisualizacaoSection(nextTab)
}
}
return (
<div className="tab-content">
<SectionBlock step="1" title="Carregar Modelo .dai" subtitle="Carregue o arquivo e o conteúdo será exibido automaticamente.">
{!modeloLoadSource ? (
<div className="model-source-choice-grid">
<button
type="button"
className="model-source-choice-btn model-source-choice-btn-primary"
onClick={() => setModeloLoadSource('repo')}
disabled={loading}
>
Carregar modelo do repositório
</button>
<button
type="button"
className="model-source-choice-btn model-source-choice-btn-secondary"
onClick={() => setModeloLoadSource('upload')}
disabled={loading}
>
Fazer upload de modelo
</button>
</div>
) : (
<div className="model-source-flow">
<div className="model-source-flow-head">
<button
type="button"
className="model-source-back-btn"
onClick={() => setModeloLoadSource('')}
disabled={loading}
>
Voltar
</button>
</div>
{modeloLoadSource === 'repo' ? (
<div className="row upload-repo-row">
<label className="upload-repo-field">
Modelo do repositório
<SinglePillAutocomplete
value={repoModeloSelecionado}
onChange={setRepoModeloSelecionado}
options={repoModeloOptions}
placeholder={repoModelosLoading ? 'Carregando lista...' : repoModeloOptions.length > 0 ? 'Digite para buscar modelo' : 'Nenhum modelo disponível'}
emptyMessage={repoModeloOptions.length > 0 ? 'Nenhum modelo encontrado.' : 'Nenhum modelo disponível.'}
loading={repoModelosLoading}
disabled={loading || repoModelosLoading || repoModeloOptions.length === 0}
/>
</label>
<div className="row compact upload-repo-actions">
<button type="button" onClick={onCarregarModeloRepositorio} disabled={loading || repoModelosLoading || !repoModeloSelecionado}>
Carregar do repositório
</button>
<button type="button" onClick={() => void carregarModelosRepositorio()} disabled={loading || repoModelosLoading}>
Atualizar lista
</button>
</div>
{repoFonteModelos ? <div className="section1-empty-hint">{repoFonteModelos}</div> : null}
</div>
) : null}
{modeloLoadSource === 'upload' ? (
<div
className={`upload-dropzone${uploadDragOver ? ' is-dragover' : ''}`}
onDragOver={onUploadDropZoneDragOver}
onDragEnter={onUploadDropZoneDragOver}
onDragLeave={onUploadDropZoneDragLeave}
onDrop={onUploadDropZoneDrop}
>
<input
ref={uploadInputRef}
type="file"
className="upload-hidden-input"
accept=".dai"
onChange={onUploadInputChange}
/>
<div className="row upload-dropzone-main">
<button
type="button"
className="btn-upload-select"
onClick={() => uploadInputRef.current?.click()}
disabled={loading}
>
Selecionar arquivo
</button>
</div>
<div className="upload-dropzone-hint">Ou arraste e solte aqui para carregar automaticamente.</div>
</div>
) : null}
</div>
)}
{status ? <div className="status-line">{status}</div> : null}
{badgeHtml ? <div className="upload-badge-block" dangerouslySetInnerHTML={{ __html: badgeHtml }} /> : null}
</SectionBlock>
{modeloCarregado ? (
<SectionBlock step="2" title="Conteúdo do Modelo" subtitle="Carregue o modelo no topo e navegue pelas abas internas abaixo.">
<div className="inner-tabs" role="tablist" aria-label="Abas internas de visualização">
{INNER_TABS.map((tab) => (
<button
key={tab.key}
type="button"
className={activeInnerTab === tab.key ? 'inner-tab-pill active' : 'inner-tab-pill'}
onClick={() => onInnerTabSelect(tab.key)}
>
{tab.label}
</button>
))}
</div>
<div className="inner-tab-panel">
{activeInnerTab === 'mapa' ? (
!loadedTabs.mapa ? (
<div className="empty-box">Carregando mapa do modelo...</div>
) : (
<>
<div className="row compact visualizacao-mapa-controls pesquisa-mapa-controls-row">
<label className="pesquisa-field pesquisa-mapa-modo-field">
Variável no mapa
<select value={mapaVar} onChange={(e) => onMapChange(e.target.value)}>
{mapaChoices.map((choice) => (
<option key={choice} value={choice}>{choice}</option>
))}
</select>
</label>
<label className="pesquisa-field pesquisa-mapa-trabalhos-field">
Exibição dos trabalhos técnicos
<select
value={mapaTrabalhosTecnicosModelosModo === 'selecionados_e_anteriores'
? TRABALHOS_TECNICOS_MODELOS_PADRAO
: mapaTrabalhosTecnicosModelosModo}
onChange={(event) => void onMapTrabalhosTecnicosModeChange(event.target.value)}
autoComplete="off"
>
<option value="selecionados">Somente deste modelo</option>
<option value="selecionados_e_outras_versoes">Incluir demais versões do modelo</option>
</select>
</label>
</div>
<MapFrame html={mapaHtml} payload={mapaPayload} sessionId={sessionId} />
</>
)
) : null}
{activeInnerTab === 'dados_mercado' ? (
loadedTabs.dados_mercado ? <DataTable table={dados} maxHeight={620} /> : <div className="empty-box">Carregando dados de mercado...</div>
) : null}
{activeInnerTab === 'metricas' ? (
loadedTabs.metricas ? <DataTable table={estatisticas} maxHeight={620} /> : <div className="empty-box">Carregando métricas do modelo...</div>
) : null}
{activeInnerTab === 'transformacoes' ? (
loadedTabs.transformacoes ? (
<>
<div dangerouslySetInnerHTML={{ __html: escalasHtml }} />
<h4 className="visualizacao-table-title">Dados com variáveis transformadas</h4>
<DataTable table={dadosTransformados} />
</>
) : (
<div className="empty-box">Carregando transformações do modelo...</div>
)
) : null}
{activeInnerTab === 'resumo' ? (
loadedTabs.resumo ? (
<>
<div className="equation-formats-section">
<h4>Equações do Modelo</h4>
<EquationFormatsPanel
equacoes={equacoes}
onDownload={(mode) => void onDownloadEquacao(mode)}
disabled={loading}
/>
</div>
<div dangerouslySetInnerHTML={{ __html: resumoHtml }} />
</>
) : (
<div className="empty-box">Carregando resumo do modelo...</div>
)
) : null}
{activeInnerTab === 'coeficientes' ? (
loadedTabs.coeficientes ? <DataTable table={coeficientes} maxHeight={620} /> : <div className="empty-box">Carregando coeficientes do modelo...</div>
) : null}
{activeInnerTab === 'obs_calc' ? (
loadedTabs.obs_calc ? <DataTable table={obsCalc} maxHeight={620} /> : <div className="empty-box">Carregando observados x calculados...</div>
) : null}
{activeInnerTab === 'graficos' ? (
loadedTabs.graficos ? (
<>
<div className="plot-grid-2-fixed">
<PlotFigure figure={plotObsCalc} title="Obs x Calc" />
<PlotFigure figure={plotResiduos} title="Resíduos" />
<PlotFigure figure={plotHistograma} title="Histograma" />
<PlotFigure figure={plotCook} title="Cook" forceHideLegend />
</div>
<div className="plot-full-width">
<PlotFigure figure={plotCorr} title="Correlação" className="plot-correlation-card" />
</div>
</>
) : (
<div className="empty-box">Carregando gráficos do modelo...</div>
)
) : null}
{activeInnerTab === 'avaliacao' ? (
<>
<div className="equation-formats-section avaliacao-equacao-section">
<h5>Equação (estilo SAB)</h5>
<div className="equation-box equation-box-plain">
{equacoes?.excel_sab || 'Equação indisponível.'}
</div>
</div>
<div className="avaliacao-groups">
<div className="subpanel avaliacao-group">
<h4>Parâmetros</h4>
<div className="avaliacao-grid" key={`avaliacao-grid-viz-${avaliacaoFormVersion}`}>
{camposAvaliacao.map((campo) => (
<div key={`campo-${campo.coluna}`} className="avaliacao-card">
<label>{String(campo?.rotulo || campo?.coluna || '')}</label>
{campo.tipo === 'dicotomica' ? (
<select
defaultValue={String(valoresAvaliacaoRef.current[campo.coluna] ?? '')}
onChange={(e) => {
valoresAvaliacaoRef.current[campo.coluna] = e.target.value
}}
>
<option value="">Selecione</option>
{(campo.opcoes || [0, 1]).map((opcao) => (
<option key={`op-viz-${campo.coluna}-${opcao}`} value={String(opcao)}>
{opcao}
</option>
))}
</select>
) : (
<input
type="number"
defaultValue={valoresAvaliacaoRef.current[campo.coluna] ?? ''}
placeholder={campo.placeholder || ''}
onChange={(e) => {
valoresAvaliacaoRef.current[campo.coluna] = e.target.value
}}
/>
)}
</div>
))}
</div>
<div className="row-wrap avaliacao-actions-row">
<button onClick={onCalcularAvaliacao} disabled={loading}>Calcular</button>
<button onClick={onResetCamposAvaliacao} disabled={loading}>Resetar campos</button>
</div>
</div>
{temAvaliacoes ? (
<div className="subpanel avaliacao-group">
<h4>Avaliações</h4>
<div className="row avaliacao-base-row">
<label>Base comparação</label>
<select value={baseValue || ''} onChange={(e) => onBaseChange(e.target.value)}>
<option value={BASE_COMPARACAO_SEM_BASE}>Sem base</option>
{baseChoices.map((choice) => (
<option key={`base-${choice}`} value={choice}>{choice}</option>
))}
</select>
<button type="button" className="btn-avaliacao-export" onClick={onExportAvaliacoes} disabled={loading}>
Exportar avaliações
</button>
{!confirmarLimpezaAvaliacoes ? (
<button
type="button"
className="btn-avaliacao-clear"
onClick={() => setConfirmarLimpezaAvaliacoes(true)}
disabled={loading}
>
Limpar avaliações
</button>
) : (
<div className="avaliacao-clear-confirm avaliacao-clear-confirm-inline">
<span>Confirmar limpeza?</span>
<button type="button" className="btn-avaliacao-clear" onClick={onClearAvaliacao} disabled={loading}>
Confirmar
</button>
<button type="button" onClick={() => setConfirmarLimpezaAvaliacoes(false)} disabled={loading}>
Cancelar
</button>
</div>
)}
</div>
<div
className="avaliacao-resultado-box"
onClick={onAvaliacaoResultadoClick}
dangerouslySetInnerHTML={{ __html: resultadoAvaliacaoHtml }}
/>
</div>
) : null}
</div>
</>
) : null}
{activeInnerTab === 'avaliacao_massa' ? (
<div className="empty-box">Módulo em desenvolvimento.</div>
) : null}
</div>
</SectionBlock>
) : null}
<LoadingOverlay
show={loading || Boolean(loadingTabs[activeInnerTab])}
label={loading ? 'Processando dados...' : getVisualizacaoLoadingLabel(activeInnerTab)}
/>
{error ? <div className="error-line">{error}</div> : null}
</div>
)
}