diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 5a0f9686..f323e560 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,5 +1,5 @@ import { Search, Menu, Wifi, WifiOff, Star, ArrowLeft } from 'lucide-react'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo, useCallback, memo } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { Button } from '@/components/ui/button'; @@ -8,12 +8,19 @@ interface HeaderProps { onMenuClick: () => void; } -const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => { +// Memoizar iconos para evitar re-renders +const MenuIcon = memo(() => ); +MenuIcon.displayName = 'MenuIcon'; + +const SearchIcon = memo(() => ); +SearchIcon.displayName = 'SearchIcon'; + +const Header = memo(({ onSearchClick, onMenuClick }: HeaderProps) => { const navigate = useNavigate(); const location = useLocation(); // Mostrar botón de retroceso si no estamos en la página principal - const showBackButton = location.pathname !== '/'; + const showBackButton = useMemo(() => location.pathname !== '/', [location.pathname]); const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { @@ -29,13 +36,36 @@ const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => { }; }, []); - const handleBack = () => { + const handleBack = useCallback(() => { if (window.history.length > 1) { navigate(-1); } else { navigate('/'); } - }; + }, [navigate]); + + // Memoizar handlers para evitar re-renders + const handleMenuClick = useCallback(() => { + // Usar requestAnimationFrame para no bloquear + requestAnimationFrame(() => { + onMenuClick(); + }); + }, [onMenuClick]); + + const handleSearchClick = useCallback(() => { + // Usar requestAnimationFrame para no bloquear + requestAnimationFrame(() => { + onSearchClick(); + }); + }, [onSearchClick]); + + // Memoizar clases del estado online + const onlineStatusClasses = useMemo(() => + isOnline + ? 'bg-success/20 text-success' + : 'bg-warning/20 text-warning', + [isOnline] + ); return (
@@ -83,24 +113,26 @@ const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => {
); -}; +}); + +Header.displayName = 'Header'; export default Header; diff --git a/src/components/layout/MenuSheet.tsx b/src/components/layout/MenuSheet.tsx index c7709490..3bbb0849 100644 --- a/src/components/layout/MenuSheet.tsx +++ b/src/components/layout/MenuSheet.tsx @@ -1,15 +1,23 @@ import { X, Star, History, Settings, Info, Share2, ClipboardCheck, Phone, MessageSquare, BookOpen } from 'lucide-react'; import { Link } from 'react-router-dom'; +import { useMemo, useCallback, memo } from 'react'; +import { toast } from 'sonner'; interface MenuSheetProps { isOpen: boolean; onClose: () => void; } -const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => { +// Memoizar iconos para evitar re-creación +const MenuIcon = memo(({ Icon, className }: { Icon: any; className?: string }) => ( + +)); +MenuIcon.displayName = 'MenuIcon'; + +const MenuSheet = memo(({ isOpen, onClose }: MenuSheetProps) => { if (!isOpen) return null; - const handleShare = () => { + const handleShare = useCallback(() => { // Usar setTimeout para no bloquear la UI setTimeout(async () => { const shareData = { @@ -45,19 +53,20 @@ const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => { } } }, 0); - }; + }, [onClose]); - const menuItems = [ - { icon: , label: 'Manual Completo', path: '/manual', onClick: onClose }, - { icon: , label: 'Protocolos Transtelefónicos', path: '/telefono', onClick: onClose }, - { icon: , label: 'Guiones de Comunicación', path: '/comunicacion', onClick: onClose }, - { icon: , label: 'Checklists Material', path: '/material', onClick: onClose }, - { icon: , label: 'Favoritos', path: '/favoritos', onClick: onClose }, - { icon: , label: 'Historial', path: '/historial', onClick: onClose }, - { icon: , label: 'Compartir App', onClick: handleShare }, - { icon: , label: 'Ajustes', path: '/ajustes', onClick: onClose }, - { icon: , label: 'Acerca de', path: '/acerca', onClick: onClose }, - ]; + // Memoizar menuItems para evitar re-creación en cada render + const menuItems = useMemo(() => [ + { icon: BookOpen, label: 'Manual Completo', path: '/manual', onClick: onClose }, + { icon: Phone, label: 'Protocolos Transtelefónicos', path: '/telefono', onClick: onClose }, + { icon: MessageSquare, label: 'Guiones de Comunicación', path: '/comunicacion', onClick: onClose }, + { icon: ClipboardCheck, label: 'Checklists Material', path: '/material', onClick: onClose }, + { icon: Star, label: 'Favoritos', path: '/favoritos', onClick: onClose }, + { icon: History, label: 'Historial', path: '/historial', onClick: onClose }, + { icon: Share2, label: 'Compartir App', onClick: handleShare }, + { icon: Settings, label: 'Ajustes', path: '/ajustes', onClick: onClose }, + { icon: Info, label: 'Acerca de', path: '/acerca', onClick: onClose }, + ], [onClose, handleShare]); return ( <> @@ -91,7 +100,14 @@ const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => { { + // Permitir navegación normal, pero ejecutar onClick de forma no bloqueante + if (item.onClick) { + requestAnimationFrame(() => { + item.onClick?.(); + }); + } + }} className="w-full flex items-center gap-4 p-4 rounded-xl hover:bg-muted transition-colors text-left" > {content}