feat(equipe): implementar montagem de equipe interdisciplinar com PDF
- Adicionar seleção múltipla de consultores entre diferentes buscas - Criar endpoint POST /api/v1/equipe/pdf para gerar documento da equipe - Implementar template HTML profissional para PDF da equipe - Exibir estatísticas: total, ativos, coordenadores, premiados, IES - Persistir seleção ao trocar termos de busca (equipe interdisciplinar) - Mostrar temas combinados na barra flutuante e no PDF - Corrigir renderização de emojis no PDF (substituir por texto) - Melhorar contraste da cor do ranking no frontend (roxo → ciano) - Corrigir validação Pydantic para ignorar campos extras do .env
This commit is contained in:
184
backend/src/infrastructure/pdf/templates/equipe_consultores.html
Normal file
184
backend/src/infrastructure/pdf/templates/equipe_consultores.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Equipe de Consultores - {{ tema }}</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="documento">
|
||||
<section class="cover">
|
||||
<div>
|
||||
<table class="cover-header">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>MINISTÉRIO DA EDUCAÇÃO</strong><br>
|
||||
Coordenação de Aperfeiçoamento de Pessoal de Nível Superior
|
||||
</td>
|
||||
<td class="right small">Sugestão de Equipe</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="cover-title">Equipe de Consultores</div>
|
||||
<div class="cover-subtitle">Sistema de Ranking AtuaCAPES · Seleção Inteligente</div>
|
||||
|
||||
<div class="cover-name">{{ tema }}</div>
|
||||
{% if area_avaliacao %}
|
||||
<div class="tag-list">
|
||||
<span class="badge">{{ area_avaliacao }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="cover-stats">
|
||||
<table class="stats-table">
|
||||
<tr>
|
||||
<td class="stat-cell">
|
||||
<div class="stat-value">{{ estatisticas.total }}</div>
|
||||
<div class="stat-label">Consultores</div>
|
||||
</td>
|
||||
<td class="stat-cell">
|
||||
<div class="stat-value">{{ estatisticas.ativos }}</div>
|
||||
<div class="stat-label">Ativos</div>
|
||||
</td>
|
||||
<td class="stat-cell">
|
||||
<div class="stat-value">{{ estatisticas.coordenadores }}</div>
|
||||
<div class="stat-label">Coordenadores</div>
|
||||
</td>
|
||||
<td class="stat-cell">
|
||||
<div class="stat-value">{{ estatisticas.premiados }}</div>
|
||||
<div class="stat-label">Premiados</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="stat-cell" colspan="2">
|
||||
<div class="stat-value">{{ estatisticas.ies_distintas }}</div>
|
||||
<div class="stat-label">IES Distintas</div>
|
||||
</td>
|
||||
<td class="stat-cell" colspan="2">
|
||||
<div class="stat-value">{{ estatisticas.areas_cobertas }}</div>
|
||||
<div class="stat-label">Áreas Cobertas</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="cover-footer">
|
||||
<p>Documento gerado em {{ data_geracao }}</p>
|
||||
<p class="small">Ranking de Consultores CAPES · Geração Automática</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary-section">
|
||||
<h2>Resumo da Equipe</h2>
|
||||
|
||||
<div class="summary-grid">
|
||||
<div class="summary-box">
|
||||
<h4>Instituições Representadas</h4>
|
||||
<div class="tag-list">
|
||||
{% for ies in estatisticas.lista_ies %}
|
||||
<span class="badge">{{ ies }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-box">
|
||||
<h4>Áreas de Avaliação</h4>
|
||||
<div class="tag-list">
|
||||
{% for area in estatisticas.lista_areas %}
|
||||
<span class="badge info">{{ area }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="team-section">
|
||||
<h2>Membros da Equipe</h2>
|
||||
|
||||
{% for consultor in consultores %}
|
||||
<div class="team-member">
|
||||
<div class="member-header">
|
||||
<div class="member-rank">#{{ loop.index }}</div>
|
||||
<div class="member-info">
|
||||
<div class="member-name">{{ consultor.nome }}</div>
|
||||
<div class="member-ies">{{ consultor.ies or '-' }}</div>
|
||||
</div>
|
||||
<div class="member-badges">
|
||||
{% if consultor.foi_coordenador %}
|
||||
<span class="badge success">Coordenador</span>
|
||||
{% endif %}
|
||||
{% if consultor.foi_premiado %}
|
||||
<span class="badge warning">Premiado</span>
|
||||
{% endif %}
|
||||
{% if consultor.situacao and 'atividade' in consultor.situacao.lower() %}
|
||||
<span class="badge">Ativo</span>
|
||||
{% else %}
|
||||
<span class="badge muted">Histórico</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="member-stats">
|
||||
{% if consultor.posicao_ranking %}
|
||||
<div class="stat-inline">
|
||||
<span class="stat-label">Ranking:</span>
|
||||
<span class="stat-value-inline">#{{ consultor.posicao_ranking }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if consultor.pontuacao_ranking %}
|
||||
<div class="stat-inline">
|
||||
<span class="stat-label">Pontos:</span>
|
||||
<span class="stat-value-inline">{{ consultor.pontuacao_ranking|int }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if consultor.motivos_match %}
|
||||
<div class="member-motivos">
|
||||
<strong>Por que este consultor:</strong>
|
||||
<div class="tag-list">
|
||||
{% for motivo in consultor.motivos_match %}
|
||||
<span class="badge info small">{{ motivo }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if consultor.areas_avaliacao %}
|
||||
<div class="member-areas">
|
||||
<strong>Áreas de Avaliação:</strong>
|
||||
<span>{{ consultor.areas_avaliacao | join(', ') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if consultor.linhas_pesquisa %}
|
||||
<div class="member-pesquisa">
|
||||
<strong>Linhas de Pesquisa:</strong>
|
||||
<ul>
|
||||
{% for linha in consultor.linhas_pesquisa[:5] %}
|
||||
<li>{{ linha }}</li>
|
||||
{% endfor %}
|
||||
{% if consultor.linhas_pesquisa|length > 5 %}
|
||||
<li class="muted">... e mais {{ consultor.linhas_pesquisa|length - 5 }} linhas</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section class="footer-section">
|
||||
<p class="disclaimer">
|
||||
Este documento foi gerado automaticamente pelo Sistema de Ranking de Consultores CAPES.
|
||||
As sugestões são baseadas em análise de relevância temática, posição no ranking geral e diversidade institucional.
|
||||
</p>
|
||||
<p class="generation-info">
|
||||
Gerado em {{ data_geracao }} · Tema: "{{ tema }}"
|
||||
{% if area_avaliacao %} · Área: {{ area_avaliacao }}{% endif %}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -305,3 +305,206 @@ h1, h2, h3, h4 {
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cover-stats {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.stats-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stat-cell {
|
||||
padding: 16px 12px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24pt;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 9pt;
|
||||
color: var(--muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
page-break-before: always;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.summary-section h2 {
|
||||
color: var(--accent);
|
||||
font-size: 14pt;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.summary-box {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--border);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.summary-box h4 {
|
||||
font-size: 10pt;
|
||||
color: var(--muted);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.team-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.team-section h2 {
|
||||
color: var(--accent);
|
||||
font-size: 14pt;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
.team-member {
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.member-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.member-rank {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 12pt;
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.member-ies {
|
||||
font-size: 9pt;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.member-badges {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 9pt;
|
||||
color: var(--muted);
|
||||
background: var(--bg-soft);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.stat-value-inline {
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.member-motivos {
|
||||
background: white;
|
||||
border: 1px solid var(--border);
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.member-areas {
|
||||
font-size: 9pt;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.member-pesquisa {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.member-pesquisa ul {
|
||||
margin-left: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.member-pesquisa li {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
margin-top: 32px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-size: 8pt;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.generation-info {
|
||||
font-size: 8pt;
|
||||
color: var(--muted);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user