diff --git a/backend/src/application/dtos/consultor_dto.py b/backend/src/application/dtos/consultor_dto.py index e2e759c..b51eb09 100644 --- a/backend/src/application/dtos/consultor_dto.py +++ b/backend/src/application/dtos/consultor_dto.py @@ -57,6 +57,7 @@ class InscricaoDTO: premio: str ano: int situacao: str + evento: str = "" @dataclass @@ -112,6 +113,15 @@ class MembroBancaDTO: ano: Optional[int] +@dataclass +class IdiomaDTO: + idioma: str + nivel_leitura: str = "" + nivel_escrita: str = "" + nivel_fala: str = "" + nivel_compreensao: str = "" + + @dataclass class PontuacaoAtuacaoDTO: codigo: str @@ -168,6 +178,7 @@ class ConsultorDetalhadoDTO: participacoes: List[ParticipacaoDTO] orientacoes: List[OrientacaoDTO] membros_banca: List[MembroBancaDTO] + idiomas: List[IdiomaDTO] pontuacao: PontuacaoCompletaDTO rank: Optional[int] = None diff --git a/backend/src/application/jobs/processar_ranking.py b/backend/src/application/jobs/processar_ranking.py index 1095b7c..21c17c0 100644 --- a/backend/src/application/jobs/processar_ranking.py +++ b/backend/src/application/jobs/processar_ranking.py @@ -143,7 +143,8 @@ class ProcessarRankingJob: "tipo": i.tipo, "premio": i.premio, "ano": i.ano, - "situacao": i.situacao + "situacao": i.situacao, + "evento": i.evento } for i in consultor.inscricoes ], @@ -223,6 +224,17 @@ class ProcessarRankingJob: } for d in consultor.docencias ], + "idiomas": [ + { + "idioma": i.idioma, + "nivel_leitura": i.nivel_leitura, + "nivel_escrita": i.nivel_escrita, + "nivel_fala": i.nivel_fala, + "nivel_compreensao": i.nivel_compreensao, + } + for i in consultor.idiomas + ], + "titulacao": consultor.titulacao, "pontuacao": pontuacao, } diff --git a/backend/src/application/use_cases/obter_ranking.py b/backend/src/application/use_cases/obter_ranking.py index 20a2d86..ba0858b 100644 --- a/backend/src/application/use_cases/obter_ranking.py +++ b/backend/src/application/use_cases/obter_ranking.py @@ -17,6 +17,7 @@ from ..dtos.consultor_dto import ( ParticipacaoDTO, OrientacaoDTO, MembroBancaDTO, + IdiomaDTO, PontuacaoAtuacaoDTO, PontuacaoBlocoDTO, PontuacaoCompletaDTO, @@ -118,6 +119,7 @@ class ObterRankingUseCase: premio=i.premio, ano=i.ano, situacao=i.situacao, + evento=i.evento, ) for i in consultor.inscricoes ], @@ -180,6 +182,16 @@ class ObterRankingUseCase: ) for m in consultor.membros_banca ], + idiomas=[ + IdiomaDTO( + idioma=i.idioma, + nivel_leitura=i.nivel_leitura, + nivel_escrita=i.nivel_escrita, + nivel_fala=i.nivel_fala, + nivel_compreensao=i.nivel_compreensao, + ) + for i in consultor.idiomas + ], pontuacao=PontuacaoCompletaDTO( bloco_a=PontuacaoBlocoDTO( bloco="A", diff --git a/backend/src/domain/entities/consultor.py b/backend/src/domain/entities/consultor.py index c368a8b..1a115e9 100644 --- a/backend/src/domain/entities/consultor.py +++ b/backend/src/domain/entities/consultor.py @@ -50,6 +50,7 @@ class Inscricao: premio: str ano: int situacao: str = "" + evento: str = "" @dataclass @@ -119,6 +120,15 @@ class DocenciaPPG: linhas_pesquisa: List[str] = field(default_factory=list) +@dataclass +class Idioma: + idioma: str + nivel_leitura: str = "" + nivel_escrita: str = "" + nivel_fala: str = "" + nivel_compreensao: str = "" + + @dataclass class Consultor: id_pessoa: int @@ -135,6 +145,8 @@ class Consultor: orientacoes: List[Orientacao] = field(default_factory=list) membros_banca: List[MembroBanca] = field(default_factory=list) docencias: List[DocenciaPPG] = field(default_factory=list) + idiomas: List[Idioma] = field(default_factory=list) + titulacao: Optional[str] = None pontuacao: Optional[PontuacaoCompleta] = None @property diff --git a/backend/src/infrastructure/elasticsearch/client.py b/backend/src/infrastructure/elasticsearch/client.py index becee49..66a21c9 100644 --- a/backend/src/infrastructure/elasticsearch/client.py +++ b/backend/src/infrastructure/elasticsearch/client.py @@ -52,7 +52,7 @@ class ElasticsearchClient: try: query = { "query": {"term": {"id": id_pessoa}}, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "size": 1, } @@ -68,6 +68,28 @@ class ElasticsearchClient: except Exception as e: raise RuntimeError(f"Erro ao buscar consultor {id_pessoa}: {e}") + async def buscar_por_ids(self, ids: list, source_fields: Optional[list] = None) -> list: + if not ids: + return [] + try: + query = { + "query": {"terms": {"id": ids}}, + "size": len(ids), + } + if source_fields: + query["_source"] = source_fields + + response = await self.client.post( + f"{self.url}/{self.index}/_search", + json=query + ) + response.raise_for_status() + + data = response.json() + return [hit["_source"] for hit in data.get("hits", {}).get("hits", [])] + except Exception as e: + raise RuntimeError(f"Erro ao buscar consultores por ids: {e}") + async def buscar_documento_completo(self, id_pessoa: int) -> Optional[dict]: try: query = { @@ -104,7 +126,7 @@ class ElasticsearchClient: "query": {"exists": {"field": "atuacoes.tipo"}} } }, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "size": size, "from": from_, "sort": [{"id": "asc"}], @@ -196,7 +218,7 @@ class ElasticsearchClient: "minimum_should_match": 1 } }, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "sort": [{"_score": "desc"}] } @@ -379,7 +401,7 @@ class ElasticsearchClient: "boost_mode": "replace" } }, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "sort": [{"_score": "desc"}] } @@ -412,7 +434,7 @@ class ElasticsearchClient: "query": {"exists": {"field": "atuacoes.tipo"}} } }, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "size": size, "sort": [{"id": "asc"}] } @@ -638,7 +660,7 @@ class ElasticsearchClient: "minimum_should_match": 1 } }, - "_source": ["id", "dadosPessoais", "atuacoes"], + "_source": ["id", "dadosPessoais", "atuacoes", "idiomas", "formacoes"], "sort": [{"_score": "desc"}] } diff --git a/backend/src/infrastructure/repositories/consultor_repository_impl.py b/backend/src/infrastructure/repositories/consultor_repository_impl.py index da6d3cf..3d53945 100644 --- a/backend/src/infrastructure/repositories/consultor_repository_impl.py +++ b/backend/src/infrastructure/repositories/consultor_repository_impl.py @@ -18,6 +18,7 @@ from ...domain.entities.consultor import ( Orientacao, MembroBanca, DocenciaPPG, + Idioma, ) from ...domain.repositories.consultor_repository import ConsultorRepository from ...domain.services.calculador_pontuacao import CalculadorPontuacao @@ -277,6 +278,7 @@ class ConsultorRepositoryImpl(ConsultorRepository): premio=nome_premio, ano=ano, situacao=dados.get("situacao", ""), + evento=dados.get("evento", ""), )) return inscricoes @@ -604,6 +606,77 @@ class ConsultorRepositoryImpl(ConsultorRepository): docencias.sort(key=lambda d: (d.periodo.fim is not None, d.periodo.inicio or datetime.min), reverse=True) return docencias + def _extrair_idiomas(self, doc: Dict[str, Any]) -> List[Idioma]: + idiomas_data = doc.get("idiomas") or [] + if not idiomas_data: + dados_pessoais = doc.get("dadosPessoais", {}) or {} + idiomas_data = ( + dados_pessoais.get("idiomas") + or dados_pessoais.get("idiomasEstrangeiros") + or [] + ) + idiomas = [] + + for item in idiomas_data: + if isinstance(item, str): + idioma_nome = item + item_dict = {} + else: + item_dict = item or {} + idioma_nome = item_dict.get("idioma") or item_dict.get("nome") or item_dict.get("descricao") + if isinstance(idioma_nome, dict): + idioma_nome = ( + idioma_nome.get("nome") + or idioma_nome.get("descricao") + or idioma_nome.get("idioma") + ) + if not idioma_nome: + continue + nivel_padrao = ( + item_dict.get("nivel") + or item_dict.get("nivelConhecimento") + or item_dict.get("nivel_leitura") + or item_dict.get("nivelLeitura") + or "" + ) + idiomas.append(Idioma( + idioma=idioma_nome, + nivel_leitura=item_dict.get("nivelLeitura", "") or item_dict.get("leitura", "") or nivel_padrao, + nivel_escrita=item_dict.get("nivelEscrita", "") or item_dict.get("escrita", "") or nivel_padrao, + nivel_fala=item_dict.get("nivelFala", "") or item_dict.get("fala", "") or nivel_padrao, + nivel_compreensao=item_dict.get("nivelCompreensao", "") or item_dict.get("compreensao", "") or nivel_padrao, + )) + + return idiomas + + def _extrair_titulacao(self, doc: Dict[str, Any]) -> Optional[str]: + dados_pessoais = doc.get("dadosPessoais", {}) or {} + titulacao = dados_pessoais.get("titulacao") or dados_pessoais.get("grauFormacao") + if titulacao: + return titulacao + + formacoes = doc.get("formacoes", []) or [] + for f in formacoes: + nivel = f.get("nivel", "").lower() + if "pós-doutorado" in nivel or "pos-doutorado" in nivel or "posdoc" in nivel: + return "Pós-Doutorado" + elif "doutorado" in nivel: + return "Doutorado" + elif "mestrado" in nivel: + return "Mestrado" + + atuacoes = doc.get("atuacoes", []) or [] + for a in atuacoes: + if a.get("tipo") == "Docência": + dados = a.get("dadosDocencia", {}) or {} + categoria = (dados.get("categoria", "") or "").lower() + if "doutor" in categoria: + return "Doutorado" + elif "mestre" in categoria: + return "Mestrado" + + return None + async def _construir_consultor(self, doc: Dict[str, Any]) -> Consultor: id_pessoa = doc["id"] dados_pessoais = doc.get("dadosPessoais", {}) @@ -621,6 +694,8 @@ class ConsultorRepositoryImpl(ConsultorRepository): membros_banca = self._extrair_membros_banca(atuacoes) docencias = self._extrair_docencias(atuacoes) coordenador_ppg = self._tem_coordenacao_ppg(atuacoes) + idiomas = self._extrair_idiomas(doc) + titulacao = self._extrair_titulacao(doc) consultor = Consultor( id_pessoa=id_pessoa, @@ -637,6 +712,8 @@ class ConsultorRepositoryImpl(ConsultorRepository): orientacoes=orientacoes + coorientacoes, membros_banca=membros_banca, docencias=docencias, + idiomas=idiomas, + titulacao=titulacao, ) consultor.pontuacao = self.calculador.calcular_pontuacao_completa(consultor) diff --git a/backend/src/interface/api/routes.py b/backend/src/interface/api/routes.py index cf42904..33403ac 100644 --- a/backend/src/interface/api/routes.py +++ b/backend/src/interface/api/routes.py @@ -206,6 +206,8 @@ async def ranking_paginado( ativo: Optional[bool] = Query(default=None, description="Filtrar por status ativo"), selos: Optional[str] = Query(default=None, description="Filtrar por selos (separados por vírgula)"), oracle_repo = Depends(get_ranking_oracle_repo), + es_client: ElasticsearchClient = Depends(get_es_client), + repository: ConsultorRepositoryImpl = Depends(get_repository), ): import json as json_lib @@ -223,12 +225,46 @@ async def ranking_paginado( total_pages = (total + size - 1) // size consultores_schema = [] + consultores_dados = [] + faltando_idiomas = [] for c in consultores: try: d = json_lib.loads(c.json_detalhes) if isinstance(c.json_detalhes, str) else c.json_detalhes or {} except (json_lib.JSONDecodeError, TypeError): d = {} + consultores_dados.append((c, d)) + if not d.get("idiomas"): + faltando_idiomas.append((c.id_pessoa, d)) + if faltando_idiomas: + ids = [item[0] for item in faltando_idiomas] + docs = await es_client.buscar_por_ids( + ids, + source_fields=["id", "dadosPessoais", "idiomas", "atuacoes", "formacoes"], + ) + docs_map = {int(doc.get("id")): doc for doc in docs if doc.get("id")} + for id_pessoa, detalhes in faltando_idiomas: + doc = docs_map.get(int(id_pessoa)) + if not doc: + continue + idiomas = repository._extrair_idiomas(doc) + if idiomas: + detalhes["idiomas"] = [ + { + "idioma": i.idioma, + "nivel_leitura": i.nivel_leitura, + "nivel_escrita": i.nivel_escrita, + "nivel_fala": i.nivel_fala, + "nivel_compreensao": i.nivel_compreensao, + } + for i in idiomas + ] + if not detalhes.get("titulacao"): + titulacao = repository._extrair_titulacao(doc) + if titulacao: + detalhes["titulacao"] = titulacao + + for c, d in consultores_dados: tipos_atuacao = RankingMapper._extrair_tipos_atuacao(d) consultores_schema.append( ConsultorRankingResumoSchema( @@ -255,6 +291,8 @@ async def ranking_paginado( orientacoes=d.get("orientacoes"), membros_banca=d.get("membros_banca"), docencias=d.get("docencias"), + idiomas=d.get("idiomas"), + titulacao=d.get("titulacao"), pontuacao=d.get("pontuacao"), ) ) diff --git a/backend/src/interface/schemas/consultor_schema.py b/backend/src/interface/schemas/consultor_schema.py index bf91590..68b2cc7 100644 --- a/backend/src/interface/schemas/consultor_schema.py +++ b/backend/src/interface/schemas/consultor_schema.py @@ -47,6 +47,7 @@ class InscricaoSchema(BaseModel): premio: str ano: int situacao: str + evento: str = "" class AvaliacaoComissaoSchema(BaseModel): @@ -96,6 +97,14 @@ class MembroBancaSchema(BaseModel): ano: Optional[int] = None +class IdiomaSchema(BaseModel): + idioma: str + nivel_leitura: str = "" + nivel_escrita: str = "" + nivel_fala: str = "" + nivel_compreensao: str = "" + + class PontuacaoAtuacaoSchema(BaseModel): codigo: str base: int @@ -147,6 +156,7 @@ class ConsultorDetalhadoSchema(BaseModel): participacoes: List[ParticipacaoSchema] orientacoes: List[OrientacaoSchema] membros_banca: List[MembroBancaSchema] + idiomas: List[IdiomaSchema] = [] pontuacao: PontuacaoCompletaSchema rank: Optional[int] = None diff --git a/backend/src/interface/schemas/ranking_schema.py b/backend/src/interface/schemas/ranking_schema.py index a52a7ee..65c7b63 100644 --- a/backend/src/interface/schemas/ranking_schema.py +++ b/backend/src/interface/schemas/ranking_schema.py @@ -27,6 +27,8 @@ class ConsultorRankingResumoSchema(BaseModel): orientacoes: Optional[list] = None membros_banca: Optional[list] = None docencias: Optional[list] = None + idiomas: Optional[list] = None + titulacao: Optional[str] = None pontuacao: Optional[dict] = None diff --git a/frontend/src/components/BlocoCriteriosModal.jsx b/frontend/src/components/BlocoCriteriosModal.jsx index 35ed1e5..42121b7 100644 --- a/frontend/src/components/BlocoCriteriosModal.jsx +++ b/frontend/src/components/BlocoCriteriosModal.jsx @@ -87,7 +87,6 @@ const DADOS_BLOCOS = { ], selos: [ { cod: 'PPG_COORD', nome: 'Coordenador PPG', obs: 'Indicador (sem pontuação V1)' }, - { cod: 'IDIOMA_BILINGUE', nome: 'Bilíngue', obs: '2+ idiomas' }, { cod: 'IDIOMA_MULTILINGUE', nome: 'Multilíngue', obs: '3+ idiomas' }, { cod: 'TITULACAO_MESTRE', nome: 'Mestre', obs: 'Maior titulação' }, { cod: 'TITULACAO_DOUTOR', nome: 'Doutor', obs: 'Maior titulação' }, diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 6be4dc5..d946ec1 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -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) => (
{ins.codigo} - {ins.premio || ins.descricao || '-'} + {ins.evento || ins.premio || ins.descricao || '-'} {ins.ano || '-'}
))} @@ -837,6 +879,12 @@ const ItemDetalheModal = ({ item, tipo, onClose }) => { Código {item.codigo} + {item.evento && ( +
+ Evento + {item.evento} +
+ )}
Prêmio {item.premio || 'N/A'} @@ -1461,7 +1509,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio onClick={handleRawDataClick} title="Ver dados completos do ATUACAPES" > - ⋮ + 📋
{expanded ? '▲' : '▼'}
@@ -1687,7 +1735,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio > {insc.codigo} {PONTOS_BASE[insc.codigo] || 0} pts - {insc.premio} + {insc.evento || insc.premio} {insc.ano} ))} diff --git a/frontend/src/components/FiltroSelos.jsx b/frontend/src/components/FiltroSelos.jsx index d1d3076..3d63f50 100644 --- a/frontend/src/components/FiltroSelos.jsx +++ b/frontend/src/components/FiltroSelos.jsx @@ -19,6 +19,9 @@ const SELOS_CONFIG = { { codigo: 'ORIENT_GP', label: 'Orient. GP', icone: '🏆' }, { codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', icone: '🎖️' }, { codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', icone: '📜' }, + { codigo: 'COORIENT_GP', label: 'Coorient. GP', icone: '🏆' }, + { codigo: 'COORIENT_PREMIO', label: 'Coorient. Prêmio', icone: '🎖️' }, + { codigo: 'COORIENT_MENCAO', label: 'Coorient. Menção', icone: '📜' }, ], }, orientacoes: { diff --git a/frontend/src/components/SugerirConsultores.css b/frontend/src/components/SugerirConsultores.css index 7eb02cc..1dea6e2 100644 --- a/frontend/src/components/SugerirConsultores.css +++ b/frontend/src/components/SugerirConsultores.css @@ -493,6 +493,9 @@ display: flex; flex-direction: column; gap: 0.25rem; + flex: 1; + min-width: 0; + overflow: hidden; } .equipe-count { @@ -518,6 +521,7 @@ .equipe-acoes { display: flex; gap: 0.75rem; + flex-shrink: 0; } .btn-limpar { @@ -530,6 +534,7 @@ font-size: 0.9rem; font-weight: 500; transition: all 0.2s; + white-space: nowrap; } .btn-limpar:hover { @@ -547,6 +552,7 @@ font-weight: 700; transition: all 0.2s; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + white-space: nowrap; } .btn-gerar-pdf:hover:not(:disabled) { diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index b795755..b0105fa 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -90,6 +90,8 @@ export const rankingService = { participacoes: c.participacoes || [], orientacoes: c.orientacoes || [], membros_banca: c.membros_banca || [], + idiomas: c.idiomas || [], + titulacao: c.titulacao || '', }; });