feat: Implementação de submenus laterais (flyout), correções de UI e proteção de rotas (AuthGuard)
This commit is contained in:
@@ -1,178 +1,168 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getUser } from "@/lib/auth";
|
||||
import {
|
||||
RocketLaunchIcon,
|
||||
ChartBarIcon,
|
||||
UserGroupIcon,
|
||||
BriefcaseIcon,
|
||||
LifebuoyIcon,
|
||||
CreditCardIcon,
|
||||
DocumentTextIcon,
|
||||
FolderIcon,
|
||||
CurrencyDollarIcon,
|
||||
ShareIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
ArrowTrendingDownIcon
|
||||
ArrowTrendingDownIcon,
|
||||
CheckCircleIcon,
|
||||
ClockIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
trend?: number;
|
||||
color: 'blue' | 'purple' | 'gray' | 'green';
|
||||
}
|
||||
|
||||
const colorClasses = {
|
||||
blue: {
|
||||
iconBg: 'bg-blue-50 dark:bg-blue-900/20',
|
||||
iconColor: 'text-blue-600 dark:text-blue-400',
|
||||
trend: 'text-blue-600 dark:text-blue-400'
|
||||
},
|
||||
purple: {
|
||||
iconBg: 'bg-purple-50 dark:bg-purple-900/20',
|
||||
iconColor: 'text-purple-600 dark:text-purple-400',
|
||||
trend: 'text-purple-600 dark:text-purple-400'
|
||||
},
|
||||
gray: {
|
||||
iconBg: 'bg-gray-50 dark:bg-gray-900/20',
|
||||
iconColor: 'text-gray-600 dark:text-gray-400',
|
||||
trend: 'text-gray-600 dark:text-gray-400'
|
||||
},
|
||||
green: {
|
||||
iconBg: 'bg-emerald-50 dark:bg-emerald-900/20',
|
||||
iconColor: 'text-emerald-600 dark:text-emerald-400',
|
||||
trend: 'text-emerald-600 dark:text-emerald-400'
|
||||
}
|
||||
};
|
||||
|
||||
function StatCard({ title, value, icon: Icon, trend, color }: StatCardProps) {
|
||||
const colors = colorClasses[color];
|
||||
const isPositive = trend && trend > 0;
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">{title}</p>
|
||||
<p className="text-3xl font-semibold text-gray-900 dark:text-white mt-2">{value}</p>
|
||||
{trend !== undefined && (
|
||||
<div className="flex items-center mt-2">
|
||||
{isPositive ? (
|
||||
<ArrowTrendingUpIcon className="w-4 h-4 text-emerald-500" />
|
||||
) : (
|
||||
<ArrowTrendingDownIcon className="w-4 h-4 text-red-500" />
|
||||
)}
|
||||
<span className={`text-sm font-medium ml-1 ${isPositive ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}`}>
|
||||
{Math.abs(trend)}%
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 ml-1">vs mês anterior</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${colors.iconBg} p-3 rounded-xl`}>
|
||||
<Icon className={`w-8 h-8 ${colors.iconColor}`} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const [stats, setStats] = useState({
|
||||
clientes: 0,
|
||||
projetos: 0,
|
||||
tarefas: 0,
|
||||
faturamento: 0
|
||||
});
|
||||
const overviewStats = [
|
||||
{ name: 'Receita Total (Mês)', value: 'R$ 124.500', change: '+12%', changeType: 'increase', icon: ChartBarIcon, color: 'green' },
|
||||
{ name: 'Novos Leads', value: '45', change: '+5%', changeType: 'increase', icon: RocketLaunchIcon, color: 'blue' },
|
||||
{ name: 'Projetos Ativos', value: '12', change: '-1', changeType: 'decrease', icon: BriefcaseIcon, color: 'purple' },
|
||||
{ name: 'Chamados Abertos', value: '3', change: '-2', changeType: 'decrease', icon: LifebuoyIcon, color: 'orange' },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
// Verificar se é SUPERADMIN e redirecionar
|
||||
const user = getUser();
|
||||
if (user && user.role === 'SUPERADMIN') {
|
||||
router.push('/superadmin');
|
||||
return;
|
||||
const modules = [
|
||||
{
|
||||
title: 'CRM & Vendas',
|
||||
icon: RocketLaunchIcon,
|
||||
color: 'blue',
|
||||
stats: [
|
||||
{ label: 'Propostas Enviadas', value: '8' },
|
||||
{ label: 'Aguardando Aprovação', value: '3' },
|
||||
{ label: 'Taxa de Conversão', value: '24%' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Financeiro & ERP',
|
||||
icon: ChartBarIcon,
|
||||
color: 'green',
|
||||
stats: [
|
||||
{ label: 'A Receber', value: 'R$ 45.200' },
|
||||
{ label: 'A Pagar', value: 'R$ 12.800' },
|
||||
{ label: 'Fluxo de Caixa', value: 'Positivo' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Projetos & Tarefas',
|
||||
icon: BriefcaseIcon,
|
||||
color: 'purple',
|
||||
stats: [
|
||||
{ label: 'Em Andamento', value: '12' },
|
||||
{ label: 'Atrasados', value: '1' },
|
||||
{ label: 'Concluídos (Mês)', value: '4' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Helpdesk',
|
||||
icon: LifebuoyIcon,
|
||||
color: 'orange',
|
||||
stats: [
|
||||
{ label: 'Novos Chamados', value: '3' },
|
||||
{ label: 'Tempo Médio Resposta', value: '2h' },
|
||||
{ label: 'Satisfação', value: '4.8/5' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Documentos & Contratos',
|
||||
icon: DocumentTextIcon,
|
||||
color: 'indigo',
|
||||
stats: [
|
||||
{ label: 'Contratos Ativos', value: '28' },
|
||||
{ label: 'A Vencer (30 dias)', value: '2' },
|
||||
{ label: 'Docs Armazenados', value: '1.2GB' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Redes Sociais',
|
||||
icon: ShareIcon,
|
||||
color: 'pink',
|
||||
stats: [
|
||||
{ label: 'Posts Agendados', value: '14' },
|
||||
{ label: 'Engajamento', value: '+8.5%' },
|
||||
{ label: 'Novos Seguidores', value: '120' },
|
||||
]
|
||||
}
|
||||
|
||||
// Simulando carregamento de dados
|
||||
setTimeout(() => {
|
||||
setStats({
|
||||
clientes: 127,
|
||||
projetos: 18,
|
||||
tarefas: 64,
|
||||
faturamento: 87500
|
||||
});
|
||||
}, 300);
|
||||
}, [router]);
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
Dashboard
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Bem-vindo ao seu painel de controle
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<StatCard
|
||||
title="Clientes Ativos"
|
||||
value={stats.clientes}
|
||||
icon={UserGroupIcon}
|
||||
trend={12.5}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="Projetos em Andamento"
|
||||
value={stats.projetos}
|
||||
icon={FolderIcon}
|
||||
trend={8.2}
|
||||
color="purple"
|
||||
/>
|
||||
<StatCard
|
||||
title="Tarefas Pendentes"
|
||||
value={stats.tarefas}
|
||||
icon={ChartBarIcon}
|
||||
trend={-3.1}
|
||||
color="gray"
|
||||
/>
|
||||
<StatCard
|
||||
title="Faturamento"
|
||||
value={new Intl.NumberFormat('pt-BR', {
|
||||
style: 'currency',
|
||||
currency: 'BRL',
|
||||
minimumFractionDigits: 0
|
||||
}).format(stats.faturamento)}
|
||||
icon={CurrencyDollarIcon}
|
||||
trend={25.3}
|
||||
color="green"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Coming Soon Card */}
|
||||
<div className="bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 rounded-2xl border border-gray-200 dark:border-gray-700 p-12">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl mb-6" style={{ background: 'var(--gradient-primary)' }}>
|
||||
<ChartBarIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
||||
Em Desenvolvimento
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
||||
Estamos construindo recursos incríveis de CRM e ERP para sua agência.
|
||||
Em breve você terá acesso a análises detalhadas, gestão completa de clientes e muito mais.
|
||||
<div className="p-6 h-full overflow-auto">
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Visão Geral da Agência
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Acompanhe o desempenho de todos os módulos em tempo real
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
{['CRM', 'ERP', 'Projetos', 'Pagamentos', 'Documentos', 'Suporte', 'Contratos'].map((item) => (
|
||||
<span
|
||||
key={item}
|
||||
className="inline-flex items-center px-4 py-2 rounded-full text-sm font-medium bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700"
|
||||
</div>
|
||||
|
||||
{/* Top Stats */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{overviewStats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div
|
||||
key={stat.name}
|
||||
className="relative overflow-hidden rounded-xl bg-white dark:bg-zinc-900 p-4 border border-gray-200 dark:border-zinc-800 shadow-sm"
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`rounded-lg p-2 bg-${stat.color}-50 dark:bg-${stat.color}-900/20`}>
|
||||
<Icon className={`h-6 w-6 text-${stat.color}-600 dark:text-${stat.color}-400`} />
|
||||
</div>
|
||||
<div className={`flex items-baseline text-sm font-semibold ${stat.changeType === 'increase' ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
{stat.changeType === 'increase' ? (
|
||||
<ArrowTrendingUpIcon className="h-4 w-4 mr-1" />
|
||||
) : (
|
||||
<ArrowTrendingDownIcon className="h-4 w-4 mr-1" />
|
||||
)}
|
||||
{stat.change}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">{stat.name}</p>
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stat.value}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Modules Grid */}
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Performance por Módulo
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{modules.map((module) => {
|
||||
const Icon = module.icon;
|
||||
return (
|
||||
<div
|
||||
key={module.title}
|
||||
className="rounded-xl bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 p-6 hover:border-gray-200 dark:hover:border-zinc-700 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className={`p-2 rounded-lg bg-${module.color}-50 dark:bg-${module.color}-900/20`}>
|
||||
<Icon className={`h-5 w-5 text-${module.color}-600 dark:text-${module.color}-400`} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||
{module.title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{module.stats.map((stat, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-500 dark:text-gray-400">{stat.label}</span>
|
||||
<span className="font-medium text-gray-900 dark:text-white">{stat.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user