Files
ranking/frontend/src/components/FiltroSelos.jsx
Frederico Castro 3254374486 fix(filtros): corrigir extração de selos e melhorar UX do filtro
- Corrigir lógica de extração de selos para usar códigos exatos
- Filtro agora exige todos os selos selecionados (AND em vez de OR)
- Botão Aplicar volta: seleção não dispara filtro automaticamente
- Layout dos controles unificado em barra com fundo
2025-12-15 13:04:06 -03:00

155 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useRef, useEffect } from 'react';
import './FiltroSelos.css';
const SELOS_CONFIG = {
funcoes: {
label: 'Funções',
selos: [
{ codigo: 'PRESID_CAMARA', label: 'Presidente Câmara', icone: '👑' },
{ codigo: 'COORD_PPG', label: 'Coord. PPG', icone: '🎓' },
{ codigo: 'BPQ', label: 'Bolsista PQ', icone: '🏅' },
],
},
premiacoes: {
label: 'Premiações',
selos: [
{ codigo: 'AUTOR_GP', label: 'Autor GP', icone: '🏆' },
{ codigo: 'AUTOR_PREMIO', label: 'Autor Prêmio', icone: '🥇' },
{ codigo: 'AUTOR_MENCAO', label: 'Autor Menção', icone: '🥈' },
{ codigo: 'ORIENT_GP', label: 'Orient. GP', icone: '🏆' },
{ codigo: 'ORIENT_PREMIO', label: 'Orient. Prêmio', icone: '🎖️' },
{ codigo: 'ORIENT_MENCAO', label: 'Orient. Menção', icone: '📜' },
],
},
orientacoes: {
label: 'Orientações',
selos: [
{ codigo: 'ORIENT_POS_DOC', label: 'Pós-Doc', icone: '🔬' },
{ codigo: 'ORIENT_TESE', label: 'Tese', icone: '📚' },
{ codigo: 'ORIENT_DISS', label: 'Dissertação', icone: '📄' },
{ codigo: 'CO_ORIENT_POS_DOC', label: 'Co-orient. Pós-Doc', icone: '🔬' },
{ codigo: 'CO_ORIENT_TESE', label: 'Co-orient. Tese', icone: '📚' },
{ codigo: 'CO_ORIENT_DISS', label: 'Co-orient. Diss.', icone: '📄' },
],
},
};
function FiltroSelos({ selecionados, onChange }) {
const [aberto, setAberto] = useState(false);
const [selosTemp, setSelosTemp] = useState([]);
const ref = useRef(null);
useEffect(() => {
if (aberto) {
setSelosTemp([...selecionados]);
}
}, [aberto, selecionados]);
useEffect(() => {
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setAberto(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const toggleSelo = (codigo) => {
if (selosTemp.includes(codigo)) {
setSelosTemp(selosTemp.filter((s) => s !== codigo));
} else {
setSelosTemp([...selosTemp, codigo]);
}
};
const limparTemp = () => {
setSelosTemp([]);
};
const limparFiltros = (e) => {
e.stopPropagation();
onChange([]);
};
const aplicarFiltro = () => {
onChange(selosTemp);
setAberto(false);
};
const totalSelos = Object.values(SELOS_CONFIG).reduce(
(acc, g) => acc + g.selos.length,
0
);
return (
<div className="filtro-selos" ref={ref}>
<button
className={`filtro-selos-trigger ${selecionados.length > 0 ? 'ativo' : ''}`}
onClick={() => setAberto(!aberto)}
>
<span className="filtro-icone">🏷</span>
<span className="filtro-label">
{selecionados.length > 0
? `${selecionados.length} selo${selecionados.length > 1 ? 's' : ''}`
: 'Filtrar por selos'}
</span>
<span className={`filtro-seta ${aberto ? 'aberto' : ''}`}></span>
{selecionados.length > 0 && (
<span className="filtro-limpar" onClick={limparFiltros} title="Limpar filtros">
</span>
)}
</button>
{aberto && (
<div className="filtro-selos-dropdown">
<div className="filtro-selos-header">
<span>Selecione os selos para filtrar</span>
{selosTemp.length > 0 && (
<button className="filtro-limpar-todos" onClick={limparTemp}>
Limpar ({selosTemp.length})
</button>
)}
</div>
<div className="filtro-selos-grupos">
{Object.entries(SELOS_CONFIG).map(([grupoKey, grupo]) => (
<div key={grupoKey} className="filtro-grupo">
<div className="filtro-grupo-titulo">{grupo.label}</div>
<div className="filtro-grupo-selos">
{grupo.selos.map((selo) => (
<label
key={selo.codigo}
className={`filtro-selo-item ${selosTemp.includes(selo.codigo) ? 'selecionado' : ''}`}
>
<input
type="checkbox"
checked={selosTemp.includes(selo.codigo)}
onChange={() => toggleSelo(selo.codigo)}
/>
<span className="selo-icone">{selo.icone}</span>
<span className="selo-label">{selo.label}</span>
</label>
))}
</div>
</div>
))}
</div>
<div className="filtro-selos-footer">
<span className="filtro-info">
{selosTemp.length} de {totalSelos} selecionado{selosTemp.length !== 1 ? 's' : ''}
</span>
<button className="filtro-aplicar" onClick={aplicarFiltro}>
Aplicar
</button>
</div>
</div>
)}
</div>
);
}
export default FiltroSelos;