Update frontend to match part 2

This commit is contained in:
2025-01-28 01:43:45 -05:00
parent 64d9ab2f83
commit 57b0e9a120
8 changed files with 82 additions and 76 deletions

View File

@@ -369,14 +369,16 @@ router.get('/pricing', async (req, res) => {
// Get price points analysis
const [pricePoints] = await pool.query(`
SELECT
p.price,
SUM(o.quantity) as salesVolume,
SUM(o.price * o.quantity) as revenue,
p.categories as category
CAST(p.price AS DECIMAL(15,3)) as price,
CAST(SUM(o.quantity) AS DECIMAL(15,3)) as salesVolume,
CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) as revenue,
c.name as category
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
JOIN product_categories pc ON p.pid = pc.pid
JOIN categories c ON pc.cat_id = c.cat_id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.price, p.categories
GROUP BY p.price, c.name
HAVING salesVolume > 0
ORDER BY revenue DESC
LIMIT 50
@@ -386,8 +388,8 @@ router.get('/pricing', async (req, res) => {
const [elasticity] = await pool.query(`
SELECT
DATE_FORMAT(o.date, '%Y-%m-%d') as date,
AVG(o.price) as price,
SUM(o.quantity) as demand
CAST(AVG(o.price) AS DECIMAL(15,3)) as price,
CAST(SUM(o.quantity) AS DECIMAL(15,3)) as demand
FROM orders o
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE_FORMAT(o.date, '%Y-%m-%d')
@@ -398,14 +400,17 @@ router.get('/pricing', async (req, res) => {
const [recommendations] = await pool.query(`
SELECT
p.title as product,
p.price as currentPrice,
CAST(p.price AS DECIMAL(15,3)) as currentPrice,
CAST(
ROUND(
CASE
WHEN AVG(o.quantity) > 10 THEN p.price * 1.1
WHEN AVG(o.quantity) < 2 THEN p.price * 0.9
ELSE p.price
END, 2
) AS DECIMAL(15,3)
) as recommendedPrice,
CAST(
ROUND(
SUM(o.price * o.quantity) *
CASE
@@ -413,6 +418,7 @@ router.get('/pricing', async (req, res) => {
WHEN AVG(o.quantity) < 2 THEN 0.95
ELSE 1
END, 2
) AS DECIMAL(15,3)
) as potentialRevenue,
CASE
WHEN AVG(o.quantity) > 10 THEN 85
@@ -422,9 +428,9 @@ router.get('/pricing', async (req, res) => {
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.pid
GROUP BY p.pid, p.price
HAVING ABS(recommendedPrice - currentPrice) > 0
ORDER BY potentialRevenue - SUM(o.price * o.quantity) DESC
ORDER BY potentialRevenue - CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) DESC
LIMIT 10
`);
@@ -536,14 +542,14 @@ router.get('/forecast', async (req, res) => {
const [results] = await pool.query(`
WITH category_metrics AS (
SELECT
c.cat_id as category_id,
c.cat_id,
c.name as category_name,
p.brand,
COUNT(DISTINCT p.pid) as num_products,
COALESCE(ROUND(SUM(o.quantity) / DATEDIFF(?, ?), 2), 0) as avg_daily_sales,
CAST(COALESCE(ROUND(SUM(o.quantity) / DATEDIFF(?, ?), 2), 0) AS DECIMAL(15,3)) as avg_daily_sales,
COALESCE(SUM(o.quantity), 0) as total_sold,
COALESCE(ROUND(SUM(o.quantity) / COUNT(DISTINCT p.pid), 2), 0) as avgTotalSold,
COALESCE(ROUND(AVG(o.price), 2), 0) as avg_price
CAST(COALESCE(ROUND(SUM(o.quantity) / COUNT(DISTINCT p.pid), 2), 0) AS DECIMAL(15,3)) as avgTotalSold,
CAST(COALESCE(ROUND(AVG(o.price), 2), 0) AS DECIMAL(15,3)) as avg_price
FROM categories c
JOIN product_categories pc ON c.cat_id = pc.cat_id
JOIN products p ON pc.pid = p.pid
@@ -564,7 +570,7 @@ router.get('/forecast', async (req, res) => {
pc.cat_id,
pm.first_received_date,
COALESCE(SUM(o.quantity), 0) as total_sold,
COALESCE(ROUND(AVG(o.price), 2), 0) as avg_price
CAST(COALESCE(ROUND(AVG(o.price), 2), 0) AS DECIMAL(15,3)) as avg_price
FROM products p
JOIN product_categories pc ON p.pid = pc.pid
JOIN product_metrics pm ON p.pid = pm.pid
@@ -589,8 +595,8 @@ router.get('/forecast', async (req, res) => {
)
) as products
FROM category_metrics cm
JOIN product_metrics pm ON cm.category_id = pm.cat_id
GROUP BY cm.category_id, cm.category_name, cm.brand, cm.num_products, cm.avg_daily_sales, cm.total_sold, cm.avgTotalSold, cm.avg_price
JOIN product_metrics pm ON cm.cat_id = pm.cat_id
GROUP BY cm.cat_id, cm.category_name, cm.brand, cm.num_products, cm.avg_daily_sales, cm.total_sold, cm.avgTotalSold, cm.avg_price
ORDER BY cm.total_sold DESC
`, [startDate, endDate, startDate, endDate, brand, startDate, endDate, startDate, endDate, brand, startDate, endDate]);

View File

@@ -11,33 +11,33 @@ interface Product {
sku: string;
title: string;
units_sold: number;
revenue: number;
profit: number;
revenue: string;
profit: string;
}
interface Category {
cat_id: number;
name: string;
total_revenue: number;
total_profit: number;
total_revenue: string;
total_profit: string;
total_units: number;
}
interface BestSellerBrand {
brand: string
units_sold: number
revenue: number
profit: number
growth_rate: number
revenue: string
profit: string
growth_rate: string
}
interface BestSellerCategory {
cat_id: number;
name: string;
units_sold: number;
revenue: number;
profit: number;
growth_rate: number;
revenue: string;
profit: string;
growth_rate: string;
}
interface BestSellersData {
@@ -98,8 +98,8 @@ export function BestSellers() {
<div className="text-sm text-muted-foreground">{product.sku}</div>
</TableCell>
<TableCell className="text-right">{product.units_sold}</TableCell>
<TableCell className="text-right">{formatCurrency(product.revenue)}</TableCell>
<TableCell className="text-right">{formatCurrency(product.profit)}</TableCell>
<TableCell className="text-right">{formatCurrency(Number(product.revenue))}</TableCell>
<TableCell className="text-right">{formatCurrency(Number(product.profit))}</TableCell>
</TableRow>
))}
</TableBody>
@@ -129,13 +129,13 @@ export function BestSellers() {
{brand.units_sold.toLocaleString()}
</TableCell>
<TableCell className="w-[15%] text-right">
{formatCurrency(brand.revenue)}
{formatCurrency(Number(brand.revenue))}
</TableCell>
<TableCell className="w-[15%] text-right">
{formatCurrency(brand.profit)}
{formatCurrency(Number(brand.profit))}
</TableCell>
<TableCell className="w-[15%] text-right">
{brand.growth_rate > 0 ? '+' : ''}{brand.growth_rate.toFixed(1)}%
{Number(brand.growth_rate) > 0 ? '+' : ''}{Number(brand.growth_rate).toFixed(1)}%
</TableCell>
</TableRow>
))}
@@ -160,8 +160,8 @@ export function BestSellers() {
<TableRow key={category.cat_id}>
<TableCell>{category.name}</TableCell>
<TableCell className="text-right">{category.total_units}</TableCell>
<TableCell className="text-right">{formatCurrency(category.total_revenue)}</TableCell>
<TableCell className="text-right">{formatCurrency(category.total_profit)}</TableCell>
<TableCell className="text-right">{formatCurrency(Number(category.total_revenue))}</TableCell>
<TableCell className="text-right">{formatCurrency(Number(category.total_profit))}</TableCell>
</TableRow>
))}
</TableBody>

View File

@@ -11,18 +11,18 @@ import { DateRangePicker } from "@/components/ui/date-range-picker-narrow"
interface ForecastData {
forecastSales: number
forecastRevenue: number
forecastRevenue: string
confidenceLevel: number
dailyForecasts: {
date: string
units: number
revenue: number
revenue: string
confidence: number
}[]
categoryForecasts: {
category: string
units: number
revenue: number
revenue: string
confidence: number
}[]
}
@@ -86,7 +86,7 @@ export function ForecastMetrics() {
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Forecast Revenue</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.forecastRevenue || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(Number(data?.forecastRevenue) || 0)}</p>
</div>
</div>
@@ -108,7 +108,7 @@ export function ForecastMetrics() {
tick={false}
/>
<Tooltip
formatter={(value: number) => [formatCurrency(value), "Revenue"]}
formatter={(value: string) => [formatCurrency(Number(value)), "Revenue"]}
labelFormatter={(date) => format(new Date(date), 'MMM d, yyyy')}
/>
<Area

View File

@@ -13,11 +13,11 @@ interface InventoryMetrics {
topVendors: {
vendor: string;
productCount: number;
averageStockLevel: number;
averageStockLevel: string;
}[];
stockTurnover: {
category: string;
rate: number;
rate: string;
}[];
}
@@ -70,7 +70,7 @@ export function InventoryStats() {
<BarChart data={data?.stockTurnover}>
<XAxis dataKey="category" />
<YAxis />
<Tooltip />
<Tooltip formatter={(value: string) => [Number(value).toFixed(2), "Rate"]} />
<Bar dataKey="rate" name="Turnover Rate" fill="#60a5fa" />
</BarChart>
</ResponsiveContainer>
@@ -93,7 +93,7 @@ export function InventoryStats() {
</div>
<div className="ml-4 text-right">
<p className="text-sm font-medium">
Avg. Stock: {vendor.averageStockLevel.toFixed(0)}
Avg. Stock: {Number(vendor.averageStockLevel).toFixed(0)}
</p>
</div>
</div>

View File

@@ -17,8 +17,8 @@ interface Product {
sku: string;
title: string;
stock_quantity: number;
daily_sales_avg: number;
days_of_inventory: number;
daily_sales_avg: string;
days_of_inventory: string;
reorder_qty: number;
last_purchase_date: string | null;
lead_time_status: string;
@@ -70,8 +70,8 @@ export function LowStockAlerts() {
<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.days_of_inventory.toFixed(1)}</TableCell>
<TableCell className="text-right">{Number(product.daily_sales_avg).toFixed(1)}</TableCell>
<TableCell className="text-right">{Number(product.days_of_inventory).toFixed(1)}</TableCell>
<TableCell className="text-right">{product.reorder_qty}</TableCell>
<TableCell>{product.last_purchase_date ? formatDate(product.last_purchase_date) : '-'}</TableCell>
<TableCell>

View File

@@ -12,13 +12,13 @@ import { DateRangePicker } from "@/components/ui/date-range-picker-narrow"
interface SalesData {
totalOrders: number
totalUnitsSold: number
totalCogs: number
totalRevenue: number
totalCogs: string
totalRevenue: string
dailySales: {
date: string
units: number
revenue: number
cogs: number
revenue: string
cogs: string
}[]
}
@@ -78,14 +78,14 @@ export function SalesMetrics() {
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Cost of Goods</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.totalCogs || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(Number(data?.totalCogs) || 0)}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Revenue</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.totalRevenue || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(Number(data?.totalRevenue) || 0)}</p>
</div>
</div>
@@ -107,7 +107,7 @@ export function SalesMetrics() {
tick={false}
/>
<Tooltip
formatter={(value: number) => [formatCurrency(value), "Revenue"]}
formatter={(value: string) => [formatCurrency(Number(value)), "Revenue"]}
labelFormatter={(date) => format(new Date(date), 'MMM d, yyyy')}
/>
<Area

View File

@@ -10,14 +10,14 @@ interface StockMetricsData {
totalProducts: number
productsInStock: number
totalStockUnits: number
totalStockCost: number
totalStockRetail: number
totalStockCost: string
totalStockRetail: string
brandStock: {
brand: string
variants: number
units: number
cost: number
retail: number
cost: string
retail: string
}[]
}
@@ -91,7 +91,7 @@ const renderActiveShape = (props: any) => {
fill="#000000"
className="text-base font-medium"
>
{formatCurrency(retail)}
{formatCurrency(Number(retail))}
</text>
</g>
);
@@ -154,14 +154,14 @@ export function StockMetrics() {
<DollarSign className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Stock Cost</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.totalStockCost || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(Number(data?.totalStockCost) || 0)}</p>
</div>
<div className="flex items-baseline justify-between">
<div className="flex items-center gap-2">
<ShoppingCart className="h-4 w-4 text-muted-foreground" />
<p className="text-sm font-medium text-muted-foreground">Stock Retail</p>
</div>
<p className="text-lg font-bold">{formatCurrency(data?.totalStockRetail || 0)}</p>
<p className="text-lg font-bold">{formatCurrency(Number(data?.totalStockRetail) || 0)}</p>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@ interface Product {
sku: string;
title: string;
stock_quantity: number;
daily_sales_avg: number;
daily_sales_avg: string;
reorder_qty: number;
last_purchase_date: string | null;
}
@@ -58,7 +58,7 @@ export function TopReplenishProducts() {
<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">{Number(product.daily_sales_avg).toFixed(1)}</TableCell>
<TableCell className="text-right">{product.reorder_qty}</TableCell>
<TableCell>{product.last_purchase_date ? product.last_purchase_date : '-'}</TableCell>
</TableRow>