From 0d355e705ebdcdd1ef7b630d419d6b58122c3cbf Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Wed, 24 Dec 2025 00:53:28 -0300 Subject: [PATCH] feat(frontend): implementar selos faltantes e corrigir alinhamento tabelas - Adicionar 10 novos selos: MB_BANCA_*, EVENTO, PROJ, IDIOMA_*, TITULACAO_* - Adicionar TETOS para Bloco A e B no calculo de pontuacao - Adicionar modais para selos de banca, evento e projeto - Corrigir alinhamento de colunas nas tabelas do painel de criterios - Corrigir alinhamento nos modais de blocos (BlocoCriteriosModal) - Ajustar layout dos selos para linha dedicada abaixo do nome - Corrigir distribuicao de espaco nas tabelas com selos - Corrigir extracao de datas de consultoria no backend --- .../repositories/consultor_repository_impl.py | 12 +- .../src/components/BlocoCriteriosModal.css | 10 ++ frontend/src/components/ConsultorCard.css | 46 ++++++ frontend/src/components/ConsultorCard.jsx | 147 +++++++++++++++--- frontend/src/components/Header.css | 41 ++++- scripts/recarregar_ranking.sh | 2 +- 6 files changed, 228 insertions(+), 30 deletions(-) diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index 1292db5..da6d3cf 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -169,17 +169,21 @@ class ConsultorRepositoryImpl(ConsultorRepository): situacoes.append(situacao) inicio = ( - self._parse_date(dc.get("inicioVinculacao")) + self._parse_date(c.get("inicio")) + or self._parse_date(dc.get("inicioVinculacao")) or self._parse_date(dc.get("inicioSituacao")) - or self._parse_date(c.get("inicio")) ) + if inicio and inicio.year < 1950: + inicio = None situacao_texto = (dc.get("situacaoConsultoria") or "").lower() is_situacao_ativa = "atividade" in situacao_texto or "ativo" in situacao_texto fim = ( - self._parse_date(dc.get("fimVinculacao")) + self._parse_date(c.get("fim")) + or self._parse_date(dc.get("fimVinculacao")) or (self._parse_date(dc.get("inativacaoSituacao")) if not is_situacao_ativa else None) - or self._parse_date(c.get("fim")) ) + if fim and fim.year < 1950: + fim = None if inicio and fim and fim < inicio: fim = None diff --git a/frontend/src/components/BlocoCriteriosModal.css b/frontend/src/components/BlocoCriteriosModal.css index 7db65c8..aee4871 100644 --- a/frontend/src/components/BlocoCriteriosModal.css +++ b/frontend/src/components/BlocoCriteriosModal.css @@ -250,6 +250,16 @@ border-bottom: 1px solid rgba(255, 255, 255, 0.1); } +.bloco-tabela th:nth-child(3), +.bloco-tabela th:nth-child(4) { + text-align: center; +} + +.bloco-tabela.bonus-valores th:nth-child(2), +.bloco-tabela.bonus-valores th:nth-child(3) { + text-align: center; +} + .bloco-tabela td { padding: 0.5rem 0.75rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index 2eb5e11..10a19a4 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -175,6 +175,22 @@ gap: 0.65rem; } +.consultant-selos-row { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; + margin-top: 0.5rem; + justify-content: flex-start; + width: 100%; +} + +.consultant-selos-row .selos-container.selos-compacto { + display: flex; + gap: 0.35rem; + justify-content: flex-start; + margin-left: 0 !important; +} + .consultant-area { color: var(--muted); font-size: 0.95rem; @@ -914,6 +930,36 @@ color: #e2e8f0; } +.selo-banca { + background: linear-gradient(135deg, rgba(167, 139, 250, 0.2), rgba(167, 139, 250, 0.08)); + border-color: rgba(167, 139, 250, 0.35); + color: #c4b5fd; +} + +.selo-evento { + background: linear-gradient(135deg, rgba(251, 146, 60, 0.2), rgba(251, 146, 60, 0.08)); + border-color: rgba(251, 146, 60, 0.35); + color: #fdba74; +} + +.selo-proj { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(16, 185, 129, 0.08)); + border-color: rgba(16, 185, 129, 0.35); + color: #6ee7b7; +} + +.selo-idioma { + background: linear-gradient(135deg, rgba(56, 189, 248, 0.2), rgba(56, 189, 248, 0.08)); + border-color: rgba(56, 189, 248, 0.35); + color: #7dd3fc; +} + +.selo-titulacao { + background: linear-gradient(135deg, rgba(192, 132, 252, 0.2), rgba(192, 132, 252, 0.08)); + border-color: rgba(192, 132, 252, 0.35); + color: #d8b4fe; +} + .tipos-section { grid-column: 1 / -1; margin-top: 1rem; diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index f88534c..6be4dc5 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -23,6 +23,16 @@ const SELOS = { CO_ORIENT_TESE: { codigo: 'CO_ORIENT_TESE', label: 'Coorient. Tese', cor: 'selo-coorient', icone: 'šŸ“š' }, CO_ORIENT_DISS: { codigo: 'CO_ORIENT_DISS', label: 'Coorient. Diss.', cor: 'selo-coorient', icone: 'šŸ“„' }, CO_ORIENT_POS_DOC: { codigo: 'CO_ORIENT_POS_DOC', label: 'Coorient. Pos-Doc', cor: 'selo-coorient', icone: 'šŸ”¬' }, + MB_BANCA_POS_DOC: { codigo: 'MB_BANCA_POS_DOC', label: 'Banca Pos-Doc', cor: 'selo-banca', icone: 'šŸ”¬' }, + MB_BANCA_TESE: { codigo: 'MB_BANCA_TESE', label: 'Banca Tese', cor: 'selo-banca', icone: 'šŸ“š' }, + MB_BANCA_DISS: { codigo: 'MB_BANCA_DISS', label: 'Banca Diss.', cor: 'selo-banca', icone: 'šŸ“„' }, + EVENTO: { codigo: 'EVENTO', label: 'Evento', cor: 'selo-evento', icone: 'šŸ“…' }, + PROJ: { codigo: 'PROJ', label: 'Projeto', cor: 'selo-proj', icone: 'šŸ“' }, + IDIOMA_BILINGUE: { codigo: 'IDIOMA_BILINGUE', label: 'Bilingue', cor: 'selo-idioma', icone: 'šŸŒ' }, + IDIOMA_MULTILINGUE: { codigo: 'IDIOMA_MULTILINGUE', label: 'Multilingue', cor: 'selo-idioma', icone: '🌐' }, + TITULACAO_MESTRE: { codigo: 'TITULACAO_MESTRE', label: 'Mestre', cor: 'selo-titulacao', icone: 'šŸŽ“' }, + TITULACAO_DOUTOR: { codigo: 'TITULACAO_DOUTOR', label: 'Doutor', cor: 'selo-titulacao', icone: 'šŸŽ“' }, + TITULACAO_POS_DOUTOR: { codigo: 'TITULACAO_POS_DOUTOR', label: 'Pos-Doutor', cor: 'selo-titulacao', icone: 'šŸŽ“' }, }; const TIPOS_ATUACAO_CONFIG = { @@ -98,6 +108,30 @@ const gerarSelos = (consultor) => { gerarSelosOrientacaoContagem('CO_ORIENT_TESE', true, SELOS.CO_ORIENT_TESE); gerarSelosOrientacaoContagem('CO_ORIENT_DISS', true, SELOS.CO_ORIENT_DISS); + const membrosBanca = Array.isArray(consultor.membros_banca) ? consultor.membros_banca : []; + const bancaPosDoc = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_POS_DOC'); + const bancaTese = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_TESE'); + const bancaDiss = membrosBanca.filter((m) => m.codigo === 'MB_BANCA_DISS'); + if (bancaPosDoc.length > 0) { + selos.push({ ...SELOS.MB_BANCA_POS_DOC, qtd: bancaPosDoc.length, hint: `Banca Pos-Doc (${bancaPosDoc.length}x)` }); + } + if (bancaTese.length > 0) { + selos.push({ ...SELOS.MB_BANCA_TESE, qtd: bancaTese.length, hint: `Banca Tese (${bancaTese.length}x)` }); + } + if (bancaDiss.length > 0) { + selos.push({ ...SELOS.MB_BANCA_DISS, qtd: bancaDiss.length, hint: `Banca Dissertacao (${bancaDiss.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)` }); + } + return selos; }; @@ -107,15 +141,14 @@ const SELOS_COM_DADOS = [ 'ORIENT_GP', 'ORIENT_PREMIO', 'ORIENT_MENCAO', 'COORIENT_GP', 'COORIENT_PREMIO', 'COORIENT_MENCAO', 'ORIENT_TESE', 'ORIENT_DISS', 'ORIENT_POS_DOC', - 'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC' + 'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC', + 'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS', + 'EVENTO', 'PROJ', ]; const SelosBadges = ({ selos, compacto = false, onSeloClick }) => { if (!selos || selos.length === 0) return null; - const selosExibidos = compacto ? selos.slice(0, 4) : selos; - const selosOcultos = compacto && selos.length > 4 ? selos.length - 4 : 0; - const handleClick = (e, selo) => { if (onSeloClick && SELOS_COM_DADOS.includes(selo.codigo)) { e.preventDefault(); @@ -126,7 +159,7 @@ const SelosBadges = ({ selos, compacto = false, onSeloClick }) => { return (
- {selosExibidos.map((selo, idx) => { + {selos.map((selo, idx) => { const temDados = SELOS_COM_DADOS.includes(selo.codigo); return ( { ); })} - {selosOcultos > 0 && ( - +{selosOcultos} - )}
); }; @@ -526,6 +556,70 @@ const SeloModal = ({ selo, consultor, onClose }) => { ); } + 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

Sem dados de bancas

; + return ( +
+
+ Total: {lista.length} {tipoLabel[selo.codigo]?.toLowerCase() || 'bancas'} +
+ {lista.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((b, i) => ( +
+ {b.nivel || b.tipo || selo.codigo} + {b.tipo || tipoLabel[selo.codigo]} + {b.ano || '-'} +
+ ))} +
+ ); + } + case 'EVENTO': { + const participacoes = consultor.participacoes || []; + const eventos = participacoes.filter(p => p.codigo === 'EVENTO'); + if (eventos.length === 0) return

Sem eventos

; + return ( +
+
+ Total: {eventos.length} participação(ões) em eventos +
+ {eventos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((e, i) => ( +
+ EVENTO + {e.descricao || e.tipo || 'Evento'} + {e.ano || '-'} +
+ ))} +
+ ); + } + case 'PROJ': { + const participacoes = consultor.participacoes || []; + const projetos = participacoes.filter(p => p.codigo === 'PROJ'); + if (projetos.length === 0) return

Sem projetos

; + return ( +
+
+ Total: {projetos.length} participação(ões) em projetos +
+ {projetos.sort((a, b) => (b.ano || 0) - (a.ano || 0)).map((p, i) => ( +
+ PROJ + {p.descricao || p.tipo || 'Projeto'} + {p.ano || '-'} +
+ ))} +
+ ); + } default: return

Sem dados detalhados para este selo

; } @@ -1009,18 +1103,25 @@ const InsightsModal = ({ consultor, totalConsultores, onClose }) => { }; const TETOS = { - INSC_AUTOR: { teto: 20, doc: '3.3 Inscrições', bonus: '+2/participação (max 10)' }, - INSC_INST_AUTOR: { teto: 50, doc: '3.3 Inscrições', bonus: '+5/participação (max 10)' }, - AVAL_COMIS_PREMIO: { teto: 60, doc: '3.4 Avaliação/Comissão', bonus: '+2/ano (max 15)' }, - AVAL_COMIS_GP: { teto: 80, doc: '3.4 Avaliação/Comissão', bonus: '+3/ano (max 20)' }, - COORD_COMIS_PREMIO: { teto: 100, doc: '3.4 Avaliação/Comissão', bonus: '+4/ano (max 20)' }, - COORD_COMIS_GP: { teto: 120, doc: '3.4 Avaliação/Comissão', bonus: '+6/ano (max 20)' }, - PREMIACAO_GP_AUTOR: { teto: 300, doc: '3.4 Premiações e Bolsas' }, - PREMIACAO_AUTOR: { teto: 150, doc: '3.4 Premiações e Bolsas' }, - MENCAO_AUTOR: { teto: 90, doc: '3.4 Premiações e Bolsas' }, - EVENTO: { teto: 5, doc: '3.5 Participações Acadêmicas', bonus: '+1/participação (max 10)' }, - PROJ: { teto: 30, doc: '3.5 Participações Acadêmicas', bonus: '+2/participação (max 10)' }, - BOL_BPQ_NIVEL: { teto: 60, doc: '3.4 Premiações e Bolsas' }, + 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 }) => { @@ -1308,12 +1409,16 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio {consultor.ativo && ATIVO} {!consultor.ativo && HISTORICO} {consultor.veterano && VETERANO} -
{consultor.anos_atuacao} anos de atuacao {consultoria?.inicio && ` | Desde ${formatDate(consultoria.inicio)}`}
+ {selos.length > 0 && ( +
+ +
+ )}
diff --git a/frontend/src/components/Header.css b/frontend/src/components/Header.css index 925e566..040e1a0 100644 --- a/frontend/src/components/Header.css +++ b/frontend/src/components/Header.css @@ -204,6 +204,7 @@ margin-top: 0.25rem; font-size: 0.75rem; border-collapse: collapse; + table-layout: fixed; } .criteria-table.compact { @@ -220,10 +221,13 @@ text-transform: uppercase; letter-spacing: 0.3px; white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .criteria-table th:first-child { text-align: left; + width: 35%; } .criteria-table td { @@ -231,6 +235,8 @@ color: var(--muted); border-bottom: 1px dashed rgba(255,255,255,0.05); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .criteria-table tr:last-child td { @@ -280,6 +286,10 @@ color: #a5b4fc; } +.bloco-a .criteria-table th:first-child { + width: 28%; +} + /* Bloco B */ .criteria-section.bloco-b { border-color: rgba(234, 179, 8, 0.3); @@ -304,6 +314,14 @@ color: #fcd34d; } +.bloco-b .criteria-table th:first-child { + width: 25%; +} + +.bloco-b .criteria-table th:last-child { + width: 28%; +} + /* Bloco C */ .criteria-section.bloco-c { border-color: rgba(16, 185, 129, 0.3); @@ -352,6 +370,10 @@ color: #fbbf24; } +.bloco-d .criteria-table th:first-child { + width: 40%; +} + /* Bloco E */ .criteria-section.bloco-e { border-color: rgba(139, 92, 246, 0.3); @@ -377,10 +399,21 @@ } /* Selos na Legenda */ -.selos-table th:last-child, -.selos-table td:last-child { - text-align: center; - width: 2.5rem; +.selos-table { + table-layout: auto; +} + +.selos-table th:first-child { + width: auto; +} + +.selos-table th, +.selos-table td { + padding: 0.15rem 0.35rem; +} + +.selos-table.compact th:first-child { + width: auto; } .selo-legenda { diff --git a/scripts/recarregar_ranking.sh b/scripts/recarregar_ranking.sh index 275acc2..eec4d0c 100755 --- a/scripts/recarregar_ranking.sh +++ b/scripts/recarregar_ranking.sh @@ -66,7 +66,7 @@ if [ "$STATS" != "{}" ]; then import sys, json data = json.load(sys.stdin) print(f\" Total consultores: {data.get('total_consultores', 'N/A'):,}\") -print(f\" Com pontuação: {data.get('com_pontuacao', 'N/A'):,}\") +print(f\" Total ativos: {data.get('total_ativos', 'N/A'):,}\") print(f\" Pontuação mÔxima: {data.get('pontuacao_maxima', 'N/A')}\") print(f\" Pontuação média: {data.get('pontuacao_media', 'N/A'):.2f}\") " 2>/dev/null || echo " (não foi possível obter estatísticas)"