Frontend fixes - dashboard, products, forecasting
This commit is contained in:
@@ -584,7 +584,7 @@ router.get('/best-sellers', async (req, res) => {
|
|||||||
GROUP BY c.cat_id, c.name
|
GROUP BY c.cat_id, c.name
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
cat_id as category_id,
|
cat_id,
|
||||||
name,
|
name,
|
||||||
units_sold,
|
units_sold,
|
||||||
revenue,
|
revenue,
|
||||||
@@ -604,28 +604,30 @@ router.get('/best-sellers', async (req, res) => {
|
|||||||
|
|
||||||
// Format response with explicit type conversion
|
// Format response with explicit type conversion
|
||||||
const formattedProducts = products.map(p => ({
|
const formattedProducts = products.map(p => ({
|
||||||
...p,
|
pid: p.pid,
|
||||||
|
sku: p.sku,
|
||||||
|
title: p.title,
|
||||||
units_sold: parseInt(p.units_sold) || 0,
|
units_sold: parseInt(p.units_sold) || 0,
|
||||||
revenue: parseFloat(p.revenue) || 0,
|
revenue: p.revenue.toString(),
|
||||||
profit: parseFloat(p.profit) || 0,
|
profit: p.profit.toString(),
|
||||||
growth_rate: parseFloat(p.growth_rate) || 0
|
growth_rate: p.growth_rate.toString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const formattedBrands = brands.map(b => ({
|
const formattedBrands = brands.map(b => ({
|
||||||
brand: b.brand,
|
brand: b.brand,
|
||||||
units_sold: parseInt(b.units_sold) || 0,
|
units_sold: parseInt(b.units_sold) || 0,
|
||||||
revenue: parseFloat(b.revenue) || 0,
|
revenue: b.revenue.toString(),
|
||||||
profit: parseFloat(b.profit) || 0,
|
profit: b.profit.toString(),
|
||||||
growth_rate: parseFloat(b.growth_rate) || 0
|
growth_rate: b.growth_rate.toString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const formattedCategories = categories.map(c => ({
|
const formattedCategories = categories.map(c => ({
|
||||||
category_id: c.cat_id,
|
cat_id: c.cat_id,
|
||||||
name: c.name,
|
name: c.name,
|
||||||
units_sold: parseInt(c.units_sold) || 0,
|
units_sold: parseInt(c.units_sold) || 0,
|
||||||
revenue: parseFloat(c.revenue) || 0,
|
revenue: c.revenue.toString(),
|
||||||
profit: parseFloat(c.profit) || 0,
|
profit: c.profit.toString(),
|
||||||
growth_rate: parseFloat(c.growth_rate) || 0
|
growth_rate: c.growth_rate.toString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ router.get('/', async (req, res) => {
|
|||||||
p.pid,
|
p.pid,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT overstock_days FROM stock_thresholds st
|
(SELECT overstock_days FROM stock_thresholds st
|
||||||
WHERE st.cat_id IN (
|
WHERE st.category_id IN (
|
||||||
SELECT pc.cat_id
|
SELECT pc.cat_id
|
||||||
FROM product_categories pc
|
FROM product_categories pc
|
||||||
WHERE pc.pid = p.pid
|
WHERE pc.pid = p.pid
|
||||||
@@ -181,7 +181,7 @@ router.get('/', async (req, res) => {
|
|||||||
ORDER BY st.vendor IS NULL
|
ORDER BY st.vendor IS NULL
|
||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
(SELECT overstock_days FROM stock_thresholds st
|
(SELECT overstock_days FROM stock_thresholds st
|
||||||
WHERE st.cat_id IS NULL
|
WHERE st.category_id IS NULL
|
||||||
AND (st.vendor = p.vendor OR st.vendor IS NULL)
|
AND (st.vendor = p.vendor OR st.vendor IS NULL)
|
||||||
ORDER BY st.vendor IS NULL
|
ORDER BY st.vendor IS NULL
|
||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
@@ -651,11 +651,10 @@ router.get('/:id/time-series', async (req, res) => {
|
|||||||
const [monthlySales] = await pool.query(`
|
const [monthlySales] = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(date, '%Y-%m') as month,
|
DATE_FORMAT(date, '%Y-%m') as month,
|
||||||
COUNT(DISTINCT order_id) as order_count,
|
COUNT(DISTINCT order_number) as order_count,
|
||||||
SUM(quantity) as units_sold,
|
SUM(quantity) as units_sold,
|
||||||
CAST(SUM(price * quantity) AS DECIMAL(15,3)) as revenue,
|
CAST(SUM(price * quantity) AS DECIMAL(15,3)) as revenue
|
||||||
CAST(SUM((price - cost_price) * quantity) AS DECIMAL(15,3)) as profit
|
FROM orders
|
||||||
FROM order_items
|
|
||||||
WHERE pid = ?
|
WHERE pid = ?
|
||||||
AND canceled = false
|
AND canceled = false
|
||||||
GROUP BY DATE_FORMAT(date, '%Y-%m')
|
GROUP BY DATE_FORMAT(date, '%Y-%m')
|
||||||
@@ -669,20 +668,22 @@ router.get('/:id/time-series', async (req, res) => {
|
|||||||
order_count: parseInt(month.order_count),
|
order_count: parseInt(month.order_count),
|
||||||
units_sold: parseInt(month.units_sold),
|
units_sold: parseInt(month.units_sold),
|
||||||
revenue: parseFloat(month.revenue),
|
revenue: parseFloat(month.revenue),
|
||||||
profit: parseFloat(month.profit)
|
profit: 0 // Set to 0 since we don't have cost data in orders table
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Get recent orders
|
// Get recent orders
|
||||||
const [recentOrders] = await pool.query(`
|
const [recentOrders] = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(date, '%Y-%m-%d') as date,
|
DATE_FORMAT(date, '%Y-%m-%d') as date,
|
||||||
order_id,
|
order_number,
|
||||||
quantity,
|
quantity,
|
||||||
price,
|
price,
|
||||||
discount,
|
discount,
|
||||||
tax,
|
tax,
|
||||||
shipping
|
shipping,
|
||||||
FROM order_items
|
customer_name as customer,
|
||||||
|
status
|
||||||
|
FROM orders
|
||||||
WHERE pid = ?
|
WHERE pid = ?
|
||||||
AND canceled = false
|
AND canceled = false
|
||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
@@ -711,7 +712,7 @@ router.get('/:id/time-series', async (req, res) => {
|
|||||||
END as lead_time_days
|
END as lead_time_days
|
||||||
FROM purchase_orders
|
FROM purchase_orders
|
||||||
WHERE pid = ?
|
WHERE pid = ?
|
||||||
AND status != ${PurchaseOrderStatus.CANCELED}
|
AND status != ${PurchaseOrderStatus.Canceled}
|
||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
LIMIT 10
|
LIMIT 10
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ interface Product {
|
|||||||
interface Category {
|
interface Category {
|
||||||
cat_id: number;
|
cat_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
total_revenue: string;
|
units_sold: number;
|
||||||
total_profit: string;
|
revenue: string;
|
||||||
total_units: number;
|
profit: string;
|
||||||
|
growth_rate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BestSellerBrand {
|
interface BestSellerBrand {
|
||||||
@@ -159,9 +160,9 @@ export function BestSellers() {
|
|||||||
{data?.categories.map((category) => (
|
{data?.categories.map((category) => (
|
||||||
<TableRow key={category.cat_id}>
|
<TableRow key={category.cat_id}>
|
||||||
<TableCell>{category.name}</TableCell>
|
<TableCell>{category.name}</TableCell>
|
||||||
<TableCell className="text-right">{category.total_units}</TableCell>
|
<TableCell className="text-right">{category.units_sold}</TableCell>
|
||||||
<TableCell className="text-right">{formatCurrency(Number(category.total_revenue))}</TableCell>
|
<TableCell className="text-right">{formatCurrency(Number(category.revenue))}</TableCell>
|
||||||
<TableCell className="text-right">{formatCurrency(Number(category.total_profit))}</TableCell>
|
<TableCell className="text-right">{formatCurrency(Number(category.profit))}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ export function ProductDetail({ productId, onClose }: ProductDetailProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold">{product?.title || 'Loading...'}</h2>
|
<VaulDrawer.Title className="text-xl font-semibold">{product?.title || 'Loading...'}</VaulDrawer.Title>
|
||||||
<p className="text-sm text-muted-foreground">{product?.SKU || ''}</p>
|
<VaulDrawer.Description className="text-sm text-muted-foreground">{product?.SKU || ''}</VaulDrawer.Description>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||||
|
|||||||
@@ -67,12 +67,14 @@ export default function Forecasting() {
|
|||||||
avgTotalSold: Number(item.avgTotalSold) || 0,
|
avgTotalSold: Number(item.avgTotalSold) || 0,
|
||||||
products: item.products?.map((p: any) => ({
|
products: item.products?.map((p: any) => ({
|
||||||
pid: p.pid,
|
pid: p.pid,
|
||||||
name: p.title,
|
title: p.title,
|
||||||
sku: p.sku,
|
sku: p.sku,
|
||||||
stock_quantity: Number(p.stock_quantity) || 0,
|
stock_quantity: Number(p.stock_quantity) || 0,
|
||||||
total_sold: Number(p.total_sold) || 0,
|
total_sold: Number(p.total_sold) || 0,
|
||||||
avg_price: Number(p.avg_price) || 0,
|
daily_sales_avg: Number(p.daily_sales_avg) || 0,
|
||||||
first_received_date: p.first_received_date,
|
forecast_units: Number(p.forecast_units) || 0,
|
||||||
|
forecast_revenue: Number(p.forecast_revenue) || 0,
|
||||||
|
confidence_level: Number(p.confidence_level) || 0
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user