diff --git a/frontend/src/pages/Retiros.jsx b/frontend/src/pages/Retiros.jsx
index 1cdf8b6..3f3510c 100644
--- a/frontend/src/pages/Retiros.jsx
+++ b/frontend/src/pages/Retiros.jsx
@@ -1,5 +1,5 @@
// src/pages/Retiros.jsx
-import { useState, useEffect, useMemo } from "react";
+import { useState, useEffect, useMemo, useRef } from "react";
import { api } from "../lib/api";
import { Loader, ErrorBox, Filters, Select } from "../components/UI";
import { useColumnasAjustables } from "../lib/useColumnasAjustables";
@@ -81,8 +81,8 @@ export default function Retiros() {
const hoy = new Date();
const [ano, setAno] = useState(hoy.getFullYear());
const [mes, setMes] = useState(hoy.getMonth() + 1); // mes actual (sin TODOS)
- const [sede, setSede] = useState("TODOS");
- const [sectorista, setSectorista] = useState("TODOS");
+ const [sedesSel, setSedesSel] = useState([]); // multi; vacío = todas
+ const [sectoristasSel, setSectoristasSel] = useState([]); // multi; vacío = todos
const [ordCol, setOrdCol] = useState(null); // key de la columna ordenada
const [ordDir, setOrdDir] = useState(null);
const clickOrden = (k, esInfo) => {
@@ -166,30 +166,28 @@ export default function Retiros() {
return () => { activo = false; };
}, [ano, mes]);
- // Opciones de sectorista (de los datos cargados)
+ // Opciones de sectorista (de los datos cargados) — sin "TODOS" (checks)
const sectoristas = useMemo(() => {
- const s = [...new Set(filas.map((f) => f.asesor).filter(Boolean))].sort();
- return ["TODOS", ...s];
+ return [...new Set(filas.map((f) => f.asesor).filter(Boolean))].sort();
}, [filas]);
- // Opciones de sede (de los datos cargados)
+ // Opciones de sede (de los datos cargados) — sin "TODOS" (checks)
const sedes = useMemo(() => {
- const s = [...new Set(filas.map((f) => f.sede).filter(Boolean))].sort();
- return ["TODOS", ...s];
+ return [...new Set(filas.map((f) => f.sede).filter(Boolean))].sort();
}, [filas]);
- // El backend ya trae solo el mes; aquí solo se aplican Sede y Sectorista (cliente).
+ // El backend ya trae solo el mes; aquí solo se aplican Sede y Sectorista (cliente, multi).
const filasFiltradas = useMemo(() => {
let base = filas.filter((f) => {
- if (sede !== "TODOS" && f.sede !== sede) return false;
- if (sectorista !== "TODOS" && f.asesor !== sectorista) return false;
+ if (sedesSel.length && !sedesSel.includes(f.sede)) return false;
+ if (sectoristasSel.length && !sectoristasSel.includes(f.asesor)) return false;
return true;
});
if (ordCol != null && ordDir) {
base = [...base].sort((a, b) => _cmp(a[ordCol], b[ordCol], ordDir));
}
return base;
- }, [filas, sede, sectorista, ordCol, ordDir]);
+ }, [filas, sedesSel, sectoristasSel, ordCol, ordDir]);
// Fecha yyyy-mm-dd -> dd/mm/yyyy
const fmtFecha = (s) => {
@@ -252,12 +250,10 @@ export default function Retiros() {
-
{msgTotal && (
@@ -521,3 +517,100 @@ const inpStyle = {
};
const onFoco = (e) => { e.target.style.borderColor = "#7a9d8f"; e.target.style.boxShadow = "0 0 0 3px rgba(122,157,143,.18)"; };
const offFoco = (e) => { e.target.style.borderColor = "#cbd5e1"; e.target.style.boxShadow = "none"; };
+
+// Desplegable de UNA sola opción (Año/Mes) con diseño bonito. Opciones: strings o {value,label}.
+function SelectUnico({ label, opciones, valor, onChange, minWidth = 150 }) {
+ const [abierto, setAbierto] = useState(false);
+ const ref = useRef(null);
+ const opts = opciones.map((o) => (typeof o === "object" ? o : { value: o, label: String(o) }));
+ const actual = opts.find((o) => String(o.value) === String(valor));
+ useEffect(() => {
+ const fuera = (e) => { if (ref.current && !ref.current.contains(e.target)) setAbierto(false); };
+ document.addEventListener("mousedown", fuera);
+ return () => document.removeEventListener("mousedown", fuera);
+ }, []);
+ return (
+
+
+
+ {abierto && (
+
+ {opts.map((o) => {
+ const on = String(o.value) === String(valor);
+ return (
+
{ onChange(o.value); setAbierto(false); }}
+ style={{ padding: "8px 10px", borderRadius: 6, cursor: "pointer", fontSize: 13,
+ fontWeight: on ? 700 : 500, color: on ? "#1e3a5f" : "#334155",
+ background: on ? "#eff6ff" : "transparent" }}
+ onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = "#f8fafc"; }}
+ onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = "transparent"; }}>
+ {o.label}
+
+ );
+ })}
+
+ )}
+
+ );
+}
+
+// Filtro multi-selección con casillas (Sede, Sectorista). Vacío = todos.
+function ListaMulti({ label, opciones, seleccion, onChange, plural = "opciones", minWidth = 150 }) {
+ const [abierto, setAbierto] = useState(false);
+ const ref = useRef(null);
+ useEffect(() => {
+ const fuera = (e) => { if (ref.current && !ref.current.contains(e.target)) setAbierto(false); };
+ document.addEventListener("mousedown", fuera);
+ return () => document.removeEventListener("mousedown", fuera);
+ }, []);
+ const toggle = (v) => {
+ const set = new Set(seleccion);
+ set.has(v) ? set.delete(v) : set.add(v);
+ onChange([...set]);
+ };
+ const texto = seleccion.length === 0 ? "— Todos —"
+ : seleccion.length === 1 ? seleccion[0]
+ : `${seleccion.length} ${plural}`;
+ return (
+
+
+
+ {abierto && (
+
+ {opciones.length === 0 &&
Sin opciones.
}
+ {opciones.map((o) => {
+ const on = seleccion.includes(o);
+ return (
+
+ );
+ })}
+
+ )}
+
+ );
+}