Refactor AdminAnalytics helpers and formatting
All checks were successful
Build Frontend / build (push) Successful in 1m12s
All checks were successful
Build Frontend / build (push) Successful in 1m12s
Refactored helper functions (transformChartData, combineOrdersAndRevenue, CustomTooltip, formatCurrency, calculateBestMonth) to be defined outside the main component for improved readability and maintainability. Updated code formatting and indentation for consistency, with no changes to business logic.
This commit is contained in:
@@ -58,6 +58,15 @@ import {
|
|||||||
TableSkeleton
|
TableSkeleton
|
||||||
} from "../analytics/SkeletonLoaders";
|
} from "../analytics/SkeletonLoaders";
|
||||||
|
|
||||||
|
// Format currency for admin analytics
|
||||||
|
const formatAdminCurrency = (value: number) => {
|
||||||
|
return new Intl.NumberFormat("en-GB", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "GBP",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(value);
|
||||||
|
};
|
||||||
|
|
||||||
const renderActiveShape = (props: any) => {
|
const renderActiveShape = (props: any) => {
|
||||||
const RADIAN = Math.PI / 180;
|
const RADIAN = Math.PI / 180;
|
||||||
const {
|
const {
|
||||||
@@ -86,7 +95,7 @@ const renderActiveShape = (props: any) => {
|
|||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill} className="text-xl font-bold">
|
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill} className="text-xl font-bold">
|
||||||
{payload.name.split(" ")[0]}
|
{payload.name ? payload.name.split(" ")[0] : "Product"}
|
||||||
</text>
|
</text>
|
||||||
<Sector
|
<Sector
|
||||||
cx={cx}
|
cx={cx}
|
||||||
@@ -148,7 +157,7 @@ const getSmartInsights = (data?: AnalyticsData | null, growth?: GrowthData | nul
|
|||||||
if (data.predictions && data.predictions.predicted > 0) {
|
if (data.predictions && data.predictions.predicted > 0) {
|
||||||
insights.push({
|
insights.push({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: `AI Forecast: Projected ${formatCurrency(data.predictions.predicted)} revenue over the next 7 days.`,
|
message: `AI Forecast: Projected ${formatAdminCurrency(data.predictions.predicted)} revenue over the next 7 days.`,
|
||||||
icon: Zap,
|
icon: Zap,
|
||||||
color: 'text-purple-500',
|
color: 'text-purple-500',
|
||||||
bg: 'bg-purple-500/10'
|
bg: 'bg-purple-500/10'
|
||||||
@@ -185,7 +194,7 @@ const getSmartInsights = (data?: AnalyticsData | null, growth?: GrowthData | nul
|
|||||||
rawInsights.push({
|
rawInsights.push({
|
||||||
score: Math.abs(revPercent) * (label === '1w' ? 1.5 : 1), // Weight recent changes higher
|
score: Math.abs(revPercent) * (label === '1w' ? 1.5 : 1), // Weight recent changes higher
|
||||||
type: revPercent > 0 ? 'positive' : 'neutral',
|
type: revPercent > 0 ? 'positive' : 'neutral',
|
||||||
message: `Revenue is ${revPercent > 0 ? 'up' : 'down'} ${Math.abs(revPercent).toFixed(1)}% vs ${timeframeName} (${formatCurrency(Math.abs(revDiff))} difference).`,
|
message: `Revenue is ${revPercent > 0 ? 'up' : 'down'} ${Math.abs(revPercent).toFixed(1)}% vs ${timeframeName} (${formatAdminCurrency(Math.abs(revDiff))} difference).`,
|
||||||
icon: revPercent > 0 ? TrendingUp : TrendingDown,
|
icon: revPercent > 0 ? TrendingUp : TrendingDown,
|
||||||
color: revPercent > 0 ? 'text-green-500' : 'text-orange-500',
|
color: revPercent > 0 ? 'text-green-500' : 'text-orange-500',
|
||||||
bg: revPercent > 0 ? 'bg-green-500/10' : 'bg-orange-500/10'
|
bg: revPercent > 0 ? 'bg-green-500/10' : 'bg-orange-500/10'
|
||||||
@@ -544,7 +553,6 @@ export default function AdminAnalytics() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to transform data for recharts
|
// Helper to transform data for recharts
|
||||||
const transformChartData = (
|
const transformChartData = (
|
||||||
data: Array<{ date: string;[key: string]: any }>,
|
data: Array<{ date: string;[key: string]: any }>,
|
||||||
@@ -554,6 +562,16 @@ export default function AdminAnalytics() {
|
|||||||
|
|
||||||
return data.map((item) => {
|
return data.map((item) => {
|
||||||
const dateStr = item.date;
|
const dateStr = item.date;
|
||||||
|
|
||||||
|
if (!dateStr) {
|
||||||
|
return {
|
||||||
|
date: "Unknown",
|
||||||
|
formattedDate: "Unknown",
|
||||||
|
value: Number(item[valueKey]) || 0,
|
||||||
|
[valueKey]: Number(item[valueKey]) || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Parse YYYY-MM-DD format
|
// Parse YYYY-MM-DD format
|
||||||
const parts = dateStr.split("-");
|
const parts = dateStr.split("-");
|
||||||
const date =
|
const date =
|
||||||
@@ -600,15 +618,28 @@ export default function AdminAnalytics() {
|
|||||||
>();
|
>();
|
||||||
if (revenue && revenue.length > 0) {
|
if (revenue && revenue.length > 0) {
|
||||||
revenue.forEach((r) => {
|
revenue.forEach((r) => {
|
||||||
revenueMap.set(r.date, {
|
if (r.date) {
|
||||||
amount: r.amount || 0,
|
revenueMap.set(r.date, {
|
||||||
avgOrderValue: r.avgOrderValue || 0,
|
amount: r.amount || 0,
|
||||||
});
|
avgOrderValue: r.avgOrderValue || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return orders.map((order) => {
|
return orders.map((order) => {
|
||||||
const dateStr = order.date;
|
const dateStr = order.date;
|
||||||
|
|
||||||
|
if (!dateStr) {
|
||||||
|
return {
|
||||||
|
date: "Unknown",
|
||||||
|
formattedDate: "Unknown",
|
||||||
|
orders: order.count || 0,
|
||||||
|
revenue: 0,
|
||||||
|
avgOrderValue: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const parts = dateStr.split("-");
|
const parts = dateStr.split("-");
|
||||||
const date =
|
const date =
|
||||||
parts.length === 3
|
parts.length === 3
|
||||||
@@ -641,6 +672,7 @@ export default function AdminAnalytics() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Custom tooltip for charts
|
// Custom tooltip for charts
|
||||||
const CustomTooltip = ({ active, payload, label }: any) => {
|
const CustomTooltip = ({ active, payload, label }: any) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
@@ -701,14 +733,7 @@ export default function AdminAnalytics() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Format currency
|
|
||||||
const formatCurrency = (value: number) => {
|
|
||||||
return new Intl.NumberFormat("en-GB", {
|
|
||||||
style: "currency",
|
|
||||||
currency: "GBP",
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
}).format(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate best month from daily data (for YTD, full year, or previous years)
|
// Calculate best month from daily data (for YTD, full year, or previous years)
|
||||||
const calculateBestMonth = (): { month: string; revenue: number; orders: number } | null => {
|
const calculateBestMonth = (): { month: string; revenue: number; orders: number } | null => {
|
||||||
@@ -950,7 +975,7 @@ export default function AdminAnalytics() {
|
|||||||
Revenue
|
Revenue
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold bg-gradient-to-r from-green-600 to-green-400 bg-clip-text text-transparent">
|
<div className="text-2xl font-bold bg-gradient-to-r from-green-600 to-green-400 bg-clip-text text-transparent">
|
||||||
{formatCurrency(bestMonth.revenue)}
|
{formatAdminCurrency(bestMonth.revenue)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground mt-1">
|
<div className="text-sm text-muted-foreground mt-1">
|
||||||
{formatNumber(bestMonth.orders)} orders
|
{formatNumber(bestMonth.orders)} orders
|
||||||
@@ -993,10 +1018,10 @@ export default function AdminAnalytics() {
|
|||||||
icon={DollarSign}
|
icon={DollarSign}
|
||||||
iconColorClass="text-green-500"
|
iconColorClass="text-green-500"
|
||||||
iconBgClass="bg-green-500/10"
|
iconBgClass="bg-green-500/10"
|
||||||
value={formatCurrency(analyticsData?.revenue?.total || 0)}
|
value={formatAdminCurrency(analyticsData?.revenue?.total || 0)}
|
||||||
subtext={
|
subtext={
|
||||||
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
|
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
|
||||||
Today: {formatCurrency(analyticsData?.revenue?.today || 0)}
|
Today: {formatAdminCurrency(analyticsData?.revenue?.today || 0)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
trend={{
|
trend={{
|
||||||
@@ -1221,7 +1246,7 @@ export default function AdminAnalytics() {
|
|||||||
Total Revenue
|
Total Revenue
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-2xl font-bold text-green-600">
|
||||||
{formatCurrency(
|
{formatAdminCurrency(
|
||||||
analyticsData.revenue.dailyRevenue.reduce(
|
analyticsData.revenue.dailyRevenue.reduce(
|
||||||
(sum, day) => sum + (day.amount || 0),
|
(sum, day) => sum + (day.amount || 0),
|
||||||
0,
|
0,
|
||||||
@@ -1377,7 +1402,7 @@ export default function AdminAnalytics() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="font-semibold text-green-600">
|
<div className="font-semibold text-green-600">
|
||||||
{formatCurrency(vendor.totalRevenue)}
|
{formatAdminCurrency(vendor.totalRevenue)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1437,7 +1462,7 @@ export default function AdminAnalytics() {
|
|||||||
<Card className="col-span-1 border-green-500/20 bg-green-500/5 backdrop-blur-sm hover:bg-green-500/10 transition-colors">
|
<Card className="col-span-1 border-green-500/20 bg-green-500/5 backdrop-blur-sm hover:bg-green-500/10 transition-colors">
|
||||||
<CardContent className="pt-4 flex flex-col items-center justify-center text-center">
|
<CardContent className="pt-4 flex flex-col items-center justify-center text-center">
|
||||||
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Total Revenue</div>
|
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Total Revenue</div>
|
||||||
<div className="text-2xl font-bold text-green-600">{formatCurrency(growthData.cumulative.revenue)}</div>
|
<div className="text-2xl font-bold text-green-600">{formatAdminCurrency(growthData.cumulative.revenue)}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
|
<Card className="col-span-1 border-border/40 bg-background/50 backdrop-blur-sm hover:bg-background/60 transition-colors">
|
||||||
@@ -1461,7 +1486,7 @@ export default function AdminAnalytics() {
|
|||||||
<Card className="col-span-1 border-purple-500/20 bg-purple-500/5 backdrop-blur-sm hover:bg-purple-500/10 transition-colors">
|
<Card className="col-span-1 border-purple-500/20 bg-purple-500/5 backdrop-blur-sm hover:bg-purple-500/10 transition-colors">
|
||||||
<CardContent className="pt-4 flex flex-col items-center justify-center text-center">
|
<CardContent className="pt-4 flex flex-col items-center justify-center text-center">
|
||||||
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Avg Order Value</div>
|
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Avg Order Value</div>
|
||||||
<div className="text-2xl font-bold text-purple-600">{formatCurrency(growthData.cumulative.avgOrderValue)}</div>
|
<div className="text-2xl font-bold text-purple-600">{formatAdminCurrency(growthData.cumulative.avgOrderValue)}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1542,7 +1567,7 @@ export default function AdminAnalytics() {
|
|||||||
<span className="text-sm text-muted-foreground flex items-center gap-2">
|
<span className="text-sm text-muted-foreground flex items-center gap-2">
|
||||||
<div className="w-2 h-2 rounded-full bg-green-500" /> Revenue
|
<div className="w-2 h-2 rounded-full bg-green-500" /> Revenue
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium text-green-600">{formatCurrency(data.revenue)}</span>
|
<span className="font-medium text-green-600">{formatAdminCurrency(data.revenue)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="text-sm text-muted-foreground flex items-center gap-2">
|
<span className="text-sm text-muted-foreground flex items-center gap-2">
|
||||||
@@ -1747,13 +1772,13 @@ export default function AdminAnalytics() {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right p-3 text-green-600 font-semibold">
|
<td className="text-right p-3 text-green-600 font-semibold">
|
||||||
{formatCurrency(month.revenue)}
|
{formatAdminCurrency(month.revenue)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right p-3 text-muted-foreground">
|
<td className="text-right p-3 text-muted-foreground">
|
||||||
{month.customers.toLocaleString()}
|
{month.customers.toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right p-3 text-muted-foreground">
|
<td className="text-right p-3 text-muted-foreground">
|
||||||
{formatCurrency(month.avgOrderValue)}
|
{formatAdminCurrency(month.avgOrderValue)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right p-3 text-muted-foreground">{month.newVendors}</td>
|
<td className="text-right p-3 text-muted-foreground">{month.newVendors}</td>
|
||||||
<td className="text-right p-3 text-muted-foreground">
|
<td className="text-right p-3 text-muted-foreground">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "600ba1e",
|
"commitHash": "38779c2",
|
||||||
"buildTime": "2026-01-13T06:14:18.909Z"
|
"buildTime": "2026-01-13T08:38:56.729Z"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user