From f802a83773ad0ae1aefd9fad4ba993529de06cb7 Mon Sep 17 00:00:00 2001 From: chattie Date: Sat, 22 Aug 2026 18:02:11 +0200 Subject: [PATCH] feat(F-159): completed feature --- backlog/features.json | 77 +++++++ project/apps/admin/next-env.d.ts | 4 +- .../apps/admin/src/app/(dashboard)/layout.tsx | 201 ++++++++++++++---- work/artifacts/F-159/architect.md | 24 +++ work/artifacts/F-159/documenter.md | 5 + work/artifacts/F-159/implementer.md | 20 ++ work/artifacts/F-159/leader-close.json | 14 ++ work/artifacts/F-159/qa.json | 15 ++ work/artifacts/F-159/reviewer.json | 14 ++ work/artifacts/F-159/security.json | 13 ++ work/current.md | 32 +-- work/runtime-status.json | 69 +++++- 12 files changed, 427 insertions(+), 61 deletions(-) create mode 100644 work/artifacts/F-159/architect.md create mode 100644 work/artifacts/F-159/documenter.md create mode 100644 work/artifacts/F-159/implementer.md create mode 100644 work/artifacts/F-159/leader-close.json create mode 100644 work/artifacts/F-159/qa.json create mode 100644 work/artifacts/F-159/reviewer.json create mode 100644 work/artifacts/F-159/security.json diff --git a/backlog/features.json b/backlog/features.json index cfc34af..9cd5efd 100644 --- a/backlog/features.json +++ b/backlog/features.json @@ -6709,6 +6709,83 @@ "close": true }, "completed_at": "2026-08-22T15:50:54Z" + }, + { + "id": "F-159", + "type": "feature", + "title": "Admin responsive collapsible sidebar", + "description": "Add accessible persisted SidebarToggle: desktop icon-only collapse and mobile/tablet navigation drawer without duplicated navigation logic.", + "priority": "high", + "risk": "med", + "status": "done", + "created_at": "2026-08-22", + "gates": { + "reviewer": true, + "security": true, + "qa": true, + "close": true + }, + "completed_at": "2026-08-22T16:02:11Z" + }, + { + "id": "F-160", + "type": "fix", + "title": "Fix Reporting sales grouped queries returning 500", + "description": "Diagnose and correct SQL/runtime failures for day, channel, store and terminal grouped reporting sales endpoints.", + "priority": "high", + "risk": "high", + "status": "pending", + "created_at": "2026-08-22", + "gates": { + "reviewer": false, + "security": false, + "qa": false + } + }, + { + "id": "F-161", + "type": "fix", + "title": "Complete admin order detail information", + "description": "Order detail must show shipping address, payment method, customer details and correct customer email without false missing-email warning.", + "priority": "high", + "risk": "med", + "status": "pending", + "created_at": "2026-08-22", + "gates": { + "reviewer": false, + "security": false, + "qa": false + } + }, + { + "id": "F-162", + "type": "fix", + "title": "Add storefront product link to inventory", + "description": "Add final Tienda column to inventory, matching product list, linking to storefront product page.", + "priority": "med", + "risk": "low", + "status": "pending", + "created_at": "2026-08-22", + "gates": { + "reviewer": false, + "security": false, + "qa": false + } + }, + { + "id": "F-163", + "type": "feature", + "title": "Make POS terminal and cash session setup usable", + "description": "Clarify and expose terminal binding and cash session workflow so operator can configure and open TPV without manual database/API steps.", + "priority": "high", + "risk": "med", + "status": "pending", + "created_at": "2026-08-22", + "gates": { + "reviewer": false, + "security": false, + "qa": false + } } ] } diff --git a/project/apps/admin/next-env.d.ts b/project/apps/admin/next-env.d.ts index a419cbe..ce4e94a 100644 --- a/project/apps/admin/next-env.d.ts +++ b/project/apps/admin/next-env.d.ts @@ -1,7 +1,7 @@ /// /// -import "./.next/dev/types/routes.d.ts"; -import "./.next/dev/types/root-params.d.ts"; +import "./.next/types/routes.d.ts"; +import "./.next/types/root-params.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/project/apps/admin/src/app/(dashboard)/layout.tsx b/project/apps/admin/src/app/(dashboard)/layout.tsx index 64192ae..483c8b9 100644 --- a/project/apps/admin/src/app/(dashboard)/layout.tsx +++ b/project/apps/admin/src/app/(dashboard)/layout.tsx @@ -1,32 +1,74 @@ 'use client'; -import { useEffect } from 'react'; -import { usePathname, useRouter } from 'next/navigation'; + +import { useEffect, useState } from 'react'; import Link from 'next/link'; +import { usePathname, useRouter } from 'next/navigation'; import { AuthProvider, useAuth } from '@/features/auth/components/AuthProvider'; import { visibleNavItems, type NavItem } from '@/lib/permissions'; import type { Role } from '@/types'; -function NavItemRow({ item }: { item: NavItem }) { +const SIDEBAR_STORAGE_KEY = 'mdv.admin.sidebar.collapsed'; +const SIDEBAR_ID = 'admin-sidebar'; + +interface SidebarToggleProps { + expanded: boolean; + onToggle: () => void; + mode: 'desktop' | 'mobile'; +} + +function SidebarToggle({ expanded, onToggle, mode }: SidebarToggleProps) { + const label = mode === 'mobile' + ? expanded ? 'Cerrar menú de navegación' : 'Abrir menú de navegación' + : expanded ? 'Colapsar menú lateral' : 'Expandir menú lateral'; + + return ( + + ); +} + +function NavItemRow({ item, collapsed, onNavigate }: { item: NavItem; collapsed: boolean; onNavigate: () => void }) { const pathname = usePathname(); - const active = item.href === '/' - ? pathname === '/' - : pathname.startsWith(item.href); + const active = item.href === '/' ? pathname === '/' : pathname.startsWith(item.href); return ( - {item.icon} - {item.label} + + {item.label} {item.badge != null && item.badge > 0 && ( - + {item.badge} )} @@ -34,51 +76,107 @@ function NavItemRow({ item }: { item: NavItem }) { ); } -function Sidebar({ role, email }: { role: Role; email: string }) { +interface SidebarProps { + role: Role; + email: string; + collapsed: boolean; + mobileOpen: boolean; + onCloseMobile: () => void; + onLogout: () => void; +} + +function Sidebar({ role, email, collapsed, mobileOpen, onCloseMobile, onLogout }: SidebarProps) { const items = visibleNavItems(role); return ( -
- {/* Logo */} -
+ ); } function DashboardShell({ children }: { children: React.ReactNode }) { const { user, loading, logout } = useAuth(); const router = useRouter(); + const pathname = usePathname(); + const [desktopCollapsed, setDesktopCollapsed] = useState(false); + const [mobileOpen, setMobileOpen] = useState(false); useEffect(() => { - if (!loading && !user) { - router.push('/login'); - } + if (!loading && !user) router.push('/login'); }, [user, loading, router]); + useEffect(() => { + setDesktopCollapsed(localStorage.getItem(SIDEBAR_STORAGE_KEY) === 'true'); + }, []); + + useEffect(() => { + setMobileOpen(false); + }, [pathname]); + + useEffect(() => { + if (!mobileOpen) return; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') setMobileOpen(false); + }; + document.addEventListener('keydown', onKeyDown); + return () => document.removeEventListener('keydown', onKeyDown); + }, [mobileOpen]); + + const toggleDesktop = () => { + setDesktopCollapsed((current) => { + const next = !current; + localStorage.setItem(SIDEBAR_STORAGE_KEY, String(next)); + return next; + }); + }; + if (loading) { return ( -
+
Cargando...
); @@ -88,12 +186,35 @@ function DashboardShell({ children }: { children: React.ReactNode }) { return (
- -
-
- {children} -
-
+ {mobileOpen && ( +
); } diff --git a/work/artifacts/F-159/architect.md b/work/artifacts/F-159/architect.md new file mode 100644 index 0000000..c56a5e8 --- /dev/null +++ b/work/artifacts/F-159/architect.md @@ -0,0 +1,24 @@ +# F-159 — Diseño + +## Arquitectura +El estado vive en `DashboardShell`, única composición del layout autenticado: + +- `desktopCollapsed`: preferencia persistida en `localStorage` bajo `mdv.admin.sidebar.collapsed`. +- `mobileOpen`: estado efímero del drawer. + +`Sidebar` y `NavItemRow` siguen consumiendo la misma lista `visibleNavItems(role)`. No se crean menús alternativos. + +## Responsive +- Sidebar base: drawer `fixed`, 240px y desplazado fuera de viewport cuando está cerrado. +- `lg+`: pasa a `sticky`, siempre visible, ancho 240px/80px según preferencia. +- Etiquetas usan `lg:hidden` solo durante collapse, por lo que el drawer móvil siempre conserva texto. +- Backdrop solo se renderiza con drawer abierto. + +## Accesibilidad +- Toggle es `