feat(frontend): adicionar modais de calculo nas caixas de pontuacao
- Substituir tooltips por modais clicaveis nos score items - Adicionar PontuacaoModal com formula de calculo detalhada - Criar ScoreItemClickable para itens de pontuacao clicaveis - Exibir breakdown: base, tempo, bonus, teto e total - Estilar formula-box com fonte monospace para clareza - Manter consistencia visual com outros modais do sistema
This commit is contained in:
@@ -1149,3 +1149,136 @@
|
||||
letter-spacing: 0.5px;
|
||||
margin: 1rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.score-item-clicavel {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.score-item-clicavel:hover .score-item {
|
||||
transform: scale(1.08);
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.modal-formula-section {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--stroke);
|
||||
}
|
||||
|
||||
.modal-formula-section .modal-detalhe-label {
|
||||
display: block;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--accent-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-formula-box {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--stroke);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.modal-formula-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.modal-formula-line:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.formula-label {
|
||||
color: var(--muted);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.formula-calc {
|
||||
color: var(--text);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.modal-formula-line.formula-subtotal {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px dashed var(--stroke);
|
||||
}
|
||||
|
||||
.modal-formula-line.formula-subtotal .formula-label,
|
||||
.modal-formula-line.formula-subtotal .formula-calc {
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-formula-line.formula-total {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 2px solid var(--accent);
|
||||
background: rgba(79, 70, 229, 0.1);
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
border-radius: 0 0 7px 7px;
|
||||
}
|
||||
|
||||
.modal-formula-line.formula-total .formula-label {
|
||||
color: var(--accent-2);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.modal-formula-line.formula-total .formula-calc {
|
||||
color: var(--accent-2);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.modal-atuacoes-section {
|
||||
margin-top: 1.25rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--stroke);
|
||||
}
|
||||
|
||||
.modal-atuacoes-section .modal-detalhe-label {
|
||||
display: block;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--accent-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-atuacoes-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-atuacao-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--stroke);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.modal-atuacao-item .badge {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.modal-atuacao-valor {
|
||||
color: var(--accent-2);
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.modal-atuacao-detalhe {
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
@@ -905,13 +905,188 @@ const TETOS = {
|
||||
MB_BANCA_DISS: { teto: 0, doc: 'Selo (sem pontuação)' },
|
||||
};
|
||||
|
||||
const ScoreItemWithTooltip = ({ value, label, formula, style }) => (
|
||||
<div className="score-item-wrapper">
|
||||
const PontuacaoModal = ({ dados, onClose }) => {
|
||||
if (!dados) return null;
|
||||
|
||||
const { tipo, label, value, formula, atuacao, bloco } = dados;
|
||||
|
||||
const getTitulo = () => {
|
||||
if (tipo === 'bloco') return `${label} - Detalhes`;
|
||||
if (tipo === 'atuacao') return `${atuacao?.codigo || label} - Cálculo`;
|
||||
if (tipo === 'total') return 'Pontuação Total';
|
||||
return label;
|
||||
};
|
||||
|
||||
const getIcone = () => {
|
||||
if (label?.includes('BLOCO A') || label === 'A') return '🎯';
|
||||
if (label?.includes('BLOCO B') || label === 'B') return '🎓';
|
||||
if (label?.includes('BLOCO C') || label === 'C') return '💼';
|
||||
if (label?.includes('BLOCO D') || label === 'D') return '🏆';
|
||||
if (tipo === 'total') return '📊';
|
||||
return '📈';
|
||||
};
|
||||
|
||||
const renderBlocoContent = () => {
|
||||
const formulaLines = formula ? formula.split('\n') : [];
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação</span>
|
||||
<span className="modal-detalhe-value pontos">{value} pts</span>
|
||||
</div>
|
||||
{formulaLines.length > 0 && (
|
||||
<div className="modal-formula-section">
|
||||
<span className="modal-detalhe-label">Fórmula de Cálculo</span>
|
||||
<div className="modal-formula-box">
|
||||
{formulaLines.map((line, idx) => (
|
||||
<div key={idx} className="modal-formula-line">{line}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{bloco?.atuacoes?.length > 0 && (
|
||||
<div className="modal-atuacoes-section">
|
||||
<span className="modal-detalhe-label">Composição</span>
|
||||
<div className="modal-atuacoes-list">
|
||||
{bloco.atuacoes.map((at, idx) => (
|
||||
<div key={idx} className="modal-atuacao-item">
|
||||
<span className="badge">{at.codigo}</span>
|
||||
<span className="modal-atuacao-valor">{at.total} pts</span>
|
||||
<span className="modal-atuacao-detalhe">
|
||||
{at.quantidade > 1 ? `(${at.quantidade}x)` : ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderAtuacaoContent = () => {
|
||||
if (!atuacao) return null;
|
||||
const base = atuacao.base || 0;
|
||||
const tempo = atuacao.tempo || 0;
|
||||
const bonus = atuacao.bonus || 0;
|
||||
const bruto = base + tempo + bonus;
|
||||
const meta = TETOS[atuacao.codigo];
|
||||
const hasTeto = meta && meta.teto > 0;
|
||||
const capped = hasTeto && bruto > meta.teto;
|
||||
const unidade = atuacao.quantidade > 1 ? Math.round(base / atuacao.quantidade) : null;
|
||||
|
||||
return (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Código</span>
|
||||
<span className="badge">{atuacao.codigo}</span>
|
||||
</div>
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Final</span>
|
||||
<span className="modal-detalhe-value pontos">{atuacao.total} pts</span>
|
||||
</div>
|
||||
|
||||
<div className="modal-formula-section">
|
||||
<span className="modal-detalhe-label">Cálculo Detalhado</span>
|
||||
<div className="modal-formula-box">
|
||||
{unidade ? (
|
||||
<div className="modal-formula-line">
|
||||
<span className="formula-label">Base:</span>
|
||||
<span className="formula-calc">{unidade} × {atuacao.quantidade} = {base}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="modal-formula-line">
|
||||
<span className="formula-label">Base:</span>
|
||||
<span className="formula-calc">{base}</span>
|
||||
</div>
|
||||
)}
|
||||
{tempo > 0 && (
|
||||
<div className="modal-formula-line">
|
||||
<span className="formula-label">Tempo:</span>
|
||||
<span className="formula-calc">+{tempo}</span>
|
||||
</div>
|
||||
)}
|
||||
{bonus > 0 && (
|
||||
<div className="modal-formula-line">
|
||||
<span className="formula-label">Bônus:</span>
|
||||
<span className="formula-calc">+{bonus}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-formula-line formula-subtotal">
|
||||
<span className="formula-label">Subtotal:</span>
|
||||
<span className="formula-calc">{bruto}</span>
|
||||
</div>
|
||||
{hasTeto && (
|
||||
<div className="modal-formula-line">
|
||||
<span className="formula-label">Teto:</span>
|
||||
<span className="formula-calc">{meta.teto}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-formula-line formula-total">
|
||||
<span className="formula-label">Total:</span>
|
||||
<span className="formula-calc">{atuacao.total} {capped ? '(limitado pelo teto)' : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{meta?.doc && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Referência</span>
|
||||
<span className="modal-detalhe-value muted">{meta.doc}</span>
|
||||
</div>
|
||||
)}
|
||||
{meta?.bonus && (
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Regra de Bônus</span>
|
||||
<span className="modal-detalhe-value muted">{meta.bonus}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTotalContent = () => (
|
||||
<div className="modal-detalhe-content">
|
||||
<div className="modal-detalhe-row">
|
||||
<span className="modal-detalhe-label">Pontuação Total</span>
|
||||
<span className="modal-detalhe-value pontos">{value} pts</span>
|
||||
</div>
|
||||
<div className="modal-formula-section">
|
||||
<span className="modal-detalhe-label">Fórmula</span>
|
||||
<div className="modal-formula-box">
|
||||
<div className="modal-formula-line">Bloco A + Bloco B + Bloco C + Bloco D</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
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">
|
||||
{tipo === 'bloco' && renderBlocoContent()}
|
||||
{tipo === 'atuacao' && renderAtuacaoContent()}
|
||||
{tipo === 'total' && renderTotalContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
const ScoreItemClickable = ({ value, label, formula, style, onClick }) => (
|
||||
<div className="score-item-wrapper score-item-clicavel" onClick={onClick}>
|
||||
<div className="score-item" style={style}>
|
||||
<div className="score-item-value" style={style}>{value}</div>
|
||||
<div className="score-item-label">{label}</div>
|
||||
</div>
|
||||
{formula && <div className="score-tooltip">{formula}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -921,6 +1096,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
const [tipoAtuacaoModal, setTipoAtuacaoModal] = useState(null);
|
||||
const [seloModal, setSeloModal] = useState(null);
|
||||
const [itemDetalhe, setItemDetalhe] = useState(null);
|
||||
const [pontuacaoModal, setPontuacaoModal] = useState(null);
|
||||
const cardRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1058,54 +1234,81 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
<div className="detail-section">
|
||||
<h4>Pontuacao Total</h4>
|
||||
<div className="score-breakdown-total">
|
||||
<ScoreItemWithTooltip
|
||||
<ScoreItemClickable
|
||||
value={blocoA.total}
|
||||
label="BLOCO A"
|
||||
formula={FORMULAS.bloco_a.descricao}
|
||||
style={{ color: blocoA.total > 0 ? 'var(--accent-2)' : 'var(--muted)' }}
|
||||
onClick={() => setPontuacaoModal({
|
||||
tipo: 'bloco',
|
||||
label: 'BLOCO A - Coordenação CAPES',
|
||||
value: blocoA.total,
|
||||
formula: FORMULAS.bloco_a.descricao,
|
||||
bloco: blocoA
|
||||
})}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
<ScoreItemClickable
|
||||
value={blocoB.total}
|
||||
label="BLOCO B"
|
||||
formula={FORMULAS.bloco_b.descricao}
|
||||
style={{ color: blocoB.total > 0 ? 'var(--accent)' : 'var(--muted)' }}
|
||||
onClick={() => setPontuacaoModal({
|
||||
tipo: 'bloco',
|
||||
label: 'BLOCO B - Coordenação PPG',
|
||||
value: blocoB.total,
|
||||
formula: FORMULAS.bloco_b.descricao,
|
||||
bloco: blocoB
|
||||
})}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
<ScoreItemClickable
|
||||
value={blocoC.total}
|
||||
label="BLOCO C"
|
||||
formula={FORMULAS.bloco_c.descricao}
|
||||
style={{ color: blocoC.total > 0 ? 'var(--gold)' : 'var(--muted)' }}
|
||||
onClick={() => setPontuacaoModal({
|
||||
tipo: 'bloco',
|
||||
label: 'BLOCO C - Consultoria',
|
||||
value: blocoC.total,
|
||||
formula: FORMULAS.bloco_c.descricao,
|
||||
bloco: blocoC
|
||||
})}
|
||||
/>
|
||||
<ScoreItemWithTooltip
|
||||
<ScoreItemClickable
|
||||
value={blocoD.total}
|
||||
label="BLOCO D"
|
||||
formula={FORMULAS.bloco_d.descricao}
|
||||
style={{ color: blocoD.total > 0 ? 'var(--bronze)' : 'var(--muted)' }}
|
||||
onClick={() => setPontuacaoModal({
|
||||
tipo: 'bloco',
|
||||
label: 'BLOCO D - Premiações/Avaliações',
|
||||
value: blocoD.total,
|
||||
formula: FORMULAS.bloco_d.descricao,
|
||||
bloco: blocoD
|
||||
})}
|
||||
/>
|
||||
<ScoreItemClickable
|
||||
value={pontuacaoTotal}
|
||||
label="TOTAL"
|
||||
style={{ background: 'var(--accent)', color: 'white' }}
|
||||
onClick={() => setPontuacaoModal({
|
||||
tipo: 'total',
|
||||
label: 'TOTAL',
|
||||
value: pontuacaoTotal
|
||||
})}
|
||||
/>
|
||||
<div className="score-item-wrapper">
|
||||
<div className="score-item score-total">
|
||||
<div className="score-item-value">{pontuacaoTotal}</div>
|
||||
<div className="score-item-label">TOTAL</div>
|
||||
</div>
|
||||
<div className="score-tooltip">Bloco A + Bloco B + Bloco C + Bloco D</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{blocoA.atuacoes && blocoA.atuacoes.length > 0 && (
|
||||
<BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" />
|
||||
<BlocoDetalhes titulo="A - Coordenacao CAPES" bloco={blocoA} cor="var(--accent-2)" onItemClick={setPontuacaoModal} />
|
||||
)}
|
||||
|
||||
{(blocoB.total > 0 || (blocoB.atuacoes && blocoB.atuacoes.length > 0)) && (
|
||||
<BlocoDetalhes titulo="B - Coordenacao PPG" bloco={blocoB} cor="var(--accent)" />
|
||||
<BlocoDetalhes titulo="B - Coordenacao PPG" bloco={blocoB} cor="var(--accent)" onItemClick={setPontuacaoModal} />
|
||||
)}
|
||||
|
||||
{blocoC.atuacoes && blocoC.atuacoes.length > 0 && (
|
||||
<BlocoDetalhes titulo="C - Consultoria" bloco={blocoC} cor="var(--gold)" />
|
||||
<BlocoDetalhes titulo="C - Consultoria" bloco={blocoC} cor="var(--gold)" onItemClick={setPontuacaoModal} />
|
||||
)}
|
||||
|
||||
{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)" onItemClick={setPontuacaoModal} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1355,47 +1558,38 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
|
||||
onClose={() => setItemDetalhe(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{pontuacaoModal && (
|
||||
<PontuacaoModal
|
||||
dados={pontuacaoModal}
|
||||
onClose={() => setPontuacaoModal(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ConsultorCard.displayName = 'ConsultorCard';
|
||||
|
||||
const BlocoDetalhes = memo(({ titulo, bloco, cor }) => (
|
||||
const BlocoDetalhes = memo(({ titulo, bloco, cor, onItemClick }) => (
|
||||
<div className="detail-section">
|
||||
<h4 style={{ color: cor }}>{titulo}</h4>
|
||||
<div className="score-breakdown">
|
||||
{bloco.atuacoes?.map((at, idx) => (
|
||||
<div key={idx} className="score-item-wrapper">
|
||||
<div
|
||||
key={idx}
|
||||
className="score-item-wrapper score-item-clicavel"
|
||||
onClick={() => onItemClick && onItemClick({
|
||||
tipo: 'atuacao',
|
||||
label: at.codigo,
|
||||
value: at.total,
|
||||
atuacao: at
|
||||
})}
|
||||
>
|
||||
<div className="score-item">
|
||||
<div className="score-item-value">{at.total}</div>
|
||||
<div className="score-item-label">{at.codigo}</div>
|
||||
</div>
|
||||
<div className="score-tooltip">
|
||||
{(() => {
|
||||
const base = at.base || 0;
|
||||
const tempo = at.tempo || 0;
|
||||
const bonus = at.bonus || 0;
|
||||
const bruto = base + tempo + bonus;
|
||||
const meta = TETOS[at.codigo];
|
||||
const hasTeto = meta && meta.teto > 0;
|
||||
const capped = hasTeto && bruto > meta.teto;
|
||||
const unidade = at.quantidade > 1 ? Math.round(base / at.quantidade) : null;
|
||||
|
||||
const partes = [];
|
||||
partes.push(
|
||||
unidade
|
||||
? `Base ${unidade} x ${at.quantidade} = ${base}`
|
||||
: `Base ${base}`
|
||||
);
|
||||
if (tempo) partes.push(`Tempo ${tempo}`);
|
||||
if (bonus) partes.push(`Bônus ${bonus}`);
|
||||
if (hasTeto) partes.push(`Teto ${meta.teto}`);
|
||||
if (meta && meta.bonus) partes.push(meta.bonus);
|
||||
partes.push(capped ? `Total ${at.total} (teto)` : `Total ${at.total}`);
|
||||
return partes.join(" | ");
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="score-item-wrapper">
|
||||
|
||||
Reference in New Issue
Block a user