Files
ranking/frontend/src/components/ConsultorCard.jsx
Frederico Castro 9e6ba459a8 feat: Sistema de Ranking de Consultores CAPES - versão inicial
Backend (FastAPI + DDD):
- Arquitetura DDD com camadas Domain, Application, Infrastructure, Interface
- Integração com Elasticsearch (ATUACAPES) para dados de consultores
- Integração com Oracle (SUCUPIRA_PAINEL) para coordenações PPG
- Cálculo dos 4 componentes de pontuação (A, B, C, D)
- Cache em memória para otimização de performance
- API REST com endpoints /ranking, /ranking/detalhado, /consultor/{id}

Frontend (React + Vite):
- Interface responsiva com cards expansíveis
- Visualização detalhada de pontuação por componente
- Filtro por quantidade de consultores (Top 10, 50, 100, etc)

Docker:
- docker-compose com shared_network externa
- Backend com Oracle Instant Client
- Frontend com Vite dev server
2025-12-09 01:24:35 -03:00

217 lines
8.2 KiB
JavaScript

import React, { useState } from 'react';
import './ConsultorCard.css';
const ConsultorCard = ({ consultor }) => {
const [expanded, setExpanded] = useState(false);
const getRankClass = (rank) => {
if (rank === 1) return 'rank-1';
if (rank === 2) return 'rank-2';
if (rank === 3) return 'rank-3';
return '';
};
const formatDate = (dateStr) => {
if (!dateStr) return 'Atual';
return new Date(dateStr).toLocaleDateString('pt-BR');
};
const { pontuacao } = consultor;
const { consultoria } = consultor;
return (
<div className={`ranking-card ${expanded ? 'expanded' : ''}`} onClick={() => setExpanded(!expanded)}>
<div className="card-main">
<div className={`rank ${getRankClass(consultor.rank)}`}>#{consultor.rank}</div>
<div className="card-info">
<div className="consultant-name">
{consultor.nome}
{consultor.ativo && <span className="badge badge-ativo">ATIVO</span>}
{!consultor.ativo && <span className="badge badge-historico">HISTÓRICO</span>}
{consultor.veterano && <span className="badge badge-veterano">VETERANO</span>}
</div>
<div className="consultant-area">
{consultor.anos_atuacao} anos de atuação
{consultoria && ` | Desde ${formatDate(consultoria.primeiro_evento)}`}
</div>
</div>
<div className="card-stats">
{consultoria && (
<>
<div className="stat">
<div className="stat-value">{consultoria.total_eventos}</div>
<div className="stat-label">Eventos</div>
</div>
<div className="stat">
<div className="stat-value">{consultoria.eventos_recentes}</div>
<div className="stat-label">Recentes</div>
</div>
<div className="stat">
<div className="stat-value">{consultoria.vezes_responsavel}</div>
<div className="stat-label">Responsável</div>
</div>
</>
)}
<div className="stat">
<div className="score-value">{consultor.pontuacao_total}</div>
<div className="stat-label">Score</div>
</div>
<div className="expand-icon">{expanded ? '▲' : '▼'}</div>
</div>
</div>
{expanded && (
<div className="card-details">
<div className="details-grid">
<div className="detail-section">
<h4>Pontuação Total</h4>
<div className="score-breakdown-total">
<div className="score-item">
<div className="score-item-value" style={{ color: pontuacao.componente_a.total > 0 ? 'var(--accent-2)' : 'var(--muted)' }}>
{pontuacao.componente_a.total}
</div>
<div className="score-item-label">COMP A</div>
</div>
<div className="score-item">
<div className="score-item-value" style={{ color: pontuacao.componente_b.total > 0 ? 'var(--success)' : 'var(--muted)' }}>
{pontuacao.componente_b.total}
</div>
<div className="score-item-label">COMP B</div>
</div>
<div className="score-item">
<div className="score-item-value" style={{ color: pontuacao.componente_c.total > 0 ? 'var(--gold)' : 'var(--muted)' }}>
{pontuacao.componente_c.total}
</div>
<div className="score-item-label">COMP C</div>
</div>
<div className="score-item">
<div className="score-item-value" style={{ color: pontuacao.componente_d.total > 0 ? 'var(--bronze)' : 'var(--muted)' }}>
{pontuacao.componente_d.total}
</div>
<div className="score-item-label">COMP D</div>
</div>
<div className="score-item score-total">
<div className="score-item-value">{pontuacao.pontuacao_total}</div>
<div className="score-item-label">TOTAL</div>
</div>
</div>
</div>
<ComponenteDetalhes
titulo="A - Coordenação CAPES"
componente={pontuacao.componente_a}
cor="var(--accent-2)"
/>
<ComponenteDetalhes
titulo="B - Coordenação PPG"
componente={pontuacao.componente_b}
cor="var(--success)"
/>
<ComponenteDetalhes
titulo="C - Consultoria"
componente={pontuacao.componente_c}
cor="var(--gold)"
/>
<ComponenteDetalhes
titulo="D - Premiações"
componente={pontuacao.componente_d}
cor="var(--bronze)"
/>
</div>
{consultor.coordenacoes_capes?.length > 0 && (
<div className="extra-details">
<h4>Coordenações CAPES</h4>
<div className="list-items">
{consultor.coordenacoes_capes.map((coord, idx) => (
<div key={idx} className="list-item">
<span className="badge">{coord.tipo}</span>
<span>{coord.area_avaliacao}</span>
<span className="muted">
{formatDate(coord.periodo.inicio)} - {formatDate(coord.periodo.fim)}
</span>
</div>
))}
</div>
</div>
)}
{consultor.coordenacoes_programas?.length > 0 && (
<div className="extra-details">
<h4>Coordenações de Programa (PPG)</h4>
<div className="list-items">
{consultor.coordenacoes_programas.map((coord, idx) => (
<div key={idx} className="list-item">
<span className="badge">{coord.nota_ppg}</span>
<span>{coord.nome_programa}</span>
<span className="muted">{coord.area_avaliacao}</span>
<span className="muted">
{formatDate(coord.periodo.inicio)} - {formatDate(coord.periodo.fim)}
</span>
</div>
))}
</div>
</div>
)}
{consultor.premiacoes?.length > 0 && (
<div className="extra-details">
<h4>Premiações</h4>
<div className="list-items">
{consultor.premiacoes.map((prem, idx) => (
<div key={idx} className="list-item">
<span className="badge">{prem.pontos} pts</span>
<span>{prem.nome_premio}</span>
<span className="muted">{prem.ano}</span>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
);
};
const ComponenteDetalhes = ({ titulo, componente, cor }) => (
<div className="detail-section">
<h4 style={{ color: cor }}>{titulo}</h4>
<div className="score-breakdown">
<div className="score-item">
<div className="score-item-value">{componente.base}</div>
<div className="score-item-label">BASE</div>
</div>
<div className="score-item">
<div className="score-item-value">{componente.tempo}</div>
<div className="score-item-label">TEMPO</div>
</div>
<div className="score-item">
<div className="score-item-value">{componente.extras}</div>
<div className="score-item-label">EXTRAS</div>
</div>
<div className="score-item">
<div className="score-item-value">{componente.bonus}</div>
<div className="score-item-label">BÔNUS</div>
</div>
{componente.retorno > 0 && (
<div className="score-item">
<div className="score-item-value">{componente.retorno}</div>
<div className="score-item-label">RETORNO</div>
</div>
)}
<div className="score-item score-total">
<div className="score-item-value">{componente.total}</div>
<div className="score-item-label">TOTAL</div>
</div>
</div>
</div>
);
export default ConsultorCard;