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:
2026-08-22 18:03:38 -07:00
parent 43959056f8
commit 544bbf38a9
23 changed files with 1876 additions and 247 deletions
+10 -5
View File
@@ -2,6 +2,9 @@
import AppLayout from '@/layouts/AppLayout.astro';
import CartProvider from '@/components/alumno/SolicitudCart.tsx';
import FiltroCategorias from '@/components/alumno/FiltroCategorias.tsx';
import BuscadorCatalogo from '@/components/alumno/BuscadorCatalogo.tsx';
const qInitial = Astro.url.searchParams.get('q') ?? '';
type Material = {
id: number;
@@ -10,12 +13,13 @@ type Material = {
cantidad_disponible: number;
cantidad_total: number;
numero_inventario: string | null;
imagen_path: string | null;
categoria: { id: number; nombre: string } | null;
};
const { data, error } = await Astro.locals.supabase
.from('materiales')
.select('id, nombre, descripcion, cantidad_disponible, cantidad_total, numero_inventario, categoria:categorias(id, nombre)')
.select('id, nombre, descripcion, cantidad_disponible, cantidad_total, numero_inventario, imagen_path, categoria:categorias(id, nombre)')
.eq('estado', 'disponible')
.order('nombre');
@@ -57,11 +61,12 @@ const categoriasFiltro = grupoList
{materiales.length > 0 && (
<>
{categoriasFiltro.length > 0 && (
<div class="mb-6">
<div class="mb-6 flex flex-col gap-3">
<BuscadorCatalogo q={qInitial} client:load />
{categoriasFiltro.length > 0 && (
<FiltroCategorias categorias={categoriasFiltro} client:idle />
</div>
)}
)}
</div>
<p id="catalogo-empty-filter" hidden class="text-sm opacity-70 mb-4">
Ningún material en esta categoría. Prueba con otra.