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
This commit is contained in:
@@ -45,12 +45,14 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
|
|||||||
papel = (prem.get("papel") or "").lower()
|
papel = (prem.get("papel") or "").lower()
|
||||||
codigo = prem.get("codigo", "")
|
codigo = prem.get("codigo", "")
|
||||||
|
|
||||||
if "GP" in codigo or "grande" in codigo.lower():
|
if codigo == "PREMIACAO":
|
||||||
tipo_prem = "GP"
|
tipo_prem = "GP"
|
||||||
elif "MENCAO" in codigo or "menção" in codigo.lower():
|
elif codigo == "PREMIACAO_GP":
|
||||||
|
tipo_prem = "PREMIO"
|
||||||
|
elif codigo == "MENCAO":
|
||||||
tipo_prem = "MENCAO"
|
tipo_prem = "MENCAO"
|
||||||
else:
|
else:
|
||||||
tipo_prem = "PREMIO"
|
continue
|
||||||
|
|
||||||
if "autor" in papel:
|
if "autor" in papel:
|
||||||
selos.add(f"AUTOR_{tipo_prem}")
|
selos.add(f"AUTOR_{tipo_prem}")
|
||||||
@@ -64,18 +66,18 @@ def extrair_selos_entry(detalhes: Dict[str, Any]) -> Set[str]:
|
|||||||
is_coorient = orient.get("coorientacao", False)
|
is_coorient = orient.get("coorientacao", False)
|
||||||
|
|
||||||
if is_coorient:
|
if is_coorient:
|
||||||
if "POS_DOC" in codigo:
|
if codigo == "CO_ORIENT_POS_DOC":
|
||||||
selos.add("CO_ORIENT_POS_DOC")
|
selos.add("CO_ORIENT_POS_DOC")
|
||||||
elif "TESE" in codigo:
|
elif codigo == "CO_ORIENT_TESE":
|
||||||
selos.add("CO_ORIENT_TESE")
|
selos.add("CO_ORIENT_TESE")
|
||||||
elif "DISS" in codigo:
|
elif codigo == "CO_ORIENT_DISS":
|
||||||
selos.add("CO_ORIENT_DISS")
|
selos.add("CO_ORIENT_DISS")
|
||||||
else:
|
else:
|
||||||
if "POS_DOC" in codigo:
|
if codigo == "ORIENT_POS_DOC":
|
||||||
selos.add("ORIENT_POS_DOC")
|
selos.add("ORIENT_POS_DOC")
|
||||||
elif "TESE" in codigo:
|
elif codigo == "ORIENT_TESE":
|
||||||
selos.add("ORIENT_TESE")
|
selos.add("ORIENT_TESE")
|
||||||
elif "DISS" in codigo:
|
elif codigo == "ORIENT_DISS":
|
||||||
selos.add("ORIENT_DISS")
|
selos.add("ORIENT_DISS")
|
||||||
|
|
||||||
return selos
|
return selos
|
||||||
@@ -146,7 +148,7 @@ class RankingStore:
|
|||||||
selos_set = set(filtro_selos)
|
selos_set = set(filtro_selos)
|
||||||
entries = [
|
entries = [
|
||||||
e for e in entries
|
e for e in entries
|
||||||
if selos_set & extrair_selos_entry(e.detalhes)
|
if selos_set.issubset(extrair_selos_entry(e.detalhes))
|
||||||
]
|
]
|
||||||
|
|
||||||
total = len(entries)
|
total = len(entries)
|
||||||
|
|||||||
@@ -48,22 +48,30 @@
|
|||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
|
background: linear-gradient(165deg, rgba(15, 23, 42, 0.6), rgba(30, 41, 59, 0.4));
|
||||||
|
border: 1px solid var(--stroke);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1.5rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls label {
|
.control-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.9rem;
|
}
|
||||||
|
|
||||||
|
.control-group-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls select {
|
.controls select {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 0.75rem;
|
||||||
background: rgba(255,255,255,0.06);
|
background: rgba(255,255,255,0.06);
|
||||||
border: 1px solid var(--stroke);
|
border: 1px solid var(--stroke);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -81,23 +89,25 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box input {
|
.search-box input {
|
||||||
padding: 0.55rem 0.8rem;
|
padding: 0.5rem 0.75rem;
|
||||||
background: rgba(255,255,255,0.06);
|
background: rgba(255,255,255,0.06);
|
||||||
border: 1px solid var(--stroke);
|
border: 1px solid var(--stroke);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
min-width: 240px;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box input:focus {
|
.search-box input:focus {
|
||||||
outline: 1px solid var(--accent-2);
|
outline: none;
|
||||||
|
border-color: var(--accent-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box button {
|
.search-box button {
|
||||||
padding: 0.55rem 1rem;
|
padding: 0.5rem 0.9rem;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
border: 1px solid var(--accent);
|
border: 1px solid var(--accent);
|
||||||
color: white;
|
color: white;
|
||||||
@@ -120,17 +130,17 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination button {
|
.pagination button {
|
||||||
padding: 0.6rem 0.9rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
border: 1px solid var(--stroke);
|
border: 1px solid var(--stroke);
|
||||||
background: rgba(255,255,255,0.06);
|
background: rgba(255,255,255,0.06);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 150ms ease;
|
transition: all 150ms ease;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination button:hover:not(:disabled) {
|
.pagination button:hover:not(:disabled) {
|
||||||
@@ -145,6 +155,20 @@
|
|||||||
.page-info {
|
.page-info {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.controls {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
min-width: 100%;
|
||||||
|
order: 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-list {
|
.ranking-list {
|
||||||
|
|||||||
@@ -182,16 +182,16 @@ function App() {
|
|||||||
<Header total={total} />
|
<Header total={total} />
|
||||||
|
|
||||||
<div className="controls">
|
<div className="controls">
|
||||||
<label>
|
<div className="control-group">
|
||||||
Limite de consultores:
|
<span className="control-group-label">Exibir:</span>
|
||||||
<select value={pageSize} onChange={(e) => { setPageSize(Number(e.target.value)); setPage(1); }}>
|
<select value={pageSize} onChange={(e) => { setPageSize(Number(e.target.value)); setPage(1); }}>
|
||||||
<option value={10}>Top 10</option>
|
<option value={10}>10</option>
|
||||||
<option value={50}>Top 50</option>
|
<option value={50}>50</option>
|
||||||
<option value={100}>Top 100</option>
|
<option value={100}>100</option>
|
||||||
<option value={200}>Top 200</option>
|
<option value={200}>200</option>
|
||||||
<option value={500}>Top 500</option>
|
<option value={500}>500</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</div>
|
||||||
|
|
||||||
<FiltroSelos
|
<FiltroSelos
|
||||||
selecionados={filtroSelos}
|
selecionados={filtroSelos}
|
||||||
@@ -201,23 +201,21 @@ function App() {
|
|||||||
<form className="search-box" onSubmit={handleSubmitBuscar}>
|
<form className="search-box" onSubmit={handleSubmitBuscar}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Digite o nome para localizar"
|
placeholder="Buscar por nome..."
|
||||||
value={busca}
|
value={busca}
|
||||||
onChange={(e) => setBusca(e.target.value)}
|
onChange={(e) => setBusca(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<button type="submit" disabled={buscando || busca.length < 3}>
|
<button type="submit" disabled={buscando || busca.length < 3}>
|
||||||
{buscando ? 'Buscando...' : 'Buscar'}
|
{buscando ? '...' : 'Buscar'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="pagination">
|
<div className="pagination">
|
||||||
<button onClick={() => setPage(1)} disabled={page <= 1}>« Primeira</button>
|
<button onClick={() => setPage(1)} disabled={page <= 1}>«</button>
|
||||||
<button onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1}>‹ Anterior</button>
|
<button onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1}>‹</button>
|
||||||
<span className="page-info">
|
<span className="page-info">{page} / {totalPages || '?'}</span>
|
||||||
Página {page} de {totalPages || '?'}
|
<button onClick={() => setPage((p) => (totalPages ? Math.min(totalPages, p + 1) : p + 1))} disabled={totalPages && page >= totalPages}>›</button>
|
||||||
</span>
|
<button onClick={() => totalPages && setPage(totalPages)} disabled={totalPages && page >= totalPages}>»</button>
|
||||||
<button onClick={() => setPage((p) => (totalPages ? Math.min(totalPages, p + 1) : p + 1))} disabled={totalPages && page >= totalPages}>Próxima ›</button>
|
|
||||||
<button onClick={() => totalPages && setPage(totalPages)} disabled={totalPages && page >= totalPages}>Última »</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,15 @@ const SELOS_CONFIG = {
|
|||||||
|
|
||||||
function FiltroSelos({ selecionados, onChange }) {
|
function FiltroSelos({ selecionados, onChange }) {
|
||||||
const [aberto, setAberto] = useState(false);
|
const [aberto, setAberto] = useState(false);
|
||||||
|
const [selosTemp, setSelosTemp] = useState([]);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (aberto) {
|
||||||
|
setSelosTemp([...selecionados]);
|
||||||
|
}
|
||||||
|
}, [aberto, selecionados]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (e) => {
|
const handleClickOutside = (e) => {
|
||||||
if (ref.current && !ref.current.contains(e.target)) {
|
if (ref.current && !ref.current.contains(e.target)) {
|
||||||
@@ -49,18 +56,27 @@ function FiltroSelos({ selecionados, onChange }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSelo = (codigo) => {
|
const toggleSelo = (codigo) => {
|
||||||
if (selecionados.includes(codigo)) {
|
if (selosTemp.includes(codigo)) {
|
||||||
onChange(selecionados.filter((s) => s !== codigo));
|
setSelosTemp(selosTemp.filter((s) => s !== codigo));
|
||||||
} else {
|
} else {
|
||||||
onChange([...selecionados, codigo]);
|
setSelosTemp([...selosTemp, codigo]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const limparTemp = () => {
|
||||||
|
setSelosTemp([]);
|
||||||
|
};
|
||||||
|
|
||||||
const limparFiltros = (e) => {
|
const limparFiltros = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onChange([]);
|
onChange([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const aplicarFiltro = () => {
|
||||||
|
onChange(selosTemp);
|
||||||
|
setAberto(false);
|
||||||
|
};
|
||||||
|
|
||||||
const totalSelos = Object.values(SELOS_CONFIG).reduce(
|
const totalSelos = Object.values(SELOS_CONFIG).reduce(
|
||||||
(acc, g) => acc + g.selos.length,
|
(acc, g) => acc + g.selos.length,
|
||||||
0
|
0
|
||||||
@@ -90,9 +106,9 @@ function FiltroSelos({ selecionados, onChange }) {
|
|||||||
<div className="filtro-selos-dropdown">
|
<div className="filtro-selos-dropdown">
|
||||||
<div className="filtro-selos-header">
|
<div className="filtro-selos-header">
|
||||||
<span>Selecione os selos para filtrar</span>
|
<span>Selecione os selos para filtrar</span>
|
||||||
{selecionados.length > 0 && (
|
{selosTemp.length > 0 && (
|
||||||
<button className="filtro-limpar-todos" onClick={limparFiltros}>
|
<button className="filtro-limpar-todos" onClick={limparTemp}>
|
||||||
Limpar ({selecionados.length})
|
Limpar ({selosTemp.length})
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -105,11 +121,11 @@ function FiltroSelos({ selecionados, onChange }) {
|
|||||||
{grupo.selos.map((selo) => (
|
{grupo.selos.map((selo) => (
|
||||||
<label
|
<label
|
||||||
key={selo.codigo}
|
key={selo.codigo}
|
||||||
className={`filtro-selo-item ${selecionados.includes(selo.codigo) ? 'selecionado' : ''}`}
|
className={`filtro-selo-item ${selosTemp.includes(selo.codigo) ? 'selecionado' : ''}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selecionados.includes(selo.codigo)}
|
checked={selosTemp.includes(selo.codigo)}
|
||||||
onChange={() => toggleSelo(selo.codigo)}
|
onChange={() => toggleSelo(selo.codigo)}
|
||||||
/>
|
/>
|
||||||
<span className="selo-icone">{selo.icone}</span>
|
<span className="selo-icone">{selo.icone}</span>
|
||||||
@@ -123,9 +139,9 @@ function FiltroSelos({ selecionados, onChange }) {
|
|||||||
|
|
||||||
<div className="filtro-selos-footer">
|
<div className="filtro-selos-footer">
|
||||||
<span className="filtro-info">
|
<span className="filtro-info">
|
||||||
{selecionados.length} de {totalSelos} selecionado{selecionados.length !== 1 ? 's' : ''}
|
{selosTemp.length} de {totalSelos} selecionado{selosTemp.length !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
<button className="filtro-aplicar" onClick={() => setAberto(false)}>
|
<button className="filtro-aplicar" onClick={aplicarFiltro}>
|
||||||
Aplicar
|
Aplicar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user