From 16bb970060b6f18a14af858cabeb706f3dbb5469 Mon Sep 17 00:00:00 2001 From: Alpha Diop Date: Fri, 4 Sep 2026 09:37:56 +0000 Subject: [PATCH] =?UTF-8?q?Remplacer=20les=20wrappers=20shadcn=20chart=20p?= =?UTF-8?q?ar=20recharts=20direct=20sur=20l=E2=80=99analytique.=20Supprime?= =?UTF-8?q?=20chart.tsx=20et=20chart-configs,=20tooltips=20custom,=20et=20?= =?UTF-8?q?masque=20les=20mois=20futurs=20pour=20l=E2=80=99ann=C3=A9e=20en?= =?UTF-8?q?=20cours.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(dashboard)/dashboard/analytics/page.tsx | 289 ++++++++------ apps/web/components/ui/chart.tsx | 374 ------------------ apps/web/lib/chart-configs.ts | 15 - 3 files changed, 171 insertions(+), 507 deletions(-) delete mode 100644 apps/web/components/ui/chart.tsx delete mode 100644 apps/web/lib/chart-configs.ts diff --git a/apps/web/app/(dashboard)/dashboard/analytics/page.tsx b/apps/web/app/(dashboard)/dashboard/analytics/page.tsx index 934c2d4..01e3a5c 100644 --- a/apps/web/app/(dashboard)/dashboard/analytics/page.tsx +++ b/apps/web/app/(dashboard)/dashboard/analytics/page.tsx @@ -12,6 +12,7 @@ import { UsersIcon, } from 'lucide-react'; import { useState } from 'react'; +import type { TooltipProps as RechartsTooltipProps } from 'recharts'; import { Area, AreaChart, @@ -19,32 +20,101 @@ import { BarChart, CartesianGrid, Cell, + ResponsiveContainer, + Tooltip, XAxis, YAxis, } from 'recharts'; +import type { + NameType, + ValueType, +} from 'recharts/types/component/DefaultTooltipContent'; + +type TooltipRenderProps< + TValue extends ValueType = ValueType, + TName extends NameType = NameType, +> = Pick, 'active'> & { + payload?: Array<{ value?: TValue; name?: TName }>; + label?: string | number; +}; + import { AnalyticsSkeleton } from '@/components/analytics-skeleton'; import { ErrorState } from '@/components/error-state'; import { PageHeader } from '@/components/page-header'; import { PaymentRateRing } from '@/components/payment-rate-ring'; import { StatCard } from '@/components/stat-card'; import { TopClientRow } from '@/components/top-client-row'; -import { - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from '@/components/ui/chart'; import { Caption, Text } from '@/components/ui/typography'; import type { AnalyticsData } from '@/lib/api'; -import { invoiceChartConfig, revenueChartConfig } from '@/lib/chart-configs'; import { classifyError } from '@/lib/error-utils'; import { formatCurrency } from '@/lib/format'; import { useAnalytics } from '@/lib/hooks'; +const CHART_COLOR_1 = 'var(--chart-1)'; +const CHART_COLOR_2 = 'var(--chart-2)'; + +const RECHARTS_WRAPPER = + 'text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line]:stroke-border/50'; + function pct(value: number, total: number) { if (total === 0) return 0; return Math.round((value / total) * 100); } +function ChartTooltipShell({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function RevenueTooltip({ + active, + payload, + label, +}: TooltipRenderProps) { + if (!active || !payload?.length) return null; + return ( + +

{label}

+
+ + Chiffre d'affaires + + {formatCurrency(Number(payload[0].value ?? 0))} + +
+
+ ); +} + +function InvoiceTooltip({ + active, + payload, + label, +}: TooltipRenderProps) { + if (!active || !payload?.length) return null; + return ( + +

{label}

+
+ + Factures + + {payload[0].value} + +
+
+ ); +} + export default function AnalyticsPage() { const currentYear = new Date().getFullYear(); const [year, setYear] = useState(currentYear); @@ -101,8 +171,13 @@ export default function AnalyticsPage() { } function AnalyticsContent({ data }: { data: AnalyticsData }) { - // Couleur des barres par mois — met en évidence le mois courant const currentMonth = new Date().getMonth() + 1; + const currentYear = new Date().getFullYear(); + + const chartData = + data.year === currentYear + ? data.monthlyRevenue.filter((m) => m.month <= currentMonth) + : data.monthlyRevenue; return (
@@ -146,7 +221,6 @@ function AnalyticsContent({ data }: { data: AnalyticsData }) {
- {/* Statut breakdown */}
Répartition des statuts
@@ -205,7 +279,7 @@ function AnalyticsContent({ data }: { data: AnalyticsData }) {
- Chiffre d'affaires mensuel + Chiffre d'affaires mensuel Factures payées en {data.year}
@@ -228,137 +302,116 @@ function AnalyticsContent({ data }: { data: AnalyticsData }) { )}
- - - - - - - - - - value} - /> - - v >= 1_000_000 - ? `${(v / 1_000_000).toFixed(1)}M` - : v >= 1_000 - ? `${(v / 1_000).toFixed(0)}k` - : String(v) - } - tickLine={false} - axisLine={false} - tickMargin={8} - /> - `${value}`} - formatter={(value) => formatCurrency(value as number)} - /> - } - /> - - - -
- - {/* ── Factures par mois + Top clients ── */} -
- {/* Factures payées par mois — Bar chart */} -
-
- - Factures payées par mois - - Nombre de factures encaissées -
- - - + + + + + + + + value} /> + v >= 1_000_000 + ? `${(v / 1_000_000).toFixed(1)}M` + : v >= 1_000 + ? `${(v / 1_000).toFixed(0)}k` + : String(v) + } tickLine={false} axisLine={false} tickMargin={8} /> - `${value}`} - formatter={(value) => `${value} factures`} - /> - } + } /> + - - {data.monthlyRevenue.map((entry) => ( - - ))} - - - + + +
+
+ + {/* ── Factures par mois + Top clients ── */} +
+
+
+ + Factures payées par mois + + Nombre de factures encaissées +
+ +
+ + + + + + } /> + + {chartData.map((entry) => ( + + ))} + + + +
- {/* Top clients */}
Top clients - Par chiffre d'affaires payé + Par chiffre d'affaires payé
Payées/Total diff --git a/apps/web/components/ui/chart.tsx b/apps/web/components/ui/chart.tsx deleted file mode 100644 index 5994cc1..0000000 --- a/apps/web/components/ui/chart.tsx +++ /dev/null @@ -1,374 +0,0 @@ -'use client'; - -import * as React from 'react'; -import type { TooltipValueType } from 'recharts'; -import * as RechartsPrimitive from 'recharts'; - -import { cn } from '@/lib/utils'; - -// Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { light: '', dark: '.dark' } as const; - -const INITIAL_DIMENSION = { width: 320, height: 200 } as const; -type TooltipNameType = number | string; - -export type ChartConfig = Record< - string, - { - label?: React.ReactNode; - icon?: React.ComponentType; - } & ( - | { color?: string; theme?: never } - | { color?: never; theme: Record } - ) ->; - -type ChartContextProps = { - config: ChartConfig; -}; - -const ChartContext = React.createContext(null); - -function useChart() { - const context = React.useContext(ChartContext); - - if (!context) { - throw new Error('useChart must be used within a '); - } - - return context; -} - -function ChartContainer({ - id, - className, - children, - config, - initialDimension = INITIAL_DIMENSION, - ...props -}: React.ComponentProps<'div'> & { - config: ChartConfig; - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >['children']; - initialDimension?: { - width: number; - height: number; - }; -}) { - const uniqueId = React.useId(); - const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`; - - return ( - -
- - - {children} - -
-
- ); -} - -const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter( - ([, config]) => config.theme ?? config.color, - ); - - if (!colorConfig.length) { - return null; - } - - return ( -