feat: dashboard com dados reais de projetos, servicos e contatos

This commit is contained in:
Erik
2025-11-27 19:12:26 -03:00
parent 82d56506c7
commit b02a7d176a
3 changed files with 268 additions and 50 deletions

View File

@@ -1,4 +1,146 @@
"use client";
import { useState, useEffect } from 'react';
import Link from 'next/link';
interface Project {
id: string;
title: string;
category: string;
status: string;
coverImage: string | null;
createdAt: string;
}
interface Service {
id: string;
title: string;
icon: string;
active: boolean;
}
interface Contact {
id: string;
name: string;
email: string;
subject: string | null;
message: string;
createdAt: string;
read: boolean;
}
interface Stats {
projects: number;
activeProjects: number;
services: number;
activeServices: number;
contacts: number;
unreadContacts: number;
}
export default function AdminDashboard() {
const [stats, setStats] = useState<Stats>({
projects: 0,
activeProjects: 0,
services: 0,
activeServices: 0,
contacts: 0,
unreadContacts: 0,
});
const [recentProjects, setRecentProjects] = useState<Project[]>([]);
const [recentContacts, setRecentContacts] = useState<Contact[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
// Buscar projetos
const projectsRes = await fetch('/api/projects');
const projects: Project[] = projectsRes.ok ? await projectsRes.json() : [];
// Buscar serviços
const servicesRes = await fetch('/api/services');
const services: Service[] = servicesRes.ok ? await servicesRes.json() : [];
// Buscar contatos
const contactsRes = await fetch('/api/contacts');
const contacts: Contact[] = contactsRes.ok ? await contactsRes.json() : [];
// Calcular estatísticas
setStats({
projects: projects.length,
activeProjects: projects.filter(p => p.status === 'Concluído' || p.status === 'Em andamento').length,
services: services.length,
activeServices: services.filter(s => s.active).length,
contacts: contacts.length,
unreadContacts: contacts.filter(c => !c.read).length,
});
// Projetos recentes (últimos 5)
setRecentProjects(
projects
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 5)
);
// Contatos recentes (últimos 5)
setRecentContacts(
contacts
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 5)
);
} catch (error) {
console.error('Erro ao carregar dados do dashboard:', error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
const formatTimeAgo = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 60) return `${diffMins} min`;
if (diffHours < 24) return `${diffHours}h`;
if (diffDays < 7) return `${diffDays}d`;
return date.toLocaleDateString('pt-BR');
};
const getInitials = (name: string) => {
return name
.split(' ')
.map(n => n[0])
.slice(0, 2)
.join('')
.toUpperCase();
};
const getStatusStyle = (status: string) => {
switch (status) {
case 'Concluído':
return 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400';
case 'Em andamento':
return 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400';
default:
return 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400';
}
};
if (loading) {
return (
<div className="flex items-center justify-center py-20">
<i className="ri-loader-4-line animate-spin text-4xl text-primary"></i>
</div>
);
}
return (
<div>
<div className="mb-8">
@@ -7,71 +149,124 @@ export default function AdminDashboard() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{[
{ label: 'Projetos Ativos', value: '12', icon: 'ri-briefcase-line', color: 'text-blue-500', bg: 'bg-blue-50 dark:bg-blue-900/20' },
{ label: 'Mensagens Novas', value: '5', icon: 'ri-message-3-line', color: 'text-green-500', bg: 'bg-green-50 dark:bg-green-900/20' },
{ label: 'Serviços', value: '8', icon: 'ri-tools-line', color: 'text-orange-500', bg: 'bg-orange-50 dark:bg-orange-900/20' },
{ label: 'Visitas Hoje', value: '145', icon: 'ri-eye-line', color: 'text-purple-500', bg: 'bg-purple-50 dark:bg-purple-900/20' },
].map((stat, index) => (
<div key={index} className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm cursor-pointer hover:shadow-md transition-all">
<div className="flex items-center justify-between mb-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${stat.bg} ${stat.color}`}>
<i className={`${stat.icon} text-2xl`}></i>
</div>
<span className="text-2xl font-bold text-secondary dark:text-white">{stat.value}</span>
<Link href="/admin/projetos" className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm cursor-pointer hover:shadow-md transition-all">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center bg-blue-50 dark:bg-blue-900/20 text-blue-500">
<i className="ri-briefcase-line text-2xl"></i>
</div>
<h3 className="text-gray-500 dark:text-gray-400 font-medium">{stat.label}</h3>
<span className="text-2xl font-bold text-secondary dark:text-white">{stats.projects}</span>
</div>
))}
<h3 className="text-gray-500 dark:text-gray-400 font-medium">Projetos</h3>
<p className="text-xs text-gray-400 mt-1">{stats.activeProjects} ativos</p>
</Link>
<Link href="/admin/contatos" className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm cursor-pointer hover:shadow-md transition-all">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center bg-green-50 dark:bg-green-900/20 text-green-500">
<i className="ri-message-3-line text-2xl"></i>
</div>
<span className="text-2xl font-bold text-secondary dark:text-white">{stats.contacts}</span>
</div>
<h3 className="text-gray-500 dark:text-gray-400 font-medium">Mensagens</h3>
<p className="text-xs text-gray-400 mt-1">{stats.unreadContacts} não lidas</p>
</Link>
<Link href="/admin/servicos" className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm cursor-pointer hover:shadow-md transition-all">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center bg-orange-50 dark:bg-orange-900/20 text-orange-500">
<i className="ri-tools-line text-2xl"></i>
</div>
<span className="text-2xl font-bold text-secondary dark:text-white">{stats.services}</span>
</div>
<h3 className="text-gray-500 dark:text-gray-400 font-medium">Serviços</h3>
<p className="text-xs text-gray-400 mt-1">{stats.activeServices} ativos</p>
</Link>
<div className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm">
<div className="flex items-center justify-between mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center bg-purple-50 dark:bg-purple-900/20 text-purple-500">
<i className="ri-eye-line text-2xl"></i>
</div>
<span className="text-2xl font-bold text-secondary dark:text-white"></span>
</div>
<h3 className="text-gray-500 dark:text-gray-400 font-medium">Visitas</h3>
<p className="text-xs text-gray-400 mt-1">Em breve</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-bold text-secondary dark:text-white">Últimas Mensagens</h3>
<button className="text-primary text-sm font-bold hover:underline cursor-pointer">Ver todas</button>
<Link href="/admin/contatos" className="text-primary text-sm font-bold hover:underline">Ver todas</Link>
</div>
<div className="space-y-4">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-start gap-4 p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-white/5 transition-colors border border-transparent hover:border-gray-100 dark:hover:border-white/5 cursor-pointer">
<div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-white/10 flex items-center justify-center shrink-0">
<span className="font-bold text-gray-500 dark:text-gray-400">JD</span>
</div>
<div>
<div className="flex items-center justify-between mb-1">
<h4 className="font-bold text-secondary dark:text-white text-sm">João da Silva</h4>
<span className="text-xs text-gray-400"> 2 horas</span>
{recentContacts.length === 0 ? (
<p className="text-gray-500 dark:text-gray-400 text-center py-8">Nenhuma mensagem recebida.</p>
) : (
recentContacts.map((contact) => (
<Link
key={contact.id}
href="/admin/contatos"
className="flex items-start gap-4 p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-white/5 transition-colors border border-transparent hover:border-gray-100 dark:hover:border-white/5 cursor-pointer"
>
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<span className="font-bold text-primary text-sm">{getInitials(contact.name)}</span>
</div>
<p className="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
Gostaria de solicitar um orçamento para adequação de frota conforme NR-12...
</p>
</div>
</div>
))}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<h4 className="font-bold text-secondary dark:text-white text-sm truncate">
{contact.name}
{!contact.read && (
<span className="ml-2 w-2 h-2 bg-primary rounded-full inline-block"></span>
)}
</h4>
<span className="text-xs text-gray-400 shrink-0 ml-2">{formatTimeAgo(contact.createdAt)}</span>
</div>
<p className="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
{contact.message}
</p>
</div>
</Link>
))
)}
</div>
</div>
<div className="bg-white dark:bg-secondary p-6 rounded-xl border border-gray-200 dark:border-white/10 shadow-sm">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-bold text-secondary dark:text-white">Projetos Recentes</h3>
<button className="text-primary text-sm font-bold hover:underline cursor-pointer">Ver todos</button>
<Link href="/admin/projetos" className="text-primary text-sm font-bold hover:underline">Ver todos</Link>
</div>
<div className="space-y-4">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-white/5 transition-colors border border-transparent hover:border-gray-100 dark:hover:border-white/5 cursor-pointer">
<div className="w-16 h-12 rounded-lg bg-gray-200 dark:bg-white/10 overflow-hidden">
{/* Placeholder image */}
<div className="w-full h-full bg-gray-300 dark:bg-white/20"></div>
</div>
<div className="flex-1">
<h4 className="font-bold text-secondary dark:text-white text-sm">Adequação Coca-Cola</h4>
<p className="text-xs text-gray-500 dark:text-gray-400">Engenharia Veicular</p>
</div>
<span className="px-3 py-1 rounded-full text-xs font-bold bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400">
Concluído
</span>
</div>
))}
{recentProjects.length === 0 ? (
<p className="text-gray-500 dark:text-gray-400 text-center py-8">Nenhum projeto cadastrado.</p>
) : (
recentProjects.map((project) => (
<Link
key={project.id}
href={`/admin/projetos/${project.id}/editar`}
className="flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 dark:hover:bg-white/5 transition-colors border border-transparent hover:border-gray-100 dark:hover:border-white/5 cursor-pointer"
>
<div className="w-16 h-12 rounded-lg bg-gray-200 dark:bg-white/10 overflow-hidden shrink-0">
{project.coverImage ? (
<img src={project.coverImage} alt={project.title} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full bg-gray-300 dark:bg-white/20 flex items-center justify-center">
<i className="ri-image-line text-gray-400"></i>
</div>
)}
</div>
<div className="flex-1 min-w-0">
<h4 className="font-bold text-secondary dark:text-white text-sm truncate">{project.title}</h4>
<p className="text-xs text-gray-500 dark:text-gray-400">{project.category}</p>
</div>
<span className={`px-3 py-1 rounded-full text-xs font-bold shrink-0 ${getStatusStyle(project.status)}`}>
{project.status}
</span>
</Link>
))
)}
</div>
</div>
</div>