'use client' import { useState, useEffect } from 'react' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, AreaChart, BarChart, Bar, Line, } from 'recharts' import { formatCurrency } from '@/lib/utils' import { Users, FileText, TrendingUp, Euro, AlertCircle, FileSignature, Server } from 'lucide-react' interface DashboardStats { yearlyCA: number monthlyRecurringRevenue: number monthlyRecurringCost: number monthlyMargin: number monthlyMarginPercent: number invoicedThisMonth: number costThisMonth: number salesCostThisMonth: number marginThisMonth: number activeContracts: number activeClients: number prospectsCount: number unpaidRevenue: number pendingProposalCount: number pendingProposalMonthly: number pausedCount: number pausedMonthly: number projectedMonthlyRecurring: number projectedOneTimeSetup: number projectedTotal: number serverMonthlyCost: number totalServerCostShared: number totalServerCostSharedRounded: number totalProposalValue: number topQuotes: { id: string; name: string; clientName: string; totalValue: number; status: string }[] topClients: { id: string; name: string; monthlyRecurring: number; oneTimeTotal: number; invoiceTotal: number; totalRevenue: number; contractCount: number }[] } interface ChartDataPoint { month: string monthLabel: string recurringRevenue: number recurringCost: number oneTimeRevenue: number oneTimeCost: number invoiceRevenue: number newClients: number revenue: number cost: number margin: number } export function DashboardContent({ onViewChange }: { onViewChange?: (view: string) => void }) { const [stats, setStats] = useState(null) const [chartData, setChartData] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { fetchDashboard() }, []) const fetchDashboard = async () => { try { const res = await fetch('/api/dashboard') if (res.ok) { const data = await res.json() setStats(data.stats) setChartData(data.chartData || []) } } finally { setLoading(false) } } if (loading) { return (
) } if (!stats) { return (
Erreur lors du chargement des données
) } const cards = [ { label: 'CA annuel', value: formatCurrency(stats.yearlyCA), sub: `année ${new Date().getFullYear()}`, icon: Euro, color: 'text-primary-600 bg-primary-100 dark:bg-primary-900/30', }, { label: 'Revenus récurrents/mois', value: formatCurrency(stats.monthlyRecurringRevenue), sub: `${stats.activeContracts} contrats actifs`, icon: Euro, color: 'text-green-600 bg-green-100 dark:bg-green-900/30', }, { label: 'Coûts récurrents/mois', value: formatCurrency(stats.monthlyRecurringCost), sub: `Marge: ${formatCurrency(stats.monthlyMargin)} (${stats.monthlyMarginPercent.toFixed(1)}%)`, icon: TrendingUp, color: 'text-blue-600 bg-blue-100 dark:bg-blue-900/30', }, { label: 'Facturé ce mois', value: formatCurrency(stats.invoicedThisMonth), sub: `Marge: ${formatCurrency(stats.invoicedThisMonth - stats.salesCostThisMonth)}`, icon: FileText, color: 'text-purple-600 bg-purple-100 dark:bg-purple-900/30', }, { label: 'Non payé', value: formatCurrency(stats.unpaidRevenue), sub: `dont factures impayées incluses`, icon: AlertCircle, color: 'text-red-600 bg-red-100 dark:bg-red-900/30', onClick: () => onViewChange?.('invoices'), }, { label: 'Propositions en cours', value: stats.pendingProposalCount.toString(), sub: `${formatCurrency(stats.pendingProposalMonthly)}/mois`, icon: FileSignature, color: 'text-purple-600 bg-purple-100 dark:bg-purple-900/30', }, { label: 'Total devis en cours', value: formatCurrency(stats.totalProposalValue), sub: `setup + mensualité`, icon: FileText, color: 'text-indigo-600 bg-indigo-100 dark:bg-indigo-900/30', }, { label: 'Clients actifs', value: stats.activeClients.toString(), sub: `${stats.prospectsCount} prospects`, icon: Users, color: 'text-amber-600 bg-amber-100 dark:bg-amber-900/30', }, { label: 'Coûts serveurs/mois', value: formatCurrency(stats.serverMonthlyCost), sub: `Réparti: ${formatCurrency(stats.totalServerCostSharedRounded)}`, icon: Server, color: 'text-orange-600 bg-orange-100 dark:bg-orange-900/30', }, ] return (

Tableau de bord

{cards.map((card) => { const Tag = card.onClick ? 'button' : 'div' return (

{card.label}

{card.value}

{card.sub}

) })}

Projection récurrente

Récurrent actuel

{formatCurrency(stats.monthlyRecurringRevenue)}

En proposition

{formatCurrency(stats.pendingProposalMonthly)}

{stats.pendingProposalCount} contrat(s)

En pause

{formatCurrency(stats.pausedMonthly)}

{stats.pausedCount} contrat(s)

Projection totale

{formatCurrency(stats.projectedMonthlyRecurring)}

Revenus mensuels

formatCurrency(value)} />

Nouveaux clients par mois

[value, 'Nouveaux clients']} />

Top Clients

{stats.topClients.length === 0 ? (

Aucun client pour le moment

) : ( stats.topClients.map((client, i) => (
{i + 1}

{client.name}

{formatCurrency(client.totalRevenue)}
{client.monthlyRecurring > 0 && Récurrent: {formatCurrency(client.monthlyRecurring)}/mois} {client.oneTimeTotal > 0 && Ventes: {formatCurrency(client.oneTimeTotal)}} {client.invoiceTotal > 0 && Facturé: {formatCurrency(client.invoiceTotal)}} {client.contractCount} contrat(s)
)) )}

Top Devis

{stats.topQuotes.length === 0 ? (

Aucun devis pour le moment

) : ( stats.topQuotes.map((q, i) => (
{i + 1}

{q.name}

{q.clientName}

{formatCurrency(q.totalValue)}
)) )}

Détail mensuel

{chartData.length === 0 ? ( ) : ( [...chartData].reverse().map((row) => { const totalFacturesVentes = row.invoiceRevenue + row.oneTimeRevenue const marginPct = totalFacturesVentes > 0 ? (row.margin / totalFacturesVentes) * 100 : 0 return ( ) }) )}
Mois Factures Ventes Total Coût Nvx clients Marge
Aucune donnée
{row.monthLabel} {formatCurrency(row.invoiceRevenue)} {formatCurrency(row.oneTimeRevenue)} {formatCurrency(totalFacturesVentes)} {formatCurrency(row.cost)} {row.newClients} = 0 ? 'text-green-600' : 'text-red-500'}`}> {formatCurrency(row.margin)} ({marginPct.toFixed(0)}%)
) }