Reimplementa sistema de ranking com novos critérios V2
Mudanças principais: - Substitui 4 Componentes (A,B,C,D) por 3 Blocos (A,C,D) - Remove Componente B (Coordenação PPG = 0 pts no V1) - Adiciona novos tipos de atuação do Elasticsearch - Implementa critérios de pontuação com tetos individuais Bloco A - Coordenação CAPES: - CA (max 450), CAJ (max 370), CAJ_MP (max 315), CAM (max 280) - Calcula base + tempo + bônus atualidade + bônus retorno Bloco C - Consultoria: - CONS_ATIVO (base 150), CONS_HIST (base 100), CONS_FALECIDO (base 100) - Bônus continuidade: 3anos=+5, 5anos=+10, 8anos=+15 - Bônus retorno: +15 Bloco D - Premiações/Avaliações: - Inscrições (INSC_AUTOR, INSC_INST) - Avaliações (AVAL_COMIS_PREMIO, AVAL_COMIS_GP) - Coordenações (COORD_COMIS_PREMIO, COORD_COMIS_GP) - Premiações (PREMIACAO, PREMIACAO_GP, MENCAO) - Bolsas CNPQ, Participações, Orientações, Membros de Banca Frontend: - Header, ConsultorCard, CompararModal atualizados para 3 blocos - API service atualizado para nova estrutura de dados
This commit is contained in:
@@ -4,11 +4,6 @@ import './CompararModal.css';
|
||||
const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
if (!consultor1 || !consultor2) return null;
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return 'Atual';
|
||||
return new Date(dateStr).toLocaleDateString('pt-BR');
|
||||
};
|
||||
|
||||
const calcularDiferenca = (val1, val2) => {
|
||||
const diff = val1 - val2;
|
||||
if (diff === 0) return { texto: '=', classe: 'igual' };
|
||||
@@ -35,11 +30,27 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const p1 = consultor1.pontuacao;
|
||||
const p2 = consultor2.pontuacao;
|
||||
const p1 = consultor1.pontuacao || {};
|
||||
const p2 = consultor2.pontuacao || {};
|
||||
|
||||
const blocoA1 = p1.bloco_a || { total: consultor1.bloco_a || 0 };
|
||||
const blocoA2 = p2.bloco_a || { total: consultor2.bloco_a || 0 };
|
||||
const blocoC1 = p1.bloco_c || { total: consultor1.bloco_c || 0 };
|
||||
const blocoC2 = p2.bloco_c || { total: consultor2.bloco_c || 0 };
|
||||
const blocoD1 = p1.bloco_d || { total: consultor1.bloco_d || 0 };
|
||||
const blocoD2 = p2.bloco_d || { total: consultor2.bloco_d || 0 };
|
||||
|
||||
const total1 = p1.pontuacao_total || consultor1.pontuacao_total || 0;
|
||||
const total2 = p2.pontuacao_total || consultor2.pontuacao_total || 0;
|
||||
|
||||
const c1 = consultor1.consultoria;
|
||||
const c2 = consultor2.consultoria;
|
||||
|
||||
const somarAtuacoes = (atuacoes, campo) => {
|
||||
if (!atuacoes || !Array.isArray(atuacoes)) return 0;
|
||||
return atuacoes.reduce((sum, a) => sum + (a[campo] || 0), 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
@@ -49,7 +60,7 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
|
||||
<div className="comparacao-header">
|
||||
<div className="consultor-header consultor-1">
|
||||
<div className="rank-badge">#{consultor1.rank}</div>
|
||||
<div className="rank-badge">#{consultor1.posicao || consultor1.rank}</div>
|
||||
<div className="info">
|
||||
<span className="nome">{consultor1.nome}</span>
|
||||
<span className="anos">{consultor1.anos_atuacao} anos</span>
|
||||
@@ -59,7 +70,7 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
</div>
|
||||
<div className="vs">VS</div>
|
||||
<div className="consultor-header consultor-2">
|
||||
<div className="rank-badge">#{consultor2.rank}</div>
|
||||
<div className="rank-badge">#{consultor2.posicao || consultor2.rank}</div>
|
||||
<div className="info">
|
||||
<span className="nome">{consultor2.nome}</span>
|
||||
<span className="anos">{consultor2.anos_atuacao} anos</span>
|
||||
@@ -71,51 +82,50 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
|
||||
<div className="comparacao-secao">
|
||||
<h3>Pontuacao Total</h3>
|
||||
{renderLinhaComparacao('TOTAL', p1.pontuacao_total, p2.pontuacao_total, 'var(--accent)')}
|
||||
{renderLinhaComparacao('TOTAL', total1, total2, 'var(--accent)')}
|
||||
</div>
|
||||
|
||||
<div className="comparacao-secao">
|
||||
<h3 style={{ color: 'var(--accent-2)' }}>A - Coordenacao CAPES</h3>
|
||||
{renderLinhaComparacao('Total', p1.componente_a.total, p2.componente_a.total, 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Base', p1.componente_a.base, p2.componente_a.base, 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Tempo', p1.componente_a.tempo, p2.componente_a.tempo, 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Extras', p1.componente_a.extras, p2.componente_a.extras, 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Bonus', p1.componente_a.bonus, p2.componente_a.bonus, 'var(--accent-2)')}
|
||||
{(p1.componente_a.retorno > 0 || p2.componente_a.retorno > 0) &&
|
||||
renderLinhaComparacao('Retorno', p1.componente_a.retorno, p2.componente_a.retorno, 'var(--accent-2)')}
|
||||
</div>
|
||||
|
||||
<div className="comparacao-secao">
|
||||
<h3 style={{ color: 'var(--success)' }}>B - Coordenacao PPG</h3>
|
||||
{renderLinhaComparacao('Total', p1.componente_b.total, p2.componente_b.total, 'var(--success)')}
|
||||
{renderLinhaComparacao('Base', p1.componente_b.base, p2.componente_b.base, 'var(--success)')}
|
||||
{renderLinhaComparacao('Tempo', p1.componente_b.tempo, p2.componente_b.tempo, 'var(--success)')}
|
||||
{renderLinhaComparacao('Extras', p1.componente_b.extras, p2.componente_b.extras, 'var(--success)')}
|
||||
{renderLinhaComparacao('Bonus', p1.componente_b.bonus, p2.componente_b.bonus, 'var(--success)')}
|
||||
{renderLinhaComparacao('Total', blocoA1.total, blocoA2.total, 'var(--accent-2)')}
|
||||
{blocoA1.atuacoes && blocoA2.atuacoes && (
|
||||
<>
|
||||
{renderLinhaComparacao('Base', somarAtuacoes(blocoA1.atuacoes, 'base'), somarAtuacoes(blocoA2.atuacoes, 'base'), 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Tempo', somarAtuacoes(blocoA1.atuacoes, 'tempo'), somarAtuacoes(blocoA2.atuacoes, 'tempo'), 'var(--accent-2)')}
|
||||
{renderLinhaComparacao('Bonus', somarAtuacoes(blocoA1.atuacoes, 'bonus'), somarAtuacoes(blocoA2.atuacoes, 'bonus'), 'var(--accent-2)')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="comparacao-secao">
|
||||
<h3 style={{ color: 'var(--gold)' }}>C - Consultoria</h3>
|
||||
{renderLinhaComparacao('Total', p1.componente_c.total, p2.componente_c.total, 'var(--gold)')}
|
||||
{renderLinhaComparacao('Base', p1.componente_c.base, p2.componente_c.base, 'var(--gold)')}
|
||||
{renderLinhaComparacao('Tempo', p1.componente_c.tempo, p2.componente_c.tempo, 'var(--gold)')}
|
||||
{renderLinhaComparacao('Bonus', p1.componente_c.bonus, p2.componente_c.bonus, 'var(--gold)')}
|
||||
{(p1.componente_c.retorno > 0 || p2.componente_c.retorno > 0) &&
|
||||
renderLinhaComparacao('Retorno', p1.componente_c.retorno, p2.componente_c.retorno, 'var(--gold)')}
|
||||
{renderLinhaComparacao('Total', blocoC1.total, blocoC2.total, 'var(--gold)')}
|
||||
{blocoC1.atuacoes && blocoC2.atuacoes && (
|
||||
<>
|
||||
{renderLinhaComparacao('Base', somarAtuacoes(blocoC1.atuacoes, 'base'), somarAtuacoes(blocoC2.atuacoes, 'base'), 'var(--gold)')}
|
||||
{renderLinhaComparacao('Tempo', somarAtuacoes(blocoC1.atuacoes, 'tempo'), somarAtuacoes(blocoC2.atuacoes, 'tempo'), 'var(--gold)')}
|
||||
{renderLinhaComparacao('Bonus', somarAtuacoes(blocoC1.atuacoes, 'bonus'), somarAtuacoes(blocoC2.atuacoes, 'bonus'), 'var(--gold)')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="comparacao-secao">
|
||||
<h3 style={{ color: 'var(--bronze)' }}>D - Premiacoes</h3>
|
||||
{renderLinhaComparacao('Total', p1.componente_d.total, p2.componente_d.total, 'var(--bronze)')}
|
||||
{renderLinhaComparacao('Base', p1.componente_d.base, p2.componente_d.base, 'var(--bronze)')}
|
||||
<h3 style={{ color: 'var(--bronze)' }}>D - Premiacoes/Avaliacoes</h3>
|
||||
{renderLinhaComparacao('Total', blocoD1.total, blocoD2.total, 'var(--bronze)')}
|
||||
{blocoD1.atuacoes && blocoD2.atuacoes && (
|
||||
<>
|
||||
{renderLinhaComparacao('Base', somarAtuacoes(blocoD1.atuacoes, 'base'), somarAtuacoes(blocoD2.atuacoes, 'base'), 'var(--bronze)')}
|
||||
{renderLinhaComparacao('Tempo', somarAtuacoes(blocoD1.atuacoes, 'tempo'), somarAtuacoes(blocoD2.atuacoes, 'tempo'), 'var(--bronze)')}
|
||||
{renderLinhaComparacao('Bonus', somarAtuacoes(blocoD1.atuacoes, 'bonus'), somarAtuacoes(blocoD2.atuacoes, 'bonus'), 'var(--bronze)')}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(c1 || c2) && (
|
||||
<div className="comparacao-secao">
|
||||
<h3>Estatisticas de Consultoria</h3>
|
||||
{renderLinhaComparacao('Eventos', c1?.total_eventos || 0, c2?.total_eventos || 0, 'var(--muted)')}
|
||||
{renderLinhaComparacao('Recentes', c1?.eventos_recentes || 0, c2?.eventos_recentes || 0, 'var(--muted)')}
|
||||
{renderLinhaComparacao('Responsavel', c1?.vezes_responsavel || 0, c2?.vezes_responsavel || 0, 'var(--muted)')}
|
||||
<h3>Dados de Consultoria</h3>
|
||||
{renderLinhaComparacao('Anos Consec.', c1?.anos_consecutivos || 0, c2?.anos_consecutivos || 0, 'var(--muted)')}
|
||||
{renderLinhaComparacao('Retornos', c1?.retornos || 0, c2?.retornos || 0, 'var(--muted)')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -123,9 +133,9 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
<div className="resumo-item">
|
||||
<span className="resumo-label">Vencedor por pontuacao:</span>
|
||||
<span className="resumo-valor">
|
||||
{p1.pontuacao_total > p2.pontuacao_total
|
||||
{total1 > total2
|
||||
? consultor1.nome.split(' ').slice(0, 2).join(' ')
|
||||
: p2.pontuacao_total > p1.pontuacao_total
|
||||
: total2 > total1
|
||||
? consultor2.nome.split(' ').slice(0, 2).join(' ')
|
||||
: 'Empate'}
|
||||
</span>
|
||||
@@ -133,7 +143,7 @@ const CompararModal = ({ consultor1, consultor2, onClose }) => {
|
||||
<div className="resumo-item">
|
||||
<span className="resumo-label">Diferenca total:</span>
|
||||
<span className="resumo-valor diferenca">
|
||||
{Math.abs(p1.pontuacao_total - p2.pontuacao_total)} pts
|
||||
{Math.abs(total1 - total2)} pts
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,41 +2,17 @@ import React, { useState, useRef, useEffect } from 'react';
|
||||
import './ConsultorCard.css';
|
||||
|
||||
const FORMULAS = {
|
||||
componente_a: {
|
||||
titulo: 'Coordenação CAPES',
|
||||
base: 'CA=200 | CAJ=150 | CAJ-MP=120 | CAM=100',
|
||||
tempo: 'CA: 10pts/ano (máx 100)\nCAJ: 8pts/ano (máx 80)\nCAJ-MP: 6pts/ano (máx 60)\nCAM: 5pts/ano (máx 50)',
|
||||
extras: '20 pts por área adicional (máx 100)',
|
||||
bonus: 'Bônus atualidade:\nCA=30 | CAJ=20 | CAJ-MP=15 | CAM=10',
|
||||
retorno: '+20 pts se retornou à coordenação',
|
||||
total: 'Base + Tempo + Extras + Bônus + Retorno\n(máx 450 pts)',
|
||||
bloco_a: {
|
||||
titulo: 'Coordenacao CAPES',
|
||||
descricao: 'CA=200 | CAJ=150 | CAJ_MP=120 | CAM=100\nTempo: multiplicador por ano\nBonus atualidade + Retorno',
|
||||
},
|
||||
componente_b: {
|
||||
titulo: 'Coordenação PPG',
|
||||
base: '70 pts por ser coordenador de programa',
|
||||
tempo: '5 pts por ano completo (máx 50)',
|
||||
extras: '20 pts por programa adicional (máx 40)',
|
||||
bonus: 'Nota CAPES do PPG (máx 20): 7=20 | 6=15 | 5=10 | 4=5 | 3=0',
|
||||
retorno: '',
|
||||
total: 'Base + Tempo + Extras + Nota (máx 180 pts)',
|
||||
},
|
||||
componente_c: {
|
||||
bloco_c: {
|
||||
titulo: 'Consultoria',
|
||||
base: 'Ativo (recente): 150 pts | Histórico/Falecido: 100 pts',
|
||||
tempo: '5 pts por ano de consultoria (máx 50)',
|
||||
extras: 'Extras: não se aplicam (0)',
|
||||
bonus: 'Continuidade: 3 anos=+5 | 5 anos=+10 | 8+ anos=+15',
|
||||
retorno: '+15 pts se retornou à consultoria',
|
||||
total: 'Base + Tempo + Bônus + Retorno (máx 230 pts)',
|
||||
descricao: 'CONS_ATIVO=150 | CONS_HIST=100 | CONS_FALECIDO=100\nTempo: 5 pts/ano (max 50)\nContinuidade 8a+=15 | Retorno=15',
|
||||
},
|
||||
componente_d: {
|
||||
titulo: 'Premiações',
|
||||
base: 'Soma dos pontos das premiações',
|
||||
tempo: '',
|
||||
extras: 'Avaliação (avaliador) soma até 20 pts; demais pontos somam à base',
|
||||
bonus: '',
|
||||
retorno: '',
|
||||
total: 'Pontos totais das premiações (teto 180 pts)',
|
||||
bloco_d: {
|
||||
titulo: 'Premiacoes/Avaliacoes',
|
||||
descricao: 'PREMIACAO=150 | PREMIACAO_GP=30 | MENCAO=10\nAVAL_COMIS=30-50 | COORD_COMIS=50-60\nINSC_AUTOR=10 | INSC_INST=30',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -79,21 +55,11 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado
|
||||
onToggleSelecionado(consultor);
|
||||
};
|
||||
|
||||
const { pontuacao } = consultor;
|
||||
const { consultoria } = consultor;
|
||||
const temPPGDetalhado = (consultor.coordenacoes_programas || []).length > 0;
|
||||
|
||||
const formulasB = temPPGDetalhado
|
||||
? FORMULAS.componente_b
|
||||
: {
|
||||
titulo: 'Coordenação PPG',
|
||||
base: `Total apurado (sem detalhamento): ${pontuacao.componente_b.total} pts`,
|
||||
tempo: '',
|
||||
extras: '',
|
||||
bonus: '',
|
||||
retorno: '',
|
||||
total: `Total ${pontuacao.componente_b.total} pts`,
|
||||
};
|
||||
const { consultoria, pontuacao } = consultor;
|
||||
const blocoA = pontuacao?.bloco_a || { total: consultor.bloco_a || 0 };
|
||||
const blocoC = pontuacao?.bloco_c || { total: consultor.bloco_c || 0 };
|
||||
const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 0 };
|
||||
const pontuacaoTotal = pontuacao?.pontuacao_total || consultor.pontuacao_total || 0;
|
||||
|
||||
return (
|
||||
<div ref={cardRef} className={`ranking-card ${expanded ? 'expanded' : ''} ${highlight ? 'highlight' : ''} ${selecionado ? 'selecionado' : ''}`} onClick={() => setExpanded(!expanded)}>
|
||||
@@ -106,43 +72,39 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
</div>
|
||||
<div className={`rank ${getRankClass(consultor.rank)}`}>#{consultor.rank}</div>
|
||||
<div className={`rank ${getRankClass(consultor.posicao || consultor.rank)}`}>#{consultor.posicao || consultor.rank}</div>
|
||||
|
||||
<div className="card-info">
|
||||
<div className="consultant-name">
|
||||
{consultor.nome}
|
||||
{consultor.ativo && <span className="badge badge-ativo">ATIVO</span>}
|
||||
{!consultor.ativo && <span className="badge badge-historico">HISTÓRICO</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 atuação
|
||||
{consultoria && ` | Desde ${formatDate(consultoria.primeiro_evento)}`}
|
||||
{consultor.anos_atuacao} anos de atuacao
|
||||
{consultoria?.inicio && ` | Desde ${formatDate(consultoria.inicio)}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-stats">
|
||||
{consultoria && (
|
||||
<>
|
||||
<div className="stat">
|
||||
<div className="stat-value">{consultoria.total_eventos}</div>
|
||||
<div className="stat-label">Eventos</div>
|
||||
<div className="stat" title={`Codigo: ${consultoria.codigo}`}>
|
||||
<div className="stat-value">{consultoria.codigo?.replace('CONS_', '')}</div>
|
||||
<div className="stat-label">Status</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-value">{consultoria.eventos_recentes}</div>
|
||||
<div className="stat-label">Recentes</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-value">{consultoria.vezes_responsavel}</div>
|
||||
<div className="stat-label">Responsável</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">{consultor.pontuacao_total}</div>
|
||||
<div className="score-value">{pontuacaoTotal}</div>
|
||||
<div className="stat-label">Score</div>
|
||||
</div>
|
||||
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
|
||||
<div className="expand-icon">{expanded ? '?' : '?'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -150,99 +112,59 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado
|
||||
<div className="card-details">
|
||||
<div className="details-grid">
|
||||
<div className="detail-section">
|
||||
<h4>Pontuação Total</h4>
|
||||
<h4>Pontuacao Total</h4>
|
||||
<div className="score-breakdown-total">
|
||||
<ScoreItemWithTooltip
|
||||
value={pontuacao.componente_a.total}
|
||||
label="COMP A"
|
||||
formula={`Coordenação CAPES\n${FORMULAS.componente_a.total}`}
|
||||
style={{ color: pontuacao.componente_a.total > 0 ? 'var(--accent-2)' : 'var(--muted)' }}
|
||||
value={blocoA.total}
|
||||
label="BLOCO A"
|
||||
formula={FORMULAS.bloco_a.descricao}
|
||||
style={{ color: blocoA.total > 0 ? 'var(--accent-2)' : 'var(--muted)' }}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
value={pontuacao.componente_b.total}
|
||||
label="COMP B"
|
||||
formula={`Coordenação PPG\n${FORMULAS.componente_b.total}`}
|
||||
style={{ color: pontuacao.componente_b.total > 0 ? 'var(--success)' : 'var(--muted)' }}
|
||||
value={blocoC.total}
|
||||
label="BLOCO C"
|
||||
formula={FORMULAS.bloco_c.descricao}
|
||||
style={{ color: blocoC.total > 0 ? 'var(--gold)' : 'var(--muted)' }}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
value={pontuacao.componente_c.total}
|
||||
label="COMP C"
|
||||
formula={`Consultoria\n${FORMULAS.componente_c.total}`}
|
||||
style={{ color: pontuacao.componente_c.total > 0 ? 'var(--gold)' : 'var(--muted)' }}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
value={pontuacao.componente_d.total}
|
||||
label="COMP D"
|
||||
formula={`Premiações\n${FORMULAS.componente_d.total}`}
|
||||
style={{ color: pontuacao.componente_d.total > 0 ? 'var(--bronze)' : 'var(--muted)' }}
|
||||
value={blocoD.total}
|
||||
label="BLOCO D"
|
||||
formula={FORMULAS.bloco_d.descricao}
|
||||
style={{ color: blocoD.total > 0 ? 'var(--bronze)' : 'var(--muted)' }}
|
||||
/>
|
||||
<div className="score-item-wrapper">
|
||||
<div className="score-item score-total">
|
||||
<div className="score-item-value">{pontuacao.pontuacao_total}</div>
|
||||
<div className="score-item-value">{pontuacaoTotal}</div>
|
||||
<div className="score-item-label">TOTAL</div>
|
||||
</div>
|
||||
<div className="score-tooltip">Comp A + Comp B + Comp C + Comp D</div>
|
||||
<div className="score-tooltip">Bloco A + Bloco C + Bloco D</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ComponenteDetalhes
|
||||
titulo="A - Coordenação CAPES"
|
||||
componente={pontuacao.componente_a}
|
||||
cor="var(--accent-2)"
|
||||
formulas={FORMULAS.componente_a}
|
||||
/>
|
||||
{blocoA.atuacoes && blocoA.atuacoes.length > 0 && (
|
||||
<BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" />
|
||||
)}
|
||||
|
||||
<ComponenteDetalhes
|
||||
titulo="B - Coordenação PPG"
|
||||
componente={pontuacao.componente_b}
|
||||
cor="var(--success)"
|
||||
formulas={formulasB}
|
||||
/>
|
||||
{blocoC.atuacoes && blocoC.atuacoes.length > 0 && (
|
||||
<BlocoDetalhes titulo="C - Consultoria" bloco={blocoC} cor="var(--gold)" />
|
||||
)}
|
||||
|
||||
<ComponenteDetalhes
|
||||
titulo="C - Consultoria"
|
||||
componente={pontuacao.componente_c}
|
||||
cor="var(--gold)"
|
||||
formulas={FORMULAS.componente_c}
|
||||
/>
|
||||
|
||||
<ComponenteDetalhes
|
||||
titulo="D - Premiações"
|
||||
componente={pontuacao.componente_d}
|
||||
cor="var(--bronze)"
|
||||
formulas={FORMULAS.componente_d}
|
||||
/>
|
||||
{blocoD.atuacoes && blocoD.atuacoes.length > 0 && (
|
||||
<BlocoDetalhes titulo="D - Premiacoes/Avaliacoes" bloco={blocoD} cor="var(--bronze)" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{consultor.coordenacoes_capes?.length > 0 && (
|
||||
<div className="extra-details">
|
||||
<h4>Coordenações CAPES</h4>
|
||||
<h4>Coordenacoes CAPES</h4>
|
||||
<div className="list-items">
|
||||
{consultor.coordenacoes_capes.map((coord, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{coord.tipo}</span>
|
||||
<span className="badge">{coord.codigo || coord.tipo}</span>
|
||||
<span>{coord.area_avaliacao}</span>
|
||||
<span className="muted">
|
||||
{formatDate(coord.periodo.inicio)} - {formatDate(coord.periodo.fim)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{consultor.coordenacoes_programas?.length > 0 && (
|
||||
<div className="extra-details">
|
||||
<h4>Coordenações de Programa (PPG)</h4>
|
||||
<div className="list-items">
|
||||
{consultor.coordenacoes_programas.map((coord, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{coord.nota_ppg}</span>
|
||||
<span>{coord.nome_programa}</span>
|
||||
<span className="muted">{coord.area_avaliacao}</span>
|
||||
<span className="muted">
|
||||
{formatDate(coord.periodo.inicio)} - {formatDate(coord.periodo.fim)}
|
||||
{formatDate(coord.inicio || coord.periodo?.inicio)} - {formatDate(coord.fim || coord.periodo?.fim)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -252,11 +174,11 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado
|
||||
|
||||
{consultor.premiacoes?.length > 0 && (
|
||||
<div className="extra-details">
|
||||
<h4>Premiações</h4>
|
||||
<h4>Premiacoes</h4>
|
||||
<div className="list-items">
|
||||
{consultor.premiacoes.map((prem, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{prem.pontos} pts</span>
|
||||
<span className="badge">{prem.codigo}</span>
|
||||
<span>{prem.nome_premio}</span>
|
||||
<span className="muted">{prem.ano}</span>
|
||||
</div>
|
||||
@@ -264,29 +186,81 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{consultor.avaliacoes_comissao?.length > 0 && (
|
||||
<div className="extra-details">
|
||||
<h4>Avaliacoes de Comissao</h4>
|
||||
<div className="list-items">
|
||||
{consultor.avaliacoes_comissao.map((aval, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{aval.codigo}</span>
|
||||
<span>{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.map((insc, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{insc.codigo}</span>
|
||||
<span>{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.slice(0, 10).map((part, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<span className="badge">{part.codigo}</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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ComponenteDetalhes = ({ titulo, componente, cor, formulas }) => (
|
||||
const BlocoDetalhes = ({ titulo, bloco, cor }) => (
|
||||
<div className="detail-section">
|
||||
<h4 style={{ color: cor }}>{titulo}</h4>
|
||||
<div className="score-breakdown">
|
||||
<ScoreItemWithTooltip value={componente.base} label="BASE" formula={formulas?.base} />
|
||||
<ScoreItemWithTooltip value={componente.tempo} label="TEMPO" formula={formulas?.tempo} />
|
||||
<ScoreItemWithTooltip value={componente.extras} label="EXTRAS" formula={formulas?.extras} />
|
||||
<ScoreItemWithTooltip value={componente.bonus} label="BÔNUS" formula={formulas?.bonus} />
|
||||
{componente.retorno > 0 && (
|
||||
<ScoreItemWithTooltip value={componente.retorno} label="RETORNO" formula={formulas?.retorno} />
|
||||
)}
|
||||
{bloco.atuacoes?.map((at, idx) => (
|
||||
<div key={idx} className="score-item-wrapper">
|
||||
<div className="score-item">
|
||||
<div className="score-item-value">{at.total}</div>
|
||||
<div className="score-item-label">{at.codigo}</div>
|
||||
</div>
|
||||
<div className="score-tooltip">
|
||||
Base: {at.base} | Tempo: {at.tempo} | Bonus: {at.bonus}
|
||||
{at.quantidade > 1 && ` | Qtd: ${at.quantidade}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="score-item-wrapper">
|
||||
<div className="score-item score-total">
|
||||
<div className="score-item-value">{componente.total}</div>
|
||||
<div className="score-item-value">{bloco.total}</div>
|
||||
<div className="score-item-label">TOTAL</div>
|
||||
</div>
|
||||
{formulas?.total && <div className="score-tooltip">{formulas.total}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,74 +12,64 @@ const Header = ({ total }) => {
|
||||
<div className="header-content">
|
||||
<h1>Ranking de Consultores CAPES</h1>
|
||||
<p className="subtitle">
|
||||
Sistema completo de pontuação baseado na Minuta Técnica |
|
||||
4 Componentes: Coordenação CAPES + PPG + Consultoria + Premiações
|
||||
Sistema de pontuacao baseado nos Criterios V2 |
|
||||
3 Blocos: Coordenacao CAPES + Consultoria + Premiacoes/Avaliacoes
|
||||
</p>
|
||||
<div className="meta">
|
||||
Gerado em {dataGeracao} | Total: {totalFormatado} consultores
|
||||
</div>
|
||||
|
||||
<div className="criteria-box">
|
||||
<h3>Componentes de Pontuação</h3>
|
||||
<h3>Blocos de Pontuacao</h3>
|
||||
<div className="criteria-grid">
|
||||
<div className="criteria-section">
|
||||
<h4>A - Coordenação CAPES</h4>
|
||||
<span className="max-pts">máx 450 pts</span>
|
||||
<h4>A - Coordenacao CAPES</h4>
|
||||
<span className="max-pts">max 450 pts</span>
|
||||
<table className="criteria-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Codigo</th>
|
||||
<th>Base</th>
|
||||
<th>Tempo</th>
|
||||
<th>Bônus</th>
|
||||
<th>Bonus</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>CA</td><td>200</td><td>até 100</td><td>30</td></tr>
|
||||
<tr><td>CAJ</td><td>150</td><td>até 80</td><td>20</td></tr>
|
||||
<tr><td>CAJ-MP</td><td>120</td><td>até 60</td><td>15</td></tr>
|
||||
<tr><td>CAM</td><td>100</td><td>até 50</td><td>10</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="criteria-note">+ Áreas (até 100) + Retorno (20)</div>
|
||||
</div>
|
||||
|
||||
<div className="criteria-section">
|
||||
<h4>B - Coordenação PPG</h4>
|
||||
<span className="max-pts">máx 180 pts</span>
|
||||
<table className="criteria-table">
|
||||
<tbody>
|
||||
<tr><td>Base</td><td className="pts-value">70 pts</td></tr>
|
||||
<tr><td>Tempo</td><td className="pts-value">5 pts/ano (máx 50)</td></tr>
|
||||
<tr><td>Programas extras</td><td className="pts-value">20 pts/prog (máx 40)</td></tr>
|
||||
<tr><td>Bônus ativo</td><td className="pts-value">20 pts</td></tr>
|
||||
<tr><td>CA</td><td>200</td><td>10/ano (max 100)</td><td>30</td></tr>
|
||||
<tr><td>CAJ</td><td>150</td><td>8/ano (max 80)</td><td>20</td></tr>
|
||||
<tr><td>CAJ_MP</td><td>120</td><td>6/ano (max 60)</td><td>15</td></tr>
|
||||
<tr><td>CAM</td><td>100</td><td>5/ano (max 50)</td><td>10</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="criteria-note">+ Retorno (20)</div>
|
||||
</div>
|
||||
|
||||
<div className="criteria-section">
|
||||
<h4>C - Consultoria</h4>
|
||||
<span className="max-pts">máx 230 pts</span>
|
||||
<span className="max-pts">max 230 pts</span>
|
||||
<table className="criteria-table">
|
||||
<tbody>
|
||||
<tr><td>Base (ativo)</td><td className="pts-value">150 pts</td></tr>
|
||||
<tr><td>Base (histórico)</td><td className="pts-value">100 pts</td></tr>
|
||||
<tr><td>Tempo</td><td className="pts-value">5 pts/ano (máx 50)</td></tr>
|
||||
<tr><td>Eventos</td><td className="pts-value">2 pts/ev (máx 20)</td></tr>
|
||||
<tr><td>Responsável</td><td className="pts-value">5 pts/vez (máx 25)</td></tr>
|
||||
<tr><td>Áreas extras</td><td className="pts-value">10 pts/área (máx 30)</td></tr>
|
||||
<tr><td>CONS_ATIVO</td><td className="pts-value">150 pts</td></tr>
|
||||
<tr><td>CONS_HIST</td><td className="pts-value">100 pts</td></tr>
|
||||
<tr><td>CONS_FALECIDO</td><td className="pts-value">100 pts</td></tr>
|
||||
<tr><td>Tempo</td><td className="pts-value">5 pts/ano (max 50)</td></tr>
|
||||
<tr><td>Continuidade 8a+</td><td className="pts-value">+15 pts</td></tr>
|
||||
<tr><td>Retorno</td><td className="pts-value">+15 pts</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="criteria-section">
|
||||
<h4>D - Premiações</h4>
|
||||
<span className="max-pts">máx 180 pts</span>
|
||||
<h4>D - Premiacoes e Avaliacoes</h4>
|
||||
<span className="max-pts">max 180 pts</span>
|
||||
<table className="criteria-table">
|
||||
<tbody>
|
||||
<tr><td>Premiação</td><td className="pts-value">60 pts</td></tr>
|
||||
<tr><td>Avaliação</td><td className="pts-value">40 pts</td></tr>
|
||||
<tr><td>Inscrição</td><td className="pts-value">20 pts</td></tr>
|
||||
<tr><td>PREMIACAO (GP)</td><td className="pts-value">150 pts (max 180)</td></tr>
|
||||
<tr><td>PREMIACAO_GP</td><td className="pts-value">30 pts (max 60)</td></tr>
|
||||
<tr><td>MENCAO</td><td className="pts-value">10 pts (max 20)</td></tr>
|
||||
<tr><td>COORD_COMIS_GP</td><td className="pts-value">60 pts (max 120)</td></tr>
|
||||
<tr><td>AVAL_COMIS_GP</td><td className="pts-value">50 pts (max 100)</td></tr>
|
||||
<tr><td>INSC_INST</td><td className="pts-value">30 pts (max 60)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -8,46 +8,8 @@ const api = axios.create({
|
||||
timeout: 180000,
|
||||
});
|
||||
|
||||
const calcularComponenteB = (coordenacoesProgramas = []) => {
|
||||
if (!coordenacoesProgramas.length) {
|
||||
return { base: 0, tempo: 0, extras: 0, bonus: 0, total: 0 };
|
||||
}
|
||||
|
||||
const agora = new Date();
|
||||
const base = 70;
|
||||
|
||||
let anosTotais = 0;
|
||||
coordenacoesProgramas.forEach((coord) => {
|
||||
const inicio = coord.periodo?.inicio ? new Date(coord.periodo.inicio) : null;
|
||||
const fim = coord.periodo?.fim ? new Date(coord.periodo.fim) : agora;
|
||||
if (inicio && fim >= inicio) {
|
||||
const diffAnos = Math.floor((fim - inicio) / (365 * 24 * 60 * 60 * 1000));
|
||||
anosTotais += diffAnos;
|
||||
}
|
||||
});
|
||||
const tempo = Math.min(anosTotais * 5, 50);
|
||||
|
||||
const programasDistintos = new Set(
|
||||
coordenacoesProgramas.map((c) => c.id_programa || c.codigo_programa || c.nome_programa)
|
||||
).size;
|
||||
const extras = programasDistintos > 1 ? Math.min((programasDistintos - 1) * 20, 40) : 0;
|
||||
|
||||
let maiorNota = 0;
|
||||
coordenacoesProgramas.forEach((coord) => {
|
||||
const n = String(coord.nota_ppg || '').trim();
|
||||
if (['7', '6', '5', '4', '3'].includes(n)) {
|
||||
maiorNota = Math.max(maiorNota, parseInt(n, 10));
|
||||
}
|
||||
});
|
||||
const bonus = ({ 7: 20, 6: 15, 5: 10, 4: 5, 3: 0 }[maiorNota] ?? 0);
|
||||
|
||||
const total = base + tempo + extras + bonus;
|
||||
return { base, tempo, extras, bonus, total };
|
||||
};
|
||||
|
||||
export const rankingService = {
|
||||
async getRanking(page = 1, size = 100) {
|
||||
// Usa ranking paginado (Oracle) para percorrer os 350k
|
||||
const params = { page, size };
|
||||
const response = await api.get('/ranking/paginado', { params });
|
||||
const data = response.data;
|
||||
@@ -57,8 +19,8 @@ export const rankingService = {
|
||||
const consultores = (data.consultores || []).map((c) => {
|
||||
const anos = Number(c.anos_atuacao || 0);
|
||||
const consultoria = c.consultoria || {};
|
||||
const primeiroEvento = consultoria.primeiro_evento
|
||||
? new Date(consultoria.primeiro_evento)
|
||||
const primeiroEvento = consultoria.inicio
|
||||
? new Date(consultoria.inicio)
|
||||
: (() => {
|
||||
const d = new Date(hoje);
|
||||
d.setFullYear(d.getFullYear() - Math.floor(anos));
|
||||
@@ -75,62 +37,41 @@ export const rankingService = {
|
||||
periodo: mapPeriodo(coord),
|
||||
}));
|
||||
|
||||
const coordenacoesProgramas = (c.coordenacoes_programas || []).map((coord) => ({
|
||||
...coord,
|
||||
periodo: mapPeriodo(coord),
|
||||
}));
|
||||
|
||||
let compB;
|
||||
if (coordenacoesProgramas.length > 0) {
|
||||
compB = calcularComponenteB(coordenacoesProgramas);
|
||||
} else {
|
||||
const totalB = Number(c.componente_b || 0);
|
||||
compB = {
|
||||
base: totalB > 0 ? totalB : 0,
|
||||
tempo: 0,
|
||||
extras: 0,
|
||||
bonus: 0,
|
||||
total: totalB,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id_pessoa: c.id_pessoa,
|
||||
nome: c.nome,
|
||||
rank: c.posicao,
|
||||
posicao: c.posicao,
|
||||
pontuacao_total: c.pontuacao_total,
|
||||
componente_a: c.componente_a,
|
||||
componente_b: compB.total,
|
||||
componente_c: c.componente_c,
|
||||
componente_d: c.componente_d,
|
||||
bloco_a: c.bloco_a,
|
||||
bloco_c: c.bloco_c,
|
||||
bloco_d: c.bloco_d,
|
||||
ativo: c.ativo,
|
||||
anos_atuacao: anos,
|
||||
veterano: anos >= 10,
|
||||
pontuacao: {
|
||||
pontuacao_total: c.pontuacao_total,
|
||||
componente_a: { base: c.componente_a, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_a },
|
||||
componente_b: {
|
||||
base: compB.base,
|
||||
tempo: compB.tempo,
|
||||
extras: compB.extras,
|
||||
bonus: compB.bonus,
|
||||
retorno: 0,
|
||||
total: compB.total,
|
||||
},
|
||||
componente_c: { base: c.componente_c, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_c },
|
||||
componente_d: { base: c.componente_d, tempo: 0, extras: 0, bonus: 0, retorno: 0, total: c.componente_d },
|
||||
bloco_a: { total: c.bloco_a, atuacoes: [] },
|
||||
bloco_c: { total: c.bloco_c, atuacoes: [] },
|
||||
bloco_d: { total: c.bloco_d, atuacoes: [] },
|
||||
},
|
||||
consultoria: {
|
||||
total_eventos: consultoria.total_eventos ?? 0,
|
||||
eventos_recentes: consultoria.eventos_recentes ?? 0,
|
||||
vezes_responsavel: consultoria.vezes_responsavel ?? 0,
|
||||
primeiro_evento: consultoria.primeiro_evento || primeiroEvento.toISOString(),
|
||||
ultimo_evento: consultoria.ultimo_evento || null,
|
||||
codigo: consultoria.codigo || null,
|
||||
situacao: consultoria.situacao || null,
|
||||
inicio: consultoria.inicio || primeiroEvento.toISOString(),
|
||||
fim: consultoria.fim || null,
|
||||
areas: consultoria.areas || [],
|
||||
anos_consecutivos: consultoria.anos_consecutivos || 0,
|
||||
retornos: consultoria.retornos || 0,
|
||||
},
|
||||
coordenacoes_capes: coordenacoesCapes,
|
||||
coordenacoes_programas: coordenacoesProgramas,
|
||||
inscricoes: c.inscricoes || [],
|
||||
avaliacoes_comissao: c.avaliacoes_comissao || [],
|
||||
premiacoes: c.premiacoes || [],
|
||||
bolsas_cnpq: c.bolsas_cnpq || [],
|
||||
participacoes: c.participacoes || [],
|
||||
orientacoes: c.orientacoes || [],
|
||||
membros_banca: c.membros_banca || [],
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user