feat(frontend): adicionar link para perfil no ATUACAPES
- Adiciona ícone de link externo antes do nome do consultor
- Link abre perfil no ATUACAPES em nova aba (/perfil/{id_pessoa})
- Variável de ambiente HOST_ATUACAPES configurável
- Adiciona retry automático (10 tentativas) ao carregar ranking
- Corrige espaçamento da seção de selos
- Atualiza arquivos .env.example
This commit is contained in:
@@ -6,3 +6,5 @@ ES_VERIFY_SSL=true
|
|||||||
|
|
||||||
SCHEDULER_ENABLED=false
|
SCHEDULER_ENABLED=false
|
||||||
SCHEDULER_HOUR=3
|
SCHEDULER_HOUR=3
|
||||||
|
|
||||||
|
HOST_ATUACAPES=https://atuacapes.capes.gov.br
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
ES_URL=http://localhost:9200
|
ES_URL=http://localhost:9200
|
||||||
ES_INDEX=atuacapes__1763197236
|
ES_INDEX=atuacapes
|
||||||
ES_USER=seu_usuario_elastic
|
ES_USER=seu_usuario_elastic
|
||||||
ES_PASSWORD=sua_senha_elastic
|
ES_PASSWORD=sua_senha_elastic
|
||||||
ES_VERIFY_SSL=true
|
ES_PASS=sua_senha_elastic
|
||||||
|
|
||||||
ORACLE_LOCAL_USER=ranking
|
ORACLE_USER=seu_usuario_oracle
|
||||||
ORACLE_LOCAL_PASSWORD=senha_oracle
|
ORACLE_PASSWORD=sua_senha_oracle
|
||||||
ORACLE_LOCAL_DSN=localhost:1521/XEPDB1
|
ORACLE_DSN=oracle:1521/XEPDB1
|
||||||
|
|
||||||
API_HOST=0.0.0.0
|
ORACLE_LOCAL_USER=seu_usuario_local
|
||||||
API_PORT=8000
|
ORACLE_LOCAL_PASSWORD=sua_senha_local
|
||||||
API_RELOAD=true
|
ORACLE_LOCAL_DSN=XEPDB1
|
||||||
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
||||||
|
|
||||||
LOG_LEVEL=INFO
|
ORACLE_CLIENT=oracle-local
|
||||||
SCHEDULER_ENABLED=false
|
|
||||||
SCHEDULER_HOUR=3
|
HOST_ATUACAPES=https://atuacapes.capes.gov.br
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ services:
|
|||||||
- backend
|
- backend
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
|
environment:
|
||||||
|
- VITE_HOST_ATUACAPES=${HOST_ATUACAPES:-https://atuacapes.capes.gov.br}
|
||||||
volumes:
|
volumes:
|
||||||
- ./frontend/src:/app/src
|
- ./frontend/src:/app/src
|
||||||
- ./frontend/index.html:/app/index.html
|
- ./frontend/index.html:/app/index.html
|
||||||
|
|||||||
2
frontend/.env.example
Normal file
2
frontend/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_API_URL=http://localhost:8010/api/v1
|
||||||
|
VITE_HOST_ATUACAPES=https://atuacapes.capes.gov.br
|
||||||
@@ -44,7 +44,10 @@ function App() {
|
|||||||
loadRanking();
|
loadRanking();
|
||||||
}, [page, pageSize]);
|
}, [page, pageSize]);
|
||||||
|
|
||||||
const loadRanking = async () => {
|
const loadRanking = async (retryCount = 0) => {
|
||||||
|
const MAX_RETRIES = 10;
|
||||||
|
const RETRY_DELAY = 2000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -56,6 +59,14 @@ function App() {
|
|||||||
setPage(response.page || page);
|
setPage(response.page || page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const status = err?.response?.status;
|
const status = err?.response?.status;
|
||||||
|
const isNetworkError = !err?.response || err?.code === 'ERR_NETWORK';
|
||||||
|
|
||||||
|
if (isNetworkError && retryCount < MAX_RETRIES) {
|
||||||
|
setProcessMessage(`Aguardando API... (tentativa ${retryCount + 1}/${MAX_RETRIES})`);
|
||||||
|
await new Promise((r) => setTimeout(r, RETRY_DELAY));
|
||||||
|
return loadRanking(retryCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (status === 503) {
|
if (status === 503) {
|
||||||
try {
|
try {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
@@ -146,7 +157,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="loading">
|
<div className="loading">
|
||||||
{processing ? (processMessage || 'Processando ranking...') : 'Carregando ranking...'}
|
{processMessage || (processing ? 'Processando ranking...' : 'Carregando ranking...')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -628,6 +628,7 @@
|
|||||||
|
|
||||||
.selos-section {
|
.selos-section {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selos-section .selos-container {
|
.selos-section .selos-container {
|
||||||
@@ -651,3 +652,26 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-atuacapes {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--muted);
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border: 1px solid var(--stroke);
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-atuacapes:hover {
|
||||||
|
color: var(--accent-2);
|
||||||
|
background: rgba(79, 70, 229, 0.15);
|
||||||
|
border-color: rgba(79, 70, 229, 0.4);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -237,6 +237,18 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
|||||||
|
|
||||||
<div className="card-info">
|
<div className="card-info">
|
||||||
<div className="consultant-name">
|
<div className="consultant-name">
|
||||||
|
{import.meta.env.VITE_HOST_ATUACAPES && consultor.id_pessoa && (
|
||||||
|
<a
|
||||||
|
href={`${import.meta.env.VITE_HOST_ATUACAPES}/perfil/${consultor.id_pessoa}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="link-atuacapes"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
title="Ver perfil no ATUACAPES"
|
||||||
|
>
|
||||||
|
↗
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
{consultor.nome}
|
{consultor.nome}
|
||||||
{consultor.ativo && <span className="badge badge-ativo">ATIVO</span>}
|
{consultor.ativo && <span className="badge badge-ativo">ATIVO</span>}
|
||||||
{!consultor.ativo && <span className="badge badge-historico">HISTORICO</span>}
|
{!consultor.ativo && <span className="badge badge-historico">HISTORICO</span>}
|
||||||
@@ -325,6 +337,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
|||||||
{blocoD.atuacoes && blocoD.atuacoes.length > 0 && (
|
{blocoD.atuacoes && blocoD.atuacoes.length > 0 && (
|
||||||
<BlocoDetalhes titulo="D - Premiacoes/Avaliacoes" bloco={blocoD} cor="var(--bronze)" />
|
<BlocoDetalhes titulo="D - Premiacoes/Avaliacoes" bloco={blocoD} cor="var(--bronze)" />
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{selos.length > 0 && (
|
{selos.length > 0 && (
|
||||||
<div className="detail-section selos-section">
|
<div className="detail-section selos-section">
|
||||||
@@ -332,7 +345,6 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
|||||||
<SelosBadges selos={selos} />
|
<SelosBadges selos={selos} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{consultor.coordenacoes_capes?.length > 0 && (
|
{consultor.coordenacoes_capes?.length > 0 && (
|
||||||
<div className="extra-details">
|
<div className="extra-details">
|
||||||
|
|||||||
Reference in New Issue
Block a user