192 lines
7.5 KiB
TypeScript
192 lines
7.5 KiB
TypeScript
import { useQuery } from "@tanstack/react-query"
|
|
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import config from "@/config"
|
|
import { formatCurrency } from "@/lib/utils"
|
|
|
|
interface BestSellerProduct {
|
|
product_id: number
|
|
sku: string
|
|
title: string
|
|
units_sold: number
|
|
revenue: number
|
|
profit: number
|
|
growth_rate: number
|
|
}
|
|
|
|
interface BestSellerBrand {
|
|
brand: string
|
|
units_sold: number
|
|
revenue: number
|
|
profit: number
|
|
growth_rate: number
|
|
}
|
|
|
|
interface BestSellerCategory {
|
|
category_id: number
|
|
name: string
|
|
units_sold: number
|
|
revenue: number
|
|
profit: number
|
|
growth_rate: number
|
|
}
|
|
|
|
interface BestSellersData {
|
|
products: BestSellerProduct[]
|
|
brands: BestSellerBrand[]
|
|
categories: BestSellerCategory[]
|
|
}
|
|
|
|
export function BestSellers() {
|
|
const { data } = useQuery<BestSellersData>({
|
|
queryKey: ["best-sellers"],
|
|
queryFn: async () => {
|
|
const response = await fetch(`${config.apiUrl}/dashboard/best-sellers`)
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch best sellers")
|
|
}
|
|
return response.json()
|
|
},
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<Tabs defaultValue="products">
|
|
<CardHeader>
|
|
<div className="flex flex-row items-center justify-between">
|
|
<CardTitle className="text-lg font-medium">Best Sellers</CardTitle>
|
|
<TabsList>
|
|
<TabsTrigger value="products">Products</TabsTrigger>
|
|
<TabsTrigger value="brands">Brands</TabsTrigger>
|
|
<TabsTrigger value="categories">Categories</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<TabsContent value="products">
|
|
<ScrollArea className="h-[385px] w-full">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[40%]">Product</TableHead>
|
|
<TableHead className="w-[15%] text-right">Sales</TableHead>
|
|
<TableHead className="w-[15%] text-right">Revenue</TableHead>
|
|
<TableHead className="w-[15%] text-right">Profit</TableHead>
|
|
<TableHead className="w-[15%] text-right">Growth</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data?.products.map((product) => (
|
|
<TableRow key={product.product_id}>
|
|
<TableCell className="w-[40%]">
|
|
<div>
|
|
<a
|
|
href={`https://backend.acherryontop.com/product/${product.product_id}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="font-medium hover:underline"
|
|
>
|
|
{product.title}
|
|
</a>
|
|
<p className="text-sm text-muted-foreground">{product.sku}</p>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{product.units_sold.toLocaleString()}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(product.revenue)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(product.profit)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{product.growth_rate > 0 ? '+' : ''}{product.growth_rate.toFixed(1)}%
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</ScrollArea>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="brands">
|
|
<ScrollArea className="h-[400px] w-full">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[40%]">Brand</TableHead>
|
|
<TableHead className="w-[15%] text-right">Sales</TableHead>
|
|
<TableHead className="w-[15%] text-right">Revenue</TableHead>
|
|
<TableHead className="w-[15%] text-right">Profit</TableHead>
|
|
<TableHead className="w-[15%] text-right">Growth</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data?.brands.map((brand) => (
|
|
<TableRow key={brand.brand}>
|
|
<TableCell className="w-[40%]">
|
|
<p className="font-medium">{brand.brand}</p>
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{brand.units_sold.toLocaleString()}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(brand.revenue)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(brand.profit)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{brand.growth_rate > 0 ? '+' : ''}{brand.growth_rate.toFixed(1)}%
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</ScrollArea>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="categories">
|
|
<ScrollArea className="h-[400px] w-full">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[40%]">Category</TableHead>
|
|
<TableHead className="w-[15%] text-right">Sales</TableHead>
|
|
<TableHead className="w-[15%] text-right">Revenue</TableHead>
|
|
<TableHead className="w-[15%] text-right">Profit</TableHead>
|
|
<TableHead className="w-[15%] text-right">Growth</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data?.categories.map((category) => (
|
|
<TableRow key={category.category_id}>
|
|
<TableCell className="w-[40%]">
|
|
<p className="font-medium">{category.name}</p>
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{category.units_sold.toLocaleString()}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(category.revenue)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{formatCurrency(category.profit)}
|
|
</TableCell>
|
|
<TableCell className="w-[15%] text-right">
|
|
{category.growth_rate > 0 ? '+' : ''}{category.growth_rate.toFixed(1)}%
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</ScrollArea>
|
|
</TabsContent>
|
|
</CardContent>
|
|
</Tabs>
|
|
</>
|
|
)
|
|
}
|