import React, { useState, useEffect, useMemo } from 'react'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell, PieChart, Pie, Legend } from 'recharts'; import { Users, Clock, Phone, TrendingUp, Filter } from 'lucide-react'; import { getAttendances, getUsers } from '../services/dataService'; import { CURRENT_TENANT_ID, COLORS } from '../constants'; import { Attendance, DashboardFilter, FunnelStage, User } from '../types'; import { KPICard } from '../components/KPICard'; import { DateRangePicker } from '../components/DateRangePicker'; import { SellersTable } from '../components/SellersTable'; import { ProductLists } from '../components/ProductLists'; // Interface for seller statistics accumulator interface SellerStats { total: number; converted: number; scoreSum: number; count: number; timeSum: number; } export const Dashboard: React.FC = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState([]); const [users, setUsers] = useState([]); const [filters, setFilters] = useState({ dateRange: { start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // Last 30 days end: new Date(), }, userId: 'all', teamId: 'all', }); useEffect(() => { const fetchData = async () => { setLoading(true); try { // Fetch users and attendances in parallel const [fetchedUsers, fetchedData] = await Promise.all([ getUsers(CURRENT_TENANT_ID), getAttendances(CURRENT_TENANT_ID, filters) ]); setUsers(fetchedUsers); setData(fetchedData); } catch (error) { console.error("Error loading dashboard data:", error); } finally { setLoading(false); } }; fetchData(); }, [filters]); // --- Metrics Calculations --- const totalLeads = data.length; const avgScore = data.length > 0 ? (data.reduce((acc, curr) => acc + curr.score, 0) / data.length).toFixed(1) : "0"; const avgResponseTime = data.length > 0 ? (data.reduce((acc, curr) => acc + curr.first_response_time_min, 0) / data.length).toFixed(0) : "0"; const avgHandleTime = data.length > 0 ? (data.reduce((acc, curr) => acc + curr.handling_time_min, 0) / data.length).toFixed(0) : "0"; // --- Chart Data: Funnel --- const funnelData = useMemo(() => { const stagesOrder = [ FunnelStage.NO_CONTACT, FunnelStage.IDENTIFICATION, FunnelStage.NEGOTIATION, FunnelStage.WON, FunnelStage.LOST ]; const counts = data.reduce((acc, curr) => { acc[curr.funnel_stage] = (acc[curr.funnel_stage] || 0) + 1; return acc; }, {} as Record); return stagesOrder.map(stage => ({ name: stage, value: counts[stage] || 0 })); }, [data]); // --- Chart Data: Origin --- const originData = useMemo(() => { const origins = data.reduce((acc, curr) => { acc[curr.origin] = (acc[curr.origin] || 0) + 1; return acc; }, {} as Record); // Ensure type safety for value in sort return (Object.entries(origins) as [string, number][]) .map(([name, value]) => ({ name, value })) .sort((a, b) => b.value - a.value); }, [data]); // --- Table Data: Sellers Ranking --- const sellersRanking = useMemo(() => { const stats = data.reduce>((acc, curr) => { if (!acc[curr.user_id]) { acc[curr.user_id] = { total: 0, converted: 0, scoreSum: 0, count: 0, timeSum: 0 }; } acc[curr.user_id].total += 1; if (curr.converted) acc[curr.user_id].converted += 1; acc[curr.user_id].scoreSum += curr.score; acc[curr.user_id].timeSum += curr.first_response_time_min; acc[curr.user_id].count += 1; return acc; }, {}); return Object.entries(stats) .map(([userId, s]) => { const stat = s as SellerStats; const user = users.find(u => u.id === userId); if (!user) return null; return { user, total: stat.total, avgScore: (stat.scoreSum / stat.count).toFixed(1), conversionRate: ((stat.converted / stat.total) * 100).toFixed(1), responseTime: (stat.timeSum / stat.count).toFixed(0) }; }) .filter((item): item is NonNullable => item !== null); }, [data, users]); // --- Lists Data: Products --- const productStats = useMemo(() => { const requested: Record = {}; const sold: Record = {}; data.forEach(d => { if (d.product_requested) requested[d.product_requested] = (requested[d.product_requested] || 0) + 1; if (d.product_sold) sold[d.product_sold] = (sold[d.product_sold] || 0) + 1; }); const formatList = (record: Record, total: number) => Object.entries(record) .map(([name, count]) => ({ name, count, percentage: Math.round((count / (total || 1)) * 100) })) .sort((a, b) => b.count - a.count) .slice(0, 5); const totalReq = Object.values(requested).reduce((a, b) => a + b, 0); const totalSold = Object.values(sold).reduce((a, b) => a + b, 0); return { requested: formatList(requested, totalReq), sold: formatList(sold, totalSold) }; }, [data]); const handleFilterChange = (key: keyof DashboardFilter, value: any) => { setFilters(prev => ({ ...prev, [key]: value })); }; if (loading && data.length === 0) { return
Carregando Dashboard...
; } return (
{/* Filters Bar */}
Filtros:
handleFilterChange('dateRange', range)} />
{/* KPI Cards */}
75 ? 'up' : 'down'} trendValue="2.1" icon={TrendingUp} colorClass="text-purple-600" />
{/* Charts Section */}
{/* Funnel Chart */}

Funil de Vendas

{funnelData.map((entry, index) => ( ))}
{/* Origin Pie Chart */}

Origem dos Leads

{originData.map((entry, index) => ( ))}
{/* Ranking Table */} {/* Product Lists */}
); };