"use client"; import { useState, useEffect, useRef, useCallback } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { usePathname, useRouter } from 'next/navigation'; import { useToast } from '@/contexts/ToastContext'; import { useConfirm } from '@/contexts/ConfirmContext'; import { useTheme } from 'next-themes'; type TranslationSummary = { slug: string; timestamps: Partial>; pendingLocales: Array<'en' | 'es'>; }; export default function AdminLayout({ children, }: { children: React.ReactNode; }) { const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [user, setUser] = useState<{ name: string; email: string; avatar?: string | null } | null>(null); const [logo, setLogo] = useState(null); const [isLoading, setIsLoading] = useState(true); const [showAvatarModal, setShowAvatarModal] = useState(false); const [isUploading, setIsUploading] = useState(false); const [mounted, setMounted] = useState(false); const pathname = usePathname(); const router = useRouter(); const { success, error } = useToast(); const { confirm } = useConfirm(); const { theme, setTheme } = useTheme(); const [showNotifications, setShowNotifications] = useState(false); const [translationSummary, setTranslationSummary] = useState([]); const [isFetchingTranslations, setIsFetchingTranslations] = useState(false); const notificationsRef = useRef(null); const pendingTranslationsRef = useRef>(new Set()); const fetchTranslationStatus = useCallback(async (withLoader = false) => { if (withLoader) { setIsFetchingTranslations(true); } try { const response = await fetch('/api/admin/translate-pages'); if (!response.ok) return; const data = await response.json(); const pages: Record>> = data.pages || {}; const summary: TranslationSummary[] = Object.entries(pages).map(([slug, timestamps]) => { const pendingLocales: Array<'en' | 'es'> = []; const ptDate = timestamps.pt ? new Date(timestamps.pt) : null; (['en', 'es'] as const).forEach((locale) => { const localeDate = timestamps[locale] ? new Date(timestamps[locale] as string) : null; if (ptDate && (!localeDate || localeDate < ptDate)) { pendingLocales.push(locale); } }); return { slug, timestamps, pendingLocales }; }); setTranslationSummary(summary); const pendingSlugs = summary.filter((page) => page.pendingLocales.length > 0).map((page) => page.slug); const previousPending = pendingTranslationsRef.current; previousPending.forEach((slug) => { if (!pendingSlugs.includes(slug)) { success(`Tradução da página "${slug}" concluída!`); } }); pendingTranslationsRef.current = new Set(pendingSlugs); } catch (err) { console.error('Erro ao buscar status das traduções:', err); } finally { if (withLoader) { setIsFetchingTranslations(false); } } }, [success]); useEffect(() => { setMounted(true); const fetchUser = async () => { try { const response = await fetch('/api/auth/me'); if (response.ok) { const data = await response.json(); setUser(data.user); } else { // Não autenticado - redirecionar para login router.push('/acesso'); return; } } catch (err) { console.error('Erro ao buscar dados do usuário:', err); router.push('/acesso'); return; } finally { setIsLoading(false); } }; fetchUser(); // Buscar logo das configurações const fetchLogo = async () => { try { const response = await fetch('/api/settings'); if (response.ok) { const data = await response.json(); if (data.logo) { setLogo(data.logo); } } } catch (err) { console.error('Erro ao buscar logo:', err); } }; fetchLogo(); // Listener para atualização em tempo real const handleSettingsRefresh = () => fetchLogo(); window.addEventListener('settings:refresh', handleSettingsRefresh); return () => window.removeEventListener('settings:refresh', handleSettingsRefresh); }, [router]); useEffect(() => { if (!user) { return; } fetchTranslationStatus(); const interval = setInterval(() => fetchTranslationStatus(), 10000); return () => clearInterval(interval); }, [user, fetchTranslationStatus]); useEffect(() => { const handler = () => fetchTranslationStatus(); window.addEventListener('translation:refresh', handler); return () => window.removeEventListener('translation:refresh', handler); }, [fetchTranslationStatus]); useEffect(() => { if (!showNotifications) return; const handleClick = (event: MouseEvent) => { if (notificationsRef.current && !notificationsRef.current.contains(event.target as Node)) { setShowNotifications(false); } }; document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [showNotifications]); // Mostrar loading enquanto verifica autenticação if (isLoading) { return (

Verificando autenticação...

); } // Se não tem usuário após loading, não renderizar nada (está redirecionando) if (!user) { return null; } const handleLogout = async () => { try { await fetch('/api/auth/logout', { method: 'POST' }); router.push('/acesso'); } catch (error) { console.error('Erro ao fazer logout:', error); // Fallback: clear cookie manually document.cookie = "auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT"; router.push('/acesso'); } }; const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsUploading(true); try { const formData = new FormData(); formData.append('avatar', file); const response = await fetch('/api/auth/avatar', { method: 'POST', body: formData, }); if (response.ok) { const data = await response.json(); setUser(data.user); setShowAvatarModal(false); success('Foto atualizada com sucesso!'); } else { const errorData = await response.json(); error(errorData.error || 'Erro ao fazer upload'); } } catch (err) { console.error('Erro ao fazer upload:', err); error('Erro ao fazer upload do avatar'); } finally { setIsUploading(false); } }; const handleRemoveAvatar = async () => { const confirmed = await confirm({ title: 'Remover Foto', message: 'Deseja remover sua foto de perfil?', confirmText: 'Remover', cancelText: 'Cancelar', type: 'warning', }); if (!confirmed) return; try { const response = await fetch('/api/auth/avatar', { method: 'DELETE' }); if (response.ok) { const data = await response.json(); setUser(data.user); setShowAvatarModal(false); success('Foto removida com sucesso!'); } } catch (err) { console.error('Erro ao remover avatar:', err); error('Erro ao remover avatar'); } }; const menuItems = [ { icon: 'ri-dashboard-line', label: 'Dashboard', href: '/admin' }, { icon: 'ri-briefcase-line', label: 'Projetos', href: '/admin/projetos' }, { icon: 'ri-tools-line', label: 'Serviços', href: '/admin/servicos' }, { icon: 'ri-pages-line', label: 'Páginas', href: '/admin/paginas' }, { icon: 'ri-message-3-line', label: 'Mensagens', href: '/admin/mensagens' }, { icon: 'ri-user-settings-line', label: 'Usuários', href: '/admin/usuarios' }, { icon: 'ri-settings-3-line', label: 'Configurações', href: '/admin/configuracoes' }, ]; const pendingCount = translationSummary.filter((page) => page.pendingLocales.length > 0).length; return (
{/* Sidebar */} {/* Main Content */}
{/* Header */}
{/* Dark Mode Toggle */}
{showNotifications && (

Traduções

{translationSummary.length === 0 ? (

Nenhuma tradução registrada.

) : ( translationSummary.map((page) => (

{page.slug}

{page.pendingLocales.length > 0 ? (

Atualizando {page.pendingLocales.map((loc) => loc.toUpperCase()).join(', ')}

) : (

Tudo traduzido

)}
{page.pendingLocales.length > 0 ? ( Em andamento ) : ( Concluída )}
)) )}
)}

{user?.name || 'Carregando...'}

{user?.email || ''}

{/* Page Content */}
{children}
{/* Avatar Modal */} {showAvatarModal && (
setShowAvatarModal(false)}>
e.stopPropagation()}>

Foto de Perfil

{user?.avatar ? ( {user.name} ) : (
)}
{user?.avatar && ( )}

Formatos: JPEG, PNG, WEBP • Tamanho máximo: 5MB

)}
); }