196 lines
5.7 KiB
TypeScript
196 lines
5.7 KiB
TypeScript
import { ColumnDef } from "@tanstack/react-table";
|
|
import { ArrowUpDown, ChevronDown, ChevronRight } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
|
|
interface Product {
|
|
pid: string;
|
|
sku: string;
|
|
title: string;
|
|
stock_quantity: number;
|
|
daily_sales_avg: number;
|
|
forecast_units: number;
|
|
forecast_revenue: number;
|
|
confidence_level: number;
|
|
}
|
|
|
|
export interface ForecastItem {
|
|
category: string;
|
|
categoryPath: string;
|
|
avgDailySales: number;
|
|
totalSold: number;
|
|
numProducts: number;
|
|
avgPrice: number;
|
|
avgTotalSold: number;
|
|
products?: Product[];
|
|
}
|
|
|
|
export const columns: ColumnDef<ForecastItem>[] = [
|
|
{
|
|
id: "expander",
|
|
header: () => null,
|
|
cell: ({ row }) => {
|
|
return row.getCanExpand() ? (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => row.toggleExpanded()}
|
|
className="p-0 h-auto"
|
|
>
|
|
{row.getIsExpanded() ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
|
</Button>
|
|
) : null;
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "category",
|
|
header: "Category",
|
|
cell: ({ row }) => (
|
|
<div>
|
|
<div className="font-medium">{row.original.category}</div>
|
|
{row.original.categoryPath && (
|
|
<div className="text-sm text-muted-foreground">
|
|
{row.original.categoryPath}
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
accessorKey: "avgDailySales",
|
|
header: ({ column }) => {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
className="whitespace-nowrap"
|
|
>
|
|
Avg Daily Sales
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
);
|
|
},
|
|
cell: ({ row }) => {
|
|
const value = row.getValue("avgDailySales") as number;
|
|
return value?.toFixed(2) || "0.00";
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "totalSold",
|
|
header: ({ column }) => {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Total Sold
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
);
|
|
},
|
|
cell: ({ row }) => {
|
|
const value = row.getValue("totalSold") as number;
|
|
return value?.toLocaleString() || "0";
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "numProducts",
|
|
header: ({ column }) => {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
className="whitespace-nowrap"
|
|
>
|
|
# Products
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
);
|
|
},
|
|
cell: ({ row }) => {
|
|
const value = row.getValue("numProducts") as number;
|
|
return value?.toLocaleString() || "0";
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "avgTotalSold",
|
|
header: ({ column }) => {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
className="whitespace-nowrap"
|
|
>
|
|
Avg Total Sold
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
);
|
|
},
|
|
cell: ({ row }) => {
|
|
const value = row.getValue("avgTotalSold") as number;
|
|
return value?.toFixed(2) || "0.00";
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "avgPrice",
|
|
header: ({ column }) => {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
className="whitespace-nowrap"
|
|
>
|
|
Avg Price
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
);
|
|
},
|
|
cell: ({ row }) => {
|
|
const value = row.getValue("avgPrice") as number;
|
|
return `$${value?.toFixed(2) || "0.00"}`;
|
|
},
|
|
},
|
|
];
|
|
|
|
export const renderSubComponent = ({ row }: { row: any }) => {
|
|
const products = row.original.products || [];
|
|
|
|
return (
|
|
<ScrollArea className="h-[400px] w-full rounded-md border p-4">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Product</TableHead>
|
|
<TableHead className="text-right">Stock</TableHead>
|
|
<TableHead className="text-right">Daily Sales</TableHead>
|
|
<TableHead className="text-right">Forecast Units</TableHead>
|
|
<TableHead className="text-right">Forecast Revenue</TableHead>
|
|
<TableHead className="text-right">Confidence</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{products.map((product: Product) => (
|
|
<TableRow key={product.pid}>
|
|
<TableCell>
|
|
<a
|
|
href={`https://backend.acherryontop.com/product/${product.pid}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="hover:underline"
|
|
>
|
|
{product.title}
|
|
</a>
|
|
<div className="text-sm text-muted-foreground">{product.sku}</div>
|
|
</TableCell>
|
|
<TableCell className="text-right">{product.stock_quantity}</TableCell>
|
|
<TableCell className="text-right">{product.daily_sales_avg.toFixed(1)}</TableCell>
|
|
<TableCell className="text-right">{product.forecast_units.toFixed(1)}</TableCell>
|
|
<TableCell className="text-right">{product.forecast_revenue.toFixed(2)}</TableCell>
|
|
<TableCell className="text-right">{product.confidence_level.toFixed(1)}%</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</ScrollArea>
|
|
);
|
|
};
|