From 7928062cb852ce7782818fd6a2c38d313ce53d86 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Sun, 14 Dec 2025 21:06:31 -0300 Subject: [PATCH] Add visual badges (selos) to ConsultorCard - Add SELOS constant with 18 badge types (coord, bolsa, premio, orientacoes, etc) - Add gerarSelos() function to extract badges from consultor data - Add SelosBadges component with compact and full display modes - Integrate badges in card header (compact) and expanded details (full) - Add CSS styles with gradient backgrounds and hover effects for each badge type - Badges show achievement counts and support tooltips --- frontend/src/components/ConsultorCard.css | 143 ++++++++++++++++++++++ frontend/src/components/ConsultorCard.jsx | 100 +++++++++++++++ 2 files changed, 243 insertions(+) diff --git a/frontend/src/components/ConsultorCard.css b/frontend/src/components/ConsultorCard.css index edc38b7..79fa062 100644 --- a/frontend/src/components/ConsultorCard.css +++ b/frontend/src/components/ConsultorCard.css @@ -494,3 +494,146 @@ grid-template-columns: 1fr; } } + +.selos-container { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + align-items: center; +} + +.selos-container.selos-compacto { + display: inline-flex; + margin-left: 0.3rem; +} + +.selo { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.7rem; + padding: 0.2rem 0.45rem; + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.08); + color: var(--text); + font-weight: 500; + transition: transform 150ms ease, box-shadow 150ms ease; +} + +.selo:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.selo-icone { + font-size: 0.85rem; + line-height: 1; +} + +.selo-label { + font-size: 0.68rem; + letter-spacing: 0.2px; +} + +.selo-qtd { + font-size: 0.6rem; + font-weight: 700; + background: rgba(255, 255, 255, 0.2); + padding: 0.1rem 0.3rem; + border-radius: 4px; + margin-left: 0.15rem; +} + +.selo-mais { + background: rgba(255, 255, 255, 0.05); + border-style: dashed; + font-size: 0.65rem; + color: var(--muted); +} + +.selo-coord { + background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(139, 92, 246, 0.1)); + border-color: rgba(139, 92, 246, 0.4); + color: #c4b5fd; +} + +.selo-camara { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.25), rgba(59, 130, 246, 0.1)); + border-color: rgba(59, 130, 246, 0.4); + color: #93c5fd; +} + +.selo-bpq { + background: linear-gradient(135deg, rgba(34, 197, 94, 0.25), rgba(34, 197, 94, 0.1)); + border-color: rgba(34, 197, 94, 0.4); + color: #86efac; +} + +.selo-gp { + background: linear-gradient(135deg, rgba(251, 191, 36, 0.35), rgba(251, 191, 36, 0.15)); + border-color: rgba(251, 191, 36, 0.6); + color: #fcd34d; +} + +.selo-premio { + background: linear-gradient(135deg, rgba(249, 115, 22, 0.25), rgba(249, 115, 22, 0.1)); + border-color: rgba(249, 115, 22, 0.4); + color: #fdba74; +} + +.selo-mencao { + background: linear-gradient(135deg, rgba(203, 213, 225, 0.2), rgba(203, 213, 225, 0.08)); + border-color: rgba(203, 213, 225, 0.35); + color: #e2e8f0; +} + +.selo-orient { + background: linear-gradient(135deg, rgba(14, 165, 233, 0.2), rgba(14, 165, 233, 0.08)); + border-color: rgba(14, 165, 233, 0.35); + color: #7dd3fc; +} + +.selo-orient-prem { + background: linear-gradient(135deg, rgba(14, 165, 233, 0.35), rgba(251, 191, 36, 0.2)); + border-color: rgba(251, 191, 36, 0.5); + color: #fcd34d; +} + +.selo-coorient { + background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(99, 102, 241, 0.08)); + border-color: rgba(99, 102, 241, 0.35); + color: #a5b4fc; +} + +.selo-coorient-prem { + background: linear-gradient(135deg, rgba(99, 102, 241, 0.35), rgba(251, 191, 36, 0.2)); + border-color: rgba(251, 191, 36, 0.5); + color: #fcd34d; +} + +.selos-section { + grid-column: 1 / -1; +} + +.selos-section .selos-container { + gap: 0.5rem; +} + +.selos-section .selo { + padding: 0.35rem 0.6rem; +} + +.selos-section .selo-icone { + font-size: 1rem; +} + +.selos-section .selo-label { + font-size: 0.72rem; +} + +@media (max-width: 900px) { + .selos-container.selos-compacto { + display: none; + } +} diff --git a/frontend/src/components/ConsultorCard.jsx b/frontend/src/components/ConsultorCard.jsx index 49f149e..de2cc80 100644 --- a/frontend/src/components/ConsultorCard.jsx +++ b/frontend/src/components/ConsultorCard.jsx @@ -1,6 +1,96 @@ import React, { useState, useRef, useEffect } from 'react'; import './ConsultorCard.css'; +const SELOS = { + COORD_PPG: { label: 'Coord. PPG', cor: 'selo-coord', icone: '๐ŸŽ“' }, + PRESID_CAMARA: { label: 'Presid. Cรขmara', cor: 'selo-camara', icone: '๐Ÿ›๏ธ' }, + BPQ: { label: 'Bolsista PQ', cor: 'selo-bpq', icone: '๐Ÿ”ฌ' }, + GRANDE_PREMIO: { label: 'Grande Prรชmio', cor: 'selo-gp', icone: '๐Ÿ†' }, + PREMIO: { label: 'Prรชmio', cor: 'selo-premio', icone: '๐Ÿฅ‡' }, + MENCAO: { label: 'Menรงรฃo Honrosa', cor: 'selo-mencao', icone: '๐ŸŽ–๏ธ' }, + ORIENT_POS_DOC: { label: 'Orient. Pรณs-Doc', cor: 'selo-orient', icone: '๐Ÿ“š' }, + ORIENT_POS_DOC_PREM: { label: 'Orient. Pรณs-Doc Premiada', cor: 'selo-orient-prem', icone: '๐Ÿ“š๐Ÿ†' }, + ORIENT_TESE: { label: 'Orient. Tese', cor: 'selo-orient', icone: '๐Ÿ“–' }, + ORIENT_TESE_PREM: { label: 'Orient. Tese Premiada', cor: 'selo-orient-prem', icone: '๐Ÿ“–๐Ÿ†' }, + ORIENT_DISS: { label: 'Orient. Dissertaรงรฃo', cor: 'selo-orient', icone: '๐Ÿ“' }, + ORIENT_DISS_PREM: { label: 'Orient. Diss. Premiada', cor: 'selo-orient-prem', icone: '๐Ÿ“๐Ÿ†' }, + CO_ORIENT_POS_DOC: { label: 'Co-Orient. Pรณs-Doc', cor: 'selo-coorient', icone: '๐Ÿ“š' }, + CO_ORIENT_POS_DOC_PREM: { label: 'Co-Orient. Pรณs-Doc Prem.', cor: 'selo-coorient-prem', icone: '๐Ÿ“š๐Ÿ†' }, + CO_ORIENT_TESE: { label: 'Co-Orient. Tese', cor: 'selo-coorient', icone: '๐Ÿ“–' }, + CO_ORIENT_TESE_PREM: { label: 'Co-Orient. Tese Premiada', cor: 'selo-coorient-prem', icone: '๐Ÿ“–๐Ÿ†' }, + CO_ORIENT_DISS: { label: 'Co-Orient. Diss.', cor: 'selo-coorient', icone: '๐Ÿ“' }, + CO_ORIENT_DISS_PREM: { label: 'Co-Orient. Diss. Prem.', cor: 'selo-coorient-prem', icone: '๐Ÿ“๐Ÿ†' }, +}; + +const gerarSelos = (consultor) => { + const selos = []; + + if (consultor.coordenacoes_capes?.some(c => c.codigo === 'CAM' && c.presidente)) { + selos.push({ ...SELOS.PRESID_CAMARA, qtd: 1 }); + } + + if (consultor.coordenador_ppg) { + selos.push({ ...SELOS.COORD_PPG, qtd: 1 }); + } + + if (consultor.bolsas_cnpq?.length > 0) { + selos.push({ ...SELOS.BPQ, qtd: consultor.bolsas_cnpq.length }); + } + + const gp = consultor.premiacoes?.filter(p => p.codigo === 'PREMIACAO')?.length || 0; + const premio = consultor.premiacoes?.filter(p => p.codigo === 'PREMIACAO_GP')?.length || 0; + const mencao = consultor.premiacoes?.filter(p => p.codigo === 'MENCAO')?.length || 0; + + if (gp > 0) selos.push({ ...SELOS.GRANDE_PREMIO, qtd: gp }); + if (premio > 0) selos.push({ ...SELOS.PREMIO, qtd: premio }); + if (mencao > 0) selos.push({ ...SELOS.MENCAO, qtd: mencao }); + + const orientPosDoc = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_POS_DOC')?.length || 0; + const orientTese = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_TESE')?.length || 0; + const orientDiss = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_DISS')?.length || 0; + + const orientPosDocPrem = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_POS_DOC' && o.premiada)?.length || 0; + const orientTesePrem = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_TESE' && o.premiada)?.length || 0; + const orientDissPrem = consultor.orientacoes?.filter(o => o.codigo === 'ORIENT_DISS' && o.premiada)?.length || 0; + + if (orientPosDocPrem > 0) selos.push({ ...SELOS.ORIENT_POS_DOC_PREM, qtd: orientPosDocPrem }); + else if (orientPosDoc > 0) selos.push({ ...SELOS.ORIENT_POS_DOC, qtd: orientPosDoc }); + + if (orientTesePrem > 0) selos.push({ ...SELOS.ORIENT_TESE_PREM, qtd: orientTesePrem }); + else if (orientTese > 0) selos.push({ ...SELOS.ORIENT_TESE, qtd: orientTese }); + + if (orientDissPrem > 0) selos.push({ ...SELOS.ORIENT_DISS_PREM, qtd: orientDissPrem }); + else if (orientDiss > 0) selos.push({ ...SELOS.ORIENT_DISS, qtd: orientDiss }); + + return selos; +}; + +const SelosBadges = ({ selos, compacto = false }) => { + if (!selos || selos.length === 0) return null; + + const selosExibidos = compacto ? selos.slice(0, 4) : selos; + const selosOcultos = compacto && selos.length > 4 ? selos.length - 4 : 0; + + return ( +
+ {selosExibidos.map((selo, idx) => ( + 1 ? ` (${selo.qtd}x)` : ''}`} + > + {selo.icone} + {!compacto && {selo.label}} + {selo.qtd > 1 && {selo.qtd}} + + ))} + {selosOcultos > 0 && ( + +{selosOcultos} + )} +
+ ); +}; + const FORMULAS = { bloco_a: { titulo: 'Coordenacao CAPES', @@ -105,6 +195,8 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado const blocoD = pontuacao?.bloco_d || { total: consultor.bloco_d || 0 }; const pontuacaoTotal = (blocoA.total || 0) + (blocoB.total || 0) + (blocoC.total || 0) + (blocoD.total || 0); + const selos = gerarSelos(consultor); + return (
setExpanded(!expanded)}>
@@ -124,6 +216,7 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado {consultor.ativo && ATIVO} {!consultor.ativo && HISTORICO} {consultor.veterano && VETERANO} +
{consultor.anos_atuacao} anos de atuacao @@ -207,6 +300,13 @@ const ConsultorCard = ({ consultor, highlight, selecionado, onToggleSelecionado {blocoD.atuacoes && blocoD.atuacoes.length > 0 && ( )} + + {selos.length > 0 && ( +
+

Selos e Reconhecimentos

+ +
+ )}
{consultor.coordenacoes_capes?.length > 0 && (