feat(backend/frontend): integrar dados do Lattes no ranking
- Adicionar endpoint /consultor/{id}/lattes para buscar producoes
- Incluir id_lattes e titulacoes na resposta do ranking paginado
- Adicionar LattesSchema no backend
- Adicionar funcao getLattes no servico frontend
- Simplificar botao Producoes removendo estado loading desnecessario
This commit is contained in:
@@ -697,6 +697,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
|
||||
|
||||
const getTitulo = () => {
|
||||
switch (tipo) {
|
||||
case 'titulacao': return 'Titulação';
|
||||
case 'producoes_lattes': return 'Produções Lattes';
|
||||
case 'vinculo': return 'Vínculo de Consultoria';
|
||||
case 'coordenacao': return 'Coordenação CAPES';
|
||||
case 'premiacao': return 'Premiação';
|
||||
@@ -710,6 +712,8 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
|
||||
|
||||
const getIcone = () => {
|
||||
switch (tipo) {
|
||||
case 'titulacao': return '🎓';
|
||||
case 'producoes_lattes': return '📚';
|
||||
case 'vinculo': return '💼';
|
||||
case 'coordenacao': return '🎯';
|
||||
case 'premiacao': return '🏆';
|
||||
@@ -723,6 +727,90 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
|
||||
|
||||
const renderContent = () => {
|
||||
switch (tipo) {
|
||||
case 'titulacao': {
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Grau</span>
|
||||
<span className="modal-detalhe-value">{item.grau || 'N/A'}</span>
|
||||
</div>
|
||||
{item.area && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Área</span>
|
||||
<span className="modal-detalhe-value">{item.area}</span>
|
||||
</div>
|
||||
)}
|
||||
{(item.ies_sigla || item.ies_nome) && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Instituição</span>
|
||||
<span className="modal-detalhe-value">
|
||||
{item.ies_sigla && item.ies_nome ? `${item.ies_sigla} - ${item.ies_nome}` : (item.ies_sigla || item.ies_nome)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{item.pais && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">País</span>
|
||||
<span className="modal-detalhe-value">{item.pais}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.ano && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Ano</span>
|
||||
<span className="modal-detalhe-value">{item.ano}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'producoes_lattes': {
|
||||
const producoes = item.producoes || item.producoes_recentes || [];
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Total</span>
|
||||
<span className="modal-detalhe-value">{item.total_producoes ?? 0}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Bibliográfica</span>
|
||||
<span className="modal-detalhe-value">{item.producao_bibliografica ?? 0}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Técnica</span>
|
||||
<span className="modal-detalhe-value">{item.producao_tecnica ?? 0}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Orientações</span>
|
||||
<span className="modal-detalhe-value">{item.orientacoes_lattes ?? 0}</span>
|
||||
</div>
|
||||
{item.data_atualizacao_lattes && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Atualização</span>
|
||||
<span className="modal-detalhe-value">{formatDate(item.data_atualizacao_lattes)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Produções recentes</span>
|
||||
<span className="modal-detalhe-value">{producoes.length}</span>
|
||||
</div>
|
||||
{producoes.length > 0 ? (
|
||||
<ul className="modal-list" style={{ marginTop: '0.6rem' }}>
|
||||
{producoes.map((prod, idx) => (
|
||||
<li key={idx} className="modal-item">
|
||||
<span className="modal-item-main">{prod.titulo || 'Sem título'}</span>
|
||||
<span className="muted">{prod.ano || '-'}</span>
|
||||
{prod.natureza && <span className="badge">{prod.natureza}</span>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="modal-empty">Nenhuma produção encontrada.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'vinculo': {
|
||||
const periodo = item.periodo || {};
|
||||
const isAtivo = periodo.ativo ?? !periodo.fim;
|
||||
@@ -1366,6 +1454,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
const [tipoAtuacaoModal, setTipoAtuacaoModal] = useState(null);
|
||||
const [seloModal, setSeloModal] = useState(null);
|
||||
const [itemDetalhe, setItemDetalhe] = useState(null);
|
||||
const [loadingLattes, setLoadingLattes] = useState(false);
|
||||
const [pontuacaoModal, setPontuacaoModal] = useState(null);
|
||||
const [showInsights, setShowInsights] = useState(false);
|
||||
const cardRef = useRef(null);
|
||||
@@ -1618,6 +1707,71 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
</div>
|
||||
)}
|
||||
|
||||
{consultor.lattes?.id_lattes && (
|
||||
<div className="detail-section lattes-section">
|
||||
<h4>Curriculo Lattes</h4>
|
||||
<div className="lattes-content">
|
||||
<a
|
||||
href={consultor.lattes.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="lattes-link"
|
||||
>
|
||||
<span className="lattes-icon">📄</span>
|
||||
<span className="lattes-id">{consultor.lattes.id_lattes}</span>
|
||||
<span className="lattes-external">↗</span>
|
||||
</a>
|
||||
<div className="titulacoes-resumo">
|
||||
{consultor.lattes.titulacoes
|
||||
?.sort((a, b) => {
|
||||
const ordem = { 'Pos-Doutorado': 1, 'Doutorado': 2, 'Mestrado': 3, 'Graduacao': 4 };
|
||||
return (ordem[a.grau] || 99) - (ordem[b.grau] || 99);
|
||||
})
|
||||
.map((t, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="titulacao-badge titulacao-clicavel"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setItemDetalhe({
|
||||
item: t,
|
||||
tipo: 'titulacao'
|
||||
});
|
||||
}}
|
||||
title={`${t.grau}${t.area ? ` em ${t.area}` : ''}${t.ies_nome ? ` - ${t.ies_nome}` : ''}${t.pais ? ` (${t.pais})` : ''}`}
|
||||
>
|
||||
{t.grau}{t.ies_sigla ? ` (${t.ies_sigla})` : ''}{t.ano ? ` - ${t.ano}` : ''}
|
||||
</span>
|
||||
))}
|
||||
<span
|
||||
className="titulacao-badge titulacao-clicavel lattes-producoes"
|
||||
title="Ver producoes do Lattes"
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
const data = await rankingService.getLattes(consultor.id_pessoa);
|
||||
if (data.encontrado) {
|
||||
setItemDetalhe({
|
||||
item: { ...data, filtro: null, titulo: 'Producoes Lattes' },
|
||||
tipo: 'producoes_lattes'
|
||||
});
|
||||
} else if (data.motivo) {
|
||||
alert(data.motivo);
|
||||
} else {
|
||||
alert('Nenhuma producao encontrada no Lattes');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Erro ao buscar Lattes: ' + err.message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
📚 Producoes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{consultoria?.vinculos?.length > 0 && (
|
||||
<div className="extra-details">
|
||||
<h4>Vinculos de Consultoria</h4>
|
||||
|
||||
Reference in New Issue
Block a user