544bbf38a9
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
35 lines
1.4 KiB
TypeScript
35 lines
1.4 KiB
TypeScript
import type { APIRoute } from 'astro';
|
|
|
|
const json = (body: unknown, status = 200) =>
|
|
new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
|
|
|
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
if (!locals.user) return json({ error: 'no autenticado' }, 401);
|
|
if (locals.profile?.rol !== 'admin') return json({ error: 'no autorizado' }, 403);
|
|
|
|
const id = Number(params.id);
|
|
if (!Number.isInteger(id) || id <= 0) return json({ error: 'id inválido' }, 400);
|
|
|
|
const prestamoQ = locals.supabase
|
|
.from('solicitudes')
|
|
.select(
|
|
'id, estado, fecha_solicitud, fecha_aprobacion, fecha_devolucion_estimada, fecha_devolucion_real, notas, maestro_responsable, alumno:profiles!alumno_id(id, nombre, email, matricula), items:solicitud_items(id, cantidad, descripcion, material:materiales(id, nombre, numero_inventario, trackeado_por_unidad), material_unidad:material_unidades(id, etiqueta))'
|
|
)
|
|
.eq('id', id)
|
|
.maybeSingle();
|
|
|
|
const logQ = locals.supabase
|
|
.from('audit_log')
|
|
.select('*')
|
|
.eq('solicitud_id', id)
|
|
.order('at', { ascending: false })
|
|
.limit(20);
|
|
|
|
const [{ data: prestamo, error: pErr }, { data: audit_log, error: lErr }] = await Promise.all([prestamoQ, logQ]);
|
|
|
|
if (pErr) return json({ error: pErr.message }, 500);
|
|
if (!prestamo) return json({ error: 'no encontrado' }, 404);
|
|
|
|
return json({ prestamo, audit_log: lErr ? [] : (audit_log ?? []) });
|
|
};
|