+ {/* Header móvil - siempre visible en móviles */}
+
+
+
EFC Dashboard
+ {/* Spacer para centrar el título */}
+
+
+ {/* Contenido */}
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/SIDEBAR_README.md b/src/components/SIDEBAR_README.md
new file mode 100644
index 0000000..d1320be
--- /dev/null
+++ b/src/components/SIDEBAR_README.md
@@ -0,0 +1,115 @@
+# Sidebar Responsivo - Instrucciones de Uso
+
+El sidebar ha sido actualizado para ser completamente responsivo. Aquí tienes las opciones de implementación:
+
+## Opción 1: Usar ResponsiveSidebar (Recomendado para nuevos proyectos)
+
+```jsx
+import ResponsiveSidebar from './components/ResponsiveSidebar';
+
+function App() {
+ return (
+
+
Contenido de tu aplicación
+
Todo tu contenido va aquí...
+
+ );
+}
+```
+
+**Características:**
+- Header fijo en móviles con botón de menú
+- Control completo del estado del sidebar
+- Diseño consistente en todas las pantallas
+
+## Opción 2: Sidebar Standalone (Para proyectos existentes)
+
+```jsx
+import Sidebar from './components/Sidebar';
+
+function App() {
+ return (
+
+ {/* ¡Ahora funciona automáticamente en móviles! */}
+
+ Tu contenido aquí...
+
+
+ );
+}
+```
+
+**Características:**
+- Botón flotante automático en móviles (solo cuando es necesario)
+- Funciona sin configuración adicional
+- Mantiene compatibilidad con código existente
+
+## Funcionalidades Responsivas
+
+### Desktop (≥1024px)
+- Sidebar fijo en el lado izquierdo
+- Botón de colapsar/expandir
+- Ancho: 256px (expandido) / 64px (colapsado)
+
+### Móvil (<1024px)
+
+#### Con ResponsiveSidebar:
+- Header fijo con botón de menú siempre visible
+- Sidebar se desliza desde la izquierda
+- Overlay oscuro de fondo
+
+#### Con Sidebar standalone:
+- Botón flotante elegante en esquina superior izquierda (solo cuando está cerrado)
+- Sidebar se desliza desde la izquierda al hacer clic
+- Se oculta automáticamente al navegar
+
+### Auto-cierre en móviles:
+- Al hacer clic en el overlay
+- Al navegar a otra página
+- Al redimensionar la ventana a desktop
+- Al hacer clic en el botón X
+
+## Componentes Exportados
+
+- `Sidebar`: Componente principal del sidebar
+- `MobileMenuButton`: Botón para abrir el menú móvil
+- `ResponsiveSidebar`: Wrapper completo con header móvil
+
+## Props del Sidebar
+
+```typescript
+interface SidebarProps {
+ isMobileOpen?: boolean; // Estado del menú móvil (opcional)
+ onMobileClose?: () => void; // Función para cerrar el menú móvil (opcional)
+}
+```
+
+**Nota:** Si no pasas estas props, el Sidebar manejará su propio estado automáticamente.
+
+## Migración de Código Existente
+
+### Si ya usas ``:
+✅ **No necesitas cambiar nada!** El sidebar ahora funciona automáticamente en móviles.
+
+### Si quieres el header móvil:
+```jsx
+// Cambia esto:
+
+
+ Contenido
+
+
+// Por esto:
+
+ Contenido
+
+```
+
+## Estilos y Diseño
+
+- **Botón flotante**: Diseño sutil que coincide con el tema del sidebar
+- **Backdrop blur**: Efecto de cristal esmerilado en el botón
+- **Transiciones suaves**: Animaciones consistentes
+- **Z-index apropiado**: Sin conflictos con otros elementos
+
+¡El sidebar es ahora completamente responsivo y funciona perfectamente en cualquier dispositivo! 📱💻
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 000d8ab..3a9436d 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -1,8 +1,8 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useUser } from '../context/UserContext';
-export default function Sidebar() {
+export default function Sidebar({ isMobileOpen, onMobileClose }) {
// Leer si el usuario es importador desde localStorage
const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true';
// Leer grupos del usuario desde localStorage
@@ -18,7 +18,15 @@ export default function Sidebar() {
const isGroup35 = Array.isArray(userGroups) && userGroups.length === 2 && userGroups.includes(3) && userGroups.includes(5);
// Leer DEBUG_MODE desde variables de entorno
const isDebugMode = import.meta.env.VITE_DEBUG_MODE === 'true';
+
+ // Estados para responsividad
const [isCollapsed, setIsCollapsed] = useState(false);
+ const [internalMobileOpen, setInternalMobileOpen] = useState(false);
+
+ // Usar estado interno si no se pasan props
+ const mobileOpen = isMobileOpen !== undefined ? isMobileOpen : internalMobileOpen;
+ const handleMobileClose = onMobileClose || (() => setInternalMobileOpen(false));
+ const handleMobileOpen = () => setInternalMobileOpen(true);
const location = useLocation();
const navigate = useNavigate();
const { user: currentUser, loading } = useUser();
@@ -30,6 +38,23 @@ export default function Sidebar() {
navigate('/login');
};
+ // Cerrar menú móvil cuando se navega o cuando la pantalla es grande
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth >= 1024) { // lg breakpoint
+ handleMobileClose();
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ // Cerrar menú móvil cuando cambia la ubicación
+ useEffect(() => {
+ handleMobileClose();
+ }, [location.pathname]);
+
// El usuario y loading ahora vienen del contexto global
// Definir todas las secciones
@@ -196,35 +221,79 @@ export default function Sidebar() {
.filter(Boolean);
return (
-
- {/* Header - Logo y colapsar */}
-
-
- {!isCollapsed && (
-
- {/* Logo de la organización */}
-
-
+ <>
+ {/* Botón flotante para abrir menú en móvil - solo cuando se usa standalone */}
+ {!mobileOpen && isMobileOpen === undefined && (
+
+ )}
+
+ {/* Overlay para móviles */}
+ {mobileOpen && (
+
+ )}
+
+ {/* Sidebar */}
+
+ {/* Header - Logo y colapsar */}
+
+
+ {!isCollapsed && (
+
+ {/* Logo de la organización */}
+
+
+
+
EFC Dashboard
-
EFC Dashboard
+ )}
+
+ {/* Botones de control */}
+
+ {/* Botón cerrar en móvil */}
+
+
+ {/* Botón colapsar en desktop */}
+
- )}
-
+
-
{/* Navigation */}
)}
-
+
+ >
+ );
+}
+
+// Hook personalizado para manejar el menú móvil desde otros componentes
+export function useMobileSidebar() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const toggle = () => setIsOpen(prev => !prev);
+ const close = () => setIsOpen(false);
+
+ return { isOpen, toggle, close };
+}
+
+// Componente botón para abrir menú móvil
+export function MobileMenuButton({ onClick }) {
+ return (
+
);
}
diff --git a/src/pages/Admin.jsx b/src/pages/Admin.jsx
index d5aa601..aa979f9 100644
--- a/src/pages/Admin.jsx
+++ b/src/pages/Admin.jsx
@@ -87,123 +87,124 @@ export default function Admin() {
}
return (
-
+
{/* Header + Estado del Sistema alineados horizontalmente */}
-