feat: implementa sistema de logotipo dinâmico

- Adiciona campo 'logo' ao modelo Settings no Prisma
- Atualiza API /api/settings para lidar com upload de logo
- Cria aba Logotipo funcional no admin com upload de imagem
- Atualiza Header para exibir logo dinâmico (fallback para ícone)
- Atualiza Footer para exibir logo dinâmico
- Atualiza Admin Layout para exibir logo dinâmico
- Logo é atualizado em tempo real via evento settings:refresh
This commit is contained in:
Erik
2025-11-29 16:36:25 -03:00
parent cbad251b39
commit e503069a86
6 changed files with 308 additions and 16 deletions

View File

@@ -2,6 +2,7 @@
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';
@@ -20,6 +21,7 @@ export default function AdminLayout({
}) {
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [user, setUser] = useState<{ name: string; email: string; avatar?: string | null } | null>(null);
const [logo, setLogo] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [showAvatarModal, setShowAvatarModal] = useState(false);
const [isUploading, setIsUploading] = useState(false);
@@ -104,6 +106,27 @@ export default function AdminLayout({
}
};
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(() => {
@@ -239,7 +262,18 @@ export default function AdminLayout({
<aside className={`fixed inset-y-0 left-0 z-50 bg-white dark:bg-secondary border-r border-gray-200 dark:border-white/10 transition-all duration-300 ${isSidebarOpen ? 'w-64' : 'w-20'} hidden md:flex flex-col`}>
<div className="h-20 flex items-center justify-center border-b border-gray-200 dark:border-white/10">
<Link href="/admin" className="flex items-center gap-3">
<i className="ri-building-2-fill text-3xl text-primary"></i>
{logo ? (
<Image
src={logo}
alt="Logo"
width={32}
height={32}
className="object-contain"
unoptimized
/>
) : (
<i className="ri-building-2-fill text-3xl text-primary"></i>
)}
{isSidebarOpen && (
<div className="flex items-center gap-2 animate-in fade-in duration-300">
<span className="text-xl font-bold text-secondary dark:text-white font-headline leading-none">OCCTO</span>