ModelMuse02/AI_Sales_Forecasting
0
1/**2 * Charts Component3 * Interactive visualizations using Recharts4 */5 6import React from 'react';7import {8 XAxis,9 YAxis,10 CartesianGrid,11 Tooltip,12 Legend,13 ResponsiveContainer,14 AreaChart,15 Area,16 BarChart,17 Bar,18 PieChart,19 Pie,20 Cell,21 ComposedChart,22 Line,23} from 'recharts';24import {25 MonthlyAggregation,26 ForecastResult,27 ProductAggregation,28 KPIMetrics,29} from '../types';30import { formatCurrency } from '../modules/kpiCalculation';31 32const COLORS = [33 '#3B82F6', // blue34 '#10B981', // green35 '#F59E0B', // amber36 '#EF4444', // red37 '#8B5CF6', // purple38 '#EC4899', // pink39 '#06B6D4', // cyan40 '#84CC16', // lime41];42 43interface ChartContainerProps {44 title: string;45 subtitle?: string;46 children: React.ReactNode;47}48 49const ChartContainer: React.FC<ChartContainerProps> = ({ title, subtitle, children }) => (50 <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">51 <div className="mb-4">52 <h3 className="text-lg font-semibold text-gray-900">{title}</h3>53 {subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}54 </div>55 <div className="h-80">56 {children}57 </div>58 </div>59);60 61// eslint-disable-next-line @typescript-eslint/no-explicit-any62const currencyFormatter = (value: any) => {63 if (value === undefined || value === null) return '';64 const numVal = typeof value === 'number' ? value : parseFloat(value);65 return formatCurrency(numVal);66};67 68// eslint-disable-next-line @typescript-eslint/no-explicit-any69const percentFormatter = (value: any) => {70 if (value === undefined || value === null) return '';71 const numVal = typeof value === 'number' ? value : parseFloat(value);72 return `${numVal > 0 ? '+' : ''}${numVal.toFixed(1)}%`;73};74 75interface RevenueTrendChartProps {76 data: MonthlyAggregation[];77}78 79export const RevenueTrendChart: React.FC<RevenueTrendChartProps> = ({ data }) => {80 const chartData = data.map(d => ({81 period: d.period,82 revenue: d.totalRevenue,83 units: d.totalUnits,84 }));85 86 return (87 <ChartContainer 88 title="Revenue Trend" 89 subtitle="Monthly revenue over time"90 >91 <ResponsiveContainer width="100%" height="100%">92 <ComposedChart data={chartData}>93 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />94 <XAxis 95 dataKey="period" 96 tick={{ fontSize: 12 }} 97 tickLine={false}98 axisLine={{ stroke: '#E5E7EB' }}99 />100 <YAxis 101 yAxisId="left"102 tick={{ fontSize: 12 }} 103 tickLine={false}104 axisLine={{ stroke: '#E5E7EB' }}105 tickFormatter={(value: number) => `$${(value / 1000).toFixed(0)}K`}106 />107 <YAxis 108 yAxisId="right"109 orientation="right"110 tick={{ fontSize: 12 }} 111 tickLine={false}112 axisLine={{ stroke: '#E5E7EB' }}113 />114 <Tooltip 115 formatter={currencyFormatter}116 contentStyle={{ 117 backgroundColor: 'white', 118 border: '1px solid #E5E7EB',119 borderRadius: '8px',120 }}121 />122 <Legend />123 <Bar 124 yAxisId="left"125 dataKey="revenue" 126 name="Revenue" 127 fill="#3B82F6" 128 radius={[4, 4, 0, 0]}129 opacity={0.8}130 />131 <Line 132 yAxisId="right"133 type="monotone" 134 dataKey="units" 135 name="Units Sold"136 stroke="#10B981" 137 strokeWidth={2}138 dot={false}139 />140 </ComposedChart>141 </ResponsiveContainer>142 </ChartContainer>143 );144};145 146interface ForecastChartProps {147 forecast: ForecastResult;148}149 150export const ForecastChart: React.FC<ForecastChartProps> = ({ forecast }) => {151 const chartData = forecast.combinedData.map(d => ({152 period: d.dateStr,153 actual: d.actual,154 predicted: Math.round(d.predicted),155 upperBound: Math.round(d.upperBound),156 lowerBound: Math.round(d.lowerBound),157 isHistorical: d.isHistorical,158 }));159 160 return (161 <ChartContainer 162 title="Revenue Forecast" 163 subtitle={`12-month forecast with ${(forecast.metrics.r2 * 100).toFixed(0)}% confidence`}164 >165 <ResponsiveContainer width="100%" height="100%">166 <AreaChart data={chartData}>167 <defs>168 <linearGradient id="colorConfidence" x1="0" y1="0" x2="0" y2="1">169 <stop offset="5%" stopColor="#3B82F6" stopOpacity={0.2}/>170 <stop offset="95%" stopColor="#3B82F6" stopOpacity={0.05}/>171 </linearGradient>172 </defs>173 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />174 <XAxis 175 dataKey="period" 176 tick={{ fontSize: 11 }} 177 tickLine={false}178 axisLine={{ stroke: '#E5E7EB' }}179 interval="preserveStartEnd"180 />181 <YAxis 182 tick={{ fontSize: 12 }} 183 tickLine={false}184 axisLine={{ stroke: '#E5E7EB' }}185 tickFormatter={(value: number) => `$${(value / 1000).toFixed(0)}K`}186 />187 <Tooltip 188 formatter={currencyFormatter}189 contentStyle={{ 190 backgroundColor: 'white', 191 border: '1px solid #E5E7EB',192 borderRadius: '8px',193 }}194 />195 <Legend />196 <Area197 type="monotone"198 dataKey="upperBound"199 stroke="none"200 fill="url(#colorConfidence)"201 name="Confidence Range"202 />203 <Area204 type="monotone"205 dataKey="lowerBound"206 stroke="none"207 fill="white"208 legendType="none"209 />210 <Line 211 type="monotone" 212 dataKey="actual" 213 name="Actual Revenue"214 stroke="#10B981" 215 strokeWidth={2}216 dot={{ r: 3 }}217 connectNulls218 />219 <Line 220 type="monotone" 221 dataKey="predicted" 222 name="Forecast"223 stroke="#3B82F6" 224 strokeWidth={2}225 strokeDasharray="5 5"226 dot={false}227 />228 </AreaChart>229 </ResponsiveContainer>230 </ChartContainer>231 );232};233 234interface ProductChartProps {235 data: ProductAggregation[];236}237 238export const ProductPieChart: React.FC<ProductChartProps> = ({ data }) => {239 const chartData = data.slice(0, 6).map(d => ({240 name: d.product,241 value: d.totalRevenue,242 percentage: d.percentage,243 }));244 245 return (246 <ChartContainer 247 title="Revenue by Product" 248 subtitle="Product contribution to total revenue"249 >250 <ResponsiveContainer width="100%" height="100%">251 <PieChart>252 <Pie253 data={chartData}254 cx="50%"255 cy="50%"256 labelLine={false}257 outerRadius={120}258 fill="#8884d8"259 dataKey="value"260 label={({ percent }) => (percent ?? 0) > 0.05 ? `${((percent ?? 0) * 100).toFixed(0)}%` : ''}261 >262 {chartData.map((_, index) => (263 <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />264 ))}265 </Pie>266 <Tooltip 267 formatter={currencyFormatter}268 contentStyle={{ 269 backgroundColor: 'white', 270 border: '1px solid #E5E7EB',271 borderRadius: '8px',272 }}273 />274 <Legend 275 layout="vertical" 276 align="right" 277 verticalAlign="middle"278 />279 </PieChart>280 </ResponsiveContainer>281 </ChartContainer>282 );283};284 285interface GrowthChartProps {286 kpis: KPIMetrics;287 monthlyData: MonthlyAggregation[];288}289 290export const GrowthChart: React.FC<GrowthChartProps> = ({ kpis, monthlyData }) => {291 const chartData = monthlyData.slice(1).map((d, i) => ({292 period: d.period,293 growthRate: kpis.monthlyGrowthRates[i] || 0,294 revenue: d.totalRevenue,295 }));296 297 return (298 <ChartContainer 299 title="Monthly Growth Rate" 300 subtitle="Month-over-month revenue change"301 >302 <ResponsiveContainer width="100%" height="100%">303 <BarChart data={chartData}>304 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />305 <XAxis 306 dataKey="period" 307 tick={{ fontSize: 11 }} 308 tickLine={false}309 axisLine={{ stroke: '#E5E7EB' }}310 interval="preserveStartEnd"311 />312 <YAxis 313 tick={{ fontSize: 12 }} 314 tickLine={false}315 axisLine={{ stroke: '#E5E7EB' }}316 tickFormatter={(value: number) => `${value.toFixed(0)}%`}317 />318 <Tooltip 319 formatter={percentFormatter}320 contentStyle={{ 321 backgroundColor: 'white', 322 border: '1px solid #E5E7EB',323 borderRadius: '8px',324 }}325 />326 <Legend />327 <Bar 328 dataKey="growthRate" 329 name="Growth Rate"330 radius={[4, 4, 0, 0]}331 >332 {chartData.map((entry, index) => (333 <Cell 334 key={`cell-${index}`} 335 fill={entry.growthRate >= 0 ? '#10B981' : '#EF4444'} 336 />337 ))}338 </Bar>339 </BarChart>340 </ResponsiveContainer>341 </ChartContainer>342 );343};344 345interface SeasonalityChartProps {346 forecast: ForecastResult;347}348 349export const SeasonalityChart: React.FC<SeasonalityChartProps> = ({ forecast }) => {350 const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];351 352 const chartData = monthNames.map((name, index) => ({353 month: name,354 factor: ((forecast.seasonality.pattern[index] || 1) - 1) * 100,355 baseline: 0,356 }));357 358 return (359 <ChartContainer 360 title="Seasonality Pattern" 361 subtitle="Monthly deviation from average"362 >363 <ResponsiveContainer width="100%" height="100%">364 <BarChart data={chartData}>365 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />366 <XAxis 367 dataKey="month" 368 tick={{ fontSize: 12 }} 369 tickLine={false}370 axisLine={{ stroke: '#E5E7EB' }}371 />372 <YAxis 373 tick={{ fontSize: 12 }} 374 tickLine={false}375 axisLine={{ stroke: '#E5E7EB' }}376 tickFormatter={(value: number) => `${value > 0 ? '+' : ''}${value.toFixed(0)}%`}377 />378 <Tooltip 379 formatter={percentFormatter}380 contentStyle={{ 381 backgroundColor: 'white', 382 border: '1px solid #E5E7EB',383 borderRadius: '8px',384 }}385 />386 <Bar 387 dataKey="factor" 388 name="Seasonal Factor"389 radius={[4, 4, 0, 0]}390 >391 {chartData.map((entry, index) => (392 <Cell 393 key={`cell-${index}`} 394 fill={entry.factor >= 0 ? '#3B82F6' : '#F59E0B'} 395 />396 ))}397 </Bar>398 </BarChart>399 </ResponsiveContainer>400 </ChartContainer>401 );402};403 404interface ProductBarChartProps {405 data: ProductAggregation[];406}407 408export const ProductBarChart: React.FC<ProductBarChartProps> = ({ data }) => {409 const chartData = data.slice(0, 8).map(d => ({410 product: d.product.length > 15 ? d.product.slice(0, 15) + '...' : d.product,411 revenue: d.totalRevenue,412 units: d.totalUnits,413 aov: d.averageOrderValue,414 }));415 416 return (417 <ChartContainer 418 title="Product Performance" 419 subtitle="Revenue and units by product"420 >421 <ResponsiveContainer width="100%" height="100%">422 <BarChart data={chartData} layout="vertical">423 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />424 <XAxis 425 type="number"426 tick={{ fontSize: 12 }} 427 tickLine={false}428 axisLine={{ stroke: '#E5E7EB' }}429 tickFormatter={(value: number) => `$${(value / 1000).toFixed(0)}K`}430 />431 <YAxis 432 type="category"433 dataKey="product" 434 tick={{ fontSize: 11 }} 435 tickLine={false}436 axisLine={{ stroke: '#E5E7EB' }}437 width={100}438 />439 <Tooltip 440 formatter={currencyFormatter}441 contentStyle={{ 442 backgroundColor: 'white', 443 border: '1px solid #E5E7EB',444 borderRadius: '8px',445 }}446 />447 <Legend />448 <Bar 449 dataKey="revenue" 450 name="Revenue" 451 fill="#3B82F6" 452 radius={[0, 4, 4, 0]}453 />454 </BarChart>455 </ResponsiveContainer>456 </ChartContainer>457 );458};459 460interface ProfitMarginChartProps {461 data: MonthlyAggregation[];462}463 464export const ProfitMarginChart: React.FC<ProfitMarginChartProps> = ({ data }) => {465 const chartData = data466 .filter(d => d.totalProfit !== null)467 .map(d => ({468 period: d.period,469 revenue: d.totalRevenue,470 profit: d.totalProfit || 0,471 margin: d.totalRevenue > 0 ? ((d.totalProfit || 0) / d.totalRevenue) * 100 : 0,472 }));473 474 if (chartData.length === 0) return null;475 476 return (477 <ChartContainer 478 title="Profit Analysis" 479 subtitle="Revenue, profit, and margin over time"480 >481 <ResponsiveContainer width="100%" height="100%">482 <ComposedChart data={chartData}>483 <CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" />484 <XAxis 485 dataKey="period" 486 tick={{ fontSize: 11 }} 487 tickLine={false}488 axisLine={{ stroke: '#E5E7EB' }}489 interval="preserveStartEnd"490 />491 <YAxis 492 yAxisId="left"493 tick={{ fontSize: 12 }} 494 tickLine={false}495 axisLine={{ stroke: '#E5E7EB' }}496 tickFormatter={(value: number) => `$${(value / 1000).toFixed(0)}K`}497 />498 <YAxis 499 yAxisId="right"500 orientation="right"501 tick={{ fontSize: 12 }} 502 tickLine={false}503 axisLine={{ stroke: '#E5E7EB' }}504 tickFormatter={(value: number) => `${value.toFixed(0)}%`}505 />506 <Tooltip 507 formatter={currencyFormatter}508 contentStyle={{ 509 backgroundColor: 'white', 510 border: '1px solid #E5E7EB',511 borderRadius: '8px',512 }}513 />514 <Legend />515 <Bar 516 yAxisId="left"517 dataKey="revenue" 518 name="Revenue" 519 fill="#3B82F6" 520 radius={[4, 4, 0, 0]}521 opacity={0.8}522 />523 <Bar 524 yAxisId="left"525 dataKey="profit" 526 name="Profit" 527 fill="#10B981" 528 radius={[4, 4, 0, 0]}529 opacity={0.8}530 />531 <Line 532 yAxisId="right"533 type="monotone" 534 dataKey="margin" 535 name="Margin %"536 stroke="#F59E0B" 537 strokeWidth={2}538 dot={false}539 />540 </ComposedChart>541 </ResponsiveContainer>542 </ChartContainer>543 );544};545 