"use client";
import * as React from "react";
import { useState, useEffect } from "react";
import { CalendarIcon } from "lucide-react";
import { format, parse } from "date-fns";
import {
Button,
} from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "sonner";
import { DataTable } from "@/components/data-table";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
interface ShopMetrics {
total_sales: number;
target_amount: number;
total_orders: number;
avg_order_value: number;
cancelled_orders: number;
cancelled_value: number;
ads_revenue: number;
ads_spend: number;
changes: {
sales_change: number;
orders_change: number;
ads_revenue_change: number;
ads_spend_change: number;
};
}
interface DailyData {
date: string;
sales: number;
ads_revenue: number;
}
export default function ShopDashboardImported() {
const [selectedMonth, setSelectedMonth] = useState<string | null>("2024-03");
const [metrics, setMetrics] = useState<ShopMetrics | null>(null);
const [dailyData, setDailyData] = useState<DailyData[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const availableMonths = Array.from({ length: 24 }, (_, i) => {
const date = new Date(2024, 0, 1);
date.setMonth(date.getMonth() + i);
return format(date, "yyyy-MM");
});
const fetchMetrics = async (month: string) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/metrics?month=${month}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
setMetrics(data);
} catch (e: any) {
setError(e.message || "An unexpected error occurred.");
console.error("Error fetching metrics:", e);
} finally {
setLoading(false);
}
};
const fetchDailyData = async (month: string) => {
try {
const response = await fetch(`/api/metrics`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ month: month, compareMonth: month }), // Fetch data for the same month for now
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
setDailyData(data.data.map((item: any) => ({
date: item.date,
sales: item.sales,
ads_revenue: item.adsRevenue,
})));
} catch (e: any) {
setError(e.message || "An unexpected error occurred.");
console.error("Error fetching daily data:", e);
}
};
useEffect(() => {
if (selectedMonth) {
fetchMetrics(selectedMonth);
fetchDailyData(selectedMonth);
}
}, [selectedMonth]);
const targetAchieved = metrics
? (Number(metrics.total_sales) / Number(metrics.target_amount)) * 100
: 0;
const handleDownload = () => {
toast.success("Download completed");
};
// Sample data for the DataTable
const tableData = [
{
id: 1,
header: "Executive Summary",
type: "Narrative",
status: "Done",
target: "5",
limit: "10",
reviewer: "Eddie Lake",
},
{
id: 2,
header: "Product Performance",
type: "Table of Contents",
status: "In Progress",
target: "8",
limit: "15",
reviewer: "Assign reviewer",
},
{
id: 3,
header: "Marketing Strategy",
type: "Technical Approach",
status: "Done",
target: "12",
limit: "20",
reviewer: "Jamik Tashpulatov",
},
];
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: 'Monthly Sales and Ads Revenue',
},
},
scales: {
x: {
title: {
display: true,
text: 'Day',
},
},
y: {
title: {
display: true,
text: 'Amount',
},
},
},
};
const chartData = {
labels: dailyData ? dailyData.map((item) => item.date) : [],
datasets: [
{
label: 'Total Sales',
data: dailyData ? dailyData.map((item) => item.sales) : [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
{
label: 'Ads Revenue',
data: dailyData ? dailyData.map((item) => item.ads_revenue) : [],
borderColor: 'rgb(53, 162, 235)',
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};
return (
<div className="flex min-h-screen flex-col bg-background">
<main className="flex-1 p-4 md:p-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Dashboard</h1>
<div className="flex items-center gap-2">
<Select
value={selectedMonth || ""}
onValueChange={(value) => setSelectedMonth(value)}
>
<SelectTrigger className="w-[200px]">
<CalendarIcon className="h-4 w-4 mr-2" />
<SelectValue>
{selectedMonth
? format(parse(selectedMonth, "yyyy-MM", new Date()), "MMMM<ctrl98>")
: "Select Month"}
</SelectValue>
</SelectTrigger>
<SelectContent>
{availableMonths.map((month) => (
<SelectItem key={month} value={month}>
{format(parse(month, "yyyy-MM", new Date()), "MMMM<ctrl98>")}
</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={handleDownload}>Download</Button>
</div>
</div>
{error && <div className="mt-4 text-red-500">Error: {error}</div>}
<Tabs defaultValue="overview" className="mt-6">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="HighSoStore">HighSoStore</TabsTrigger>
<TabsTrigger value="DataManagement">DataManagement</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Target Achieved
</CardTitle>
<TargetIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
{Number(targetAchieved).toFixed(1)}%
</div>
<p className="text-xs text-muted-foreground">
{targetAchieved >= 100
? "Target reached!"
: `${(100 - targetAchieved).toFixed(1)}% to go`}
</p>
<div className="mt-4 h-2 w-full rounded-full bg-muted">
<div
className="h-2 rounded-full bg-primary"
style={{ width: `${Math.min(targetAchieved, 100)}%` }}
/>
</div>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Shop Revenue
</CardTitle>
<DollarSignIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
${Number(metrics?.total_sales || 0).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{Number(metrics?.changes?.sales_change || 0) > 0
? "+"
: ""}
{Number(metrics?.changes?.sales_change || 0).toFixed(1)}%
from last month
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Orders
</CardTitle>
<ShoppingCartIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
{Number(metrics?.total_orders || 0).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{Number(metrics?.changes?.orders_change || 0) > 0
? "+"
: ""}
{Number(metrics?.changes?.orders_change || 0).toFixed(1)}%
from last month
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Average Order Value
</CardTitle>
<BarChartIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
${Number(metrics?.avg_order_value || 0).toFixed(2)}
</div>
<p className="text-xs text-muted-foreground">
Average value per order
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Ads ROAS</CardTitle>
<TrendingUpIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
{(
Number(metrics?.ads_revenue || 0) /
Number(metrics?.ads_spend || 1)
).toFixed(2)}
x
</div>
<p className="text-xs text-muted-foreground">
{Number(metrics?.changes?.ads_revenue_change || 0) > 0
? "+"
: ""}
{Number(metrics?.changes?.ads_revenue_change || 0).toFixed(
1
)}
% from last month
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Ads Revenue
</CardTitle>
<LineChartIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
${Number(metrics?.ads_revenue || 0).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{Number(metrics?.changes?.ads_revenue_change || 0) > 0
? "+"
: ""}
{Number(metrics?.changes?.ads_revenue_change || 0).toFixed(
1
)}
% from last month
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Cancelled Orders
</CardTitle>
<XCircleIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
{Number(metrics?.cancelled_orders || 0).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{(
(Number(metrics?.cancelled_orders || 0) /
Number(metrics?.total_orders || 1)) *
100
).toFixed(1)}
% of total orders
</p>
</>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Total Cancelled Value
</CardTitle>
<AlertCircleIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-6 w-24" />
) : (
<>
<div className="text-2xl font-bold">
${Number(metrics?.cancelled_value || 0).toLocaleString()}
</div>
<p className="text-xs text-muted-foreground">
{(
(Number(metrics?.cancelled_value || 0) /
Number(metrics?.total_sales || 1)) *
100
).toFixed(1)}
% of total sales
</p>
</>
)}
</CardContent>
</Card>
</div>
<div className="mt-6">
{dailyData && <Line data={chartData} options={chartOptions} />}
</div>
<div className="mt-6">
<Card>
<CardHeader>
<CardTitle>Performance Metrics</CardTitle>
<CardDescription>
Track and manage your shop's key performance indicators
</CardDescription>
</CardHeader>
<CardContent>
<DataTable data={tableData} />
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="DataManagement" className="space-y-6">
{/* <DataImport /> */}
</TabsContent>
</Tabs>
</main>
</div>
);
}
type IconProps = React.ComponentProps<"svg">;
function AlertCircleIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" x2="12" y1="8" y2="12" />
<line x1="12" x2="12.01" y1="16" y2="16" />
</svg>
);
}
function BarChartIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="12" x2="12" y1="20" y2="10" />
<line x1="18" x2="18" y1="20" y2="4" />
<line x1="6" x2="6" y1="20" y2="16" />
</svg>
);
}
function DollarSignIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="12" x2="12" y1="2" y2="22" />
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
</svg>
);
}
function LineChartIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3 3v18h18" />
<path d="m19 9-5 5-4-4-3 3" />
</svg>
);
}
function TargetIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
);
}
function TrendingUpIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="22 7 13.5 15.5 8.5 10.5 2 17" />
<polyline points="16 7 22 7 22 13" />
</svg>
);
}
function XCircleIcon(props: IconProps) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="m15 9-6 6" />
<path d="m9 9 6 6" />
</svg>
);
}