Spaces:
Running
Running
| 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 ( | |
| <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> | |
| {dados ? ( | |
| <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={() => setActiveInnerTab(tab.key)} | |
| > | |
| {tab.label} | |
| </button> | |
| ))} | |
| </div> | |
| <div className="inner-tab-panel"> | |
| {activeInnerTab === 'mapa' ? ( | |
| <> | |
| <div className="row compact visualizacao-mapa-controls"> | |
| <label>Variável no mapa</label> | |
| <select value={mapaVar} onChange={(e) => onMapChange(e.target.value)}> | |
| {mapaChoices.map((choice) => ( | |
| <option key={choice} value={choice}>{choice}</option> | |
| ))} | |
| </select> | |
| </div> | |
| <MapFrame html={mapaHtml} /> | |
| </> | |
| ) : null} | |
| {activeInnerTab === 'dados_mercado' ? ( | |
| <DataTable table={dados} maxHeight={620} /> | |
| ) : null} | |
| {activeInnerTab === 'metricas' ? ( | |
| <DataTable table={estatisticas} maxHeight={620} /> | |
| ) : null} | |
| {activeInnerTab === 'transformacoes' ? ( | |
| <> | |
| <div dangerouslySetInnerHTML={{ __html: escalasHtml }} /> | |
| <h4 className="visualizacao-table-title">Dados com variáveis transformadas</h4> | |
| <DataTable table={dadosTransformados} /> | |
| </> | |
| ) : null} | |
| {activeInnerTab === '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 }} /> | |
| </> | |
| ) : null} | |
| {activeInnerTab === 'coeficientes' ? ( | |
| <DataTable table={coeficientes} maxHeight={620} /> | |
| ) : null} | |
| {activeInnerTab === 'obs_calc' ? ( | |
| <DataTable table={obsCalc} maxHeight={620} /> | |
| ) : null} | |
| {activeInnerTab === '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> | |
| </> | |
| ) : 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} label="Processando dados..." /> | |
| {error ? <div className="error-line">{error}</div> : null} | |
| </div> | |
| ) | |
| } | |