From 57f8024bb2af8abab183e6acb0587ca31c191239 Mon Sep 17 00:00:00 2001 From: Caio Miguel Lima Nunes Date: Wed, 26 Nov 2025 22:15:58 -0300 Subject: [PATCH] =?UTF-8?q?Minhas=20altera=C3=A7=C3=B5es=20nos=20detalhes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/PagesAdm/gestao.css | 242 +++++++---- src/PagesAdm/gestao.jsx | 28 +- src/components/Header/Header.css | 159 ++----- src/components/Header/Header.jsx | 710 ++++++++++--------------------- src/data/sidebar-items-adm.json | 5 + 5 files changed, 442 insertions(+), 702 deletions(-) diff --git a/src/PagesAdm/gestao.css b/src/PagesAdm/gestao.css index 5991041..9951eae 100644 --- a/src/PagesAdm/gestao.css +++ b/src/PagesAdm/gestao.css @@ -1,4 +1,3 @@ - .dashboard-container { padding: 2rem; font-family: 'Arial', sans-serif; @@ -6,7 +5,6 @@ min-height: 100vh; } - .dashboard-header { display: flex; justify-content: space-between; @@ -34,143 +32,133 @@ border: none; border-radius: 8px; font-size: 1rem; + font-weight: 600; cursor: pointer; - transition: background-color 0.3s, transform 0.25s ease, box-shadow 0.25s ease; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(30, 58, 138, 0.3); } .new-user-btn:hover { background-color: #162d6b; transform: translateY(-2px); - box-shadow: 0px 4px 12px rgba(30, 58, 138, 0.3); + box-shadow: 0 4px 12px rgba(30, 58, 138, 0.4); } - .filters-container { background: #fff; border-radius: 12px; - padding: 1.5rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 1.2rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.filters-container:hover { - transform: translateY(-3px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } .filters-title { - font-size: 18px; + font-size: 16px; font-weight: bold; margin-bottom: 0.3rem; color: #333; } .filters-subtitle { - font-size: 0.9rem; + font-size: 0.85rem; color: #666; margin-bottom: 1rem; } .filters-content { display: flex; - gap: 1rem; + gap: 0.8rem; align-items: center; } .filters-input { flex: 1; - padding: 0.6rem 1rem; + padding: 0.5rem 0.8rem; border: 1px solid #d1d5db; - border-radius: 8px; - font-size: 0.95rem; + border-radius: 6px; + font-size: 0.9rem; color: #333; - transition: border-color 0.2s, box-shadow 0.2s; + min-width: 200px; + transition: all 0.2s ease; } .filters-input:focus { border-color: #1e3a8a; - box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2); + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.1); outline: none; } .filters-select { - padding: 0.6rem 1rem; + padding: 0.5rem 0.8rem; border: 1px solid #d1d5db; - border-radius: 8px; - font-size: 0.95rem; + border-radius: 6px; + font-size: 0.9rem; background: #fff; color: #333; cursor: pointer; - transition: border-color 0.2s, box-shadow 0.2s; + min-width: 140px; + transition: all 0.2s ease; } .filters-select:focus { border-color: #1e3a8a; - box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2); + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.1); outline: none; } - .cards-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1.5rem; + gap: 1.2rem; margin-bottom: 2rem; } .card { background-color: white; - padding: 1.5rem; - border-radius: 12px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 1.2rem; + border-radius: 10px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); border: 1px solid transparent; - transition: transform 0.25s ease, box-shadow 0.25s ease, border 0.25s ease, background 0.25s ease; + transition: all 0.25s ease; cursor: pointer; } .highlight:hover { - transform: translateY(-6px); - box-shadow: 0 8px 20px rgba(30, 58, 138, 0.2); + transform: translateY(-4px); + box-shadow: 0 6px 16px rgba(30, 58, 138, 0.2); background: #f8faff; border: 1px solid #1e3a8a33; } .card-label { - font-size: 0.9rem; + font-size: 0.85rem; color: #999; margin-bottom: 0.5rem; } .card-value { - font-size: 1.8rem; + font-size: 1.6rem; font-weight: bold; margin: 0; color: #333; } .card-extra { - font-size: 0.85rem; + font-size: 0.8rem; color: #666; } .card-extra.positive { color: #1e3a8a; + font-weight: 600; } - .user-table-container { background: #fff; border-radius: 12px; - padding: 1.5rem; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); + padding: 1.2rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-top: 2rem; - transition: transform 0.25s ease, box-shadow 0.25s ease; -} - -.user-table-container:hover { - transform: translateY(-4px); - box-shadow: 0 6px 14px rgba(0,0,0,0.15); } .user-table-container h2 { @@ -193,7 +181,7 @@ .user-table th, .user-table td { - padding: 12px 15px; + padding: 10px 12px; text-align: left; border-bottom: 1px solid #e0e0e0; } @@ -202,10 +190,11 @@ background-color: #f3f4f6; color: #333; font-weight: 600; + font-size: 0.9rem; } .user-table tr { - transition: background-color 0.25s ease; + transition: background-color 0.2s ease; } .user-table tr:hover { @@ -214,44 +203,115 @@ .profile-badge { background-color: #1e3a8a; - color: #f7f7f7; - padding: 3px 8px; - border-radius: 8px; - font-size: 0.85rem; + color: white; + padding: 4px 10px; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; display: inline-block; } .status-badge { - padding: 3px 8px; - border-radius: 8px; - font-size: 0.85rem; + padding: 4px 10px; + border-radius: 6px; + font-size: 0.8rem; color: #fff; + font-weight: 500; display: inline-block; text-transform: capitalize; } .status-badge.ativo { - background-color: #28a745; + background-color: #1e3a8a; } .status-badge.inativo { - background-color: #dc3545; + background-color: #6c757d; } .actions { display: flex; - gap: 10px; + gap: 8px; + flex-wrap: wrap; } -.action-icon { +.action-btn { + border: none; + padding: 6px 12px; + font-size: 0.8rem; + font-weight: 500; cursor: pointer; - color: #555; - transition: color 0.2s, transform 0.2s; + transition: all 0.2s ease; + border-radius: 4px; + display: inline-flex; + align-items: center; + gap: 4px; } -.action-icon:hover { - color: #1e3a8a; - transform: scale(1.2); +.action-btn.detalhes { + background-color: #e6f2ff; + color: #004085; + border: 1px solid #b8d4ff; +} + +.action-btn.detalhes:hover { + background-color: #cce4ff; + transform: translateY(-1px); +} + +.action-btn.editar { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; +} + +.action-btn.editar:hover { + background-color: #ffeaa7; + transform: translateY(-1px); +} + +.action-btn.excluir { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f1b0b7; +} + +.action-btn.excluir:hover { + background-color: #f1b0b7; + transform: translateY(-1px); +} + +.save-btn { + background-color: #1e3a8a; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.save-btn:hover { + background-color: #162d6b; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(30, 58, 138, 0.3); +} + +.edit-btn { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.edit-btn:hover { + background-color: #ffeaa7; + transform: translateY(-1px); } html[data-bs-theme="dark"] .dashboard-container { @@ -266,18 +326,17 @@ html[data-bs-theme="dark"] .dashboard-subtitle { } html[data-bs-theme="dark"] .new-user-btn { - background-color: #2563eb; - color: #fff; + background-color: #1e3a8a; } html[data-bs-theme="dark"] .new-user-btn:hover { - background-color: #1e40af; + background-color: #162d6b; } html[data-bs-theme="dark"] .filters-container, html[data-bs-theme="dark"] .user-table-container { background: #1a1a1a; - box-shadow: 0 4px 6px rgba(0,0,0,0.4); + box-shadow: 0 2px 8px rgba(0,0,0,0.4); } html[data-bs-theme="dark"] .filters-title, @@ -299,19 +358,19 @@ html[data-bs-theme="dark"] .filters-select { html[data-bs-theme="dark"] .filters-input:focus, html[data-bs-theme="dark"] .filters-select:focus { - border-color: #2563eb; - box-shadow: 0px 0px 0px 3px rgba(37, 99, 235, 0.2); + border-color: #1e3a8a; + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.2); } html[data-bs-theme="dark"] .cards-container .card { background-color: #181818; color: #e0e0e0; - box-shadow: 0 4px 6px rgba(0,0,0,0.4); + box-shadow: 0 2px 6px rgba(0,0,0,0.4); } html[data-bs-theme="dark"] .highlight:hover { - background: #232a3a; - border: 1px solid #2563eb33; + background: #1a1f2e; + border: 1px solid #1e3a8a33; } html[data-bs-theme="dark"] .card-label { @@ -327,7 +386,7 @@ html[data-bs-theme="dark"] .card-extra { } html[data-bs-theme="dark"] .card-extra.positive { - color: #2563eb; + color: #1e3a8a; } html[data-bs-theme="dark"] .user-table th { @@ -341,26 +400,39 @@ html[data-bs-theme="dark"] .user-table td { } html[data-bs-theme="dark"] .user-table tr:hover { - background-color: #232a3a; + background-color: #1a1f2e; } html[data-bs-theme="dark"] .profile-badge { - background-color: #2563eb; - color: #fff; + background-color: #1e3a8a; } -html[data-bs-theme="dark"] .status-badge.ativo { - background-color: #28a745; +html[data-bs-theme="dark"] .action-btn.detalhes { + background-color: #e6f2ff; + color: #004085; + border: 1px solid #b8d4ff; } -html[data-bs-theme="dark"] .status-badge.inativo { - background-color: #dc3545; +html[data-bs-theme="dark"] .action-btn.detalhes:hover { + background-color: #cce4ff; } -html[data-bs-theme="dark"] .action-icon { - color: #bdbdbd; +html[data-bs-theme="dark"] .action-btn.editar { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; } -html[data-bs-theme="dark"] .action-icon:hover { - color: #2563eb; +html[data-bs-theme="dark"] .action-btn.editar:hover { + background-color: #ffeaa7; +} + +html[data-bs-theme="dark"] .action-btn.excluir { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f1b0b7; +} + +html[data-bs-theme="dark"] .action-btn.excluir:hover { + background-color: #f1b0b7; } \ No newline at end of file diff --git a/src/PagesAdm/gestao.jsx b/src/PagesAdm/gestao.jsx index 7d982b9..a5ecd88 100644 --- a/src/PagesAdm/gestao.jsx +++ b/src/PagesAdm/gestao.jsx @@ -1,14 +1,9 @@ - import React from "react"; import "./gestao.css"; -import { FaEdit, FaTrash } from "react-icons/fa"; - function UserDashboard() { return ( - -
- +

Gestão de Usuários

@@ -91,8 +86,9 @@ function UserDashboard() { Ativo 20/12/2024, 08:30 - - + + + @@ -103,8 +99,9 @@ function UserDashboard() { Ativo 19/12/2024, 14:20 - - + + + @@ -115,8 +112,9 @@ function UserDashboard() { Ativo 20/12/2024, 07:45 - - + + + @@ -127,8 +125,9 @@ function UserDashboard() { Inativo 15/12/2024, 16:30 - - + + + @@ -138,5 +137,4 @@ function UserDashboard() { ); } - export default UserDashboard; \ No newline at end of file diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css index c52a86d..9effbec 100644 --- a/src/components/Header/Header.css +++ b/src/components/Header/Header.css @@ -461,115 +461,6 @@ background-color: #0056b3; } -/* Avatar Modal */ -.avatar-modal-content { - max-width: 500px; -} - -.avatar-preview-container { - text-align: center; - margin: 20px 0; -} - -.avatar-preview-wrapper { - width: 150px; - height: 150px; - margin: 0 auto 20px; - border-radius: 50%; - overflow: hidden; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - display: flex; - align-items: center; - justify-content: center; -} - -.avatar-preview-image { - width: 100%; - height: 100%; - object-fit: cover; -} - -.avatar-preview-placeholder { - font-size: 4rem; - color: white; -} - -.avatar-error-message { - color: #dc3545; - margin-bottom: 10px; - font-size: 0.9rem; -} - -.avatar-file-input { - display: none; -} - -.avatar-actions { - display: flex; - gap: 10px; - justify-content: center; - flex-wrap: wrap; -} - -.avatar-upload-button { - padding: 10px 20px; - border: none; - border-radius: 8px; - background: #007bff; - color: white; - cursor: pointer; - font-size: 0.9rem; - transition: background-color 0.2s; -} - -.avatar-upload-button:hover { - background: #0056b3; -} - -.avatar-upload-button:disabled { - cursor: not-allowed; - opacity: 0.6; -} - -.avatar-remove-button { - padding: 10px 20px; - border: 1px solid #dc3545; - border-radius: 8px; - background: transparent; - color: #dc3545; - cursor: pointer; - font-size: 0.9rem; - transition: all 0.2s; -} - -.avatar-remove-button:hover { - background: #dc3545; - color: white; -} - -.avatar-remove-button:disabled { - cursor: not-allowed; - opacity: 0.6; -} - -.avatar-info-text { - margin-top: 15px; - font-size: 0.85rem; - color: #666; -} - -.avatar-actions-spacing { - margin-top: 20px; -} - -.avatar-close-button { - background: #6c757d; -} - -.avatar-close-button:hover { - background: #5a6268; -} - /* Responsividade */ @media (max-width: 768px) { .header-container { @@ -596,13 +487,43 @@ width: calc(100vw - 20px); max-width: none; } - - .avatar-modal-content { - max-width: 90%; - } - - .avatar-preview-wrapper { - width: 120px; - height: 120px; - } -} \ No newline at end of file +} + +/* permite que cliques "passem" através do header (exceto para os elementos interativos) */ +.header-container { + pointer-events: none; /* header não captura cliques */ +} + +/* mas permite que os controles no canto (telefone e profile) continuem clicáveis */ +.phone-icon-container, +.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 2426e0b..2bea179 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,11 +1,11 @@ +// src/components/Header/Header.jsx import React, { useState, useRef, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { createPortal } from 'react-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import './Header.css'; -import API_KEY from '../utils/apiKeys'; - -const SUPABASE_URL = 'https://yuanqfswhberkoevtmfr.supabase.co'; const Header = () => { + // --- hooks (sempre na mesma ordem) --- const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isSuporteCardOpen, setIsSuporteCardOpen] = useState(false); const [isChatOpen, setIsChatOpen] = useState(false); @@ -13,142 +13,70 @@ const Header = () => { const [mensagens, setMensagens] = useState([]); const [showLogoutModal, setShowLogoutModal] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); - const [showAvatarModal, setShowAvatarModal] = useState(false); - const [isUploading, setIsUploading] = useState(false); - const [avatarError, setAvatarError] = useState(null); + const navigate = useNavigate(); + const location = useLocation(); + const chatInputRef = useRef(null); const mensagensContainerRef = useRef(null); - const fileInputRef = useRef(null); - - const loadAvatarFromSupabase = async (userId) => { - const extensions = ['jpg', 'jpeg', 'png', 'webp', 'gif']; - - for (const ext of extensions) { - try { - const avatarUrl = `${SUPABASE_URL}/storage/v1/object/public/avatars/${userId}/avatar.${ext}`; - - const response = await fetch(avatarUrl, { - method: 'GET', - mode: 'cors' - }); - - if (response.ok) { - const blob = await response.blob(); - const imageUrl = URL.createObjectURL(blob); - return imageUrl; - } - } catch (err) { - continue; - } - } - - return null; - }; - - useEffect(() => { - const loadAvatar = async () => { - let userId = localStorage.getItem('userId') || localStorage.getItem('user_id'); - - if (!userId) { - try { - const userData = localStorage.getItem('user') || localStorage.getItem('userData'); - if (userData) { - const parsed = JSON.parse(userData); - userId = parsed.id || parsed.user_id || parsed.sub || parsed.userId; - - if (userId) { - localStorage.setItem('userId', userId); - } - } - } catch (e) { - console.error('Erro ao parsear dados do usuário:', e); - } - } - - if (!userId) { - try { - const token = localStorage.getItem('token') || - localStorage.getItem('authToken') || - localStorage.getItem('access_token'); - - if (token) { - const myHeaders = new Headers(); - myHeaders.append('apikey', API_KEY); - myHeaders.append('Authorization', token.startsWith('Bearer') ? token : `Bearer ${token}`); - - const response = await fetch( - 'https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/user-info', - { method: 'GET', headers: myHeaders } - ); - - if (response.ok) { - const userInfo = await response.json(); - userId = userInfo.id || userInfo.user_id || userInfo.sub || userInfo.user?.id; - - if (userId) { - localStorage.setItem('userId', userId); - } - } - } - } catch (e) { - console.error('Erro ao buscar userId da API:', e); - } - } - - if (!userId) { - const localAvatar = localStorage.getItem('user_avatar'); - if (localAvatar) { - setAvatarUrl(localAvatar); - } - return; - } - - const localAvatar = localStorage.getItem('user_avatar'); - if (localAvatar) { - setAvatarUrl(localAvatar); - } - - try { - const supabaseAvatarUrl = await loadAvatarFromSupabase(userId); - if (supabaseAvatarUrl) { - setAvatarUrl(supabaseAvatarUrl); - localStorage.setItem('user_avatar', supabaseAvatarUrl); - } - } catch (error) { - console.error('Erro ao carregar avatar do Supabase:', error); - } - }; - - 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]); + // 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') { + if (showLogoutModal) setShowLogoutModal(false); + if (isSuporteCardOpen) setIsSuporteCardOpen(false); + if (isChatOpen) setIsChatOpen(false); + } + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [showLogoutModal, isSuporteCardOpen, isChatOpen]); + + // --- 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 () => { @@ -162,55 +90,34 @@ const Header = () => { sessionStorage.getItem("authToken"); if (token) { - const response = await fetch( - "https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout", - { + try { + await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - } - ); - - if (response.status === 204) console.log("Logout realizado com sucesso"); - else if (response.status === 401) console.log("Token inválido ou expirado"); - else { - try { - const errorData = await response.json(); - console.error("Erro no logout:", errorData); - } catch { - console.error("Erro no logout - status:", response.status); - } + }); + } catch (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", "user_avatar"].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); - }); - }); - } - }; + const handleLogoutCancel = () => setShowLogoutModal(false); + // --- profile / suporte / chat handlers --- const handleProfileClick = () => { setIsDropdownOpen(!isDropdownOpen); if (isSuporteCardOpen) setIsSuporteCardOpen(false); @@ -218,178 +125,17 @@ const Header = () => { }; const handleViewProfile = () => { - setShowAvatarModal(true); + navigate('/perfil'); setIsDropdownOpen(false); }; - const handleAvatarUpload = async (event) => { - const file = event.target.files[0]; - if (!file) return; - - setAvatarError(null); - - const MAX_FILE_SIZE = 5 * 1024 * 1024; - const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; - - if (file.size > MAX_FILE_SIZE) { - setAvatarError("Arquivo muito grande. Máximo 5MB."); - return; - } - - if (!ACCEPTED_TYPES.includes(file.type)) { - setAvatarError("Tipo de arquivo não suportado. Use JPEG, PNG, GIF ou WebP."); - return; - } - - setIsUploading(true); - - try { - let userId = localStorage.getItem('userId') || localStorage.getItem('user_id'); - - if (!userId) { - try { - const userData = localStorage.getItem('user') || localStorage.getItem('userData'); - if (userData) { - const parsed = JSON.parse(userData); - userId = parsed.id || parsed.user_id || parsed.sub || parsed.userId; - if (userId) { - localStorage.setItem('userId', userId); - } - } - } catch (e) { - console.error('Erro ao buscar userId:', e); - } - } - - if (userId) { - try { - await uploadAvatarToSupabase(file, userId); - - const ext = file.name.split('.').pop(); - const publicAvatarUrl = `${SUPABASE_URL}/storage/v1/object/public/avatars/${userId}/avatar.${ext}`; - - const response = await fetch(publicAvatarUrl); - if (response.ok) { - const blob = await response.blob(); - const imageUrl = URL.createObjectURL(blob); - setAvatarUrl(imageUrl); - localStorage.setItem('user_avatar', imageUrl); - window.dispatchEvent(new Event('storage')); - return; - } - } catch (apiError) { - console.error('Erro ao enviar para Supabase:', apiError); - } - } - - - const reader = new FileReader(); - reader.onload = (e) => { - const imageDataUrl = e.target.result; - localStorage.setItem('user_avatar', imageDataUrl); - setAvatarUrl(imageDataUrl); - window.dispatchEvent(new Event('storage')); - }; - reader.readAsDataURL(file); - - } catch (error) { - console.error('Erro no processamento:', error); - setAvatarError('Erro ao processar imagem'); - - const reader = new FileReader(); - reader.onload = (e) => { - const imageDataUrl = e.target.result; - localStorage.setItem('user_avatar', imageDataUrl); - setAvatarUrl(imageDataUrl); - }; - reader.readAsDataURL(file); - } finally { - setIsUploading(false); - if (event.target) event.target.value = ''; - } - }; - - const uploadAvatarToSupabase = async (file, userId) => { - const ext = file.name.split('.').pop(); - const filePath = `${userId}/avatar.${ext}`; - - const myHeaders = new Headers(); - myHeaders.append('apikey', API_KEY); - myHeaders.append('Content-Type', file.type); - - const token = localStorage.getItem('token') || localStorage.getItem('authToken'); - if (token) { - myHeaders.append('Authorization', `Bearer ${token}`); - } - - const response = await fetch(`${SUPABASE_URL}/storage/v1/object/avatars/${filePath}`, { - method: 'POST', - headers: myHeaders, - body: file - }); - - if (!response.ok) { - - const updateResponse = await fetch(`${SUPABASE_URL}/storage/v1/object/avatars/${filePath}`, { - method: 'PUT', - headers: myHeaders, - body: file - }); - - if (!updateResponse.ok) { - throw new Error('Erro ao fazer upload do avatar'); - } - return await updateResponse.json(); - } - - return await response.json(); - }; - - const clearAvatar = async () => { - const userId = localStorage.getItem('userId') || localStorage.getItem('user_id'); - - if (userId) { - try { - const extensions = ['jpg', 'jpeg', 'png', 'webp', 'gif']; - - for (const ext of extensions) { - const filePath = `${userId}/avatar.${ext}`; - const myHeaders = new Headers(); - myHeaders.append('apikey', API_KEY); - - const token = localStorage.getItem('token') || localStorage.getItem('authToken'); - if (token) { - myHeaders.append('Authorization', `Bearer ${token}`); - } - - try { - await fetch(`${SUPABASE_URL}/storage/v1/object/avatars/${filePath}`, { - method: 'DELETE', - headers: myHeaders - }); - } catch (e) { - continue; - } - } - } catch (error) { - console.error('Erro ao remover avatar da API:', error); - } - } - - localStorage.removeItem('user_avatar'); - setAvatarUrl(null); - window.dispatchEvent(new Event('storage')); - }; - 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); @@ -397,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' }) } @@ -409,11 +155,10 @@ const Header = () => { setMensagem(''); }; - const handleEnviarMensagem = async (e) => { + const handleEnviarMensagem = (e) => { e.preventDefault(); if (mensagem.trim() === '') return; - const novaMensagemUsuario = { id: Date.now(), texto: mensagem, @@ -424,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); }; - const SuporteCard = () => ( + // --- subcomponentes (UI) --- + const SuporteCardContent = ({ onOpenChat }) => (

Suporte

Entre em contato conosco através dos canais abaixo

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

Chat de Suporte

- +
@@ -498,7 +236,7 @@ const Header = () => { ))}
-
+ {
); + // --- 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 }) => { + if (typeof document === 'undefined') return null; + return createPortal( +
+
e.stopPropagation()} + > +

Confirmar Logout

+

Tem certeza que deseja encerrar a sessão?

+
+ + +
+
+
, + document.body + ); + }; + + 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 (header visual) --- return ( -
+
-
+
📞
-
-
- {avatarUrl ? ( - Avatar { - const userId = localStorage.getItem('userId') || localStorage.getItem('user_id'); - if (userId) { - loadAvatarFromSupabase(userId).then(newUrl => { - if (newUrl) { - setAvatarUrl(newUrl); - localStorage.setItem('user_avatar', newUrl); - } else { - setAvatarUrl(null); - localStorage.removeItem('user_avatar'); - } - }); - } - }} - /> - ) : ( -
- -
- )} +
+
+
{isDropdownOpen && ( -
- - +
e.stopPropagation()}> + +
)}
- {showLogoutModal && ( -
-
-

Confirmar Logout

-

Tem certeza que deseja encerrar a sessão?

-
- - -
-
-
- )} + {/* logout modal via portal */} + {showLogoutModal && } + {/* suporte portal */} {isSuporteCardOpen && ( -
-
e.stopPropagation()}> - -
-
+ + + )} + {/* chat portal */} {isChatOpen && ( -
-
- -
-
- )} - {showAvatarModal && ( -
setShowAvatarModal(false)}> -
e.stopPropagation()}> -

Gerenciar Avatar

- -
-
- {avatarUrl ? ( - Avatar { - setAvatarUrl(null); - localStorage.removeItem('user_avatar'); - }} - /> - ) : ( - 👤 - )} -
- - {avatarError && ( -
- {avatarError} -
- )} - - - -
- - - {avatarUrl && ( - - )} -
- -

- Formatos aceitos: JPEG, PNG, GIF, WebP (máx. 5MB) -

-
- -
- -
-
-
+ + + )}
); }; -export default Header; \ No newline at end of file +export default Header; + + + diff --git a/src/data/sidebar-items-adm.json b/src/data/sidebar-items-adm.json index 5b4c554..1eb3da2 100644 --- a/src/data/sidebar-items-adm.json +++ b/src/data/sidebar-items-adm.json @@ -25,5 +25,10 @@ "name": "Painel Administrativo", "icon": "file-bar-graph-fill", "url": "/admin/painel" + }, + { + "name": "Gestão de Usuários", + "icon": "people-fill", + "url": "/admin/gestao" } ]