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:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}]
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -90,6 +90,8 @@ export const rankingService = {
|
||||
participacoes: c.participacoes || [],
|
||||
orientacoes: c.orientacoes || [],
|
||||
membros_banca: c.membros_banca || [],
|
||||
idiomas: c.idiomas || [],
|
||||
titulacao: c.titulacao || '',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user