Files
ranking/frontend/src/components/RawDataModal.jsx
Frederico Castro e0692ee49c fix(frontend): evitar requisições duplicadas causadas pelo React StrictMode
- Adicionar ref para controlar requisições já feitas no App.jsx
- Adicionar ref para controlar fetch no RawDataModal.jsx
- Adicionar componente FiltroAtivo para filtrar consultores
2025-12-29 03:29:42 -03:00

688 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
import { rankingService } from '../services/api';
import './RawDataModal.css';
const decodeHtmlEntities = (str) => {
if (typeof str !== 'string') return str;
const textarea = document.createElement('textarea');
textarea.innerHTML = str;
return textarea.value;
};
const formatDate = (dateStr) => {
if (!dateStr) return null;
try {
if (dateStr.includes('/')) {
return dateStr.split(' ')[0];
}
const date = new Date(dateStr);
return date.toLocaleDateString('pt-BR');
} catch {
return dateStr;
}
};
const formatValue = (value) => {
if (value === null || value === undefined || value === '') return null;
if (typeof value === 'boolean') return value ? 'Sim' : 'Não';
if (typeof value === 'number') return String(value);
if (typeof value === 'string') return decodeHtmlEntities(value);
if (Array.isArray(value)) {
if (value.length === 0) return null;
return value.map(item => {
if (typeof item === 'object' && item !== null) {
const val = item.nome || item.descricao || item.sigla || item.tipo || JSON.stringify(item);
return decodeHtmlEntities(val);
}
return decodeHtmlEntities(String(item));
}).join(', ');
}
if (typeof value === 'object') {
if (value.nome) return decodeHtmlEntities(value.nome);
if (value.descricao) return decodeHtmlEntities(value.descricao);
if (value.sigla) return decodeHtmlEntities(value.sigla);
return null;
}
return decodeHtmlEntities(String(value));
};
const LABEL_MAP = {
tipo: 'Tipo',
nome: 'Nome',
sigla: 'Sigla',
codigo: 'Código',
situacao: 'Situação',
situacaoConsultoria: 'Situação',
areaAvaliacao: 'Área de Avaliação',
areaConhecimento: 'Área de Conhecimento',
areaConhecimentoPos: 'Área de Conhecimento Pós',
areaPesquisa: 'Área de Pesquisa',
colegio: 'Colégio',
ies: 'IES',
programa: 'Programa',
nomeEmpregador: 'Empregador',
emprego: 'Vínculo',
atividade: 'Atividade',
dtAdmissao: 'Admissão',
dtDesligamento: 'Desligamento',
cnpjEmpregador: 'CNPJ Empregador',
profissao: 'Profissão',
matricula: 'Matrícula',
vinculo: 'Vínculo',
historico: 'Histórico',
inicioRelacionamento: 'Início Relacionamento',
fimRelacionamento: 'Fim Relacionamento',
categoria: 'Categoria',
tipoVinculo: 'Tipo de Vínculo',
vinculoTrabalho: 'Vínculo de Trabalho',
regimeTrabalho: 'Regime de Trabalho',
cargaHoraria: 'Carga Horária',
linhaPesquisa: 'Linha de Pesquisa',
areaConcentracao: 'Área de Concentração',
areaPesquisa: 'Área de Pesquisa',
consultorResponsavel: 'Consultor Responsável',
unidadeOrganizacional: 'Unidade Organizacional',
localEvento: 'Local do Evento',
nrProcesso: 'Número do Processo',
idProcesso: 'ID do Processo',
anoInicio: 'Ano de Início',
tipoTrabalho: 'Tipo de Trabalho',
inicioVinculacao: 'Início Vinculação',
fimVinculacao: 'Fim Vinculação',
inicioSituacao: 'Início Situação',
inativacaoSituacao: 'Inativação',
portaria: 'Portaria',
dataPortaria: 'Data Portaria',
premio: 'Prêmio',
evento: 'Evento',
premiacao: 'Premiação',
ano: 'Ano',
edicao: 'Edição',
papelPessoa: 'Papel',
comissao: 'Comissão',
produto: 'Produto',
nivel: 'Nível',
modalidade: 'Modalidade',
camaraTematica: 'Câmara Temática',
totalOrientacaoFinalizadaMestrado: 'Orientações Finalizadas (Mestrado)',
totalOrientacaoFinalizadaDoutorado: 'Orientações Finalizadas (Doutorado)',
totalOrientacaoAndamentoMestrado: 'Orientações em Andamento (Mestrado)',
totalOrientacaoAndamentoDoutorado: 'Orientações em Andamento (Doutorado)',
totalAcompanhamentoPosDoutorado: 'Acompanhamentos Pós-Doutorado',
};
const formatLabel = (key) => LABEL_MAP[key] || key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
const parseDate = (dateStr) => {
if (!dateStr) return null;
try {
if (dateStr.includes('/')) {
const parts = dateStr.split('/');
if (parts.length === 3) {
const [day, month, year] = parts;
return new Date(year, month - 1, day);
}
}
const date = new Date(dateStr);
return isNaN(date.getTime()) ? null : date;
} catch {
return null;
}
};
const getAtuacaoDate = (atuacao) => {
const candidateDates = [
atuacao.inicio,
atuacao.fim,
atuacao.dadosEmprego?.dtAdmissao,
atuacao.dadosEmprego?.dtDesligamento,
atuacao.dadosDocencia?.inicioVinculacao,
atuacao.dadosDocencia?.fimVinculacao,
atuacao.dadosCoordenacaoArea?.inicioVinculacao,
atuacao.dadosCoordenacaoArea?.fimVinculacao,
atuacao.dadosHistoricoCoordenacaoArea?.inicioVinculacao,
atuacao.dadosHistoricoCoordenacaoArea?.fimVinculacao,
atuacao.dadosConsultoria?.inicioVinculacao,
atuacao.dadosConsultoria?.fimVinculacao,
atuacao.dadosConsultoria?.inicioSituacao,
atuacao.dadosConsultoria?.inativacaoSituacao,
atuacao.dadosEvento?.inicio,
atuacao.dadosEvento?.fim,
atuacao.dadosParticipacaoEvento?.inicio,
atuacao.dadosParticipacaoEvento?.fim,
atuacao.dadosPremiacaoPremio?.ano ? `01/01/${atuacao.dadosPremiacaoPremio.ano}` : null,
atuacao.dadosParticipacaoPremio?.ano ? `01/01/${atuacao.dadosParticipacaoPremio.ano}` : null,
atuacao.dadosParticipacaoInscricaoPremio?.ano ? `01/01/${atuacao.dadosParticipacaoInscricaoPremio.ano}` : null,
atuacao.dadosProjeto?.anoInicio ? `01/01/${atuacao.dadosProjeto.anoInicio}` : null,
];
for (const dateStr of candidateDates) {
const date = parseDate(dateStr);
if (date) return date;
}
return null;
};
const sortAtuacoesByDate = (atuacoes) => {
return [...atuacoes].sort((a, b) => {
const dateA = getAtuacaoDate(a);
const dateB = getAtuacaoDate(b);
if (!dateA && !dateB) return 0;
if (!dateA) return 1;
if (!dateB) return -1;
return dateB.getTime() - dateA.getTime();
});
};
const DataField = ({ label, value, className = '' }) => {
const formattedValue = formatValue(value);
if (formattedValue === null) return null;
return (
<div className={`data-field ${className}`}>
<span className="data-label">{formatLabel(label)}</span>
<span className="data-value">{formattedValue}</span>
</div>
);
};
const NestedObjectDisplay = ({ data, depth = 0 }) => {
if (!data || typeof data !== 'object') return null;
const entries = Object.entries(data).filter(([key, value]) => {
if (value === null || value === undefined || value === '') return false;
if (Array.isArray(value) && value.length === 0) return false;
return true;
});
if (entries.length === 0) return null;
return (
<div className={`nested-display depth-${depth}`}>
{entries.map(([key, value]) => {
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
const simpleValue = value.nome || value.descricao || value.sigla;
if (simpleValue) {
return <DataField key={key} label={key} value={simpleValue} />;
}
return (
<div key={key} className="data-field nested">
<span className="data-label">{formatLabel(key)}</span>
<NestedObjectDisplay data={value} depth={depth + 1} />
</div>
);
}
if (Array.isArray(value)) {
const formatted = value.map(item => {
if (typeof item === 'object' && item !== null) {
return item.nome || item.descricao || item.sigla || item.tipo || Object.values(item).filter(v => typeof v === 'string')[0] || '';
}
return String(item);
}).filter(Boolean).join(', ');
if (!formatted) return null;
return <DataField key={key} label={key} value={formatted} />;
}
return <DataField key={key} label={key} value={value} />;
})}
</div>
);
};
const Section = ({ title, icon, children, defaultOpen = true, count }) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div className={`data-section ${isOpen ? 'open' : ''}`}>
<div className="section-header" onClick={() => setIsOpen(!isOpen)}>
<span className="section-icon">{icon}</span>
<h3>{title}</h3>
{count !== undefined && <span className="section-count">{count}</span>}
<span className="section-toggle">{isOpen ? '▼' : '▶'}</span>
</div>
{isOpen && <div className="section-content">{children}</div>}
</div>
);
};
const AtuacaoCard = ({ atuacao, index }) => {
const [expanded, setExpanded] = useState(false);
const tipo = atuacao.tipo || 'Tipo não informado';
const getNestedValue = (obj, path) => {
if (!obj || !path) return undefined;
return path.split('.').reduce((acc, key) => (acc ? acc[key] : undefined), obj);
};
const joinParts = (parts) => parts.filter(Boolean).join(' · ');
const buildSummary = () => {
const tipoLower = tipo.toLowerCase();
const dadosDocencia = atuacao.dadosDocencia || {};
const dadosEmprego = atuacao.dadosEmprego || {};
const dadosEvento = atuacao.dadosEvento || atuacao.dadosParticipacaoEvento || {};
const dadosProjeto = atuacao.dadosProjeto || {};
const dadosProcesso = atuacao.dadosProcesso || {};
const dadosOrientacaoDiscente = atuacao.dadosOrientacaoDiscente || {};
if (tipoLower.includes('docência') || tipoLower.includes('docencia')) {
const ies = dadosDocencia.ies?.sigla
? `${dadosDocencia.ies.sigla}${dadosDocencia.ies.nome}`
: dadosDocencia.ies?.nome;
const primary = joinParts([
dadosDocencia.programa?.nome || dadosDocencia.areaConhecimento?.nome || atuacao.descricao,
ies,
dadosDocencia.categoria,
]);
const secondary = joinParts([
dadosDocencia.tipoVinculo,
dadosDocencia.regimeTrabalho,
dadosDocencia.cargaHoraria ? `${dadosDocencia.cargaHoraria}h` : null,
]);
return { primary, secondary };
}
if (tipoLower.includes('emprego')) {
const primary = joinParts([
dadosEmprego.nomeEmpregador || atuacao.descricao,
dadosEmprego.emprego || dadosEmprego.vinculo,
dadosEmprego.atividade,
]);
const secondary = joinParts([
dadosEmprego.dtAdmissao,
dadosEmprego.dtDesligamento ? `até ${dadosEmprego.dtDesligamento}` : null,
dadosEmprego.profissao,
]);
return { primary, secondary };
}
if (tipoLower.includes('evento')) {
const primary = joinParts([
dadosEvento.nome || atuacao.descricao,
dadosEvento.unidadeOrganizacional,
dadosEvento.localEvento,
]);
const secondary = joinParts([
dadosEvento.atividade,
dadosEvento.tipo,
dadosEvento.codigo ? `Código ${dadosEvento.codigo}` : null,
]);
return { primary, secondary };
}
if (tipoLower.includes('projeto')) {
const primary = joinParts([
dadosProjeto.nome || atuacao.descricao,
dadosProjeto.programa?.nome,
dadosProjeto.ies?.sigla || dadosProjeto.ies?.nome,
]);
const secondary = joinParts([
dadosProjeto.situacao,
dadosProjeto.linhaPesquisa,
dadosProjeto.anoInicio ? `Início ${dadosProjeto.anoInicio}` : null,
]);
return { primary, secondary };
}
if (tipoLower.includes('orientação de discentes') || tipoLower.includes('orientacao de discentes')) {
const mestradoParts = [];
if (dadosOrientacaoDiscente.totalOrientacaoFinalizadaMestrado) {
mestradoParts.push(`Finalizadas ${dadosOrientacaoDiscente.totalOrientacaoFinalizadaMestrado}`);
}
if (dadosOrientacaoDiscente.totalOrientacaoAndamentoMestrado) {
mestradoParts.push(`Andamento ${dadosOrientacaoDiscente.totalOrientacaoAndamentoMestrado}`);
}
const doutoradoParts = [];
if (dadosOrientacaoDiscente.totalOrientacaoFinalizadaDoutorado) {
doutoradoParts.push(`Finalizadas ${dadosOrientacaoDiscente.totalOrientacaoFinalizadaDoutorado}`);
}
if (dadosOrientacaoDiscente.totalOrientacaoAndamentoDoutorado) {
doutoradoParts.push(`Andamento ${dadosOrientacaoDiscente.totalOrientacaoAndamentoDoutorado}`);
}
const primary = joinParts([
mestradoParts.length ? `Mestrado ${mestradoParts.join(' / ')}` : null,
doutoradoParts.length ? `Doutorado ${doutoradoParts.join(' / ')}` : null,
]);
const secondary = dadosOrientacaoDiscente.totalAcompanhamentoPosDoutorado
? `Pós-doc ${dadosOrientacaoDiscente.totalAcompanhamentoPosDoutorado}`
: null;
return { primary, secondary };
}
if (tipoLower.includes('processo')) {
const iesLabel = dadosProcesso.ies?.sigla || dadosProcesso.ies?.nome;
const primary = joinParts([dadosProcesso.nrProcesso || atuacao.descricao, iesLabel]);
const secondary = dadosProcesso.ies?.statusJuridico
? `IES ${dadosProcesso.ies.statusJuridico.trim()}`
: null;
return { primary, secondary };
}
const fallbackPrimary = joinParts([
atuacao.descricao,
getNestedValue(atuacao, 'procedencia.origem'),
]);
return { primary: fallbackPrimary, secondary: null };
};
const getAtuacaoColor = (tipo) => {
if (tipo.includes('Coordenação')) return 'atuacao-coordenacao';
if (tipo.includes('Consultor')) return 'atuacao-consultoria';
if (tipo.includes('Premiação')) return 'atuacao-premiacao';
if (tipo.includes('Avaliação')) return 'atuacao-avaliacao';
if (tipo.includes('Inscrição')) return 'atuacao-inscricao';
if (tipo.includes('Bolsista')) return 'atuacao-bolsa';
if (tipo.includes('Orientação')) return 'atuacao-orientacao';
return 'atuacao-outros';
};
const getAllDados = () => {
const allData = {};
const dataKeys = [
'dadosCoordenacaoArea',
'dadosHistoricoCoordenacaoArea',
'dadosConsultoria',
'dadosPremiacaoPremio',
'dadosParticipacaoPremio',
'dadosParticipacaoInscricaoPremio',
'dadosBolsistaCNPq',
'dadosOrientacao',
'dadosOrientacaoDiscente',
'dadosParticipacao',
'dadosParticipacaoEvento',
'dadosGestaoPrograma',
'dadosDocencia',
'dadosEmprego',
'dadosEvento',
'dadosProjeto',
'dadosProcesso',
'dadosAnaliseProcesso',
'dadosPapelPessoa',
];
const dateKeys = ['inicio', 'fim', 'inicioVinculacao', 'fimVinculacao', 'inicioSituacao', 'inativacaoSituacao'];
const dateData = {};
dataKeys.forEach(key => {
if (atuacao[key]) {
Object.entries(atuacao[key]).forEach(([k, v]) => {
if (dateKeys.includes(k)) {
dateData[k] = v;
} else {
allData[k] = v;
}
});
}
});
const baseKeys = ['descricao', 'quantidade', 'id', 'procedencia'];
baseKeys.forEach((key) => {
if (atuacao[key] !== null && atuacao[key] !== undefined && allData[key] === undefined) {
allData[key] = atuacao[key];
}
});
if (Object.keys(allData).length === 0) {
if (atuacao.inicio) allData.inicio = atuacao.inicio;
if (atuacao.fim) allData.fim = atuacao.fim;
Object.assign(allData, dateData);
}
return allData;
};
const dados = getAllDados();
const hasData = Object.keys(dados).length > 0;
const summary = buildSummary();
return (
<div className={`atuacao-card ${getAtuacaoColor(tipo)}`}>
<div className="atuacao-header" onClick={() => setExpanded(!expanded)}>
<span className="atuacao-index">#{index + 1}</span>
<div className="atuacao-main">
<span className="atuacao-tipo">{tipo}</span>
{summary.primary && <span className="atuacao-summary">{summary.primary}</span>}
</div>
<div className="atuacao-meta">
<div className="atuacao-periodo">
{atuacao.inicio && <span>{formatDate(atuacao.inicio)}</span>}
{atuacao.inicio && atuacao.fim && <span> - </span>}
{atuacao.fim ? <span>{formatDate(atuacao.fim)}</span> : atuacao.inicio && <span className="ativo">Atual</span>}
</div>
{summary.secondary && <div className="atuacao-meta-line">{summary.secondary}</div>}
</div>
<span className="atuacao-toggle">{expanded ? '' : '+'}</span>
</div>
{expanded && (
<div className="atuacao-dados">
{hasData ? (
<NestedObjectDisplay data={dados} />
) : (
<p className="empty-message">Sem dados adicionais</p>
)}
</div>
)}
</div>
);
};
const RawDataModal = ({ idPessoa, nome, onClose }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [viewMode, setViewMode] = useState('formatted');
const [filterType, setFilterType] = useState('all');
const [copyFeedback, setCopyFeedback] = useState(false);
const [downloadingPDF, setDownloadingPDF] = useState(false);
const fetchedRef = useRef(null);
useEffect(() => {
if (fetchedRef.current === idPessoa) {
return;
}
fetchedRef.current = idPessoa;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const result = await rankingService.getConsultorRaw(idPessoa);
setData(result);
} catch (err) {
setError(err.response?.data?.detail || err.message || 'Erro ao carregar dados');
} finally {
setLoading(false);
}
};
fetchData();
}, [idPessoa]);
const handleKeyDown = useCallback((e) => {
if (e.key === 'Escape') onClose();
}, [onClose]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}, [handleKeyDown]);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
setCopyFeedback(true);
setTimeout(() => setCopyFeedback(false), 2000);
} catch (err) {
console.error('Erro ao copiar:', err);
}
};
const handleDownloadPDF = async () => {
if (downloadingPDF) return;
setDownloadingPDF(true);
try {
await rankingService.downloadFichaPDF(idPessoa, nome);
} catch (err) {
console.error('Erro ao baixar PDF:', err);
alert('Erro ao gerar PDF. Tente novamente.');
} finally {
setDownloadingPDF(false);
}
};
const source = data?._source || {};
const dadosPessoais = source.dadosPessoais || {};
const atuacoes = source.atuacoes || [];
const tiposAtuacao = [...new Set(atuacoes.map(a => a.tipo))].sort();
const atuacoesFiltradas = sortAtuacoesByDate(
filterType === 'all'
? atuacoes
: atuacoes.filter(a => a.tipo === filterType)
);
const atuacoesPorTipo = tiposAtuacao.reduce((acc, tipo) => {
acc[tipo] = atuacoes.filter(a => a.tipo === tipo).length;
return acc;
}, {});
const modalContent = (
<div className="raw-modal-overlay" onClick={(e) => e.target.classList.contains('raw-modal-overlay') && onClose()}>
<div className="raw-modal">
<div className="raw-modal-header">
<div className="raw-modal-title">
<h2>Dados Completos ATUACAPES</h2>
<span className="raw-modal-subtitle">{nome} (ID: {idPessoa})</span>
</div>
<div className="raw-modal-header-actions">
<div className="btn-export-pdf-wrap">
<button
className={`btn-export-pdf ${downloadingPDF ? 'loading' : ''}`}
onClick={handleDownloadPDF}
disabled={downloadingPDF}
title="Exportar ficha completa em PDF"
>
<svg className="pdf-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
<span>PDF</span>
</button>
</div>
<div className="view-toggle">
<button
className={viewMode === 'formatted' ? 'active' : ''}
onClick={() => setViewMode('formatted')}
>
Formatado
</button>
<button
className={viewMode === 'json' ? 'active' : ''}
onClick={() => setViewMode('json')}
>
JSON
</button>
</div>
<button className="raw-modal-close" onClick={onClose}></button>
</div>
</div>
<div className="raw-modal-content">
{loading && (
<div className="raw-modal-loading">
<div className="spinner"></div>
<span>Carregando dados do Elasticsearch...</span>
</div>
)}
{error && (
<div className="raw-modal-error">
<span className="error-icon"></span>
<span>{error}</span>
</div>
)}
{!loading && !error && data && viewMode === 'formatted' && (
<div className="formatted-view">
<Section title="Dados Pessoais" icon="👤" defaultOpen={true}>
<div className="dados-pessoais-grid">
<DataField label="ID" value={source.id} />
<DataField label="Nome" value={dadosPessoais.nome} className="destaque" />
<DataField label="CPF" value={dadosPessoais.cpf} />
<DataField label="Email" value={dadosPessoais.email} />
<DataField label="Sexo" value={dadosPessoais.sexo} />
<DataField label="Nascimento" value={formatDate(dadosPessoais.nascimento)} />
<DataField label="Ano Óbito" value={dadosPessoais.anoObito} />
<DataField label="Nacionalidade" value={dadosPessoais.nacionalidade} />
<DataField label="País Nascimento" value={dadosPessoais.paisNascimento} />
<DataField label="UF Nascimento" value={dadosPessoais.ufNascimento} />
<DataField label="Cidade Nascimento" value={dadosPessoais.cidadeNascimento} />
<DataField label="Lattes" value={dadosPessoais.lattes} />
<DataField label="ORCID" value={dadosPessoais.orcid} />
</div>
</Section>
<Section title="Atuações" icon="📋" defaultOpen={true} count={atuacoes.length}>
{atuacoes.length > 0 && (
<>
<div className="atuacoes-summary">
{tiposAtuacao.map(tipo => (
<span
key={tipo}
className={`tipo-badge ${filterType === tipo ? 'active' : ''}`}
onClick={() => setFilterType(filterType === tipo ? 'all' : tipo)}
>
{tipo.replace('Histórico de ', '').substring(0, 25)}
<span className="tipo-count">{atuacoesPorTipo[tipo]}</span>
</span>
))}
</div>
<div className="atuacoes-list">
{atuacoesFiltradas.map((atuacao, idx) => (
<AtuacaoCard key={idx} atuacao={atuacao} index={idx} />
))}
</div>
</>
)}
{atuacoes.length === 0 && (
<p className="empty-message">Nenhuma atuação registrada</p>
)}
</Section>
<Section title="Metadados Elasticsearch" icon="🔍" defaultOpen={false}>
<div className="dados-pessoais-grid">
<DataField label="Index" value={data._index} />
<DataField label="Document ID" value={data._id} />
<DataField label="Score" value={data._score} />
</div>
</Section>
</div>
)}
{!loading && !error && data && viewMode === 'json' && (
<div className="json-view">
<div className="json-toolbar">
<button onClick={copyToClipboard} className={copyFeedback ? 'copied' : ''}>
{copyFeedback ? '✓ Copiado!' : 'Copiar JSON'}
</button>
</div>
<pre className="json-content">{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
<div className="raw-modal-footer">
<span className="raw-modal-info">
Fonte: Elasticsearch ATUACAPES | Index: {data?._index || 'atuacapes'} | {atuacoes.length} atuações
</span>
</div>
</div>
</div>
);
return ReactDOM.createPortal(modalContent, document.body);
};
export default RawDataModal;