Files
ranking/frontend/src/components/ConsultorCard.jsx

2728 lines
115 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, useRef, useEffect, useMemo, memo } from 'react';
import { createPortal } from 'react-dom';
import './ConsultorCard.css';
import RawDataModal from './RawDataModal';
import { rankingService } from '../services/api';
const SELOS = {
CA: { codigo: 'CA', label: 'Coord. Área', cor: 'selo-ca', icone: '🎯' },
CAJ: { codigo: 'CAJ', label: 'Coord. Adjunto', cor: 'selo-caj', icone: '📋' },
CAJ_MP: { codigo: 'CAJ_MP', label: 'Coord. Adj. MP', cor: 'selo-caj-mp', icone: '📋' },
CAM: { codigo: 'CAM', label: 'Câmara Temática', cor: 'selo-cam', icone: '🏛️' },
PRESID_CAMARA: { codigo: 'PRESID_CAMARA', label: 'Presidente Câmara', cor: 'selo-camara', icone: '👑' },
CONS_ATIVO: { codigo: 'CONS_ATIVO', label: 'Consultor', cor: 'selo-cons-ativo', icone: '✅' },
AVAL_COMIS: { codigo: 'AVAL_COMIS', label: 'Avaliador', cor: 'selo-aval', icone: '⚖️' },
COORD_COMIS: { codigo: 'COORD_COMIS', label: 'Coord. Comissão', cor: 'selo-coord-comis', icone: '📊' },
AUTOR_GP: { codigo: 'AUTOR_GP', label: 'Autor GP', cor: 'selo-gp', icone: '🏆' },
AUTOR_PREMIO: { codigo: 'AUTOR_PREMIO', label: 'Autor Prêmio', cor: 'selo-premio', icone: '🥇' },
AUTOR_MENCAO: { codigo: 'AUTOR_MENCAO', label: 'Autor Menção', cor: 'selo-mencao', icone: '🥈' },
ORIENT_GP: { codigo: 'ORIENT_GP', label: 'Orient. GP', cor: 'selo-gp', icone: '🏆' },
ORIENT_PREMIO: { codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', cor: 'selo-orient-premio', icone: '🎖️' },
ORIENT_MENCAO: { codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', cor: 'selo-orient-mencao', icone: '📜' },
COORIENT_GP: { codigo: 'COORIENT_GP', label: 'Coorient. GP', cor: 'selo-gp', icone: '🏆' },
COORIENT_PREMIO: { codigo: 'COORIENT_PREMIO', label: 'Coorient. Prêmio', cor: 'selo-coorient-premio', icone: '🎖️' },
COORIENT_MENCAO: { codigo: 'COORIENT_MENCAO', label: 'Coorient. Menção', cor: 'selo-coorient-mencao', icone: '📜' },
ORIENT_TESE: { codigo: 'ORIENT_TESE', label: 'Orient. Tese', cor: 'selo-orient', icone: '📚' },
ORIENT_DISS: { codigo: 'ORIENT_DISS', label: 'Orient. Diss.', cor: 'selo-orient', icone: '📄' },
EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: '🎪' },
PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: '🔧' },
IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilíngue', cor: 'selo-idioma', icone: '🌐' },
};
const TIPOS_ATUACAO_CONFIG = {
'Coordenador': { cor: 'tipo-coordenador', icone: '🎯' },
'Consultor': { cor: 'tipo-consultor', icone: '💼' },
'Avaliador': { cor: 'tipo-avaliador', icone: '📋' },
'Premiado': { cor: 'tipo-premiado', icone: '🏆' },
'Orientador': { cor: 'tipo-orientador', icone: '🎓' },
'Bolsista CNPq': { cor: 'tipo-bolsista', icone: '🔬' },
'Inscrito Premio': { cor: 'tipo-inscrito', icone: '📝' },
'Projeto': { cor: 'tipo-projeto', icone: '📊' },
'Evento': { cor: 'tipo-evento', icone: '📅' },
};
const gerarSelos = (consultor) => {
const selos = [];
const normalizarIdioma = (valor) => (valor || '')
.toString()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim();
const coordenacoes = Array.isArray(consultor.coordenacoes_capes) ? consultor.coordenacoes_capes : [];
const coordCA = coordenacoes.filter((c) => c.codigo === 'CA');
const coordCAJ = coordenacoes.filter((c) => c.codigo === 'CAJ');
const coordCAJ_MP = coordenacoes.filter((c) => c.codigo === 'CAJ_MP');
const coordCAM = coordenacoes.filter((c) => c.codigo === 'CAM');
if (coordCA.length > 0) {
selos.push({ ...SELOS.CA, qtd: coordCA.length, hint: `Coordenador de Área (${coordCA.length}x)` });
}
if (coordCAJ.length > 0) {
selos.push({ ...SELOS.CAJ, qtd: coordCAJ.length, hint: `Coordenador Adjunto (${coordCAJ.length}x)` });
}
if (coordCAJ_MP.length > 0) {
selos.push({ ...SELOS.CAJ_MP, qtd: coordCAJ_MP.length, hint: `Coord. Adjunto MP (${coordCAJ_MP.length}x)` });
}
if (coordCAM.length > 0) {
selos.push({ ...SELOS.CAM, qtd: coordCAM.length, hint: `Câmara Temática (${coordCAM.length}x)` });
}
const isPresidCamaraVigente = coordenacoes.some(
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
);
if (isPresidCamaraVigente) {
selos.push({ ...SELOS.PRESID_CAMARA, qtd: 1, hint: 'Presidente Câmara Temática' });
}
const consultoria = consultor.consultoria;
if (consultoria && consultoria.codigo === 'CONS_ATIVO') {
selos.push({ ...SELOS.CONS_ATIVO, qtd: 1, hint: 'Consultor Ativo' });
}
const avaliacoes = Array.isArray(consultor.avaliacoes_comissao) ? consultor.avaliacoes_comissao : [];
const coordComissao = avaliacoes.filter((a) => (a.codigo || '').includes('COORD'));
const avalComissao = avaliacoes.filter((a) => (a.codigo || '').includes('AVAL'));
if (coordComissao.length > 0) {
selos.push({ ...SELOS.COORD_COMIS, qtd: coordComissao.length, hint: `Coord. Comissão (${coordComissao.length}x)` });
}
if (avalComissao.length > 0) {
selos.push({ ...SELOS.AVAL_COMIS, qtd: avalComissao.length, hint: `Avaliador Comissão (${avalComissao.length}x)` });
}
const premiacoes = Array.isArray(consultor.premiacoes) ? consultor.premiacoes : [];
const gerarSelosPorPapel = (papel, seloGP, seloPremio, seloMencao, hintPrefix) => {
const lista = premiacoes.filter((p) => (p.papel || '').toString().toLowerCase() === papel.toLowerCase());
const gp = lista.filter((p) => p.codigo === 'PREMIACAO_GP_AUTOR').length;
const premio = lista.filter((p) => p.codigo === 'PREMIACAO_AUTOR').length;
const mencao = lista.filter((p) => p.codigo === 'MENCAO_AUTOR').length;
if (gp > 0) selos.push({ ...seloGP, qtd: gp, hint: `${hintPrefix} - Grande Prêmio` });
if (premio > 0) selos.push({ ...seloPremio, qtd: premio, hint: `${hintPrefix} - Prêmio` });
if (mencao > 0) selos.push({ ...seloMencao, qtd: mencao, hint: `${hintPrefix} - Menção Honrosa` });
};
gerarSelosPorPapel('autor', SELOS.AUTOR_GP, SELOS.AUTOR_PREMIO, SELOS.AUTOR_MENCAO, 'Autor');
gerarSelosPorPapel('orientador', SELOS.ORIENT_GP, SELOS.ORIENT_PREMIO, SELOS.ORIENT_MENCAO, 'Orientador');
gerarSelosPorPapel('coorientador', SELOS.COORIENT_GP, SELOS.COORIENT_PREMIO, SELOS.COORIENT_MENCAO, 'Coorientador');
const orientacoes = Array.isArray(consultor.orientacoes) ? consultor.orientacoes : [];
const orientTese = orientacoes.filter((o) => o.codigo === 'ORIENT_TESE' || o.codigo === 'ORIENT_TESE_PREM');
const orientDiss = orientacoes.filter((o) => o.codigo === 'ORIENT_DISS' || o.codigo === 'ORIENT_DISS_PREM');
if (orientTese.length > 0) {
selos.push({ ...SELOS.ORIENT_TESE, qtd: orientTese.length, hint: `Orient. Tese (${orientTese.length}x)` });
}
if (orientDiss.length > 0) {
selos.push({ ...SELOS.ORIENT_DISS, qtd: orientDiss.length, hint: `Orient. Dissertação (${orientDiss.length}x)` });
}
const participacoes = Array.isArray(consultor.participacoes) ? consultor.participacoes : [];
const eventos = participacoes.filter((p) => p.codigo === 'EVENTO');
const projetos = participacoes.filter((p) => p.codigo === 'PROJ');
if (eventos.length > 0) {
selos.push({ ...SELOS.EVENTO, qtd: eventos.length, hint: `Eventos (${eventos.length}x)` });
}
if (projetos.length > 0) {
selos.push({ ...SELOS.PROJ, qtd: projetos.length, hint: `Projetos (${projetos.length}x)` });
}
const idiomas = Array.isArray(consultor.idiomas) ? consultor.idiomas : [];
const idiomasUnicosMap = new Map();
let temPortugues = false;
for (const idioma of idiomas) {
const nome = idioma?.idioma || idioma?.nome || idioma?.descricao || '';
const chave = normalizarIdioma(nome);
if (!chave) continue;
if (!idiomasUnicosMap.has(chave)) {
idiomasUnicosMap.set(chave, nome);
}
if (chave.includes('portugues') || chave.includes('portuguese')) {
temPortugues = true;
}
}
const idiomasUnicos = Array.from(idiomasUnicosMap.values());
const totalIdiomas = idiomasUnicos.length + (!temPortugues && idiomasUnicos.length > 0 ? 1 : 0);
if (totalIdiomas >= 3) {
selos.push({
...SELOS.IDIOMA_MULTILINGUE,
qtd: totalIdiomas,
hint: `Multilingue: ${idiomasUnicos.join(', ')}`,
});
}
return selos;
};
const SELOS_COM_DADOS = [
'CA', 'CAJ', 'CAJ_MP', 'CAM', 'PRESID_CAMARA',
'CONS_ATIVO',
'AVAL_COMIS', 'COORD_COMIS',
'AUTOR_GP', 'AUTOR_PREMIO', 'AUTOR_MENCAO',
'ORIENT_GP', 'ORIENT_PREMIO', 'ORIENT_MENCAO',
'COORIENT_GP', 'COORIENT_PREMIO', 'COORIENT_MENCAO',
'ORIENT_TESE', 'ORIENT_DISS',
'EVENTO', 'PROJ',
'IDIOMA_MULTILINGUE',
];
const SelosBadges = ({ selos, compacto = false, onSeloClick }) => {
if (!selos || selos.length === 0) return null;
const handleClick = (e, selo) => {
if (onSeloClick && SELOS_COM_DADOS.includes(selo.codigo)) {
e.preventDefault();
e.stopPropagation();
onSeloClick(selo);
}
};
return (
<div className={`selos-container ${compacto ? 'selos-compacto' : ''}`}>
{selos.map((selo, idx) => {
const temDados = SELOS_COM_DADOS.includes(selo.codigo);
return (
<span
key={idx}
className={`selo ${selo.cor} ${onSeloClick && temDados ? 'selo-clicavel' : ''}`}
title={selo.hint || `${selo.label}${selo.qtd > 1 ? ` (${selo.qtd}x)` : ''}`}
onMouseDown={(e) => onSeloClick && temDados && e.stopPropagation()}
onClick={(e) => handleClick(e, selo)}
>
<span className="selo-icone">{selo.icone}</span>
{!compacto && <span className="selo-label">{selo.label}</span>}
{!compacto && <span className="selo-qtd">{selo.qtd || 1}</span>}
</span>
);
})}
</div>
);
};
const TiposAtuacaoBadges = ({ tipos, exibirTodos = false, onBadgeClick, consultor }) => {
if (!tipos || tipos.length === 0) return null;
const tiposExibidos = exibirTodos ? tipos : tipos.slice(0, 4);
const tiposOcultos = !exibirTodos && tipos.length > 4 ? tipos.length - 4 : 0;
const handleClick = (e, tipo) => {
if (onBadgeClick) {
e.preventDefault();
e.stopPropagation();
onBadgeClick(tipo);
}
};
return (
<div className={`tipos-atuacao-container ${exibirTodos ? 'tipos-expandido' : ''}`}>
{tiposExibidos.map((tipo, idx) => {
const config = TIPOS_ATUACAO_CONFIG[tipo] || { cor: 'tipo-default', icone: '📌' };
return (
<span
key={idx}
className={`tipo-atuacao ${config.cor} ${onBadgeClick ? 'tipo-clicavel' : ''}`}
title={`Clique para ver detalhes de ${tipo}`}
onMouseDown={(e) => onBadgeClick && e.stopPropagation()}
onClick={(e) => handleClick(e, tipo)}
>
<span className="tipo-icone">{config.icone}</span>
<span className="tipo-label">{tipo}</span>
</span>
);
})}
{tiposOcultos > 0 && (
<span className="tipo-atuacao tipo-mais" title={tipos.slice(4).join(', ')}>
+{tiposOcultos}
</span>
)}
</div>
);
};
const TipoAtuacaoModal = ({ tipo, consultor, onClose }) => {
if (!tipo || !consultor) return null;
const formatDate = (dateStr) => {
if (!dateStr) return 'Atual';
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const renderContent = () => {
switch (tipo) {
case 'Coordenador': {
const coords = consultor.coordenacoes_capes || [];
if (coords.length === 0) return <p className="modal-empty">Sem dados de coordenação</p>;
return (
<div className="modal-list">
{[...coords].sort((a, b) => new Date(b.inicio || 0) - new Date(a.inicio || 0)).map((c, i) => (
<div key={i} className="modal-item">
<span className="badge">{c.codigo}</span>
<span className="modal-item-main">{c.area_avaliacao}</span>
{c.presidente && <span className="badge badge-premiado">👑 Presidente</span>}
<span className="muted">{formatDate(c.inicio)} - {formatDate(c.fim)}</span>
</div>
))}
</div>
);
}
case 'Consultor': {
const cons = consultor.consultoria;
if (!cons) return <p className="modal-empty">Sem dados de consultoria</p>;
const vinculos = cons.vinculos || [];
return (
<div className="modal-list">
<div className="modal-summary">
<span>Anos consecutivos: <strong>{cons.anos_consecutivos || 0}</strong></span>
<span>Início: <strong>{formatDate(cons.inicio)}</strong></span>
</div>
{vinculos.length > 0 && (
<>
<h5>Vínculos ({vinculos.length})</h5>
{[...vinculos].sort((a, b) => new Date(b.periodo?.inicio || 0) - new Date(a.periodo?.inicio || 0)).map((v, i) => {
const isAtivo = v.periodo?.ativo ?? !v.periodo?.fim;
return (
<div key={i} className="modal-item">
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
{isAtivo ? 'ATIVO' : 'ENCERRADO'}
</span>
<span className="modal-item-main">
{v.ies ? (v.ies.sigla ? `${v.ies.sigla} - ${v.ies.nome || ''}` : v.ies.nome) : 'IES não informada'}
</span>
<span className="muted">{formatDate(v.periodo?.inicio)} - {formatDate(v.periodo?.fim)}</span>
</div>
);
})}
</>
)}
</div>
);
}
case 'Avaliador': {
const avals = consultor.avaliacoes_comissao || [];
if (avals.length === 0) return <p className="modal-empty">Sem avaliações de comissão</p>;
return (
<div className="modal-list">
{[...avals].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((a, i) => (
<div key={i} className="modal-item">
<span className="badge">{a.codigo}</span>
<span className="modal-item-main">{a.nome_comissao || a.premio || a.descricao || '-'}</span>
<span className="muted">{a.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'Premiado': {
const prems = consultor.premiacoes || [];
if (prems.length === 0) return <p className="modal-empty">Sem premiações</p>;
return (
<div className="modal-list">
{[...prems].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">{p.codigo}</span>
<span className="modal-item-main">{p.nome_premio || p.premio || '-'}</span>
<span className="muted">{p.papel || ''}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'Orientador': {
const orients = consultor.orientacoes || [];
if (orients.length === 0) return <p className="modal-empty">Sem orientações</p>;
const contagem = {};
orients.forEach(o => { contagem[o.codigo] = (contagem[o.codigo] || 0) + 1; });
const labels = {
ORIENT_POS_DOC: '🔬 Pós-Doutorado',
ORIENT_TESE: '📚 Tese (Doutorado)',
ORIENT_DISS: '📄 Dissertação (Mestrado)',
CO_ORIENT_POS_DOC: '🔬 Coorient. Pós-Doc',
CO_ORIENT_TESE: '📚 Coorient. Tese',
CO_ORIENT_DISS: '📄 Coorient. Diss.'
};
return (
<div className="modal-list">
<div className="modal-summary">Total: <strong>{orients.length}</strong> orientações</div>
{Object.entries(contagem).map(([cod, qtd]) => (
<div key={cod} className="modal-item">
<span className="modal-item-main">{labels[cod] || cod}</span>
<span className="pontos">{qtd}x</span>
</div>
))}
</div>
);
}
case 'Bolsista CNPq': {
const bolsas = consultor.bolsas_cnpq || [];
if (bolsas.length === 0) return <p className="modal-empty">Sem bolsas CNPq</p>;
return (
<div className="modal-list">
{bolsas.map((b, i) => (
<div key={i} className="modal-item">
<span className="badge">BPQ</span>
<span className="modal-item-main">Nível {b.nivel || 'N/A'}</span>
</div>
))}
</div>
);
}
case 'Inscrito Premio': {
const inscs = consultor.inscricoes || [];
if (inscs.length === 0) return <p className="modal-empty">Sem inscrições em prêmios</p>;
return (
<div className="modal-list">
{[...inscs].sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((ins, i) => (
<div key={i} className="modal-item">
<span className="badge">{ins.codigo}</span>
<span className="modal-item-main">{ins.evento || ins.premio || ins.descricao || '-'}</span>
<span className="muted">{ins.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'Projeto': {
const parts = (consultor.participacoes || []).filter(p => p.codigo === 'PROJ');
if (parts.length === 0) return <p className="modal-empty">Sem projetos</p>;
return (
<div className="modal-list">
{[...parts].sort((a, b) => (b.ano || 0) - (a.ano || 0)).slice(0, 20).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">PROJ</span>
<span className="modal-item-main">{p.descricao || p.tipo || '-'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
{parts.length > 20 && <p className="muted">... e mais {parts.length - 20} projetos</p>}
</div>
);
}
case 'Evento': {
const parts = (consultor.participacoes || []).filter(p => p.codigo === 'EVENTO');
if (parts.length === 0) return <p className="modal-empty">Sem eventos</p>;
return (
<div className="modal-list">
{[...parts].sort((a, b) => (b.ano || 0) - (a.ano || 0)).slice(0, 20).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">EVENTO</span>
<span className="modal-item-main">{p.descricao || p.tipo || '-'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
{parts.length > 20 && <p className="muted">... e mais {parts.length - 20} eventos</p>}
</div>
);
}
default:
return <p className="modal-empty">Tipo não reconhecido</p>;
}
};
const config = TIPOS_ATUACAO_CONFIG[tipo] || { cor: 'tipo-default', icone: '📌' };
return createPortal(
<div className="tipo-modal-overlay" onClick={onClose}>
<div className="tipo-modal" onClick={(e) => e.stopPropagation()}>
<div className="tipo-modal-header">
<span className={`tipo-atuacao ${config.cor}`}>
<span className="tipo-icone">{config.icone}</span>
<span className="tipo-label">{tipo}</span>
</span>
<button className="tipo-modal-close" onClick={onClose}></button>
</div>
<div className="tipo-modal-body">
{renderContent()}
</div>
</div>
</div>,
document.body
);
};
const SeloModal = ({ selo, consultor, onClose }) => {
if (!selo || !consultor) return null;
const formatDate = (dateStr) => {
if (!dateStr) return 'Atual';
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const renderContent = () => {
switch (selo.codigo) {
case 'PRESID_CAMARA': {
const coords = (consultor.coordenacoes_capes || []).filter(c => c.presidente);
if (coords.length === 0) return <p className="modal-empty">Sem dados</p>;
return (
<div className="modal-list">
{coords.map((c, i) => (
<div key={i} className="modal-item">
<span className="badge">{c.codigo}</span>
<span className="modal-item-main">{c.area_avaliacao}</span>
<span className="muted">{formatDate(c.inicio)} - {formatDate(c.fim)}</span>
</div>
))}
</div>
);
}
case 'COORD_PPG':
return <p className="modal-empty">Coordenador de Programa de Pós-Graduação</p>;
case 'BPQ': {
const bolsas = consultor.bolsas_cnpq || [];
if (bolsas.length === 0) return <p className="modal-empty">Sem bolsas</p>;
return (
<div className="modal-list">
<div className="modal-summary">Total: <strong>{bolsas.length}</strong> bolsa(s) BPQ</div>
{bolsas.map((b, i) => (
<div key={i} className="modal-item">
<span className="badge">BPQ</span>
<span className="modal-item-main">Nível {b.nivel || 'N/A'}</span>
</div>
))}
</div>
);
}
case 'AUTOR_GP':
case 'AUTOR_PREMIO':
case 'AUTOR_MENCAO': {
const codMap = { AUTOR_GP: 'PREMIACAO_GP_AUTOR', AUTOR_PREMIO: 'PREMIACAO_AUTOR', AUTOR_MENCAO: 'MENCAO_AUTOR' };
const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'autor');
if (prems.length === 0) return <p className="modal-empty">Sem premiações</p>;
return (
<div className="modal-list">
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">{p.codigo}</span>
<span className="modal-item-main">{p.nome_premio || p.premio || '-'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'ORIENT_GP':
case 'ORIENT_PREMIO':
case 'ORIENT_MENCAO': {
const codMap = { ORIENT_GP: 'PREMIACAO_GP_AUTOR', ORIENT_PREMIO: 'PREMIACAO_AUTOR', ORIENT_MENCAO: 'MENCAO_AUTOR' };
const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'orientador');
if (prems.length === 0) return <p className="modal-empty">Sem premiações como orientador</p>;
return (
<div className="modal-list">
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">{p.codigo}</span>
<span className="modal-item-main">{p.nome_premio || p.premio || '-'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'COORIENT_GP':
case 'COORIENT_PREMIO':
case 'COORIENT_MENCAO': {
const codMap = { COORIENT_GP: 'PREMIACAO_GP_AUTOR', COORIENT_PREMIO: 'PREMIACAO_AUTOR', COORIENT_MENCAO: 'MENCAO_AUTOR' };
const prems = (consultor.premiacoes || []).filter(p => p.codigo === codMap[selo.codigo] && (p.papel || '').toLowerCase() === 'coorientador');
if (prems.length === 0) return <p className="modal-empty">Sem premiações como coorientador</p>;
return (
<div className="modal-list">
{prems.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">{p.codigo}</span>
<span className="modal-item-main">{p.nome_premio || p.premio || '-'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'ORIENT_TESE':
case 'ORIENT_DISS':
case 'ORIENT_POS_DOC':
case 'CO_ORIENT_TESE':
case 'CO_ORIENT_DISS':
case 'CO_ORIENT_POS_DOC': {
const orients = consultor.orientacoes || [];
const lista = orients.filter(o => o.codigo === selo.codigo);
const isCoorient = selo.codigo.startsWith('CO_');
const tipoLabel = {
ORIENT_TESE: 'Teses de Doutorado',
ORIENT_DISS: 'Dissertações de Mestrado',
ORIENT_POS_DOC: 'Pós-Doutorados',
CO_ORIENT_TESE: 'Coorientações de Tese',
CO_ORIENT_DISS: 'Coorientações de Dissertação',
CO_ORIENT_POS_DOC: 'Coorientações de Pós-Doc'
};
const premiadas = lista.filter(o => o.premiada);
return (
<div className="modal-list">
<div className="modal-summary">
<span>Total: <strong>{lista.length}</strong> {tipoLabel[selo.codigo]?.toLowerCase() || 'orientações'}</span>
{premiadas.length > 0 && <span>Premiadas: <strong>{premiadas.length}</strong> 🏆</span>}
</div>
<div className="modal-item" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: '0.5rem' }}>
<p style={{ margin: 0, color: 'var(--muted)', fontSize: '0.8rem' }}>
{isCoorient ? 'Coorientações' : 'Orientações'} de {selo.codigo.includes('TESE') ? 'Doutorado' : selo.codigo.includes('DISS') ? 'Mestrado' : 'Pós-Doutorado'} concluídas.
</p>
{premiadas.length > 0 && (
<p style={{ margin: 0, color: 'var(--gold)', fontSize: '0.8rem' }}>
🏆 {premiadas.length} orientação(ões) premiada(s) no Prêmio CAPES de Tese
</p>
)}
<p style={{ margin: 0, color: 'var(--muted)', fontSize: '0.75rem', fontStyle: 'italic' }}>
Dados agregados do currículo Lattes via ATUACAPES.
</p>
</div>
</div>
);
}
case 'MB_BANCA_POS_DOC':
case 'MB_BANCA_TESE':
case 'MB_BANCA_DISS': {
const bancas = consultor.membros_banca || [];
const lista = bancas.filter(b => b.codigo === selo.codigo);
const tipoLabel = {
MB_BANCA_POS_DOC: 'Bancas de Pós-Doutorado',
MB_BANCA_TESE: 'Bancas de Doutorado',
MB_BANCA_DISS: 'Bancas de Mestrado'
};
if (lista.length === 0) return <p className="modal-empty">Sem dados de bancas</p>;
return (
<div className="modal-list">
<div className="modal-summary">
Total: <strong>{lista.length}</strong> {tipoLabel[selo.codigo]?.toLowerCase() || 'bancas'}
</div>
{lista.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((b, i) => (
<div key={i} className="modal-item">
<span className="badge">{b.nivel || b.tipo || selo.codigo}</span>
<span className="modal-item-main">{b.tipo || tipoLabel[selo.codigo]}</span>
<span className="muted">{b.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'EVENTO': {
const participacoes = consultor.participacoes || [];
const eventos = participacoes.filter(p => p.codigo === 'EVENTO');
if (eventos.length === 0) return <p className="modal-empty">Sem eventos</p>;
return (
<div className="modal-list">
<div className="modal-summary">
Total: <strong>{eventos.length}</strong> participação(ões) em eventos
</div>
{eventos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((e, i) => (
<div key={i} className="modal-item">
<span className="badge">EVENTO</span>
<span className="modal-item-main">{e.descricao || e.tipo || 'Evento'}</span>
<span className="muted">{e.ano || '-'}</span>
</div>
))}
</div>
);
}
case 'PROJ': {
const participacoes = consultor.participacoes || [];
const projetos = participacoes.filter(p => p.codigo === 'PROJ');
if (projetos.length === 0) return <p className="modal-empty">Sem projetos</p>;
return (
<div className="modal-list">
<div className="modal-summary">
Total: <strong>{projetos.length}</strong> participação(ões) em projetos
</div>
{projetos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => (
<div key={i} className="modal-item">
<span className="badge">PROJ</span>
<span className="modal-item-main">{p.descricao || p.tipo || 'Projeto'}</span>
<span className="muted">{p.ano || '-'}</span>
</div>
))}
</div>
);
}
default:
return <p className="modal-empty">Sem dados detalhados para este selo</p>;
}
};
return createPortal(
<div className="tipo-modal-overlay" onClick={onClose}>
<div className="tipo-modal" onClick={(e) => e.stopPropagation()}>
<div className="tipo-modal-header">
<span className={`selo ${selo.cor}`} style={{ fontSize: '0.85rem', padding: '0.35rem 0.7rem' }}>
<span className="selo-icone" style={{ fontSize: '1rem' }}>{selo.icone}</span>
<span className="selo-label">{selo.label}</span>
<span className="selo-qtd">{selo.qtd}</span>
</span>
<button className="tipo-modal-close" onClick={onClose}></button>
</div>
<div className="tipo-modal-body">
{renderContent()}
</div>
</div>
</div>,
document.body
);
};
const ItemDetalheModal = ({ item, tipo, onClose }) => {
const [subDetalhe, setSubDetalhe] = useState(null);
if (!item || !tipo) return null;
const formatDate = (dateStr) => {
if (!dateStr) return 'N/A';
if (dateStr.includes('/')) {
const parts = dateStr.split(' ')[0].split('/');
if (parts.length === 3) {
return `${parts[0]}/${parts[1]}/${parts[2]}`;
}
}
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const currentItem = subDetalhe?.item || item;
const currentTipo = subDetalhe?.tipo || tipo;
const getTitulo = () => {
switch (currentTipo) {
case 'titulacao': return 'Titulação';
case 'producoes_lattes': return 'Currículo Lattes';
case 'vinculo': return 'Vínculo de Consultoria';
case 'coordenacao': return 'Coordenação CAPES';
case 'premiacao': return 'Premiação';
case 'avaliacao': return 'Avaliação de Comissão';
case 'inscricao': return 'Inscrição em Prêmio';
case 'participacao': return currentItem.codigo === 'PROJ' ? 'Projeto' : 'Evento';
case 'orientacao': return 'Orientação';
case 'orientacao_lattes': return 'Orientação Concluída';
case 'idioma': return 'Idioma';
case 'docencia': return 'Docência em PPG';
case 'emprego': return 'Vínculo Empregatício';
case 'projeto': return 'Projeto de Pesquisa';
case 'premiacao_lattes': return 'Premiação';
default: return 'Detalhes';
}
};
const getIcone = () => {
switch (currentTipo) {
case 'titulacao': return '🎓';
case 'producoes_lattes': return '📚';
case 'vinculo': return '💼';
case 'coordenacao': return '🎯';
case 'premiacao': return '🏆';
case 'avaliacao': return '📋';
case 'inscricao': return '📝';
case 'participacao': return currentItem.codigo === 'PROJ' ? '📊' : '📅';
case 'orientacao': return '🎓';
case 'orientacao_lattes': return '👨‍🏫';
case 'idioma': return '🌐';
case 'docencia': return '👨‍🏫';
case 'emprego': return '🏢';
case 'projeto': return '📊';
case 'premiacao_lattes': return '🏆';
default: return '📄';
}
};
const handleVoltar = () => {
setSubDetalhe(null);
};
const renderContent = () => {
switch (currentTipo) {
case 'titulacao': {
const it = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Grau Acadêmico</span>
<span className="modal-detalhe-value">{it.grau || 'N/A'}</span>
</div>
{it.area && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área de Conhecimento</span>
<span className="modal-detalhe-value">{it.area}</span>
</div>
)}
{it.area_avaliacao && it.area_avaliacao !== it.area && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área de Avaliação</span>
<span className="modal-detalhe-value">{it.area_avaliacao}</span>
</div>
)}
{(it.ies_sigla || it.ies_nome) && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">
{it.ies_sigla && it.ies_nome ? `${it.ies_sigla} - ${it.ies_nome}` : (it.ies_sigla || it.ies_nome)}
</span>
</div>
)}
{it.ies_status && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Natureza Jurídica</span>
<span className="modal-detalhe-value">{it.ies_status}</span>
</div>
)}
{it.programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Programa</span>
<span className="modal-detalhe-value">{it.programa}</span>
</div>
)}
{it.codigo_programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código do Programa</span>
<span className="modal-detalhe-value">{it.codigo_programa}</span>
</div>
)}
{it.programa_modalidade && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Modalidade</span>
<span className="modal-detalhe-value">{it.programa_modalidade}</span>
</div>
)}
{it.programa_situacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação Programa</span>
<span className="modal-detalhe-value">{it.programa_situacao}</span>
</div>
)}
{it.pais && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">País</span>
<span className="modal-detalhe-value">{it.pais}</span>
</div>
)}
{it.ano && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano de Conclusão</span>
<span className="modal-detalhe-value">{it.ano}</span>
</div>
)}
{it.inicio && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Início</span>
<span className="modal-detalhe-value">{formatDate(it.inicio)}</span>
</div>
)}
{it.fim && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Término</span>
<span className="modal-detalhe-value">{formatDate(it.fim)}</span>
</div>
)}
</div>
);
}
case 'orientacao_lattes': {
const o = currentItem;
return (
<div className="modal-detalhe-content">
{o.orientando && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Orientando</span>
<span className="modal-detalhe-value">{o.orientando}</span>
</div>
)}
{o.tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="modal-detalhe-value">{o.tipo}</span>
</div>
)}
{o.titulo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Título</span>
<span className="modal-detalhe-value" style={{ fontSize: '0.85rem' }}>{o.titulo}</span>
</div>
)}
{o.ano && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{o.ano}</span>
</div>
)}
{o.programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Programa</span>
<span className="modal-detalhe-value">{o.programa}</span>
</div>
)}
{o.ies && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">{o.ies}</span>
</div>
)}
</div>
);
}
case 'idioma': {
const i = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Idioma</span>
<span className="modal-detalhe-value">{i.idioma || 'N/A'}</span>
</div>
{i.proficiencia_leitura && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Leitura</span>
<span className="modal-detalhe-value">{i.proficiencia_leitura}</span>
</div>
)}
{i.proficiencia_escrita && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Escrita</span>
<span className="modal-detalhe-value">{i.proficiencia_escrita}</span>
</div>
)}
{i.proficiencia_fala && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Fala</span>
<span className="modal-detalhe-value">{i.proficiencia_fala}</span>
</div>
)}
{i.proficiencia_compreensao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Compreensão</span>
<span className="modal-detalhe-value">{i.proficiencia_compreensao}</span>
</div>
)}
</div>
);
}
case 'docencia': {
const d = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Programa</span>
<span className="modal-detalhe-value">{d.programa || 'N/A'}</span>
</div>
{d.codigo_programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código</span>
<span className="modal-detalhe-value">{d.codigo_programa}</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">{d.ies_nome} ({d.ies_sigla})</span>
</div>
{d.modalidade && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Modalidade</span>
<span className="modal-detalhe-value">{d.modalidade}</span>
</div>
)}
{d.situacao_programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação</span>
<span className="modal-detalhe-value">{d.situacao_programa}</span>
</div>
)}
{d.area && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área</span>
<span className="modal-detalhe-value">{d.area}</span>
</div>
)}
{d.area_avaliacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área de Avaliação</span>
<span className="modal-detalhe-value">{d.area_avaliacao}</span>
</div>
)}
{d.categoria && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Categoria</span>
<span className="modal-detalhe-value">{d.categoria}</span>
</div>
)}
{d.tipo_vinculo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo de Vínculo</span>
<span className="modal-detalhe-value">{d.tipo_vinculo}</span>
</div>
)}
{d.regime_trabalho && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Regime de Trabalho</span>
<span className="modal-detalhe-value">{d.regime_trabalho}</span>
</div>
)}
{d.carga_horaria && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Carga Horária</span>
<span className="modal-detalhe-value">{d.carga_horaria}h</span>
</div>
)}
{d.linhas_pesquisa_ativas?.length > 0 && (
<>
<h5 className="modal-section-title">Linhas de Pesquisa Ativas ({d.linhas_pesquisa_ativas.length} de {d.total_linhas_pesquisa})</h5>
<ul className="modal-list">
{d.linhas_pesquisa_ativas.map((linha, idx) => (
<li key={idx} className="modal-item">
<span className="modal-item-main">{linha}</span>
</li>
))}
</ul>
</>
)}
</div>
);
}
case 'emprego': {
const e = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Empregador</span>
<span className="modal-detalhe-value">{e.empregador || 'N/A'}</span>
</div>
{e.cnpj && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">CNPJ</span>
<span className="modal-detalhe-value">{e.cnpj}</span>
</div>
)}
{e.tipo_emprego && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="modal-detalhe-value">{e.tipo_emprego}</span>
</div>
)}
{e.atividade && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Atividade</span>
<span className="modal-detalhe-value">{e.atividade}</span>
</div>
)}
{e.vinculo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Vínculo</span>
<span className="modal-detalhe-value">{e.vinculo}</span>
</div>
)}
{e.profissao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Profissão</span>
<span className="modal-detalhe-value">{e.profissao}</span>
</div>
)}
{e.periodos?.length > 0 && (
<>
<h5 className="modal-section-title">Períodos ({e.periodos.length})</h5>
<ul className="modal-list">
{e.periodos.map((p, idx) => (
<li key={idx} className="modal-item">
<span className="modal-item-main">
{p.inicio ? formatDate(p.inicio) : 'Sem data'} a {p.fim ? formatDate(p.fim) : 'Atual'}
</span>
</li>
))}
</ul>
</>
)}
</div>
);
}
case 'projeto': {
const p = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Nome</span>
<span className="modal-detalhe-value">{p.nome || 'N/A'}</span>
</div>
{p.situacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação</span>
<span className={`badge ${p.situacao === 'EM ANDAMENTO' ? 'badge-ativo' : 'badge-historico'}`}>
{p.situacao}
</span>
</div>
)}
{p.ano_inicio && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano de Início</span>
<span className="modal-detalhe-value">{p.ano_inicio}</span>
</div>
)}
{p.ies_sigla && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">{p.ies_sigla}</span>
</div>
)}
{p.programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Programa</span>
<span className="modal-detalhe-value">{p.programa}</span>
</div>
)}
{p.area && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área</span>
<span className="modal-detalhe-value">{p.area}</span>
</div>
)}
{p.linha_pesquisa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Linha de Pesquisa</span>
<span className="modal-detalhe-value">{p.linha_pesquisa}</span>
</div>
)}
</div>
);
}
case 'premiacao_lattes': {
const pr = currentItem;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Evento</span>
<span className="modal-detalhe-value">{pr.evento || pr.premio || 'N/A'}</span>
</div>
{pr.premiacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="modal-detalhe-value">{pr.premiacao}</span>
</div>
)}
{pr.ano && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{pr.ano}</span>
</div>
)}
{pr.papel && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Papel</span>
<span className="modal-detalhe-value">{pr.papel}</span>
</div>
)}
{pr.situacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação</span>
<span className="modal-detalhe-value">{pr.situacao}</span>
</div>
)}
{pr.ies_sigla && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">{pr.ies_sigla}</span>
</div>
)}
{pr.programa && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Programa</span>
<span className="modal-detalhe-value">{pr.programa}</span>
</div>
)}
{pr.area && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área</span>
<span className="modal-detalhe-value">{pr.area}</span>
</div>
)}
{pr.produto_nome && (
<>
<h5 className="modal-section-title">Produto</h5>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Título</span>
<span className="modal-detalhe-value">{pr.produto_nome}</span>
</div>
{pr.produto_tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="modal-detalhe-value">{pr.produto_tipo}</span>
</div>
)}
{pr.produto_autor && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Autor</span>
<span className="modal-detalhe-value">{pr.produto_autor}</span>
</div>
)}
</>
)}
</div>
);
}
case 'producoes_lattes': {
const titulacoes = item.titulacoes || [];
const idiomas = item.idiomas || [];
const orientacoes = item.orientacoes_concluidas || [];
const endereco = item.endereco_profissional;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">ID Lattes</span>
<a
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="modal-detalhe-value lattes-link-inline"
>
{item.id_lattes}
</a>
</div>
{item.nacionalidade && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Nacionalidade</span>
<span className="modal-detalhe-value">{item.nacionalidade}</span>
</div>
)}
{titulacoes.length > 0 && (
<>
<h5 className="modal-section-title">Formação Acadêmica ({titulacoes.length})</h5>
<ul className="modal-list">
{titulacoes.map((t, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: t, tipo: 'titulacao' })}
>
<span className="modal-item-main">
<strong>{t.grau}</strong>
{t.area && ` em ${t.area}`}
</span>
<span className="modal-item-detail">
{t.ies_sigla || t.ies_nome}
{t.ano && ` (${t.ano})`}
</span>
{t.programa && (
<span className="modal-item-sub muted">Programa: {t.programa}</span>
)}
<span className="modal-item-arrow"></span>
</li>
))}
</ul>
</>
)}
{idiomas.length > 0 && (
<>
<h5 className="modal-section-title">Idiomas ({idiomas.length})</h5>
<ul className="modal-list">
{idiomas.map((i, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: i, tipo: 'idioma' })}
>
<span className="modal-item-main">{i.idioma}</span>
<span className="modal-item-detail muted">
{[
i.proficiencia_leitura && `Leitura: ${i.proficiencia_leitura}`,
i.proficiencia_escrita && `Escrita: ${i.proficiencia_escrita}`,
i.proficiencia_fala && `Fala: ${i.proficiencia_fala}`,
].filter(Boolean).join(' | ')}
</span>
<span className="modal-item-arrow"></span>
</li>
))}
</ul>
</>
)}
{orientacoes.length > 0 && (
<>
<h5 className="modal-section-title">
Orientações Concluídas ({item.total_orientacoes})
</h5>
<ul className="modal-list">
{orientacoes.slice(0, 15).map((o, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: o, tipo: 'orientacao_lattes' })}
>
<span className="modal-item-main">{o.orientando || 'Orientando não informado'}</span>
<span className="modal-item-detail">
{o.tipo?.replace('Orientação ', '').replace(' Concluída', '')}
{o.ano && ` (${o.ano})`}
{o.ies && ` - ${o.ies}`}
</span>
{o.titulo && (
<span className="modal-item-sub muted" style={{ fontSize: '0.75rem' }}>
{o.titulo.length > 60 ? o.titulo.substring(0, 60) + '...' : o.titulo}
</span>
)}
<span className="modal-item-arrow"></span>
</li>
))}
{item.total_orientacoes > 15 && (
<li className="modal-item muted">
... e mais {item.total_orientacoes - 15} orientações
</li>
)}
</ul>
</>
)}
{item.estatisticas_orientacoes && (
<>
<h5 className="modal-section-title">Estatísticas de Orientações</h5>
<div className="modal-stats-grid">
{item.estatisticas_orientacoes.doutorado_finalizado > 0 && (
<div className="modal-stat-card">
<span className="stat-number">{item.estatisticas_orientacoes.doutorado_finalizado}</span>
<span className="stat-label">Doutorados Concluídos</span>
</div>
)}
{item.estatisticas_orientacoes.mestrado_finalizado > 0 && (
<div className="modal-stat-card">
<span className="stat-number">{item.estatisticas_orientacoes.mestrado_finalizado}</span>
<span className="stat-label">Mestrados Concluídos</span>
</div>
)}
{item.estatisticas_orientacoes.doutorado_andamento > 0 && (
<div className="modal-stat-card">
<span className="stat-number">{item.estatisticas_orientacoes.doutorado_andamento}</span>
<span className="stat-label">Doutorados em Andamento</span>
</div>
)}
{item.estatisticas_orientacoes.mestrado_andamento > 0 && (
<div className="modal-stat-card">
<span className="stat-number">{item.estatisticas_orientacoes.mestrado_andamento}</span>
<span className="stat-label">Mestrados em Andamento</span>
</div>
)}
{item.estatisticas_orientacoes.pos_doutorado > 0 && (
<div className="modal-stat-card">
<span className="stat-number">{item.estatisticas_orientacoes.pos_doutorado}</span>
<span className="stat-label">Pós-Doutorados</span>
</div>
)}
</div>
</>
)}
{item.docencias?.length > 0 && (
<>
<h5 className="modal-section-title">Docência em PPG ({item.docencias.length})</h5>
<ul className="modal-list">
{item.docencias.map((d, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: d, tipo: 'docencia' })}
>
<span className="modal-item-main">
<strong>{d.programa || 'Programa não informado'}</strong>
</span>
<span className="modal-item-detail">
{d.ies_sigla} | {d.modalidade} | {d.categoria}
</span>
{d.linhas_pesquisa_ativas?.length > 0 && (
<span className="modal-item-sub muted">
{d.linhas_pesquisa_ativas.length} linhas de pesquisa ativas
</span>
)}
<span className="modal-item-arrow"></span>
</li>
))}
</ul>
</>
)}
{item.empregos?.length > 0 && (
<>
<h5 className="modal-section-title">Vínculos Empregatícios ({item.empregos.length})</h5>
<ul className="modal-list">
{item.empregos.map((e, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: e, tipo: 'emprego' })}
>
<span className="modal-item-main">{e.empregador || 'Empregador não informado'}</span>
<span className="modal-item-detail">
{e.tipo_emprego}
{e.atividade && ` | ${e.atividade}`}
</span>
{e.periodos?.length > 0 && (
<span className="modal-item-sub muted">
{e.periodos.length} período(s) registrado(s)
</span>
)}
<span className="modal-item-arrow"></span>
</li>
))}
</ul>
</>
)}
{item.projetos?.length > 0 && (
<>
<h5 className="modal-section-title">
Projetos de Pesquisa ({item.total_projetos})
</h5>
<ul className="modal-list">
{item.projetos.slice(0, 10).map((p, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: p, tipo: 'projeto' })}
>
<span className="modal-item-main">
{p.nome?.length > 60 ? p.nome.substring(0, 60) + '...' : p.nome}
</span>
<span className="modal-item-detail">
{p.situacao}
{p.ano_inicio && ` | Início: ${p.ano_inicio}`}
{p.ies_sigla && ` | ${p.ies_sigla}`}
</span>
<span className="modal-item-arrow"></span>
</li>
))}
{item.total_projetos > 10 && (
<li className="modal-item muted">
... e mais {item.total_projetos - 10} projetos
</li>
)}
</ul>
</>
)}
{item.premiacoes_detalhadas?.length > 0 && (
<>
<h5 className="modal-section-title">Premiações Detalhadas ({item.premiacoes_detalhadas.length})</h5>
<ul className="modal-list">
{item.premiacoes_detalhadas.map((pr, idx) => (
<li
key={idx}
className="modal-item modal-item-clicavel"
onClick={() => setSubDetalhe({ item: pr, tipo: 'premiacao_lattes' })}
>
<span className="modal-item-main">
<strong>{pr.evento || pr.premio}</strong>
{pr.premiacao && ` - ${pr.premiacao}`}
</span>
<span className="modal-item-detail">
{pr.papel && `${pr.papel} | `}
{pr.ano}
{pr.ies_sigla && ` | ${pr.ies_sigla}`}
</span>
{pr.produto_nome && (
<span className="modal-item-sub muted" style={{ fontSize: '0.75rem' }}>
{pr.produto_nome.length > 60 ? pr.produto_nome.substring(0, 60) + '...' : pr.produto_nome}
</span>
)}
<span className="modal-item-arrow"></span>
</li>
))}
</ul>
</>
)}
{endereco && (
<>
<h5 className="modal-section-title">Endereço Profissional</h5>
<div className="modal-detalhe-row">
<span className="modal-detalhe-value">
{[
endereco.logradouro,
endereco.numero,
endereco.bairro,
endereco.cidade,
endereco.pais
].filter(Boolean).join(', ')}
</span>
</div>
</>
)}
{titulacoes.length === 0 && idiomas.length === 0 && orientacoes.length === 0 &&
!item.docencias?.length && !item.projetos?.length && !item.premiacoes_detalhadas?.length && (
<p className="modal-empty">Dados detalhados não disponíveis no ATUACAPES.</p>
)}
</div>
);
}
case 'vinculo': {
const periodo = item.periodo || {};
const isAtivo = periodo.ativo ?? !periodo.fim;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Status</span>
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
{isAtivo ? 'ATIVO' : 'ENCERRADO'}
</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Instituição</span>
<span className="modal-detalhe-value">
{item.ies ? (item.ies.sigla ? `${item.ies.sigla} - ${item.ies.nome || ''}` : item.ies.nome) : 'Não informada'}
</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Início</span>
<span className="modal-detalhe-value">{formatDate(periodo.inicio)}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Fim</span>
<span className="modal-detalhe-value">{isAtivo ? 'Em andamento' : formatDate(periodo.fim)}</span>
</div>
{item.situacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação</span>
<span className="modal-detalhe-value">{item.situacao}</span>
</div>
)}
</div>
);
}
case 'coordenacao': {
const isAtivo = item.ativo ?? !item.fim;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="badge">{item.codigo || item.tipo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Área de Avaliação</span>
<span className="modal-detalhe-value">{item.area_avaliacao || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Status</span>
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
{isAtivo ? 'VIGENTE' : 'ENCERRADO'}
</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Início</span>
<span className="modal-detalhe-value">{formatDate(item.inicio || item.periodo?.inicio)}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Fim</span>
<span className="modal-detalhe-value">{isAtivo ? 'Em andamento' : formatDate(item.fim || item.periodo?.fim)}</span>
</div>
{item.presidente && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Função</span>
<span className="badge badge-premiado">👑 Presidente de Câmara</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
}
case 'premiacao':
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código</span>
<span className="badge">{item.codigo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Prêmio</span>
<span className="modal-detalhe-value">{item.nome_premio || item.premio || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
</div>
{item.papel && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Papel</span>
<span className="modal-detalhe-value">{item.papel}</span>
</div>
)}
{item.tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="modal-detalhe-value">{item.tipo}</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
case 'avaliacao':
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código</span>
<span className="badge">{item.codigo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Prêmio</span>
<span className="modal-detalhe-value">{item.premio || 'N/A'}</span>
</div>
{item.nome_comissao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Comissão</span>
<span className="modal-detalhe-value">{item.nome_comissao}</span>
</div>
)}
{item.comissao_tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo Comissão</span>
<span className="modal-detalhe-value">{item.comissao_tipo}</span>
</div>
)}
{item.tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Função</span>
<span className="modal-detalhe-value">{item.tipo}</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
case 'inscricao':
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código</span>
<span className="badge">{item.codigo}</span>
</div>
{item.evento && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Evento</span>
<span className="modal-detalhe-value">{item.evento}</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Prêmio</span>
<span className="modal-detalhe-value">{item.premio || 'N/A'}</span>
</div>
{item.tipo && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo Inscrição</span>
<span className="modal-detalhe-value">{item.tipo}</span>
</div>
)}
{item.situacao && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Situação</span>
<span className="modal-detalhe-value">{item.situacao}</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
case 'participacao':
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="badge">{item.codigo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Descrição</span>
<span className="modal-detalhe-value">{item.descricao || item.tipo || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Ano</span>
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
case 'orientacao':
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Tipo</span>
<span className="badge">{item.codigo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Categoria</span>
<span className="modal-detalhe-value">
{item.codigo?.includes('TESE') ? 'Doutorado' : item.codigo?.includes('DISS') ? 'Mestrado' : 'Pós-Doutorado'}
</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Função</span>
<span className="modal-detalhe-value">
{item.coorientacao || item.codigo?.startsWith('CO_') ? 'Coorientador' : 'Orientador'}
</span>
</div>
{item.premiada && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Destaque</span>
<span className="badge badge-premiado">🏆 Premiada</span>
</div>
)}
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Base</span>
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
</div>
</div>
);
default:
return <p className="modal-empty">Sem detalhes disponíveis</p>;
}
};
return createPortal(
<div className="tipo-modal-overlay" onClick={onClose}>
<div className="tipo-modal" onClick={(e) => e.stopPropagation()}>
<div className="tipo-modal-header">
{subDetalhe && (
<button className="tipo-modal-voltar" onClick={handleVoltar}>
Voltar
</button>
)}
<span className="modal-titulo-item">
<span className="modal-titulo-icone">{getIcone()}</span>
<span>{getTitulo()}</span>
</span>
<button className="tipo-modal-close" onClick={onClose}></button>
</div>
<div className="tipo-modal-body">
{renderContent()}
</div>
</div>
</div>,
document.body
);
};
const FORMULAS = {
bloco_a: {
titulo: 'Coordenacao CAPES',
descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano (anos completos)\nBônus atualidade (mandato vigente) + Retorno (mandato anterior)',
},
bloco_b: {
titulo: 'Consultoria',
descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade: 8a+=+20 (escalonado)\nRetorno (reativação): +15 (uma vez)',
},
bloco_c: {
titulo: 'Avaliacoes/Premiacoes',
descricao: 'Premiações: GP=100 | Prêmio=50 | Menção=30\nAvaliações de Comissão e Coordenação\nInscrições em Prêmios\nOrientações e Bancas de trabalhos premiados',
},
bloco_d: {
titulo: 'Indicadores',
descricao: 'Bolsas CNPq: BPQ Nível Superior/Intermediário\nParticipações: Eventos e Projetos\nOrientações e Bancas (sem premiação)',
},
};
const PONTOS_BASE = {
CA: 200, CAJ: 150, CAJ_MP: 120, CAM: 100,
CONS_ATIVO: 150, CONS_HIST: 100, CONS_FALECIDO: 100,
INSC_AUTOR: 10, INSC_INST_AUTOR: 20,
AVAL_COMIS_PREMIO: 30, AVAL_COMIS_GP: 40,
COORD_COMIS_PREMIO: 40, COORD_COMIS_GP: 50,
PREMIACAO_GP_AUTOR: 100, PREMIACAO_AUTOR: 50, MENCAO_AUTOR: 30,
BOL_BPQ_NIVEL: 30,
EVENTO: 1, PROJ: 10,
};
const gerarInsights = (consultor, totalConsultores) => {
const insights = [];
const posicao = consultor.posicao || consultor.rank || 0;
const pontuacao = consultor.pontuacao_total || 0;
const blocoA = consultor.bloco_a || 0;
const blocoB = consultor.bloco_b || 0;
const blocoC = consultor.bloco_c || 0;
const blocoD = consultor.bloco_d || 0;
if (totalConsultores > 0 && posicao > 0) {
const percentil = ((totalConsultores - posicao) / totalConsultores * 100);
if (percentil >= 99) {
insights.push({ icone: '🏆', texto: `Top 1% - Elite dos ${totalConsultores.toLocaleString('pt-BR')} consultores` });
} else if (percentil >= 95) {
insights.push({ icone: '⭐', texto: `Top 5% entre ${totalConsultores.toLocaleString('pt-BR')} consultores` });
} else if (percentil >= 90) {
insights.push({ icone: '📈', texto: `Top 10% no ranking geral` });
} else if (percentil >= 75) {
insights.push({ icone: '✓', texto: `Acima de 75% dos consultores` });
} else if (percentil >= 50) {
insights.push({ icone: '→', texto: `Metade superior do ranking` });
}
}
const blocos = [
{ nome: 'Coordenação CAPES', letra: 'A', valor: blocoA },
{ nome: 'Consultoria', letra: 'B', valor: blocoB },
{ nome: 'Avaliações/Premiações', letra: 'C', valor: blocoC },
{ nome: 'Indicadores', letra: 'D', valor: blocoD },
];
const blocosAtivos = blocos.filter(b => b.valor > 0);
if (blocosAtivos.length > 0 && pontuacao > 0) {
const maiorBloco = blocosAtivos.reduce((a, b) => a.valor > b.valor ? a : b);
const pct = Math.round(maiorBloco.valor / pontuacao * 100);
if (pct >= 50) {
insights.push({ icone: '📊', texto: `${pct}% da pontuação vem de ${maiorBloco.nome}` });
}
}
const coords = consultor.coordenacoes_capes || [];
const coordAtiva = coords.find(c => c.ativo ?? !c.fim);
if (coordAtiva) {
const labels = { CA: 'Coordenador de Área', CAJ: 'Coordenador Adjunto', CAJ_MP: 'Coord. Adjunto MP', CAM: 'Câmara Temática' };
const bonusAtual = { CA: 30, CAJ: 20, CAJ_MP: 15, CAM: 20 };
insights.push({ icone: '🎯', texto: `${labels[coordAtiva.codigo] || coordAtiva.codigo} em exercício (+${bonusAtual[coordAtiva.codigo] || 0} bônus atualidade)` });
} else if (coords.length > 0) {
insights.push({ icone: '📜', texto: `Histórico de ${coords.length} coordenação(ões) CAPES` });
}
const consultoria = consultor.consultoria || {};
if (consultor.ativo && consultoria.anos_consecutivos >= 8) {
insights.push({ icone: '💎', texto: `${consultoria.anos_consecutivos} anos consecutivos (+20 bônus continuidade)` });
} else if (consultoria.anos_consecutivos >= 3) {
insights.push({ icone: '🔷', texto: `${consultoria.anos_consecutivos} anos consecutivos de consultoria` });
}
if (consultoria.retornos > 0) {
const bonusRetorno = consultor.ativo ? 15 : 20;
insights.push({ icone: '🔄', texto: `Retorno à consultoria (+${bonusRetorno} bônus reativação)` });
}
const premiacoes = consultor.premiacoes || [];
const gps = premiacoes.filter(p => p.codigo === 'PREMIACAO_GP_AUTOR');
const premios = premiacoes.filter(p => p.codigo === 'PREMIACAO_AUTOR');
const mencoes = premiacoes.filter(p => p.codigo === 'MENCAO_AUTOR');
if (gps.length > 0) {
insights.push({ icone: '🏆', texto: `${gps.length}x Grande Prêmio CAPES (base 100 pts cada)` });
}
if (premios.length > 0) {
insights.push({ icone: '🥇', texto: `${premios.length}x Prêmio CAPES (base 50 pts cada)` });
}
if (mencoes.length > 0) {
insights.push({ icone: '🎖️', texto: `${mencoes.length}x Menção Honrosa (base 30 pts cada)` });
}
const anos = consultor.anos_atuacao || 0;
if (anos >= 15) {
insights.push({ icone: '👑', texto: `${anos} anos de contribuição ao SNPG` });
} else if (anos >= 10) {
insights.push({ icone: '🏅', texto: `Veterano com ${anos} anos de atuação` });
}
if (blocoA > 0 && blocoB > 0 && blocoC > 0) {
insights.push({ icone: '🌟', texto: 'Perfil diversificado: coordenação + consultoria + avaliações' });
}
if (insights.length === 0) {
insights.push({ icone: '📋', texto: `Pontuação total: ${pontuacao} pontos` });
if (posicao > 0) {
insights.push({ icone: '📍', texto: `Posição #${posicao.toLocaleString('pt-BR')} no ranking` });
}
}
return insights;
};
const InsightsModal = ({ consultor, totalConsultores, onClose }) => {
const insights = useMemo(() => gerarInsights(consultor, totalConsultores), [consultor, totalConsultores]);
const posicao = consultor.posicao || consultor.rank || 0;
return createPortal(
<div className="tipo-modal-overlay" onClick={onClose}>
<div className="tipo-modal insights-modal" onClick={(e) => e.stopPropagation()}>
<div className="tipo-modal-header">
<span className="modal-titulo-item">
<span className="modal-titulo-icone">💡</span>
<span>Por que estou na posição #{posicao}?</span>
</span>
<button className="tipo-modal-close" onClick={onClose}></button>
</div>
<div className="tipo-modal-body">
<div className="insights-list">
{insights.map((insight, idx) => (
<div key={idx} className="insight-item">
<span className="insight-icone">{insight.icone}</span>
<span className="insight-texto">{insight.texto}</span>
</div>
))}
</div>
<div className="insights-footer">
<small>Análise baseada nos critérios oficiais do Ranking CAPES V1.0</small>
</div>
</div>
</div>
</div>,
document.body
);
};
const TETOS = {
CA: { teto: 450, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 10pts/ano (max 100) | Atualidade: +30 | Retorno: +20' },
CAJ: { teto: 370, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 8pts/ano (max 80) | Atualidade: +20 | Retorno: +15' },
CAJ_MP: { teto: 315, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 6pts/ano (max 60) | Atualidade: +15 | Retorno: +10' },
CAM: { teto: 280, doc: 'Bloco A - Coordenação CAPES', bonus: 'Tempo: 5pts/ano (max 50) | Atualidade: +20 | Retorno: +10' },
CONS_ATIVO: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Atualidade: +20 | Continuidade 8a+: +20 | Retorno: +15' },
CONS_HIST: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Continuidade 8a+: +20 | Retorno: +20' },
CONS_FALECIDO: { teto: 230, doc: 'Bloco B - Consultoria', bonus: 'Tempo: 5pts/ano (max 50) | Continuidade 8a+: +20' },
INSC_AUTOR: { teto: 20, doc: 'Bloco C - Inscrições', bonus: '+2/participação (max 10)' },
INSC_INST_AUTOR: { teto: 50, doc: 'Bloco C - Inscrições', bonus: '+5/participação (max 10)' },
AVAL_COMIS_PREMIO: { teto: 60, doc: 'Bloco C - Avaliação/Comissão', bonus: '+2/ano (max 15)' },
AVAL_COMIS_GP: { teto: 80, doc: 'Bloco C - Avaliação/Comissão', bonus: '+3/ano (max 20)' },
COORD_COMIS_PREMIO: { teto: 100, doc: 'Bloco C - Avaliação/Comissão', bonus: '+4/ano (max 20)' },
COORD_COMIS_GP: { teto: 120, doc: 'Bloco C - Avaliação/Comissão', bonus: '+6/ano (max 20)' },
PREMIACAO_GP_AUTOR: { teto: 300, doc: 'Bloco C - Premiações' },
PREMIACAO_AUTOR: { teto: 150, doc: 'Bloco C - Premiações' },
MENCAO_AUTOR: { teto: 90, doc: 'Bloco C - Premiações' },
EVENTO: { teto: 5, doc: 'Bloco D - Participações', bonus: '+1/participação (max 10)' },
PROJ: { teto: 30, doc: 'Bloco D - Participações', bonus: '+2/participação (max 10)' },
BOL_BPQ_NIVEL: { teto: 60, doc: 'Bloco D - Bolsas CNPq' },
};
const PontuacaoModal = ({ dados, onClose }) => {
if (!dados) return null;
const { tipo, label, value, formula, atuacao, bloco } = dados;
const getTitulo = () => {
if (tipo === 'bloco') return `${label} - Detalhes`;
if (tipo === 'atuacao') return `${atuacao?.codigo || label} - Cálculo`;
if (tipo === 'total') return 'Pontuação Total';
return label;
};
const getIcone = () => {
if (label?.includes('BLOCO A') || label === 'A') return '🎯';
if (label?.includes('BLOCO B') || label === 'B') return '💼';
if (label?.includes('BLOCO C') || label === 'C') return '🏆';
if (label?.includes('BLOCO D') || label === 'D') return '📊';
if (tipo === 'total') return '📈';
return '📌';
};
const renderBlocoContent = () => {
const formulaLines = formula ? formula.split('\n') : [];
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação</span>
<span className="modal-detalhe-value pontos">{value} pts</span>
</div>
{formulaLines.length > 0 && (
<div className="modal-formula-section">
<span className="modal-detalhe-label">Fórmula de Cálculo</span>
<div className="modal-formula-box">
{formulaLines.map((line, idx) => (
<div key={idx} className="modal-formula-line">{line}</div>
))}
</div>
</div>
)}
{bloco?.atuacoes?.length > 0 && (
<div className="modal-atuacoes-section">
<span className="modal-detalhe-label">Composição</span>
<div className="modal-atuacoes-list">
{bloco.atuacoes.map((at, idx) => (
<div key={idx} className="modal-atuacao-item">
<span className="badge">{at.codigo}</span>
<span className="modal-atuacao-valor">{at.total} pts</span>
<span className="modal-atuacao-detalhe">
{at.quantidade > 1 ? `(${at.quantidade}x)` : ''}
</span>
</div>
))}
</div>
</div>
)}
</div>
);
};
const renderAtuacaoContent = () => {
if (!atuacao) return null;
const base = atuacao.base || 0;
const tempo = atuacao.tempo || 0;
const bonus = atuacao.bonus || 0;
const bruto = base + tempo + bonus;
const meta = TETOS[atuacao.codigo];
const hasTeto = meta && meta.teto > 0;
const capped = hasTeto && bruto > meta.teto;
const unidade = atuacao.quantidade > 1 ? Math.round(base / atuacao.quantidade) : null;
return (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Código</span>
<span className="badge">{atuacao.codigo}</span>
</div>
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Final</span>
<span className="modal-detalhe-value pontos">{atuacao.total} pts</span>
</div>
<div className="modal-formula-section">
<span className="modal-detalhe-label">Cálculo Detalhado</span>
<div className="modal-formula-box">
{unidade ? (
<div className="modal-formula-line">
<span className="formula-label">Base:</span>
<span className="formula-calc">{unidade} × {atuacao.quantidade} = {base}</span>
</div>
) : (
<div className="modal-formula-line">
<span className="formula-label">Base:</span>
<span className="formula-calc">{base}</span>
</div>
)}
{tempo > 0 && (
<div className="modal-formula-line">
<span className="formula-label">Tempo:</span>
<span className="formula-calc">+{tempo}</span>
</div>
)}
{bonus > 0 && (
<div className="modal-formula-line">
<span className="formula-label">Bônus:</span>
<span className="formula-calc">+{bonus}</span>
</div>
)}
<div className="modal-formula-line formula-subtotal">
<span className="formula-label">Subtotal:</span>
<span className="formula-calc">{bruto}</span>
</div>
{hasTeto && (
<div className="modal-formula-line">
<span className="formula-label">Teto:</span>
<span className="formula-calc">{meta.teto}</span>
</div>
)}
<div className="modal-formula-line formula-total">
<span className="formula-label">Total:</span>
<span className="formula-calc">{atuacao.total} {capped ? '(limitado pelo teto)' : ''}</span>
</div>
</div>
</div>
{meta?.doc && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Referência</span>
<span className="modal-detalhe-value muted">{meta.doc}</span>
</div>
)}
{meta?.bonus && (
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Regra de Bônus</span>
<span className="modal-detalhe-value muted">{meta.bonus}</span>
</div>
)}
</div>
);
};
const renderTotalContent = () => (
<div className="modal-detalhe-content">
<div className="modal-detalhe-row">
<span className="modal-detalhe-label">Pontuação Total</span>
<span className="modal-detalhe-value pontos">{value} pts</span>
</div>
<div className="modal-formula-section">
<span className="modal-detalhe-label">Fórmula</span>
<div className="modal-formula-box">
<div className="modal-formula-line">Bloco A + Bloco B + Bloco C + Bloco D</div>
<div className="modal-formula-line muted" style={{ fontSize: '0.8rem', marginTop: '0.5rem' }}>
A: Coordenação CAPES | B: Consultoria | C: Avaliações/Premiações | D: Indicadores
</div>
</div>
</div>
</div>
);
return createPortal(
<div className="tipo-modal-overlay" onClick={onClose}>
<div className="tipo-modal" onClick={(e) => e.stopPropagation()}>
<div className="tipo-modal-header">
<span className="modal-titulo-item">
<span className="modal-titulo-icone">{getIcone()}</span>
<span>{getTitulo()}</span>
</span>
<button className="tipo-modal-close" onClick={onClose}></button>
</div>
<div className="tipo-modal-body">
{tipo === 'bloco' && renderBlocoContent()}
{tipo === 'atuacao' && renderAtuacaoContent()}
{tipo === 'total' && renderTotalContent()}
</div>
</div>
</div>,
document.body
);
};
const ScoreItemClickable = ({ value, label, formula, style, onClick }) => (
<div className="score-item-wrapper score-item-clicavel" onClick={onClick}>
<div className="score-item" style={style}>
<div className="score-item-value" style={style}>{value}</div>
<div className="score-item-label">{label}</div>
</div>
</div>
);
const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecionado, totalConsultores = 0 }) => {
const [expanded, setExpanded] = useState(false);
const [showRawModal, setShowRawModal] = useState(false);
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);
useEffect(() => {
if (highlight && cardRef.current) {
const timeoutId = setTimeout(() => {
cardRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
return () => clearTimeout(timeoutId);
}
}, [highlight]);
const getRankClass = (rank) => {
if (rank === 1) return 'rank-1';
if (rank === 2) return 'rank-2';
if (rank === 3) return 'rank-3';
return '';
};
const formatDate = (dateStr) => {
if (!dateStr) return 'Atual';
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const handleCheckboxClick = (e) => {
e.stopPropagation();
onToggleSelecionado(consultor);
};
const handleRawDataClick = (e) => {
e.stopPropagation();
setShowRawModal(true);
};
const handleInsightsClick = (e) => {
e.stopPropagation();
setShowInsights(true);
};
const { consultoria, pontuacao } = consultor;
const blocoA = pontuacao?.bloco_a || { total: consultor.bloco_a || 0 };
const blocoB = pontuacao?.bloco_b || { total: consultor.bloco_b || 0 };
const blocoC = pontuacao?.bloco_c || { total: consultor.bloco_c || 0 };
const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 0 };
const pontuacaoTotal = Number(consultor.pontuacao_total ?? 0);
const selos = useMemo(() => gerarSelos(consultor), [consultor]);
return (
<div ref={cardRef} className={`ranking-card ${expanded ? 'expanded' : ''} ${highlight ? 'highlight' : ''} ${selecionado ? 'selecionado' : ''}`} onClick={() => setExpanded(!expanded)}>
<div className="card-main">
<div className="selecao-checkbox" onClick={handleCheckboxClick} title="Selecionar para comparar">
<input
type="checkbox"
checked={selecionado}
onChange={() => {}}
/>
<span className="checkmark"></span>
</div>
<div className={`rank ${getRankClass(consultor.posicao || consultor.rank)}`}>
<span className={`rank-number rank-digits-${String(consultor.posicao || consultor.rank).length}`}>
#{consultor.posicao || consultor.rank}
</span>
<button
className="btn-insights"
onClick={handleInsightsClick}
title="Por que estou nesta posição?"
>
?
</button>
</div>
<div className="card-info">
<div className="consultant-name">
{import.meta.env.VITE_HOST_ATUACAPES && consultor.id_pessoa && (
<a
href={`${import.meta.env.VITE_HOST_ATUACAPES}/perfil/${consultor.id_pessoa}`}
target="_blank"
rel="noopener noreferrer"
className="link-atuacapes"
onClick={(e) => e.stopPropagation()}
title="Ver perfil no ATUACAPES"
>
</a>
)}
{consultor.nome}
{consultor.ativo && <span className="badge badge-ativo">ATIVO</span>}
{!consultor.ativo && <span className="badge badge-historico">HISTORICO</span>}
{consultor.veterano && <span className="badge badge-veterano">VETERANO</span>}
</div>
<div className="consultant-area">
{consultor.anos_atuacao} anos de atuacao
{consultoria?.inicio && ` | Desde ${formatDate(consultoria.inicio)}`}
</div>
{selos.length > 0 && (
<div className="consultant-selos-row">
<SelosBadges selos={selos} compacto={true} onSeloClick={setSeloModal} />
</div>
)}
</div>
<div className="card-stats">
<div className="stat" title={(() => {
const coords = consultor.coordenacoes_capes || [];
if (coords.length === 0) return 'Sem coordenação CAPES';
const prioridade = { CA: 1, CAJ: 2, CAJ_MP: 3, CAM: 4 };
const sorted = [...coords].sort((a, b) => {
const aAtivo = a.ativo ?? !a.fim;
const bAtivo = b.ativo ?? !b.fim;
if (aAtivo !== bAtivo) return bAtivo ? 1 : -1;
return (prioridade[a.codigo] || 99) - (prioridade[b.codigo] || 99);
});
return sorted[0]?.area_avaliacao || 'Coordenação CAPES';
})()}>
<div className="stat-value">{(() => {
const coords = consultor.coordenacoes_capes || [];
if (coords.length === 0) return '-';
const prioridade = { CA: 1, CAJ: 2, CAJ_MP: 3, CAM: 4 };
const sorted = [...coords].sort((a, b) => {
const aAtivo = a.ativo ?? !a.fim;
const bAtivo = b.ativo ?? !b.fim;
if (aAtivo !== bAtivo) return bAtivo ? 1 : -1;
return (prioridade[a.codigo] || 99) - (prioridade[b.codigo] || 99);
});
return sorted[0]?.codigo || '-';
})()}</div>
<div className="stat-label">Coord.</div>
</div>
<div className="stat" title={`${consultoria?.anos_consecutivos || 0} anos consecutivos`}>
<div className="stat-value">{consultoria?.anos_consecutivos || 0}</div>
<div className="stat-label">Anos Consec.</div>
</div>
<div className="stat">
<div className="score-value">{pontuacaoTotal}</div>
<div className="stat-label">Score</div>
</div>
<button
className="btn-raw-data"
onClick={handleRawDataClick}
title="Ver dados completos do ATUACAPES"
>
📋
</button>
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
</div>
</div>
{expanded && (
<div className="card-details" onClick={(e) => e.stopPropagation()}>
<div className="details-grid">
<div className="detail-section">
<h4>Pontuacao Total</h4>
<div className="score-breakdown-total score-breakdown-5cols">
<ScoreItemClickable
value={blocoA.total}
label="A"
style={{ color: blocoA.total > 0 ? 'var(--accent-2)' : 'var(--muted)' }}
onClick={() => setPontuacaoModal({
tipo: 'bloco',
label: 'BLOCO A - Coordenação CAPES',
value: blocoA.total,
formula: FORMULAS.bloco_a.descricao,
bloco: blocoA
})}
/>
<ScoreItemClickable
value={blocoB.total}
label="B"
style={{ color: blocoB.total > 0 ? 'var(--gold)' : 'var(--muted)' }}
onClick={() => setPontuacaoModal({
tipo: 'bloco',
label: 'BLOCO B - Consultoria',
value: blocoB.total,
formula: FORMULAS.bloco_b.descricao,
bloco: blocoB
})}
/>
<ScoreItemClickable
value={blocoC.total}
label="C"
style={{ color: blocoC.total > 0 ? 'var(--bronze)' : 'var(--muted)' }}
onClick={() => setPontuacaoModal({
tipo: 'bloco',
label: 'BLOCO C - Avaliações/Premiações',
value: blocoC.total,
formula: FORMULAS.bloco_c.descricao,
bloco: blocoC
})}
/>
<ScoreItemClickable
value={blocoD.total}
label="D"
style={{ color: blocoD.total > 0 ? 'var(--silver)' : 'var(--muted)' }}
onClick={() => setPontuacaoModal({
tipo: 'bloco',
label: 'BLOCO D - Indicadores',
value: blocoD.total,
formula: FORMULAS.bloco_d.descricao,
bloco: blocoD
})}
/>
<ScoreItemClickable
value={pontuacaoTotal}
label="TOTAL"
style={{ background: 'var(--accent)', color: 'white' }}
onClick={() => setPontuacaoModal({
tipo: 'total',
label: 'TOTAL',
value: pontuacaoTotal
})}
/>
</div>
</div>
{blocoA.atuacoes && blocoA.atuacoes.length > 0 && (
<BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" onItemClick={setPontuacaoModal} />
)}
{blocoB.atuacoes && blocoB.atuacoes.length > 0 && (
<BlocoDetalhes titulo="B - Consultoria" bloco={blocoB} cor="var(--gold)" onItemClick={setPontuacaoModal} />
)}
{blocoC.atuacoes && blocoC.atuacoes.length > 0 && (
<BlocoDetalhes titulo="C - Avaliacoes/Premiacoes" bloco={blocoC} cor="var(--bronze)" onItemClick={setPontuacaoModal} />
)}
{blocoD.atuacoes && blocoD.atuacoes.length > 0 && (
<BlocoDetalhes titulo="D - Indicadores" bloco={blocoD} cor="var(--silver)" onItemClick={setPontuacaoModal} />
)}
</div>
{consultor.tipos_atuacao?.length > 0 && (
<div className="detail-section tipos-section">
<h4>Tipos de Atuacao</h4>
<TiposAtuacaoBadges
tipos={consultor.tipos_atuacao}
exibirTodos={true}
onBadgeClick={setTipoAtuacaoModal}
consultor={consultor}
/>
</div>
)}
{selos.length > 0 && (
<div className="detail-section selos-section">
<h4>Selos e Reconhecimentos</h4>
<SelosBadges selos={selos} onSeloClick={setSeloModal} />
</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">
<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>
{consultor.lattes.titulacoes
?.sort((a, b) => {
const ordem = { 'Pos-Doutorado': 1, 'Doutorado': 2, 'Mestrado': 3, 'Graduacao': 4, 'Bacharelado': 5 };
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={`Clique para detalhes: ${t.grau}${t.area ? ` em ${t.area}` : ''}${t.programa ? ` (${t.programa})` : ''}${t.ies_nome ? ` - ${t.ies_nome}` : ''}`}
>
{t.grau}{t.ies_sigla ? ` (${t.ies_sigla})` : ''}{t.ano ? ` - ${t.ano}` : ''}
</span>
))}
</div>
</div>
</div>
)}
{consultoria?.vinculos?.length > 0 && (
<div className="extra-details">
<h4>Vinculos de Consultoria</h4>
<div className="list-items">
{[...consultoria.vinculos]
.sort((a, b) => new Date(b.periodo?.inicio || 0) - new Date(a.periodo?.inicio || 0))
.map((vinculo, idx) => {
const periodo = vinculo.periodo || {};
const isAtivo = periodo.ativo ?? !periodo.fim;
return (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: vinculo, tipo: 'vinculo' })}
>
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
{isAtivo ? 'ATIVO' : 'ENCERRADO'}
</span>
<span className="ies-nome">
{vinculo.ies
? vinculo.ies.sigla && vinculo.ies.nome
? `${vinculo.ies.sigla} - ${vinculo.ies.nome}`
: vinculo.ies.sigla || vinculo.ies.nome
: 'IES nao informada'}
</span>
<span className="muted">
{formatDate(periodo.inicio)} - {isAtivo ? 'Atual' : formatDate(periodo.fim)}
</span>
</div>
);
})}
</div>
</div>
)}
{consultor.coordenacoes_capes?.length > 0 && (
<div className="extra-details">
<h4>Coordenacoes CAPES</h4>
<div className="list-items">
{[...consultor.coordenacoes_capes]
.sort((a, b) => new Date(b.inicio || b.periodo?.inicio || 0) - new Date(a.inicio || a.periodo?.inicio || 0))
.map((coord, idx) => (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: coord, tipo: 'coordenacao' })}
>
<span className="badge">{coord.codigo || coord.tipo}</span>
<span className="pontos">{PONTOS_BASE[coord.codigo] || 0} pts</span>
<span>{coord.area_avaliacao}</span>
<span className="muted">
{formatDate(coord.inicio || coord.periodo?.inicio)} - {formatDate(coord.fim || coord.periodo?.fim)}
</span>
</div>
))}
</div>
</div>
)}
{consultor.premiacoes?.length > 0 && (
<div className="extra-details">
<h4>Premiacoes</h4>
<div className="list-items">
{[...consultor.premiacoes]
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
.map((prem, idx) => (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: prem, tipo: 'premiacao' })}
>
<span className="badge">{prem.codigo}</span>
<span className="pontos">{PONTOS_BASE[prem.codigo] || 0} pts</span>
<span>{prem.nome_premio}</span>
<span className="muted">{prem.ano}</span>
</div>
))}
</div>
</div>
)}
{consultor.avaliacoes_comissao?.length > 0 && (
<div className="extra-details">
<h4>Avaliacoes de Comissao</h4>
<div className="list-items">
{[...consultor.avaliacoes_comissao]
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
.map((aval, idx) => (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: aval, tipo: 'avaliacao' })}
>
<span className="badge">{aval.codigo}</span>
<span className="pontos">{PONTOS_BASE[aval.codigo] || 0} pts</span>
<span>{aval.nome_comissao || aval.premio}</span>
<span className="muted">{aval.ano}</span>
</div>
))}
</div>
</div>
)}
{consultor.inscricoes?.length > 0 && (
<div className="extra-details">
<h4>Inscricoes</h4>
<div className="list-items">
{[...consultor.inscricoes]
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
.map((insc, idx) => (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: insc, tipo: 'inscricao' })}
>
<span className="badge">{insc.codigo}</span>
<span className="pontos">{PONTOS_BASE[insc.codigo] || 0} pts</span>
<span>{insc.evento || insc.premio}</span>
<span className="muted">{insc.ano}</span>
</div>
))}
</div>
</div>
)}
{consultor.participacoes?.length > 0 && (
<div className="extra-details">
<h4>Participacoes (Eventos/Projetos)</h4>
<div className="list-items">
{[...consultor.participacoes]
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
.slice(0, 10)
.map((part, idx) => (
<div
key={idx}
className="list-item list-item-clicavel"
onClick={() => setItemDetalhe({ item: part, tipo: 'participacao' })}
>
<span className="badge">{part.codigo}</span>
<span className="pontos">{PONTOS_BASE[part.codigo] || 0} pts</span>
<span>{part.descricao || part.tipo}</span>
<span className="muted">{part.ano}</span>
</div>
))}
{consultor.participacoes.length > 10 && (
<div className="list-item muted">... e mais {consultor.participacoes.length - 10} participacoes</div>
)}
</div>
</div>
)}
{consultor.orientacoes?.length > 0 && (() => {
const orientacoes = consultor.orientacoes;
const contagem = {};
const premiadas = {};
orientacoes.forEach(o => {
contagem[o.codigo] = (contagem[o.codigo] || 0) + 1;
if (o.premiada) premiadas[o.codigo] = (premiadas[o.codigo] || 0) + 1;
});
const config = {
ORIENT_POS_DOC: { label: 'Pós-Doutorado', icone: '🔬', cor: 'tipo-orientador' },
ORIENT_TESE: { label: 'Tese (Doutorado)', icone: '📚', cor: 'tipo-orientador' },
ORIENT_DISS: { label: 'Dissertação (Mestrado)', icone: '📄', cor: 'tipo-orientador' },
CO_ORIENT_POS_DOC: { label: 'Coorient. Pós-Doc', icone: '🔬', cor: 'tipo-avaliador' },
CO_ORIENT_TESE: { label: 'Coorient. Tese', icone: '📚', cor: 'tipo-avaliador' },
CO_ORIENT_DISS: { label: 'Coorient. Diss.', icone: '📄', cor: 'tipo-avaliador' },
};
const ordem = ['ORIENT_POS_DOC', 'ORIENT_TESE', 'ORIENT_DISS', 'CO_ORIENT_POS_DOC', 'CO_ORIENT_TESE', 'CO_ORIENT_DISS'];
return (
<div className="extra-details">
<h4>Orientacoes ({orientacoes.length} total)</h4>
<div className="list-items">
{ordem.filter(cod => contagem[cod] > 0).map(cod => {
const cfg = config[cod];
const qtd = contagem[cod];
const prem = premiadas[cod] || 0;
return (
<div key={cod} className="list-item">
<span className="tipo-icone">{cfg.icone}</span>
<span className={`badge ${cfg.cor}`}>{cfg.label}</span>
<span className="pontos" style={{ minWidth: '60px' }}>{qtd}x</span>
{prem > 0 && (
<span className="badge badge-premiado" title={`${prem} premiada(s)`}>
🏆 {prem}
</span>
)}
</div>
);
})}
</div>
</div>
);
})()}
</div>
)}
{showRawModal && (
<RawDataModal
idPessoa={consultor.id_pessoa}
nome={consultor.nome}
onClose={() => setShowRawModal(false)}
/>
)}
{tipoAtuacaoModal && (
<TipoAtuacaoModal
tipo={tipoAtuacaoModal}
consultor={consultor}
onClose={() => setTipoAtuacaoModal(null)}
/>
)}
{seloModal && (
<SeloModal
selo={seloModal}
consultor={consultor}
onClose={() => setSeloModal(null)}
/>
)}
{itemDetalhe && (
<ItemDetalheModal
item={itemDetalhe.item}
tipo={itemDetalhe.tipo}
onClose={() => setItemDetalhe(null)}
/>
)}
{pontuacaoModal && (
<PontuacaoModal
dados={pontuacaoModal}
onClose={() => setPontuacaoModal(null)}
/>
)}
{showInsights && (
<InsightsModal
consultor={consultor}
totalConsultores={totalConsultores}
onClose={() => setShowInsights(false)}
/>
)}
</div>
);
});
ConsultorCard.displayName = 'ConsultorCard';
const CODIGOS_APENAS_SELO = [
'ORIENT_POS_DOC', 'ORIENT_TESE', 'ORIENT_DISS',
'CO_ORIENT_POS_DOC', 'CO_ORIENT_TESE', 'CO_ORIENT_DISS',
'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS',
'ORIENT_POS_DOC_PREM', 'ORIENT_TESE_PREM', 'ORIENT_DISS_PREM',
'CO_ORIENT_POS_DOC_PREM', 'CO_ORIENT_TESE_PREM', 'CO_ORIENT_DISS_PREM',
'MB_BANCA_POS_DOC_PREM', 'MB_BANCA_TESE_PREM', 'MB_BANCA_DISS_PREM',
];
const BlocoDetalhes = memo(({ titulo, bloco, cor, onItemClick }) => {
const atuacoesFiltradas = bloco.atuacoes?.filter(at => !CODIGOS_APENAS_SELO.includes(at.codigo)) || [];
return (
<div className="detail-section">
<h4 style={{ color: cor }}>{titulo}</h4>
<div className="score-breakdown">
{atuacoesFiltradas.map((at, idx) => (
<div
key={idx}
className="score-item-wrapper score-item-clicavel"
onClick={() => onItemClick && onItemClick({
tipo: 'atuacao',
label: at.codigo,
value: at.total,
atuacao: at
})}
>
<div className="score-item">
<div className="score-item-value">{at.total}</div>
<div className="score-item-label">{at.codigo}</div>
</div>
</div>
))}
<div
className="score-item-wrapper score-item-clicavel"
onClick={() => onItemClick && onItemClick({
tipo: 'bloco',
label: titulo,
value: bloco.total,
bloco: bloco
})}
>
<div className="score-item score-total">
<div className="score-item-value">{bloco.total}</div>
<div className="score-item-label">TOTAL</div>
</div>
</div>
</div>
</div>
);
});
BlocoDetalhes.displayName = 'BlocoDetalhes';
export default ConsultorCard;