fix(backend): corrigir exibicao de idiomas e selos multilingue

- Adicionar idiomas e formacoes ao _source das queries ES (client.py)
- Corrigir type mismatch int/str no endpoint paginado (routes.py)
- Adicionar campo evento nas inscricoes para nome do premio
- Implementar extracao de idiomas do ES no repository
- Ajustar frontend para exibir selo multilingue corretamente
This commit is contained in:
Frederico Castro
2025-12-24 18:12:22 -03:00
parent 0d355e705e
commit 919d95d1e8
14 changed files with 266 additions and 12 deletions

View File

@@ -28,7 +28,6 @@ const SELOS = {
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: '🎓' },
@@ -49,6 +48,13 @@ const TIPOS_ATUACAO_CONFIG = {
const gerarSelos = (consultor) => {
const selos = [];
const normalizarIdioma = (valor) => (valor || '')
.toString()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim();
const normalizarCodigoIdioma = (valor) => normalizarIdioma(valor).replace(/[^a-z0-9]/g, '');
const isPresidCamaraVigente = consultor.coordenacoes_capes?.some(
(c) => c.codigo === 'CAM' && c.presidente && (c.ativo ?? !c.fim)
@@ -132,6 +138,40 @@ const gerarSelos = (consultor) => {
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(', ')}`,
});
}
const titulacao = consultor.titulacao || '';
const titulacaoLower = titulacao.toLowerCase();
if (titulacaoLower.includes('pós-doutorado') || titulacaoLower.includes('pos-doutorado') || titulacaoLower.includes('posdoc') || titulacaoLower.includes('pós-doc')) {
selos.push({ ...SELOS.TITULACAO_POS_DOUTOR, qtd: 1, hint: 'Pós-Doutorado' });
} else if (titulacaoLower.includes('doutorado') || titulacaoLower.includes('doutor')) {
selos.push({ ...SELOS.TITULACAO_DOUTOR, qtd: 1, hint: 'Doutorado' });
} else if (titulacaoLower.includes('mestrado') || titulacaoLower.includes('mestre')) {
selos.push({ ...SELOS.TITULACAO_MESTRE, qtd: 1, hint: 'Mestrado' });
}
return selos;
};
@@ -144,6 +184,8 @@ const SELOS_COM_DADOS = [
'CO_ORIENT_TESE', 'CO_ORIENT_DISS', 'CO_ORIENT_POS_DOC',
'MB_BANCA_POS_DOC', 'MB_BANCA_TESE', 'MB_BANCA_DISS',
'EVENTO', 'PROJ',
'IDIOMA_MULTILINGUE',
'TITULACAO_MESTRE', 'TITULACAO_DOUTOR', 'TITULACAO_POS_DOUTOR',
];
const SelosBadges = ({ selos, compacto = false, onSeloClick }) => {
@@ -355,7 +397,7 @@ const TipoAtuacaoModal = ({ tipo, consultor, onClose }) => {
{[...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.premio || ins.descricao || '-'}</span>
<span className="modal-item-main">{ins.evento || ins.premio || ins.descricao || '-'}</span>
<span className="muted">{ins.ano || '-'}</span>
</div>
))}
@@ -837,6 +879,12 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => {
<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>
@@ -1461,7 +1509,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
onClick={handleRawDataClick}
title="Ver dados completos do ATUACAPES"
>
📋
</button>
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
</div>
@@ -1687,7 +1735,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
>
<span className="badge">{insc.codigo}</span>
<span className="pontos">{PONTOS_BASE[insc.codigo] || 0} pts</span>
<span>{insc.premio}</span>
<span>{insc.evento || insc.premio}</span>
<span className="muted">{insc.ano}</span>
</div>
))}