diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css index 9ff5a50..0a4737d 100644 --- a/src/components/Header/Header.css +++ b/src/components/Header/Header.css @@ -499,3 +499,30 @@ .profile-section { pointer-events: auto; } + +/* Garantir pointer-events nos elementos do header e overlays criados por portal */ +.header-container { pointer-events: auto; } +.phone-icon-container, .profile-section { pointer-events: auto; } + +/* Força que os overlays criados por portal fiquem por cima */ +.logout-modal-overlay, .suporte-card-overlay, .chat-overlay { + z-index: 110000 !important; + pointer-events: auto !important; +} + +/* Pequeno ajuste visual dos botões do modal (pode se misturar com seu CSS atual) */ +.logout-cancel-button { + padding: 10px 18px; + border-radius: 8px; + border: 1px solid #ccc; + background: white; + cursor: pointer; +} +.logout-confirm-button { + padding: 10px 18px; + border-radius: 8px; + border: none; + background: #dc3545; + color: #fff; + cursor: pointer; +} diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 45b4d56..18df67f 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -5,7 +5,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import './Header.css'; const Header = () => { - // --- Hooks (sempre chamados na mesma ordem) --- + // --- hooks (sempre na mesma ordem) --- const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isSuporteCardOpen, setIsSuporteCardOpen] = useState(false); const [isChatOpen, setIsChatOpen] = useState(false); @@ -13,61 +13,70 @@ const Header = () => { const [mensagens, setMensagens] = useState([]); const [showLogoutModal, setShowLogoutModal] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); + const navigate = useNavigate(); const location = useLocation(); + const chatInputRef = useRef(null); const mensagensContainerRef = useRef(null); - // --- Efeitos --- - useEffect(() => { - const loadAvatar = () => { - const localAvatar = localStorage.getItem('user_avatar'); - if (localAvatar) { - setAvatarUrl(localAvatar); - } - }; - - loadAvatar(); - - const handleStorageChange = () => { - loadAvatar(); - }; - - window.addEventListener('storage', handleStorageChange); - return () => window.removeEventListener('storage', handleStorageChange); - }, []); - + // foco quando abre chat useEffect(() => { if (isChatOpen && chatInputRef.current) { chatInputRef.current.focus(); } }, [isChatOpen]); + // scroll automático quando nova mensagem useEffect(() => { if (mensagensContainerRef.current) { mensagensContainerRef.current.scrollTop = mensagensContainerRef.current.scrollHeight; } }, [mensagens]); - // Fecha modal com ESC (útil para logout) + // carrega avatar se existir + useEffect(() => { + const loadAvatar = () => { + const localAvatar = localStorage.getItem('user_avatar'); + if (localAvatar) setAvatarUrl(localAvatar); + }; + loadAvatar(); + const onStorage = () => loadAvatar(); + window.addEventListener('storage', onStorage); + return () => window.removeEventListener('storage', onStorage); + }, []); + + // ESC fecha qualquer overlay/portal aberto (logout / suporte / chat) useEffect(() => { const onKey = (e) => { - if (e.key === 'Escape' && showLogoutModal) { - setShowLogoutModal(false); + if (e.key === 'Escape') { + if (showLogoutModal) setShowLogoutModal(false); + if (isSuporteCardOpen) setIsSuporteCardOpen(false); + if (isChatOpen) setIsChatOpen(false); } }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); - }, [showLogoutModal]); + }, [showLogoutModal, isSuporteCardOpen, isChatOpen]); - // --- Lógica e handlers --- + // --- handlers logout (mantive comportamento) --- const handleLogoutClick = () => { setShowLogoutModal(true); setIsDropdownOpen(false); }; - const handleLogoutCancel = () => { - setShowLogoutModal(false); + const clearAuthData = () => { + ["token","authToken","userToken","access_token","user","auth","userData"].forEach(key => { + localStorage.removeItem(key); + sessionStorage.removeItem(key); + }); + if (window.caches) { + caches.keys().then(names => { + names.forEach(name => { + if (name.includes("auth") || name.includes("api")) caches.delete(name); + }); + }).catch(()=>{}); + } }; const handleLogoutConfirm = async () => { @@ -81,51 +90,34 @@ const Header = () => { sessionStorage.getItem("authToken"); if (token) { - // tentativa de logout no backend (se houver) try { - await fetch( - "https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - } - ); + await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); } catch (err) { - // não interrompe o fluxo se a API falhar — prosseguimos para limpar local - console.warn('Erro ao chamar endpoint de logout (ignorado):', err); + // ignora erro de rede / endpoint — prossegue para limpar local + console.warn('logout endpoint error (ignored):', err); } } clearAuthData(); - navigate("/login"); - } catch (error) { - console.error("Erro durante logout:", error); + navigate('/login'); + } catch (err) { + console.error('Erro no logout:', err); clearAuthData(); - navigate("/login"); + navigate('/login'); } finally { setShowLogoutModal(false); } }; - const clearAuthData = () => { - ["token", "authToken", "userToken", "access_token", "user", "auth", "userData"].forEach(key => { - localStorage.removeItem(key); - sessionStorage.removeItem(key); - }); - - // tenta limpar caches relacionados se existirem - if (window.caches) { - caches.keys().then(names => { - names.forEach(name => { - if (name.includes("auth") || name.includes("api")) caches.delete(name); - }); - }).catch(()=>{ /* ignore */ }); - } - }; + const handleLogoutCancel = () => setShowLogoutModal(false); + // --- profile / suporte / chat handlers --- const handleProfileClick = () => { setIsDropdownOpen(!isDropdownOpen); if (isSuporteCardOpen) setIsSuporteCardOpen(false); @@ -138,14 +130,12 @@ const Header = () => { }; const handleSuporteClick = () => { - setIsSuporteCardOpen(!isSuporteCardOpen); - if (isDropdownOpen) setIsDropdownOpen(false); + setIsSuporteCardOpen((s) => !s); + setIsDropdownOpen(false); if (isChatOpen) setIsChatOpen(false); }; - const handleCloseSuporteCard = () => { - setIsSuporteCardOpen(false); - }; + const handleCloseSuporteCard = () => setIsSuporteCardOpen(false); const handleChatClick = () => { setIsChatOpen(true); @@ -153,7 +143,7 @@ const Header = () => { setMensagens([ { id: 1, - texto: 'Olá! Me chamo Ágatha e sou sua assistente virtual. 👋 Bem-vindo ao suporte Mediconnect. Como posso te ajudar hoje?', + texto: 'Olá! Bem-vindo ao suporte Mediconnect. Como podemos ajudar você hoje?', remetente: 'suporte', hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) } @@ -165,7 +155,7 @@ const Header = () => { setMensagem(''); }; - const handleEnviarMensagem = async (e) => { + const handleEnviarMensagem = (e) => { e.preventDefault(); if (mensagem.trim() === '') return; @@ -179,37 +169,30 @@ const Header = () => { setMensagens(prev => [...prev, novaMensagemUsuario]); setMensagem(''); - try { - const response = await fetch("http://localhost:5000/api/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: mensagem }), - }); - - const data = await response.json(); + setTimeout(() => { + if (chatInputRef.current) chatInputRef.current.focus(); + }, 0); + setTimeout(() => { + const respostas = [ + 'Entendi sua dúvida. Vou verificar isso para você.', + 'Obrigado pela informação. Estou analisando seu caso.', + 'Pode me dar mais detalhes sobre o problema?', + 'Já encaminhei sua solicitação para nossa equipe técnica.', + 'Vou ajudar você a resolver isso!' + ]; const respostaSuporte = { id: Date.now() + 1, - texto: data.resposta || data.reply || "Desculpe, não consegui processar sua pergunta no momento 😅", + texto: respostas[Math.floor(Math.random() * respostas.length)], remetente: 'suporte', hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) }; - setMensagens(prev => [...prev, respostaSuporte]); - } catch (error) { - console.error("Erro ao conectar com o servidor:", error); - const erroMsg = { - id: Date.now() + 1, - texto: "Ops! Ocorreu um erro ao tentar falar com o suporte.", - remetente: 'suporte', - hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) - }; - setMensagens(prev => [...prev, erroMsg]); - } + }, 900); }; - // --- Subcomponentes --- - const SuporteCard = () => ( + // --- subcomponentes (UI) --- + const SuporteCardContent = ({ onOpenChat }) => (

Suporte

Entre em contato conosco através dos canais abaixo

@@ -228,7 +211,7 @@ const Header = () => {
-
+
Chat Online
Disponível 24/7
@@ -237,11 +220,11 @@ const Header = () => {
); - const ChatOnline = () => ( -
+ const ChatOnlineContent = ({ mensagens, onSend, onClose }) => ( +

Chat de Suporte

- +
@@ -253,7 +236,7 @@ const Header = () => { ))}
-
+ {
); - // --- Modal de logout renderizado via Portal (garante top-most e clique) --- + // --- portals: Logout / Suporte / Chat (garante top-most e clickable) --- + const PortalWrapper = ({ children, z = 99999 }) => { + if (typeof document === 'undefined') return null; + return createPortal( +
+ {children} +
, + document.body + ); + }; + const LogoutModalPortal = ({ onCancel, onConfirm }) => { - const modalContent = ( + if (typeof document === 'undefined') return null; + return createPortal(
{ display: 'flex', justifyContent: 'center', alignItems: 'center', - zIndex: 99999, - padding: '1rem' + zIndex: 110000 }} - role="dialog" - aria-modal="true" + onClick={onCancel} >
{

Confirmar Logout

Tem certeza que deseja encerrar a sessão?

- - + +
-
+
, + document.body ); - - // garante que exista document antes de criar portal (SSRed apps podem não ter) - if (typeof document === 'undefined') return null; - return createPortal(modalContent, document.body); }; - // --- Agora sim: condicional de render baseado na rota --- + const SuportePortal = ({ onClose, children }) => { + if (typeof document === 'undefined') return null; + return createPortal( +
+
e.stopPropagation()} + > + {children} +
+
, + document.body + ); + }; + + const ChatPortal = ({ onClose, children }) => { + if (typeof document === 'undefined') return null; + return createPortal( +
+
e.stopPropagation()} + > + {children} +
+
, + document.body + ); + }; + + // --- evita render na rota de login (mantendo hooks invocados) --- if (location.pathname === '/login') { return null; } - // --- JSX principal --- + // --- JSX principal (header visual) --- return ( -
+
-
+
📞
-
-
+
+
{isDropdownOpen && ( -
- - +
e.stopPropagation()}> + +
)}
- {/* Modal de Logout via portal */} - {showLogoutModal && ( - - )} + {/* logout modal via portal */} + {showLogoutModal && } + {/* suporte portal */} {isSuporteCardOpen && ( -
-
e.stopPropagation()}> - -
-
+ + + )} + {/* chat portal */} {isChatOpen && ( -
-
- -
-
+ + + )}
);