feat(frontend): adicionar modais de detalhes para itens das listas
- Criar componente ItemDetalheModal para exibir detalhes de atuações - Adicionar modais clicáveis em: vínculos, coordenações, premiações, avaliações, inscrições e participações - Melhorar healthcheck do Oracle no docker-compose - Adicionar retry com backoff na conexão Oracle - Padronizar tamanho dos badges de tipos de atuação
This commit is contained in:
@@ -503,6 +503,83 @@
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.list-item-clicavel {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.list-item-clicavel:hover {
|
||||
background: rgba(6, 182, 212, 0.1);
|
||||
border-left: 3px solid var(--accent-2);
|
||||
padding-left: calc(0.5rem - 3px);
|
||||
}
|
||||
|
||||
.modal-detalhe-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.modal-detalhe-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
padding: 0.7rem 0.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-detalhe-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-detalhe-row:hover {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.modal-detalhe-label {
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-detalhe-value {
|
||||
color: var(--text);
|
||||
font-size: 0.9rem;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-detalhe-value.pontos {
|
||||
color: var(--accent-2);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.modal-detalhe-value.muted {
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.modal-titulo-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-titulo-icone {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.modal-empty {
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.details-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@@ -749,21 +826,21 @@
|
||||
color: var(--accent-2);
|
||||
}
|
||||
|
||||
.tipos-expandido {
|
||||
.tipos-section .tipos-atuacao-container {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tipos-expandido .tipo-atuacao {
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.tipos-expandido .tipo-icone {
|
||||
font-size: 0.8rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tipos-expandido .tipo-label {
|
||||
font-size: 0.65rem;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.selos-section {
|
||||
|
||||
@@ -551,6 +551,303 @@ const SeloModal = ({ selo, consultor, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ItemDetalheModal = ({ item, tipo, onClose }) => {
|
||||
if (!item || !tipo) return null;
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return 'N/A';
|
||||
return new Date(dateStr).toLocaleDateString('pt-BR');
|
||||
};
|
||||
|
||||
const getTitulo = () => {
|
||||
switch (tipo) {
|
||||
case 'vinculo': return 'Vínculo de Consultoria';
|
||||
case 'coordenacao': return 'Coordenação CAPES';
|
||||
case 'premiacao': return 'Premiação';
|
||||
case 'avaliacao': return 'Avaliação de Comissão';
|
||||
case 'inscricao': return 'Inscrição em Prêmio';
|
||||
case 'participacao': return item.codigo === 'PROJ' ? 'Projeto' : 'Evento';
|
||||
case 'orientacao': return 'Orientação';
|
||||
default: return 'Detalhes';
|
||||
}
|
||||
};
|
||||
|
||||
const getIcone = () => {
|
||||
switch (tipo) {
|
||||
case 'vinculo': return '💼';
|
||||
case 'coordenacao': return '🎯';
|
||||
case 'premiacao': return '🏆';
|
||||
case 'avaliacao': return '📋';
|
||||
case 'inscricao': return '📝';
|
||||
case 'participacao': return item.codigo === 'PROJ' ? '📊' : '📅';
|
||||
case 'orientacao': return '🎓';
|
||||
default: return '📄';
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (tipo) {
|
||||
case 'vinculo': {
|
||||
const periodo = item.periodo || {};
|
||||
const isAtivo = periodo.ativo ?? !periodo.fim;
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Status</span>
|
||||
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
|
||||
{isAtivo ? 'ATIVO' : 'ENCERRADO'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Instituição</span>
|
||||
<span className="modal-detalhe-value">
|
||||
{item.ies ? (item.ies.sigla ? `${item.ies.sigla} - ${item.ies.nome || ''}` : item.ies.nome) : 'Não informada'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Início</span>
|
||||
<span className="modal-detalhe-value">{formatDate(periodo.inicio)}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Fim</span>
|
||||
<span className="modal-detalhe-value">{isAtivo ? 'Em andamento' : formatDate(periodo.fim)}</span>
|
||||
</div>
|
||||
{item.situacao && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Situação</span>
|
||||
<span className="modal-detalhe-value">{item.situacao}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'coordenacao': {
|
||||
const isAtivo = item.ativo ?? !item.fim;
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo</span>
|
||||
<span className="badge">{item.codigo || item.tipo}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Área de Avaliação</span>
|
||||
<span className="modal-detalhe-value">{item.area_avaliacao || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Status</span>
|
||||
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
|
||||
{isAtivo ? 'VIGENTE' : 'ENCERRADO'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Início</span>
|
||||
<span className="modal-detalhe-value">{formatDate(item.inicio || item.periodo?.inicio)}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Fim</span>
|
||||
<span className="modal-detalhe-value">{isAtivo ? 'Em andamento' : formatDate(item.fim || item.periodo?.fim)}</span>
|
||||
</div>
|
||||
{item.presidente && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Função</span>
|
||||
<span className="badge badge-premiado">👑 Presidente de Câmara</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Base</span>
|
||||
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'premiacao':
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Código</span>
|
||||
<span className="badge">{item.codigo}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Prêmio</span>
|
||||
<span className="modal-detalhe-value">{item.nome_premio || item.premio || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Ano</span>
|
||||
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
|
||||
</div>
|
||||
{item.papel && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Papel</span>
|
||||
<span className="modal-detalhe-value">{item.papel}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.tipo && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo</span>
|
||||
<span className="modal-detalhe-value">{item.tipo}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Base</span>
|
||||
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'avaliacao':
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Código</span>
|
||||
<span className="badge">{item.codigo}</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>
|
||||
</div>
|
||||
{item.nome_comissao && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Comissão</span>
|
||||
<span className="modal-detalhe-value">{item.nome_comissao}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.comissao_tipo && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo Comissão</span>
|
||||
<span className="modal-detalhe-value">{item.comissao_tipo}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.tipo && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Função</span>
|
||||
<span className="modal-detalhe-value">{item.tipo}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Ano</span>
|
||||
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Base</span>
|
||||
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'inscricao':
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Código</span>
|
||||
<span className="badge">{item.codigo}</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>
|
||||
</div>
|
||||
{item.tipo && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo Inscrição</span>
|
||||
<span className="modal-detalhe-value">{item.tipo}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.situacao && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Situação</span>
|
||||
<span className="modal-detalhe-value">{item.situacao}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Ano</span>
|
||||
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Base</span>
|
||||
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'participacao':
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo</span>
|
||||
<span className="badge">{item.codigo}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Descrição</span>
|
||||
<span className="modal-detalhe-value">{item.descricao || item.tipo || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Ano</span>
|
||||
<span className="modal-detalhe-value">{item.ano || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Base</span>
|
||||
<span className="modal-detalhe-value pontos">{PONTOS_BASE[item.codigo] || 0} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'orientacao':
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Tipo</span>
|
||||
<span className="badge">{item.codigo}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Categoria</span>
|
||||
<span className="modal-detalhe-value">
|
||||
{item.codigo?.includes('TESE') ? 'Doutorado' : item.codigo?.includes('DISS') ? 'Mestrado' : 'Pós-Doutorado'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Função</span>
|
||||
<span className="modal-detalhe-value">
|
||||
{item.coorientacao || item.codigo?.startsWith('CO_') ? 'Coorientador' : 'Orientador'}
|
||||
</span>
|
||||
</div>
|
||||
{item.premiada && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Destaque</span>
|
||||
<span className="badge badge-premiado">🏆 Premiada</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação</span>
|
||||
<span className="modal-detalhe-value muted">Apenas selo (sem pontuação)</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return <p className="modal-empty">Sem detalhes disponíveis</p>;
|
||||
}
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div className="tipo-modal-overlay" onClick={onClose}>
|
||||
<div className="tipo-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="tipo-modal-header">
|
||||
<span className="modal-titulo-item">
|
||||
<span className="modal-titulo-icone">{getIcone()}</span>
|
||||
<span>{getTitulo()}</span>
|
||||
</span>
|
||||
<button className="tipo-modal-close" onClick={onClose}>✕</button>
|
||||
</div>
|
||||
<div className="tipo-modal-body">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
const FORMULAS = {
|
||||
bloco_a: {
|
||||
titulo: 'Coordenacao CAPES',
|
||||
@@ -623,6 +920,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
const [showRawModal, setShowRawModal] = useState(false);
|
||||
const [tipoAtuacaoModal, setTipoAtuacaoModal] = useState(null);
|
||||
const [seloModal, setSeloModal] = useState(null);
|
||||
const [itemDetalhe, setItemDetalhe] = useState(null);
|
||||
const cardRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -840,7 +1138,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
const periodo = vinculo.periodo || {};
|
||||
const isAtivo = periodo.ativo ?? !periodo.fim;
|
||||
return (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: vinculo, tipo: 'vinculo' })}
|
||||
>
|
||||
<span className={`badge ${isAtivo ? 'badge-ativo' : 'badge-historico'}`}>
|
||||
{isAtivo ? 'ATIVO' : 'ENCERRADO'}
|
||||
</span>
|
||||
@@ -868,7 +1170,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
{[...consultor.coordenacoes_capes]
|
||||
.sort((a, b) => new Date(b.inicio || b.periodo?.inicio || 0) - new Date(a.inicio || a.periodo?.inicio || 0))
|
||||
.map((coord, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: coord, tipo: 'coordenacao' })}
|
||||
>
|
||||
<span className="badge">{coord.codigo || coord.tipo}</span>
|
||||
<span className="pontos">{PONTOS_BASE[coord.codigo] || 0} pts</span>
|
||||
<span>{coord.area_avaliacao}</span>
|
||||
@@ -888,7 +1194,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
{[...consultor.premiacoes]
|
||||
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
|
||||
.map((prem, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: prem, tipo: 'premiacao' })}
|
||||
>
|
||||
<span className="badge">{prem.codigo}</span>
|
||||
<span className="pontos">{PONTOS_BASE[prem.codigo] || 0} pts</span>
|
||||
<span>{prem.nome_premio}</span>
|
||||
@@ -906,7 +1216,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
{[...consultor.avaliacoes_comissao]
|
||||
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
|
||||
.map((aval, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: aval, tipo: 'avaliacao' })}
|
||||
>
|
||||
<span className="badge">{aval.codigo}</span>
|
||||
<span className="pontos">{PONTOS_BASE[aval.codigo] || 0} pts</span>
|
||||
<span>{aval.nome_comissao || aval.premio}</span>
|
||||
@@ -924,7 +1238,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
{[...consultor.inscricoes]
|
||||
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
|
||||
.map((insc, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: insc, tipo: 'inscricao' })}
|
||||
>
|
||||
<span className="badge">{insc.codigo}</span>
|
||||
<span className="pontos">{PONTOS_BASE[insc.codigo] || 0} pts</span>
|
||||
<span>{insc.premio}</span>
|
||||
@@ -943,7 +1261,11 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
.sort((a, b) => (b.ano || 0) - (a.ano || 0))
|
||||
.slice(0, 10)
|
||||
.map((part, idx) => (
|
||||
<div key={idx} className="list-item">
|
||||
<div
|
||||
key={idx}
|
||||
className="list-item list-item-clicavel"
|
||||
onClick={() => setItemDetalhe({ item: part, tipo: 'participacao' })}
|
||||
>
|
||||
<span className="badge">{part.codigo}</span>
|
||||
<span className="pontos">{PONTOS_BASE[part.codigo] || 0} pts</span>
|
||||
<span>{part.descricao || part.tipo}</span>
|
||||
@@ -1025,6 +1347,14 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
onClose={() => setSeloModal(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{itemDetalhe && (
|
||||
<ItemDetalheModal
|
||||
item={itemDetalhe.item}
|
||||
tipo={itemDetalhe.tipo}
|
||||
onClose={() => setItemDetalhe(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user