diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index cd8d218..c1dca0e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -249,6 +249,7 @@ function App() { highlight={consultor.id_pessoa === highlightId} selecionado={selecionados.some((c) => c.id_pessoa === consultor.id_pessoa)} onToggleSelecionado={toggleSelecionado} + totalConsultores={total} /> ))} diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index 595c0df..2eb5e11 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -285,6 +285,80 @@ transform: scale(0.95); } +.rank { + position: relative; +} + +.btn-insights { + position: absolute; + top: -6px; + right: -6px; + width: 18px; + height: 18px; + padding: 0; + background: linear-gradient(135deg, var(--accent), var(--accent-2)); + border: none; + border-radius: 50%; + color: white; + font-size: 0.7rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0,0,0,0.3); + z-index: 1; +} + +.btn-insights:hover { + transform: scale(1.15); + box-shadow: 0 3px 8px rgba(99, 102, 241, 0.4); +} + +.btn-insights:active { + transform: scale(0.95); +} + +.insights-modal { + max-width: 420px; +} + +.insights-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.insight-item { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.6rem 0.8rem; + background: rgba(255,255,255,0.03); + border-radius: 8px; + border-left: 3px solid var(--accent); +} + +.insight-icone { + font-size: 1.1rem; + flex-shrink: 0; +} + +.insight-texto { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.4; +} + +.insights-footer { + margin-top: 1rem; + padding-top: 0.75rem; + border-top: 1px solid var(--stroke); + text-align: center; + color: var(--muted); +} + .expand-icon { margin-left: 0.5rem; color: var(--muted); diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index a9b7031..1df2fcd 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -878,6 +878,138 @@ const PONTOS_BASE = { EVENTO: 1, PROJ: 10, }; +const TETOS_BLOCO = { A: 450, B: 230, C: 500, D: 300 }; + +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, teto: TETOS_BLOCO.A }, + { nome: 'Consultoria', letra: 'B', valor: blocoB, teto: TETOS_BLOCO.B }, + { nome: 'Avaliações/Premiações', letra: 'C', valor: blocoC, teto: TETOS_BLOCO.C }, + { nome: 'Indicadores', letra: 'D', valor: blocoD, teto: TETOS_BLOCO.D }, + ]; + + 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} (Bloco ${maiorBloco.letra})` }); + } + } + + 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' }; + insights.push({ icone: '🎯', texto: `${labels[coordAtiva.codigo] || coordAtiva.codigo} em exercício` }); + } else if (coords.length > 0) { + insights.push({ icone: '📜', texto: `Histórico de ${coords.length} coordenação(ões) CAPES` }); + } + + if (blocoA >= 300) { + insights.push({ icone: '🌟', texto: 'Destaque em Coordenação CAPES' }); + } + + const consultoria = consultor.consultoria || {}; + if (consultor.ativo && consultoria.anos_consecutivos >= 8) { + insights.push({ icone: '💎', texto: `${consultoria.anos_consecutivos} anos consecutivos de consultoria (+bônus continuidade)` }); + } else if (consultor.ativo && consultoria.anos_consecutivos >= 5) { + insights.push({ icone: '🔷', texto: `${consultoria.anos_consecutivos} anos consecutivos de consultoria` }); + } + + if (consultoria.retornos > 0) { + insights.push({ icone: '🔄', texto: `Retorno à consultoria (+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'); + if (gps.length > 0) { + insights.push({ icone: '🏆', texto: `${gps.length}x Grande Prêmio CAPES` }); + } else if (premios.length > 0) { + insights.push({ icone: '🥇', texto: `${premios.length}x Prêmio CAPES` }); + } + + 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` }); + } + + blocosAtivos.forEach(b => { + const aproveitamento = Math.round(b.valor / b.teto * 100); + if (aproveitamento >= 80) { + insights.push({ icone: '✅', texto: `${b.nome}: ${aproveitamento}% do teto (${b.valor}/${b.teto})` }); + } + }); + + 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( +