Sistema de préstamos LabRe UABC — implementación inicial
Sistema web para gestión de préstamos de material del Laboratorio de Sistemas Computacionales de la UABC. - Backend: Supabase self-hosted, schema aislado `prestamos` con RLS, triggers de stock y audit log (supabase/migrations/0001_init.sql). - Auth: Google OAuth restringido a @uabc.edu.mx, verificado en middleware y como segunda línea en trigger de DB. - Frontend: Astro 7 (SSR con adapter Node) + React islands + Tailwind v4 con paleta UABC (primary #00723F, secondary #DD971A) bajo regla 60/30/10. - Interfaz alumno mobile-first: catálogo con filtro por categorías, solicitud de préstamos, historial personal. - Interfaz admin desktop-first: panel con KPIs, bandeja de solicitudes (aprobar/rechazar/devolver), CRUD de inventario y categorías, reportes filtrables con export CSV nativo. - Modales con `<dialog>` nativo, cero librerías de UI adicionales. - Deploy: Dockerfile multi-stage node:22-alpine + docker-compose para publicar bajo prestamos.buglabs.dev vía Cloudflare Tunnel. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
---
|
||||
import Layout from './Layout.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
}
|
||||
const { title } = Astro.props;
|
||||
|
||||
const profile = Astro.locals.profile;
|
||||
const isAdmin = profile?.rol === 'admin';
|
||||
const path = Astro.url.pathname;
|
||||
const isActive = (href: string) => path === href || path.startsWith(href + '/');
|
||||
---
|
||||
<Layout title={title}>
|
||||
<div class="min-h-screen flex flex-col md:flex-row">
|
||||
<!-- Sidebar (desktop) / topbar (mobile) -->
|
||||
<aside class="md:w-64 md:min-h-screen bg-[color:var(--color-primary)] text-white flex md:flex-col">
|
||||
<a href="/" class="p-4 md:p-6 flex items-center gap-3 md:border-b border-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white" aria-label="LabPréstamos — Inicio">
|
||||
<div class="w-9 h-9 rounded-lg bg-[color:var(--color-secondary)] grid place-items-center font-bold" aria-hidden="true">L</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="font-semibold leading-tight">LabPréstamos</div>
|
||||
<div class="text-xs text-white/70">UABC · Lab. Sistemas</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<nav class="flex-1 flex md:flex-col md:p-3 gap-1 overflow-x-auto md:overflow-visible" aria-label="Navegación principal">
|
||||
{isAdmin ? (
|
||||
<>
|
||||
<a href="/admin" class:list={["nav-item", isActive('/admin') && !path.startsWith('/admin/') && "nav-item-active"]}>Panel</a>
|
||||
<a href="/admin/solicitudes" class:list={["nav-item", isActive('/admin/solicitudes') && "nav-item-active"]}>Solicitudes</a>
|
||||
<a href="/admin/inventario" class:list={["nav-item", isActive('/admin/inventario') && "nav-item-active"]}>Inventario</a>
|
||||
<a href="/admin/reportes" class:list={["nav-item", isActive('/admin/reportes') && "nav-item-active"]}>Reportes</a>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<a href="/" class:list={["nav-item", path === '/' && "nav-item-active"]}>Inicio</a>
|
||||
<a href="/alumno/catalogo" class:list={["nav-item", isActive('/alumno/catalogo') && "nav-item-active"]}>Catálogo</a>
|
||||
<a href="/alumno/mis-prestamos" class:list={["nav-item", isActive('/alumno/mis-prestamos') && "nav-item-active"]}>Mis préstamos</a>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div class="hidden md:block p-3 border-t border-white/10">
|
||||
<div class="px-3 py-2 text-sm">
|
||||
<div class="font-medium truncate">{profile?.nombre ?? profile?.email}</div>
|
||||
<div class="text-white/70 text-xs capitalize">{profile?.rol}</div>
|
||||
</div>
|
||||
<form method="POST" action="/api/auth/signout">
|
||||
<button type="submit" class="w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-white/10 transition-colors">
|
||||
Cerrar sesión
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Mobile signout -->
|
||||
<form method="POST" action="/api/auth/signout" class="md:hidden ml-auto p-2">
|
||||
<button type="submit" aria-label="Cerrar sesión" class="p-2 rounded-lg hover:bg-white/10 transition-colors">
|
||||
<span aria-hidden="true">↩</span>
|
||||
</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<main id="main" class="flex-1 p-4 md:p-8">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
@reference "tailwindcss";
|
||||
.nav-item {
|
||||
@apply block px-3 py-2 md:py-2.5 rounded-lg text-sm font-medium text-white/85
|
||||
whitespace-nowrap transition-colors duration-150
|
||||
hover:bg-white/10 hover:text-white
|
||||
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white;
|
||||
}
|
||||
.nav-item-active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
+25
-22
@@ -1,23 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro Basics</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
interface Props {
|
||||
title?: string;
|
||||
}
|
||||
const { title = 'Sistema de Préstamos — Laboratorio UABC' } = Astro.props;
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#00723F" />
|
||||
<meta name="color-scheme" content="light" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="#main" class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 btn btn-primary z-50">
|
||||
Saltar al contenido
|
||||
</a>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user