diff --git a/components/analytics/GrowthAnalyticsChart.tsx b/components/analytics/GrowthAnalyticsChart.tsx index 14be0d9..cafba56 100644 --- a/components/analytics/GrowthAnalyticsChart.tsx +++ b/components/analytics/GrowthAnalyticsChart.tsx @@ -8,23 +8,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; -import { - Users, - ShoppingCart, - DollarSign, - Package, - RefreshCw, - Calendar, - TrendingUp, -} from "lucide-react"; +import { RefreshCw } from "lucide-react"; import { getGrowthAnalyticsWithStore, type GrowthAnalytics, } from "@/lib/services/analytics-service"; -import { formatGBP } from "@/utils/format"; import { ComposedChart, Bar, @@ -37,8 +27,6 @@ import { PieChart, Pie, Cell, - BarChart, - Legend, } from "recharts"; interface GrowthAnalyticsChartProps { @@ -52,26 +40,18 @@ const SEGMENT_COLORS = { vip: "#8b5cf6", }; -const SEGMENT_LABELS = { - new: "New (1 order)", - returning: "Returning (2-3 orders)", - loyal: "Loyal (4+ orders or £300+)", - vip: "VIP (10+ orders or £1000+)", -}; - export default function GrowthAnalyticsChart({ hideNumbers = false, }: GrowthAnalyticsChartProps) { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); + const [growthData, setGrowthData] = useState(null); + const [growthLoading, setGrowthLoading] = useState(true); const { toast } = useToast(); - const fetchData = async () => { + const fetchGrowthData = async () => { try { - setLoading(true); + setGrowthLoading(true); const response = await getGrowthAnalyticsWithStore(); - setData(response); + setGrowthData(response); } catch (err) { console.error("Error fetching growth data:", err); toast({ @@ -80,18 +60,16 @@ export default function GrowthAnalyticsChart({ variant: "destructive", }); } finally { - setLoading(false); - setRefreshing(false); + setGrowthLoading(false); } }; useEffect(() => { - fetchData(); + fetchGrowthData(); }, []); - const handleRefresh = () => { - setRefreshing(true); - fetchData(); + const handleGrowthRefresh = () => { + fetchGrowthData(); }; const formatCurrency = (value: number) => { @@ -103,522 +81,398 @@ export default function GrowthAnalyticsChart({ }).format(value); }; - const formatNumber = (value: number) => { - if (hideNumbers) return "***"; - return value.toLocaleString(); - }; - - const formatDate = (dateStr: string) => { - const date = new Date(dateStr); - return date.toLocaleDateString("en-GB", { - day: "numeric", - month: "short", - year: "numeric", - }); - }; - - const getDaysSinceLaunch = () => { - if (!data?.launchDate) return 0; - const launch = new Date(data.launchDate); - const now = new Date(); - return Math.floor( - (now.getTime() - launch.getTime()) / (1000 * 60 * 60 * 24), - ); - }; - - if (loading && !data) { - return ( -
-
-
- ); - } - - if (!data) { - return ( - - -
- No growth data available. Complete your first sale to see analytics. -
-
-
- ); - } - - // Prepare chart data - const recentDaily = data.daily.slice(-30); // Last 30 days for daily chart - - // Prepare segment pie chart data - const segmentData = Object.entries(data.customers.segments) - .filter(([_, value]) => value > 0) - .map(([key, value]) => ({ - name: key.charAt(0).toUpperCase() + key.slice(1), - value, - color: SEGMENT_COLORS[key as keyof typeof SEGMENT_COLORS], - label: SEGMENT_LABELS[key as keyof typeof SEGMENT_LABELS], - })); - - const CustomTooltip = ({ active, payload }: any) => { - if (active && payload?.length) { - const item = payload[0].payload; - return ( -
-

{item.date || item.month}

-

- Orders: {hideNumbers ? "***" : item.orders?.toLocaleString()} -

-

- Revenue: {hideNumbers ? "£***" : formatGBP(item.revenue)} -

-

- Customers: {hideNumbers ? "***" : item.customers} -

- {item.avgOrderValue && ( -

- Avg Order: {hideNumbers ? "£***" : formatGBP(item.avgOrderValue)} -

- )} -
- ); - } - return null; - }; - - const MonthlyTooltip = ({ active, payload }: any) => { - if (active && payload?.length) { - const item = payload[0].payload; - return ( -
-

{item.month}

-

- Orders: {hideNumbers ? "***" : item.orders?.toLocaleString()} -

-

- Revenue: {hideNumbers ? "£***" : formatGBP(item.revenue)} -

-

- Customers: {hideNumbers ? "***" : item.customers} -

-

- New Customers: {hideNumbers ? "***" : item.newCustomers} -

-
- ); - } - return null; - }; - return (
- {/* Header */} + {/* Growth Header */}
-

- - Growth Since First Sale -

-

- - Started {formatDate(data.launchDate)} ({getDaysSinceLaunch()} days - ago) +

Growth Since First Sale

+

+ {growthData?.launchDate + ? `Tracking since ${new Date(growthData.launchDate).toLocaleDateString("en-GB", { month: "long", year: "numeric" })}` + : "Loading..."} + {growthData?.generatedAt && ( + + - Last updated:{" "} + {new Date(growthData.generatedAt).toLocaleTimeString("en-GB", { + hour: "2-digit", + minute: "2-digit", + })} + + )}

- {/* Cumulative Summary Cards */} -
- - -
- + {/* Cumulative Stats Cards */} + {growthData?.cumulative && ( +
+ + +
Total Orders - - -
- - -
- {formatNumber(data.cumulative.orders)} -
-
-
- - - -
- +
+
+ {hideNumbers + ? "***" + : growthData.cumulative.orders.toLocaleString()} +
+ +
+ + +
Total Revenue - - -
- - -
- {formatCurrency(data.cumulative.revenue)} -
-
-
- - - -
- - Total Customers - - -
-
- -
- {formatNumber(data.cumulative.customers)} -
-
-
- - - -
- Products - -
-
- -
- {formatNumber(data.cumulative.products)} -
-
-
- - - -
- Avg Order - -
-
- -
- {formatCurrency(data.cumulative.avgOrderValue)} -
-
-
-
- - {/* Tabbed Charts */} - - - Daily (Last 30 Days) - Monthly Growth - Customer Segments - - - {/* Daily Chart */} - - - - Daily Orders & Revenue - Last 30 days of activity - - - {loading || refreshing ? ( -
-
-
- ) : recentDaily.length > 0 ? ( -
- - - - { - const d = new Date(v); - return `${d.getDate()}/${d.getMonth() + 1}`; - }} - /> - (hideNumbers ? "***" : v)} - /> - - hideNumbers ? "***" : `£${(v / 1000).toFixed(0)}k` - } - /> - } /> - - - - - -
- ) : ( -
- No daily data available yet -
- )} +
+
+ {formatCurrency(growthData.cumulative.revenue)} +
- - - {/* Monthly Chart */} - - - Monthly Growth - - Orders, revenue, and new customers by month - - - - {loading || refreshing ? ( -
-
-
- ) : data.monthly.length > 0 ? ( -
- - - - { - const [year, month] = v.split("-"); - const date = new Date( - parseInt(year), - parseInt(month) - 1, - ); - return date.toLocaleDateString("en-GB", { - month: "short", - year: "2-digit", - }); - }} - /> - (hideNumbers ? "***" : v)} - /> - - hideNumbers ? "***" : `£${(v / 1000).toFixed(0)}k` - } - /> - } /> - - - - - - -
- ) : ( -
- No monthly data available yet -
- )} + +
+ Customers +
+
+ {hideNumbers + ? "***" + : growthData.cumulative.customers.toLocaleString()} +
-
+ + +
+ Products +
+
+ {hideNumbers + ? "***" + : growthData.cumulative.products.toLocaleString()} +
+
+
+ + +
+ Avg Order Value +
+
+ {formatCurrency(growthData.cumulative.avgOrderValue)} +
+
+
+
+ )} - {/* Customer Segments */} - -
- {/* Pie Chart */} - - - Customer Segments - - Breakdown by purchase behavior - - - - {segmentData.length > 0 ? ( -
- - - - hideNumbers - ? name - : `${name} ${(percent * 100).toFixed(0)}%` - } - > - {segmentData.map((entry, index) => ( - - ))} - - - hideNumbers ? "***" : value - } - /> - - -
- ) : ( -
- No customer data yet -
- )} -
-
- - {/* Segment Details */} - - - Segment Details - Customer value by segment - - -
- {Object.entries(data.customers.segments).map( - ([segment, count]) => { - const details = - data.customers.segmentDetails[segment] || {}; - const percentage = - data.customers.segmentPercentages[ - segment as keyof typeof data.customers.segmentPercentages - ] || 0; - - return ( -
-
-
-
-
- {segment} -
-
- {SEGMENT_LABELS[ - segment as keyof typeof SEGMENT_LABELS - ] || segment} -
-
-
-
-
- {hideNumbers ? "***" : count} customers -
-
- {hideNumbers ? "***" : `${percentage}%`} |{" "} + {/* Monthly Revenue & Orders Chart */} + + + Monthly Revenue & Orders + + Store performance by month since first sale + + + + {growthLoading ? ( +
+
+
+ ) : growthData?.monthly && growthData.monthly.length > 0 ? ( +
+ + ({ + ...m, + formattedMonth: new Date( + m.month + "-01", + ).toLocaleDateString("en-GB", { + month: "short", + year: "2-digit", + }), + }))} + margin={{ top: 5, right: 30, left: 20, bottom: 5 }} + > + + + + + hideNumbers ? "***" : `£${(value / 1000).toFixed(0)}k` + } + /> + { + if (active && payload?.length) { + const data = payload[0].payload; + return ( +
+

{data.month}

+

+ Orders:{" "} {hideNumbers - ? "£***" - : formatGBP(details.totalRevenue || 0)}{" "} - revenue -

+ ? "***" + : data.orders.toLocaleString()} +

+

+ Revenue: {formatCurrency(data.revenue)} +

+

+ Customers:{" "} + {hideNumbers + ? "***" + : data.customers.toLocaleString()} +

+ {data.newCustomers !== undefined && ( +

+ New Customers:{" "} + {hideNumbers ? "***" : data.newCustomers} +

+ )}
-
- ); - }, - )} -
+ ); + } + return null; + }} + /> + + + + +
+ ) : ( +
+ No growth data available +
+ )} + + - {/* Summary Stats */} -
-
-
- {formatNumber(data.customers.total)} -
-
- Total Customers -
-
-
-
- {formatCurrency( - data.cumulative.revenue / (data.customers.total || 1), - )} -
-
- Avg Revenue/Customer -
-
+ {/* Customer Segments Pie Chart */} + + + Customer Segments + Breakdown by purchase behavior + + + {growthLoading ? ( +
+
+
+ ) : growthData?.customers ? ( +
+ + + + hideNumbers + ? name + : `${name}: ${(percent * 100).toFixed(0)}%` + } + labelLine={false} + > + {[ + { color: SEGMENT_COLORS.new }, + { color: SEGMENT_COLORS.returning }, + { color: SEGMENT_COLORS.loyal }, + { color: SEGMENT_COLORS.vip }, + ].map((entry, index) => ( + + ))} + + { + if (active && payload?.length) { + const data = payload[0].payload; + const segmentKey = data.name + .split(" ")[0] + .toLowerCase(); + const details = + growthData.customers.segmentDetails[segmentKey]; + return ( +
+

{data.name}

+

+ Count:{" "} + {hideNumbers + ? "***" + : data.value.toLocaleString()} +

+ {details && ( + <> +

+ Revenue:{" "} + {formatCurrency(details.totalRevenue)} +

+

+ Avg Orders:{" "} + {hideNumbers ? "***" : details.avgOrderCount} +

+ + )} +
+ ); + } + return null; + }} + /> +
+
+
+ ) : ( +
+ No customer data available +
+ )} + + {/* Segment Stats */} + {growthData?.customers && ( +
+
+
+ {hideNumbers ? "***" : growthData.customers.segments.new}
- - -
- - +
New
+
+
+
+ {hideNumbers + ? "***" + : growthData.customers.segments.returning} +
+
Returning
+
+
+
+ {hideNumbers ? "***" : growthData.customers.segments.loyal} +
+
Loyal
+
+
+
+ {hideNumbers ? "***" : growthData.customers.segments.vip} +
+
VIP
+
+
+ )} + + + + {/* Monthly Growth Table */} + {growthData?.monthly && growthData.monthly.length > 0 && ( + + + Monthly Breakdown + Detailed metrics by month + + +
+ + + + + + + + + + + + + {growthData.monthly.map((month) => ( + + + + + + + + + ))} + +
MonthOrdersRevenueCustomersAvg Order + New Customers +
+ {new Date(month.month + "-01").toLocaleDateString( + "en-GB", + { month: "long", year: "numeric" }, + )} + + {hideNumbers ? "***" : month.orders.toLocaleString()} + + {formatCurrency(month.revenue)} + + {hideNumbers ? "***" : month.customers.toLocaleString()} + + {formatCurrency(month.avgOrderValue)} + + {hideNumbers ? "***" : (month.newCustomers ?? 0)} +
+
+
+
+ )}
); }