new com

"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> ); }
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.