Files
ember-market-frontend/components/analytics/OrderAnalyticsChart.tsx
g fe01f31538
Some checks failed
Build Frontend / build (push) Failing after 7s
Refactor UI imports and update component paths
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
2026-01-13 05:02:13 +00:00

206 lines
6.5 KiB
TypeScript

"use client"
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/common/card";
import { Badge } from "@/components/common/badge";
import { useToast } from "@/lib/hooks/use-toast";
import { Skeleton } from "@/components/common/skeleton";
import { BarChart3, Clock, CheckCircle, XCircle, AlertCircle, AlertTriangle } from "lucide-react";
import { getOrderAnalyticsWithStore, type OrderAnalytics } from "@/lib/services/analytics-service";
import { formatGBP } from "@/lib/utils/format";
import { ChartSkeleton } from './SkeletonLoaders';
interface OrderAnalyticsChartProps {
timeRange: string;
}
export default function OrderAnalyticsChart({ timeRange }: OrderAnalyticsChartProps) {
const [data, setData] = useState<OrderAnalytics | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { toast } = useToast();
useEffect(() => {
const fetchOrderData = async () => {
try {
setIsLoading(true);
setError(null);
const response = await getOrderAnalyticsWithStore(timeRange);
setData(response);
} catch (err) {
console.error('Error fetching order analytics:', err);
setError('Failed to load order data');
toast({
title: "Error",
description: "Failed to load order analytics data.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
fetchOrderData();
}, [timeRange, toast]);
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
case 'acknowledged':
return 'bg-green-100 text-green-800';
case 'paid':
case 'shipped':
return 'bg-blue-100 text-blue-800';
case 'unpaid':
case 'confirming':
return 'bg-yellow-100 text-yellow-800';
case 'cancelled':
return 'bg-red-100 text-red-800';
case 'disputed':
return 'bg-orange-100 text-orange-800';
case 'underpaid':
return 'bg-red-200 text-red-900';
default:
return 'bg-gray-100 text-gray-800';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'completed':
case 'acknowledged':
return <CheckCircle className="h-4 w-4" />;
case 'paid':
case 'shipped':
return <Clock className="h-4 w-4" />;
case 'unpaid':
case 'confirming':
return <AlertCircle className="h-4 w-4" />;
case 'cancelled':
return <XCircle className="h-4 w-4" />;
case 'underpaid':
return <AlertTriangle className="h-4 w-4" />;
default:
return <BarChart3 className="h-4 w-4" />;
}
};
const getStatusLabel = (status: string) => {
switch (status) {
case 'completed':
return 'Completed';
case 'acknowledged':
return 'Acknowledged';
case 'paid':
return 'Paid';
case 'shipped':
return 'Shipped';
case 'unpaid':
return 'Unpaid';
case 'confirming':
return 'Confirming';
case 'cancelled':
return 'Cancelled';
case 'disputed':
return 'Disputed';
case 'underpaid':
return 'Underpaid';
default:
return status.charAt(0).toUpperCase() + status.slice(1);
}
};
if (isLoading) {
return (
<ChartSkeleton
title="Order Analytics"
description="Order status distribution and trends"
icon={BarChart3}
showStats={false}
/>
);
}
if (error || !data) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
Order Analytics
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center py-8">
<BarChart3 className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
<p className="text-muted-foreground">Failed to load order data</p>
</div>
</CardContent>
</Card>
);
}
const totalOrders = data.statusDistribution.reduce((sum, item) => sum + item.count, 0);
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
Order Analytics
</CardTitle>
<CardDescription>
Order status distribution for the selected time period
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{data.statusDistribution.length === 0 ? (
<div className="text-center py-8">
<BarChart3 className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
<p className="text-muted-foreground">No order data available for this period</p>
</div>
) : (
<>
{/* Summary */}
<div className="text-center mb-6">
<div className="text-3xl font-bold text-blue-600">
{totalOrders}
</div>
<div className="text-sm text-muted-foreground">Total Orders</div>
</div>
{/* Status Distribution */}
{data.statusDistribution.map((status) => {
const percentage = totalOrders > 0 ? (status.count / totalOrders * 100).toFixed(1) : '0';
const Icon = getStatusIcon(status._id);
return (
<div key={status._id} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-full ${getStatusColor(status._id)}`}>
{Icon}
</div>
<div>
<div className="font-medium">{getStatusLabel(status._id)}</div>
<div className="text-sm text-muted-foreground">
{status.count} orders
</div>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold">{percentage}%</div>
<div className="text-sm text-muted-foreground">of total</div>
</div>
</div>
);
})}
</>
)}
</div>
</CardContent>
</Card>
);
}