5 features: buscador catálogo, grid+fotos+unidades, estadísticas separada, badge realtime
Buscador catálogo alumno: - BuscadorCatalogo.tsx con debounce 120ms, normaliza tildes, ?q= en URL - FiltroCategorias usa data-* attributes, coordinación via window.__labreFiltrar - Cards con data-nombre y data-numero-inventario para filtro combinable AND Grid + imágenes + unidades individuales (admin): - Inventario ahora es grid 2/3/4/5 col con fotos aspect-square - MaterialForm: input file con preview, checkbox trackeado_por_unidad - UnidadesManager: agregar/editar/eliminar unidades por material - Reasignar unidad desde detalles de solicitud (aprobada/activa) - Alumno ve etiqueta de unidad asignada en mis-préstamos - Helper materialImg.imgUrl(path) para URL pública del bucket - Auto-submit debounced del filtro de inventario (input + selects) Estadísticas separada: - Nueva ruta /admin/estadisticas con 6 KPIs (movidos de /admin/inventario) - 3 gráficas recharts: top 10 materiales, solicitudes/día, distribución estados - Chart.tsx wrapper con paleta UABC + respeta prefers-reduced-motion - Nav admin: Panel · Solicitudes · Inventario (box) · Estadísticas (stats) · Reportes - admin/index.astro usa todayMX() (fix inconsistencia) Notificaciones pendientes (admin): - Badge SSR con conteo de pendientes sobre ícono Solicitudes (sidebar + dock) - BadgeSolicitudes.tsx: canal Realtime en prestamos.solicitudes, +1 en INSERT con toast, refetch en UPDATE (replica identity default no trae old.estado) - Recharts añadido a package.json
This commit is contained in:
@@ -10,12 +10,21 @@ function applyFilter(value: string) {
|
||||
cards.forEach((el) => {
|
||||
const cat = el.dataset.cat;
|
||||
const show = value === ALL || cat === value;
|
||||
el.hidden = !show;
|
||||
if (show) delete el.dataset.hiddenByCat;
|
||||
else el.dataset.hiddenByCat = 'true';
|
||||
});
|
||||
const empty = document.getElementById('catalogo-empty-filter');
|
||||
if (empty) {
|
||||
const anyVisible = Array.from(cards).some((el) => !el.hidden);
|
||||
empty.hidden = anyVisible || cards.length === 0;
|
||||
// El buscador (BuscadorCatalogo, client:load) define este helper y unifica
|
||||
// ambos flags (cat + search). Fallback si aún no montó: aplicar directo.
|
||||
if (typeof window !== 'undefined' && window.__labreFiltrar) {
|
||||
window.__labreFiltrar();
|
||||
} else {
|
||||
let anyVisible = false;
|
||||
cards.forEach((el) => {
|
||||
el.hidden = el.dataset.hiddenByCat === 'true';
|
||||
if (!el.hidden) anyVisible = true;
|
||||
});
|
||||
const empty = document.getElementById('catalogo-empty-filter');
|
||||
if (empty) empty.hidden = anyVisible || cards.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +71,8 @@ export default function FiltroCategorias({ categorias }: Props) {
|
||||
aria-selected={active === ALL}
|
||||
className={chipClass(ALL)}
|
||||
style={chipStyle(ALL)}
|
||||
data-cat-value={ALL}
|
||||
data-cat-active={active === ALL ? 'true' : undefined}
|
||||
onClick={() => pick(ALL)}
|
||||
>
|
||||
Todas
|
||||
@@ -76,6 +87,8 @@ export default function FiltroCategorias({ categorias }: Props) {
|
||||
aria-selected={active === v}
|
||||
className={chipClass(v)}
|
||||
style={chipStyle(v)}
|
||||
data-cat-value={v}
|
||||
data-cat-active={active === v ? 'true' : undefined}
|
||||
onClick={() => pick(v)}
|
||||
>
|
||||
{c.nombre}
|
||||
|
||||
Reference in New Issue
Block a user