UI: navegación SPA, tabs sin bordes, patrón de fondo fijo
- ClientRouter (view transitions) para navegación sin recarga entera; Toaster y BadgeSolicitudes marcados con transition:persist para no rearmar la suscripción realtime en cada nav. - Overlay de carga ahora activa a los 250ms del swap (evita parpadeo). - Tabs de subnav (inventario/categorías/maestros, solicitudes, reportes) reescritos: sin border-b-2 ni underline verde; activo usa bg-linen como el sidebar. Coherente y sin patrón AI-slop. - Marco horizontal debajo de todos los tab strips removido. - Patrón de puntos verde del canvas ahora con background-attachment: fixed — estático en scroll. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014xaxs8cGcQe9NwbMqA1gvn
This commit is contained in:
@@ -243,8 +243,8 @@ const activeCheck = (item: NavItem) => {
|
||||
</form>
|
||||
</nav>
|
||||
|
||||
<Toaster client:idle />
|
||||
{isAdmin && <BadgeSolicitudes client:idle />}
|
||||
<Toaster client:idle transition:persist />
|
||||
{isAdmin && <BadgeSolicitudes client:idle transition:persist />}
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
|
||||
+23
-20
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import { ClientRouter } from 'astro:transitions';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
@@ -18,6 +19,7 @@ const { title = 'Sistema de Préstamos — Laboratorio UABC' } = Astro.props;
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Mulish:ital,wght@0,200..1000;1,200..1000&display=swap" rel="stylesheet" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<ClientRouter />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -63,27 +65,28 @@ const { title = 'Sistema de Préstamos — Laboratorio UABC' } = Astro.props;
|
||||
</style>
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const overlay = document.getElementById('page-loader');
|
||||
if (!overlay) return;
|
||||
const show = () => { overlay.hidden = false; };
|
||||
const show = () => {
|
||||
const overlay = document.getElementById('page-loader');
|
||||
if (overlay) overlay.hidden = false;
|
||||
};
|
||||
const hide = () => {
|
||||
const overlay = document.getElementById('page-loader');
|
||||
if (overlay) overlay.hidden = true;
|
||||
};
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const a = (e.target instanceof Element) ? e.target.closest('a[href]') : null;
|
||||
if (!a) return;
|
||||
if (e.defaultPrevented) return;
|
||||
if (e.button !== 0) return;
|
||||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
|
||||
if (a.target && a.target !== '_self') return;
|
||||
const href = a.getAttribute('href') || '';
|
||||
if (!href || href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) return;
|
||||
try {
|
||||
const url = new URL(a.href, location.href);
|
||||
if (url.origin !== location.origin) return;
|
||||
if (url.pathname === location.pathname && url.search === location.search) return;
|
||||
} catch { return; }
|
||||
show();
|
||||
}, true);
|
||||
// Con ClientRouter, la navegación entre páginas dispara eventos propios.
|
||||
// La navegación es rápida (fetch + swap sin recargar) — mostramos el
|
||||
// overlay solo si el swap tarda >250ms para no parpadear en cambios rápidos.
|
||||
let pendingShow = null;
|
||||
document.addEventListener('astro:before-preparation', () => {
|
||||
pendingShow = setTimeout(show, 250);
|
||||
});
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
if (pendingShow) { clearTimeout(pendingShow); pendingShow = null; }
|
||||
hide();
|
||||
});
|
||||
|
||||
// Submits que hagan navegación real (no fetch client-side) sí ameritan overlay inmediato.
|
||||
document.addEventListener('submit', (e) => {
|
||||
if (e.defaultPrevented) return;
|
||||
const form = e.target;
|
||||
@@ -98,7 +101,7 @@ const { title = 'Sistema de Préstamos — Laboratorio UABC' } = Astro.props;
|
||||
show();
|
||||
}, true);
|
||||
|
||||
window.addEventListener('pageshow', () => { overlay.hidden = true; });
|
||||
window.addEventListener('pageshow', hide);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -30,23 +30,23 @@ const categorias = ((data ?? []) as unknown as CategoriaRow[]).map((c) => ({
|
||||
<CategoriaForm mode="create" client:load />
|
||||
</header>
|
||||
|
||||
<nav class="flex gap-2 mb-6 border-b border-hairline-gray" aria-label="Sub-navegación de inventario">
|
||||
<nav class="flex gap-2 mb-6" aria-label="Sub-navegación de inventario">
|
||||
<a
|
||||
href="/admin/inventario"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional"
|
||||
>
|
||||
Materiales
|
||||
</a>
|
||||
<a
|
||||
href="/admin/inventario/categorias"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-verde-institucional text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors bg-linen text-verde-institucional"
|
||||
aria-current="page"
|
||||
>
|
||||
Categorías
|
||||
</a>
|
||||
<a
|
||||
href="/admin/maestros"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional"
|
||||
>
|
||||
Maestros
|
||||
</a>
|
||||
|
||||
@@ -68,14 +68,14 @@ const estadoBadge = (e: Material['estado']) => {
|
||||
<MaterialForm mode="create" categorias={categorias} client:load />
|
||||
</header>
|
||||
|
||||
<nav class="flex gap-2 mb-6 border-b border-hairline-gray" aria-label="Sub-navegación de inventario">
|
||||
<a href="/admin/inventario" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-verde-institucional text-verde-institucional" aria-current="page">
|
||||
<nav class="flex gap-2 mb-6" aria-label="Sub-navegación de inventario">
|
||||
<a href="/admin/inventario" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors bg-linen text-verde-institucional" aria-current="page">
|
||||
Materiales
|
||||
</a>
|
||||
<a href="/admin/inventario/categorias" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional">
|
||||
<a href="/admin/inventario/categorias" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional">
|
||||
Categorías
|
||||
</a>
|
||||
<a href="/admin/maestros" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional">
|
||||
<a href="/admin/maestros" class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional">
|
||||
Maestros
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -34,22 +34,22 @@ const maestros = ((data ?? []) as unknown as MaestroRow[]).map((m) => ({
|
||||
<MaestroForm mode="create" client:load />
|
||||
</header>
|
||||
|
||||
<nav class="flex gap-2 mb-6 border-b border-hairline-gray" aria-label="Sub-navegación de inventario">
|
||||
<nav class="flex gap-2 mb-6" aria-label="Sub-navegación de inventario">
|
||||
<a
|
||||
href="/admin/inventario"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional"
|
||||
>
|
||||
Materiales
|
||||
</a>
|
||||
<a
|
||||
href="/admin/inventario/categorias"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-transparent text-graphite hover:text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors text-graphite hover:bg-linen hover:text-verde-institucional"
|
||||
>
|
||||
Categorías
|
||||
</a>
|
||||
<a
|
||||
href="/admin/maestros"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors border-b-2 -mb-px border-verde-institucional text-verde-institucional"
|
||||
class="px-4 py-2 text-sm font-medium rounded-[8px] transition-colors bg-linen text-verde-institucional"
|
||||
aria-current="page"
|
||||
>
|
||||
Maestros
|
||||
|
||||
@@ -132,8 +132,8 @@ const exportFilename = `reporte-${today}.csv`;
|
||||
<p class="text-sm opacity-70 mt-1">Historial filtrable de préstamos y lista de vencidos.</p>
|
||||
</header>
|
||||
|
||||
<nav class="mb-6 border-b border-hairline-gray" aria-label="Vistas de reportes">
|
||||
<ul class="flex gap-2 -mb-px">
|
||||
<nav class="mb-6" aria-label="Vistas de reportes">
|
||||
<ul class="flex gap-2">
|
||||
<li>
|
||||
<a
|
||||
href="?vista=historial"
|
||||
@@ -141,8 +141,8 @@ const exportFilename = `reporte-${today}.csv`;
|
||||
class:list={[
|
||||
'inline-block px-4 py-2 text-sm font-medium rounded-[8px] transition-colors',
|
||||
vista === 'historial'
|
||||
? 'text-verde-institucional border-b-2 border-verde-institucional'
|
||||
: 'text-graphite hover:text-verde-institucional border-b-2 border-transparent',
|
||||
? 'bg-linen text-verde-institucional'
|
||||
: 'text-graphite hover:bg-linen hover:text-verde-institucional',
|
||||
]}
|
||||
>Historial</a>
|
||||
</li>
|
||||
@@ -153,8 +153,8 @@ const exportFilename = `reporte-${today}.csv`;
|
||||
class:list={[
|
||||
'inline-block px-4 py-2 text-sm font-medium rounded-[8px] transition-colors',
|
||||
vista === 'vencidos'
|
||||
? 'text-verde-institucional border-b-2 border-verde-institucional'
|
||||
: 'text-graphite hover:text-verde-institucional border-b-2 border-transparent',
|
||||
? 'bg-linen text-verde-institucional'
|
||||
: 'text-graphite hover:bg-linen hover:text-verde-institucional',
|
||||
]}
|
||||
>Vencidos</a>
|
||||
</li>
|
||||
|
||||
@@ -44,15 +44,15 @@ const tabs = [
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 border-b border-hairline-gray flex gap-2 overflow-x-auto">
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 flex gap-2 overflow-x-auto">
|
||||
{tabs.map((t) => {
|
||||
const active = path === t.href;
|
||||
return (
|
||||
<a
|
||||
href={t.href}
|
||||
class:list={[
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b -mb-px',
|
||||
active ? 'border-verde-institucional text-verde-institucional' : 'border-transparent text-graphite/70 hover:text-verde-institucional'
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors rounded-[8px]',
|
||||
active ? 'bg-linen text-verde-institucional' : 'text-graphite/70 hover:bg-linen hover:text-verde-institucional'
|
||||
]}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
|
||||
@@ -55,15 +55,15 @@ const chipPorEstado: Record<string, string> = {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 border-b border-hairline-gray flex gap-2 overflow-x-auto">
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 flex gap-2 overflow-x-auto">
|
||||
{tabs.map((t) => {
|
||||
const active = path === t.href;
|
||||
return (
|
||||
<a
|
||||
href={t.href}
|
||||
class:list={[
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b -mb-px',
|
||||
active ? 'border-verde-institucional text-verde-institucional' : 'border-transparent text-graphite/70 hover:text-verde-institucional'
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors rounded-[8px]',
|
||||
active ? 'bg-linen text-verde-institucional' : 'text-graphite/70 hover:bg-linen hover:text-verde-institucional'
|
||||
]}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
|
||||
@@ -36,15 +36,15 @@ const tabs = [
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 border-b border-hairline-gray flex gap-2 overflow-x-auto">
|
||||
<nav aria-label="Vistas de solicitudes" class="mb-6 flex gap-2 overflow-x-auto">
|
||||
{tabs.map((t) => {
|
||||
const active = path === t.href || (t.href === '/admin/solicitudes' && path === '/admin/solicitudes/');
|
||||
return (
|
||||
<a
|
||||
href={t.href}
|
||||
class:list={[
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b -mb-px',
|
||||
active ? 'border-verde-institucional text-verde-institucional' : 'border-transparent text-graphite/70 hover:text-verde-institucional'
|
||||
'px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors rounded-[8px]',
|
||||
active ? 'bg-linen text-verde-institucional' : 'text-graphite/70 hover:bg-linen hover:text-verde-institucional'
|
||||
]}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
background-image: radial-gradient(circle, color-mix(in oklab, var(--color-verde-institucional) 12%, transparent) 1.5px, transparent 1.5px);
|
||||
background-size: 22px 22px;
|
||||
background-position: 0 0;
|
||||
background-attachment: fixed;
|
||||
color: var(--color-graphite);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
|
||||
Reference in New Issue
Block a user