feat: versão 1.5 - CRM Beta com leads, funis, campanhas e portal do cliente
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { SolutionGuard } from '@/components/auth/SolutionGuard';
|
||||
import { useCRMFilter } from '@/contexts/CRMFilterContext';
|
||||
import KanbanBoard from '@/components/crm/KanbanBoard';
|
||||
import {
|
||||
UsersIcon,
|
||||
CurrencyDollarIcon,
|
||||
@@ -9,126 +12,238 @@ import {
|
||||
ArrowTrendingUpIcon,
|
||||
ListBulletIcon,
|
||||
ArrowRightIcon,
|
||||
MegaphoneIcon,
|
||||
RectangleStackIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
export default function CRMPage() {
|
||||
const stats = [
|
||||
{ name: 'Leads Totais', value: '124', icon: UsersIcon, color: 'blue' },
|
||||
{ name: 'Oportunidades', value: 'R$ 450k', icon: CurrencyDollarIcon, color: 'green' },
|
||||
{ name: 'Taxa de Conversão', value: '24%', icon: ChartPieIcon, color: 'purple' },
|
||||
{ name: 'Crescimento', value: '+12%', icon: ArrowTrendingUpIcon, color: 'orange' },
|
||||
];
|
||||
function CRMDashboardContent() {
|
||||
const { selectedCustomerId } = useCRMFilter();
|
||||
console.log('🏠 CRMPage (Content) render, selectedCustomerId:', selectedCustomerId);
|
||||
|
||||
const [stats, setStats] = useState([
|
||||
{ name: 'Leads Totais', value: '0', icon: UsersIcon, color: 'blue' },
|
||||
{ name: 'Clientes', value: '0', icon: UsersIcon, color: 'green' },
|
||||
{ name: 'Campanhas', value: '0', icon: MegaphoneIcon, color: 'purple' },
|
||||
{ name: 'Taxa de Conversão', value: '0%', icon: ChartPieIcon, color: 'orange' },
|
||||
]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [defaultFunnelId, setDefaultFunnelId] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🔄 CRM Dashboard: selectedCustomerId changed to:', selectedCustomerId);
|
||||
fetchDashboardData();
|
||||
fetchDefaultFunnel();
|
||||
}, [selectedCustomerId]);
|
||||
|
||||
const fetchDefaultFunnel = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/crm/funnels', {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.funnels?.length > 0) {
|
||||
setDefaultFunnelId(data.funnels[0].id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching funnels:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Adicionando um timestamp para evitar cache agressivo do navegador
|
||||
const timestamp = new Date().getTime();
|
||||
const url = selectedCustomerId
|
||||
? `/api/crm/dashboard?customer_id=${selectedCustomerId}&t=${timestamp}`
|
||||
: `/api/crm/dashboard?t=${timestamp}`;
|
||||
|
||||
console.log(`📊 Fetching dashboard data from: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
},
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('📊 Dashboard data received:', data);
|
||||
const s = data.stats;
|
||||
setStats([
|
||||
{ name: 'Leads Totais', value: s.total.toString(), icon: UsersIcon, color: 'blue' },
|
||||
{ name: 'Clientes', value: s.total_customers.toString(), icon: UsersIcon, color: 'green' },
|
||||
{ name: 'Campanhas', value: s.total_campaigns.toString(), icon: MegaphoneIcon, color: 'purple' },
|
||||
{ name: 'Taxa de Conversão', value: `${s.conversionRate || 0}%`, icon: ChartPieIcon, color: 'orange' },
|
||||
]);
|
||||
} else {
|
||||
console.error('📊 Error response from dashboard:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching CRM dashboard data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const quickLinks = [
|
||||
{
|
||||
name: 'Funis de Vendas',
|
||||
description: 'Configure seus processos e etapas',
|
||||
icon: RectangleStackIcon,
|
||||
href: '/crm/funis',
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
name: 'Clientes',
|
||||
description: 'Gerencie seus contatos e clientes',
|
||||
icon: UsersIcon,
|
||||
href: '/crm/clientes',
|
||||
color: 'blue',
|
||||
color: 'indigo',
|
||||
},
|
||||
{
|
||||
name: 'Listas',
|
||||
description: 'Organize clientes em listas',
|
||||
icon: ListBulletIcon,
|
||||
href: '/crm/listas',
|
||||
name: 'Campanhas',
|
||||
description: 'Organize leads e rastreie origens',
|
||||
icon: MegaphoneIcon,
|
||||
href: '/crm/campanhas',
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
name: 'Leads',
|
||||
description: 'Gerencie potenciais clientes',
|
||||
icon: UsersIcon,
|
||||
href: '/crm/leads',
|
||||
color: 'green',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SolutionGuard requiredSolution="crm">
|
||||
<div className="p-6 h-full overflow-auto">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
CRM
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Visão geral do relacionamento com clientes
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 h-full overflow-auto">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
CRM
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Visão geral do relacionamento com clientes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{stats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div
|
||||
key={stat.name}
|
||||
className="group relative overflow-hidden rounded-xl bg-white dark:bg-gray-900 p-4 border border-gray-200 dark:border-gray-800 transition-all"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{stat.name}
|
||||
</p>
|
||||
<p className="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{stat.value}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`rounded-lg p-2 bg-${stat.color}-100 dark:bg-${stat.color}-900/20`}
|
||||
>
|
||||
<Icon
|
||||
className={`h-5 w-5 text-${stat.color}-600 dark:text-${stat.color}-400`}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{stats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div
|
||||
key={stat.name}
|
||||
className="group relative overflow-hidden rounded-xl bg-white dark:bg-gray-900 p-4 border border-gray-200 dark:border-gray-800 transition-all"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{stat.name}
|
||||
</p>
|
||||
<p className="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{stat.value}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`rounded-lg p-2 bg-${stat.color}-100 dark:bg-${stat.color}-900/20`}
|
||||
>
|
||||
<Icon
|
||||
className={`h-5 w-5 text-${stat.color}-600 dark:text-${stat.color}-400`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Acesso Rápido
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{quickLinks.map((link) => {
|
||||
const Icon = link.icon;
|
||||
return (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="group relative overflow-hidden rounded-xl bg-white dark:bg-gray-900 p-6 border border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 transition-all hover:shadow-lg"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className={`rounded-lg p-3 bg-${link.color}-100 dark:bg-${link.color}-900/20`}
|
||||
>
|
||||
<Icon
|
||||
className={`h-6 w-6 text-${link.color}-600 dark:text-${link.color}-400`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
||||
{link.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{link.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightIcon className="w-5 h-5 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Acesso Rápido
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Monitoramento de Leads
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{quickLinks.map((link) => {
|
||||
const Icon = link.icon;
|
||||
return (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="group relative overflow-hidden rounded-xl bg-white dark:bg-gray-900 p-6 border border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 transition-all hover:shadow-lg"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className={`rounded-lg p-3 bg-${link.color}-100 dark:bg-${link.color}-900/20`}
|
||||
>
|
||||
<Icon
|
||||
className={`h-6 w-6 text-${link.color}-600 dark:text-${link.color}-400`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
||||
{link.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{link.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRightIcon className="w-5 h-5 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Link href="/crm/funis" className="text-sm font-medium text-brand-600 hover:underline">
|
||||
Gerenciar Funis
|
||||
</Link>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 p-6 min-h-[500px]">
|
||||
{defaultFunnelId ? (
|
||||
<KanbanBoard funnelId={defaultFunnelId} />
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||
<RectangleStackIcon className="h-12 w-12 text-gray-300 mb-4" />
|
||||
<p className="text-gray-500">Nenhum funil configurado.</p>
|
||||
<Link href="/crm/funis" className="mt-4 px-4 py-2 bg-brand-600 text-white rounded-lg text-sm font-bold">
|
||||
CRIAR PRIMEIRO FUNIL
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 p-6 h-64 flex items-center justify-center">
|
||||
<p className="text-gray-500">Funil de Vendas (Em breve)</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 p-6 h-64 flex items-center justify-center">
|
||||
<p className="text-gray-500">Atividades Recentes (Em breve)</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 p-6 h-64 flex items-center justify-center">
|
||||
<p className="text-gray-500">Atividades Recentes (Em breve)</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 p-6 h-64 flex items-center justify-center">
|
||||
<p className="text-gray-500">Metas de Vendas (Em breve)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CRMPage() {
|
||||
return (
|
||||
<SolutionGuard requiredSolution="crm">
|
||||
<CRMDashboardContent />
|
||||
</SolutionGuard>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user