feat(filtros): adicionar filtro multi-select por selos no ranking

- Backend: extrair selos de detalhes e filtrar por eles
- API: endpoint /ranking/selos e parâmetro selos em /ranking/paginado
- Frontend: componente FiltroSelos com dropdown e seleção múltipla
- Selos disponíveis: funções, premiações, orientações
This commit is contained in:
Frederico Castro
2025-12-15 12:32:24 -03:00
parent d215e9ac76
commit c294d4cc77
6 changed files with 527 additions and 11 deletions

View File

@@ -110,11 +110,43 @@ async def health_check():
return {"status": "ok", "message": "API Ranking CAPES funcionando"}
@router.get("/ranking/selos")
async def listar_selos():
from ...infrastructure.ranking_store import SELOS_DISPONIVEIS
selos_info = {
"PRESID_CAMARA": {"label": "Presidente Câmara", "icone": "👑", "grupo": "funcoes"},
"COORD_PPG": {"label": "Coord. PPG", "icone": "🎓", "grupo": "funcoes"},
"BPQ": {"label": "Bolsista PQ", "icone": "🏅", "grupo": "funcoes"},
"AUTOR_GP": {"label": "Autor Grande Prêmio", "icone": "🏆", "grupo": "premiacoes"},
"AUTOR_PREMIO": {"label": "Autor Prêmio", "icone": "🥇", "grupo": "premiacoes"},
"AUTOR_MENCAO": {"label": "Autor Menção", "icone": "🥈", "grupo": "premiacoes"},
"ORIENT_GP": {"label": "Orientador GP", "icone": "🏆", "grupo": "premiacoes"},
"ORIENT_PREMIO": {"label": "Orientador Prêmio", "icone": "🎖️", "grupo": "premiacoes"},
"ORIENT_MENCAO": {"label": "Orientador Menção", "icone": "📜", "grupo": "premiacoes"},
"COORIENT_GP": {"label": "Coorientador GP", "icone": "🏆", "grupo": "premiacoes"},
"COORIENT_PREMIO": {"label": "Coorientador Prêmio", "icone": "🎖️", "grupo": "premiacoes"},
"COORIENT_MENCAO": {"label": "Coorientador Menção", "icone": "📜", "grupo": "premiacoes"},
"ORIENT_POS_DOC": {"label": "Orient. Pós-Doc", "icone": "🔬", "grupo": "orientacoes"},
"ORIENT_TESE": {"label": "Orient. Tese", "icone": "📚", "grupo": "orientacoes"},
"ORIENT_DISS": {"label": "Orient. Dissertação", "icone": "📄", "grupo": "orientacoes"},
"CO_ORIENT_POS_DOC": {"label": "Coorient. Pós-Doc", "icone": "🔬", "grupo": "coorientacoes"},
"CO_ORIENT_TESE": {"label": "Coorient. Tese", "icone": "📚", "grupo": "coorientacoes"},
"CO_ORIENT_DISS": {"label": "Coorient. Dissertação", "icone": "📄", "grupo": "coorientacoes"},
}
return {
"selos": [
{"codigo": s, **selos_info.get(s, {"label": s, "icone": "🏷️", "grupo": "outros"})}
for s in SELOS_DISPONIVEIS
]
}
@router.get("/ranking/paginado", response_model=RankingPaginadoResponseSchema)
async def ranking_paginado(
page: int = Query(default=1, ge=1, description="Número da página"),
size: int = Query(default=50, ge=1, le=1000, description="Tamanho da página (máx 1000)"),
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)"),
store = Depends(get_ranking_store),
):
if not store.is_ready():
@@ -123,7 +155,8 @@ async def ranking_paginado(
detail="Ranking ainda não foi processado. Execute POST /api/v1/ranking/processar.",
)
total, entries = store.get_page(page=page, size=size, filtro_ativo=ativo)
filtro_selos = [s.strip() for s in selos.split(",") if s.strip()] if selos else None
total, entries = store.get_page(page=page, size=size, filtro_ativo=ativo, filtro_selos=filtro_selos)
total_pages = (total + size - 1) // size