Fix and update forecasting details
This commit is contained in:
@@ -575,7 +575,7 @@ router.get('/forecast', async (req, res) => {
|
||||
JSON_ARRAYAGG(
|
||||
JSON_OBJECT(
|
||||
'product_id', pm.product_id,
|
||||
'name', pm.title,
|
||||
'title', pm.title,
|
||||
'sku', pm.sku,
|
||||
'stock_quantity', pm.stock_quantity,
|
||||
'total_sold', pm.total_sold,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ColumnDef, Column } from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { ArrowUpDown, ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
|
||||
export type ProductDetail = {
|
||||
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 ProductDetail {
|
||||
product_id: string;
|
||||
name: string;
|
||||
sku: string;
|
||||
stock_quantity: number;
|
||||
total_sold: number;
|
||||
avg_price: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type ForecastItem = {
|
||||
export interface ForecastItem {
|
||||
category: string;
|
||||
avgDailySales: number;
|
||||
totalSold: number;
|
||||
@@ -20,49 +20,36 @@ export type ForecastItem = {
|
||||
avgPrice: number;
|
||||
avgTotalSold: number;
|
||||
products?: ProductDetail[];
|
||||
};
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<ForecastItem>[] = [
|
||||
{
|
||||
id: "expander",
|
||||
header: () => null,
|
||||
cell: ({ row }) => {
|
||||
return row.original.products?.length ? (
|
||||
return row.getCanExpand() ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => row.toggleExpanded()}
|
||||
className="p-0 hover:bg-transparent"
|
||||
className="p-0 h-auto"
|
||||
>
|
||||
{row.getIsExpanded() ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
{row.getIsExpanded() ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
</Button>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "category",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
Category
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
header: "Category",
|
||||
},
|
||||
{
|
||||
accessorKey: "avgDailySales",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
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" />
|
||||
@@ -71,12 +58,12 @@ export const columns: ColumnDef<ForecastItem>[] = [
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue("avgDailySales") as number;
|
||||
return value ? value.toFixed(2) : '0.00';
|
||||
return value?.toFixed(2) || "0.00";
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "totalSold",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -89,16 +76,36 @@ export const columns: ColumnDef<ForecastItem>[] = [
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue("totalSold") as number;
|
||||
return value ? value.toLocaleString() : '0';
|
||||
return value?.toLocaleString() || "0";
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "avgTotalSold",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
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" />
|
||||
@@ -107,30 +114,17 @@ export const columns: ColumnDef<ForecastItem>[] = [
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue("avgTotalSold") as number;
|
||||
return value ? value.toFixed(2) : '0.00';
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "numProducts",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
# of Products
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
return value?.toFixed(2) || "0.00";
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "avgPrice",
|
||||
header: ({ column }: { column: Column<ForecastItem> }) => {
|
||||
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" />
|
||||
@@ -139,7 +133,7 @@ export const columns: ColumnDef<ForecastItem>[] = [
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue("avgPrice") as number;
|
||||
return value ? `$${value.toFixed(2)}` : '$0.00';
|
||||
return `$${value?.toFixed(2) || "0.00"}`;
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -148,23 +142,29 @@ export const renderSubComponent = ({ row }: { row: any }) => {
|
||||
const products = row.original.products || [];
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-6 gap-4 font-medium mb-2">
|
||||
<div>Name</div>
|
||||
<div>SKU</div>
|
||||
<div>Stock</div>
|
||||
<div>Total Sold</div>
|
||||
<div>Avg Price</div>
|
||||
</div>
|
||||
{products.map((product: ProductDetail) => (
|
||||
<div key={product.product_id} className="grid grid-cols-6 gap-4 py-2 border-t">
|
||||
<div>{product.name}</div>
|
||||
<div>{product.sku}</div>
|
||||
<div>{product.stock_quantity}</div>
|
||||
<div>{product.total_sold}</div>
|
||||
<div>${product.avg_price.toFixed(2)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ScrollArea className="h-[400px] w-full rounded-md border p-4">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Product Name</TableHead>
|
||||
<TableHead>SKU</TableHead>
|
||||
<TableHead>Stock Quantity</TableHead>
|
||||
<TableHead>Total Sold</TableHead>
|
||||
<TableHead>Average Price</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{products.map((product: ProductDetail) => (
|
||||
<TableRow key={product.product_id}>
|
||||
<TableCell>{product.name}</TableCell>
|
||||
<TableCell>{product.sku}</TableCell>
|
||||
<TableCell>{product.stock_quantity.toLocaleString()}</TableCell>
|
||||
<TableCell>{product.total_sold.toLocaleString()}</TableCell>
|
||||
<TableCell>${product.avg_price.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
getExpandedRowModel,
|
||||
Row,
|
||||
Header,
|
||||
HeaderGroup,
|
||||
getExpandedRowModel,
|
||||
} from "@tanstack/react-table";
|
||||
import { columns, ForecastItem, renderSubComponent } from "@/components/forecasting/columns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
@@ -67,7 +67,7 @@ export default function Forecasting() {
|
||||
avgTotalSold: Number(item.avgTotalSold) || 0,
|
||||
products: item.products?.map((p: any) => ({
|
||||
product_id: p.product_id,
|
||||
name: p.product_name,
|
||||
name: p.title,
|
||||
sku: p.sku,
|
||||
stock_quantity: Number(p.stock_quantity) || 0,
|
||||
total_sold: Number(p.total_sold) || 0,
|
||||
@@ -85,10 +85,10 @@ export default function Forecasting() {
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getExpandedRowModel: getExpandedRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
getRowCanExpand: () => true,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
getRowCanExpand: () => true,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -158,7 +158,7 @@ export default function Forecasting() {
|
||||
</TableRow>
|
||||
{row.getIsExpanded() && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length}>
|
||||
<TableCell colSpan={columns.length} className="p-0">
|
||||
{renderSubComponent({ row })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -167,7 +167,10 @@ export default function Forecasting() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user