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:
@@ -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