Se mejoro estetica y estandarizaron estilos

This commit is contained in:
2025-08-05 10:30:25 -06:00
parent c3d800ba48
commit aa515c1d01
12 changed files with 3530 additions and 1611 deletions

View File

@@ -0,0 +1,34 @@
import React, { useState } from 'react';
import Sidebar, { MobileMenuButton } from './Sidebar';
export default function ResponsiveSidebar({ children }) {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const toggleMobileMenu = () => setIsMobileMenuOpen(prev => !prev);
const closeMobileMenu = () => setIsMobileMenuOpen(false);
return (
<div className="flex h-screen bg-gray-100">
{/* Sidebar */}
<Sidebar
isMobileOpen={isMobileMenuOpen}
onMobileClose={closeMobileMenu}
/>
{/* Contenido principal */}
<div className="flex-1 flex flex-col min-w-0">
{/* Header móvil - siempre visible en móviles */}
<header className="lg:hidden bg-white shadow-sm border-b border-gray-200 px-4 py-3 flex items-center justify-between sticky top-0 z-20">
<MobileMenuButton onClick={toggleMobileMenu} />
<h1 className="text-lg font-semibold text-gray-900">EFC Dashboard</h1>
<div className="w-8"></div> {/* Spacer para centrar el título */}
</header>
{/* Contenido */}
<main className="flex-1 overflow-y-auto p-4 lg:p-6">
{children}
</main>
</div>
</div>
);
}

View File

@@ -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 (
<ResponsiveSidebar>
<h1>Contenido de tu aplicación</h1>
<p>Todo tu contenido va aquí...</p>
</ResponsiveSidebar>
);
}
```
**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 (
<div className="flex h-screen">
<Sidebar /> {/* ¡Ahora funciona automáticamente en móviles! */}
<main className="flex-1 p-4">
Tu contenido aquí...
</main>
</div>
);
}
```
**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 `<Sidebar />`:
**No necesitas cambiar nada!** El sidebar ahora funciona automáticamente en móviles.
### Si quieres el header móvil:
```jsx
// Cambia esto:
<div className="flex">
<Sidebar />
<main>Contenido</main>
</div>
// Por esto:
<ResponsiveSidebar>
Contenido
</ResponsiveSidebar>
```
## 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! 📱💻

View File

@@ -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 (
<div className={`bg-slate-900 text-white transition-all duration-300 ${isCollapsed ? 'w-16' : 'w-64'} h-screen flex flex-col shadow-xl`}>
{/* Header - Logo y colapsar */}
<div className="p-4 border-b border-slate-700 flex-shrink-0">
<div className="flex items-center justify-between">
{!isCollapsed && (
<div className="flex items-center">
{/* Logo de la organización */}
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center mr-3 shadow-lg">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<>
{/* Botón flotante para abrir menú en móvil - solo cuando se usa standalone */}
{!mobileOpen && isMobileOpen === undefined && (
<button
onClick={handleMobileOpen}
className="lg:hidden fixed top-4 left-4 z-30 p-2.5 bg-slate-900/95 backdrop-blur-sm text-white rounded-xl shadow-lg hover:bg-slate-800/95 transition-all duration-200 border border-slate-700/50"
aria-label="Abrir menú"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
)}
{/* Overlay para móviles */}
{mobileOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
onClick={handleMobileClose}
/>
)}
{/* Sidebar */}
<div className={`
bg-slate-900 text-white transition-all duration-300 flex flex-col shadow-xl
${isCollapsed ? 'w-16' : 'w-64'}
fixed lg:relative inset-y-0 left-0 z-50
${mobileOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
h-screen
`}>
{/* Header - Logo y colapsar */}
<div className="p-4 border-b border-slate-700 flex-shrink-0">
<div className="flex items-center justify-between">
{!isCollapsed && (
<div className="flex items-center">
{/* Logo de la organización */}
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center mr-3 shadow-lg">
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<h1 className="text-lg font-bold text-white">EFC Dashboard</h1>
</div>
<h1 className="text-lg font-bold text-white">EFC Dashboard</h1>
)}
{/* Botones de control */}
<div className="flex items-center space-x-2">
{/* Botón cerrar en móvil */}
<button
onClick={handleMobileClose}
className="lg:hidden p-1.5 rounded-lg hover:bg-slate-700 transition-all duration-200"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Botón colapsar en desktop */}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="hidden lg:block p-1.5 rounded-lg hover:bg-slate-700 transition-all duration-200 hover:shadow-md"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{isCollapsed ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
)}
</svg>
</button>
</div>
)}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="p-1.5 rounded-lg hover:bg-slate-700 transition-all duration-200 hover:shadow-md"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{isCollapsed ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
)}
</svg>
</button>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 p-3 space-y-4 overflow-y-auto overflow-x-hidden">
@@ -404,6 +473,32 @@ export default function Sidebar() {
</div>
)}
</div>
</div>
</div>
</>
);
}
// 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 (
<button
onClick={onClick}
className="lg:hidden p-2 rounded-lg text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-colors duration-200"
aria-label="Abrir menú"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
);
}