Merge branch 'master' into add-product-upload-page

This commit is contained in:
2025-02-23 15:40:54 -05:00
parent 3f16413769
commit f628774267
47 changed files with 4674 additions and 3199 deletions

View File

@@ -85,9 +85,7 @@ export function DataManagement() {
const [] = useState<ImportProgress | null>(null);
const [eventSource, setEventSource] = useState<EventSource | null>(null);
const [importHistory, setImportHistory] = useState<ImportHistoryRecord[]>([]);
const [calculateHistory, setCalculateHistory] = useState<
CalculateHistoryRecord[]
>([]);
const [calculateHistory, setCalculateHistory] = useState<CalculateHistoryRecord[]>([]);
const [moduleStatus, setModuleStatus] = useState<ModuleStatus[]>([]);
const [tableStatus, setTableStatus] = useState<TableStatus[]>([]);
const [scriptOutput, setScriptOutput] = useState<string[]>([]);
@@ -368,6 +366,10 @@ export function DataManagement() {
fetch(`${config.apiUrl}/csv/status/tables`),
]);
if (!importRes.ok || !calcRes.ok || !moduleRes.ok || !tableRes.ok) {
throw new Error('One or more requests failed');
}
const [importData, calcData, moduleData, tableData] = await Promise.all([
importRes.json(),
calcRes.json(),
@@ -375,52 +377,66 @@ export function DataManagement() {
tableRes.json(),
]);
setImportHistory(importData);
setCalculateHistory(calcData);
setModuleStatus(moduleData);
setTableStatus(tableData);
// Ensure we're setting arrays even if the response is empty or invalid
setImportHistory(Array.isArray(importData) ? importData : []);
setCalculateHistory(Array.isArray(calcData) ? calcData : []);
setModuleStatus(Array.isArray(moduleData) ? moduleData : []);
setTableStatus(Array.isArray(tableData) ? tableData : []);
} catch (error) {
console.error("Error fetching history:", error);
// Set empty arrays as fallback
setImportHistory([]);
setCalculateHistory([]);
setModuleStatus([]);
setTableStatus([]);
}
};
const refreshTableStatus = async () => {
try {
const response = await fetch(`${config.apiUrl}/csv/status/tables`);
if (!response.ok) throw new Error('Failed to fetch table status');
const data = await response.json();
setTableStatus(data);
setTableStatus(Array.isArray(data) ? data : []);
} catch (error) {
toast.error("Failed to refresh table status");
setTableStatus([]);
}
};
const refreshModuleStatus = async () => {
try {
const response = await fetch(`${config.apiUrl}/csv/status/modules`);
if (!response.ok) throw new Error('Failed to fetch module status');
const data = await response.json();
setModuleStatus(data);
setModuleStatus(Array.isArray(data) ? data : []);
} catch (error) {
toast.error("Failed to refresh module status");
setModuleStatus([]);
}
};
const refreshImportHistory = async () => {
try {
const response = await fetch(`${config.apiUrl}/csv/history/import`);
if (!response.ok) throw new Error('Failed to fetch import history');
const data = await response.json();
setImportHistory(data);
setImportHistory(Array.isArray(data) ? data : []);
} catch (error) {
toast.error("Failed to refresh import history");
setImportHistory([]);
}
};
const refreshCalculateHistory = async () => {
try {
const response = await fetch(`${config.apiUrl}/csv/history/calculate`);
if (!response.ok) throw new Error('Failed to fetch calculate history');
const data = await response.json();
setCalculateHistory(data);
setCalculateHistory(Array.isArray(data) ? data : []);
} catch (error) {
toast.error("Failed to refresh calculate history");
setCalculateHistory([]);
}
};

View File

@@ -914,7 +914,7 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
const hasMultiValueRows = rowsWithValues.some(count => count > 1);
// Filter out empty rows and rows with single values (if we have multi-value rows)
const nonEmptyRows = data.filter((row, index) => {
const nonEmptyRows = data.filter((_row, index) => {
const nonEmptyCount = rowsWithValues[index];
// Keep the row if:

View File

@@ -22,7 +22,7 @@ export function Login() {
setIsLoading(true);
try {
const url = isDev ? "/auth-inv/login" : `${config.authUrl}/login`;
const url = `${config.authUrl}/login`;
console.log("Making login request:", {
url,
method: "POST",

View File

@@ -22,7 +22,6 @@ import {
import { motion } from 'motion/react';
import {
PurchaseOrderStatus,
ReceivingStatus as ReceivingStatusCode,
getPurchaseOrderStatusLabel,
getReceivingStatusLabel,
getPurchaseOrderStatusVariant,
@@ -113,7 +112,7 @@ export default function PurchaseOrders() {
statuses: string[];
}>({
vendors: [],
statuses: [],
statuses: []
});
const [pagination, setPagination] = useState({
total: 0,
@@ -154,15 +153,57 @@ export default function PurchaseOrders() {
fetch('/api/purchase-orders/cost-analysis')
]);
const [
purchaseOrdersData,
vendorMetricsData,
costAnalysisData
] = await Promise.all([
purchaseOrdersRes.json() as Promise<PurchaseOrdersResponse>,
vendorMetricsRes.json(),
costAnalysisRes.json()
]);
// Initialize default data
let purchaseOrdersData: PurchaseOrdersResponse = {
orders: [],
summary: {
order_count: 0,
total_ordered: 0,
total_received: 0,
fulfillment_rate: 0,
total_value: 0,
avg_cost: 0
},
pagination: {
total: 0,
pages: 0,
page: 1,
limit: 100
},
filters: {
vendors: [],
statuses: []
}
};
let vendorMetricsData: VendorMetrics[] = [];
let costAnalysisData: CostAnalysis = {
unique_products: 0,
avg_cost: 0,
min_cost: 0,
max_cost: 0,
cost_variance: 0,
total_spend_by_category: []
};
// Only try to parse responses if they were successful
if (purchaseOrdersRes.ok) {
purchaseOrdersData = await purchaseOrdersRes.json();
} else {
console.error('Failed to fetch purchase orders:', await purchaseOrdersRes.text());
}
if (vendorMetricsRes.ok) {
vendorMetricsData = await vendorMetricsRes.json();
} else {
console.error('Failed to fetch vendor metrics:', await vendorMetricsRes.text());
}
if (costAnalysisRes.ok) {
costAnalysisData = await costAnalysisRes.json();
} else {
console.error('Failed to fetch cost analysis:', await costAnalysisRes.text());
}
setPurchaseOrders(purchaseOrdersData.orders);
setPagination(purchaseOrdersData.pagination);
@@ -172,6 +213,27 @@ export default function PurchaseOrders() {
setCostAnalysis(costAnalysisData);
} catch (error) {
console.error('Error fetching data:', error);
// Set default values in case of error
setPurchaseOrders([]);
setPagination({ total: 0, pages: 0, page: 1, limit: 100 });
setFilterOptions({ vendors: [], statuses: [] });
setSummary({
order_count: 0,
total_ordered: 0,
total_received: 0,
fulfillment_rate: 0,
total_value: 0,
avg_cost: 0
});
setVendorMetrics([]);
setCostAnalysis({
unique_products: 0,
avg_cost: 0,
min_cost: 0,
max_cost: 0,
cost_variance: 0,
total_spend_by_category: []
});
} finally {
setLoading(false);
}
@@ -311,7 +373,7 @@ export default function PurchaseOrders() {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Vendors</SelectItem>
{filterOptions.vendors.map(vendor => (
{filterOptions?.vendors?.map(vendor => (
<SelectItem key={vendor} value={vendor}>
{vendor}
</SelectItem>

View File

@@ -3,10 +3,10 @@ export interface Product {
title: string;
SKU: string;
stock_quantity: number;
price: string; // DECIMAL(15,3)
regular_price: string; // DECIMAL(15,3)
cost_price: string; // DECIMAL(15,3)
landing_cost_price: string | null; // DECIMAL(15,3)
price: string; // numeric(15,3)
regular_price: string; // numeric(15,3)
cost_price: string; // numeric(15,3)
landing_cost_price: string | null; // numeric(15,3)
barcode: string;
vendor: string;
vendor_reference: string;
@@ -24,32 +24,32 @@ export interface Product {
updated_at: string;
// Metrics
daily_sales_avg?: string; // DECIMAL(15,3)
weekly_sales_avg?: string; // DECIMAL(15,3)
monthly_sales_avg?: string; // DECIMAL(15,3)
avg_quantity_per_order?: string; // DECIMAL(15,3)
daily_sales_avg?: string; // numeric(15,3)
weekly_sales_avg?: string; // numeric(15,3)
monthly_sales_avg?: string; // numeric(15,3)
avg_quantity_per_order?: string; // numeric(15,3)
number_of_orders?: number;
first_sale_date?: string;
last_sale_date?: string;
last_purchase_date?: string;
days_of_inventory?: string; // DECIMAL(15,3)
weeks_of_inventory?: string; // DECIMAL(15,3)
reorder_point?: string; // DECIMAL(15,3)
safety_stock?: string; // DECIMAL(15,3)
avg_margin_percent?: string; // DECIMAL(15,3)
total_revenue?: string; // DECIMAL(15,3)
inventory_value?: string; // DECIMAL(15,3)
cost_of_goods_sold?: string; // DECIMAL(15,3)
gross_profit?: string; // DECIMAL(15,3)
gmroi?: string; // DECIMAL(15,3)
avg_lead_time_days?: string; // DECIMAL(15,3)
days_of_inventory?: string; // numeric(15,3)
weeks_of_inventory?: string; // numeric(15,3)
reorder_point?: string; // numeric(15,3)
safety_stock?: string; // numeric(15,3)
avg_margin_percent?: string; // numeric(15,3)
total_revenue?: string; // numeric(15,3)
inventory_value?: string; // numeric(15,3)
cost_of_goods_sold?: string; // numeric(15,3)
gross_profit?: string; // numeric(15,3)
gmroi?: string; // numeric(15,3)
avg_lead_time_days?: string; // numeric(15,3)
last_received_date?: string;
abc_class?: string;
stock_status?: string;
turnover_rate?: string; // DECIMAL(15,3)
current_lead_time?: string; // DECIMAL(15,3)
target_lead_time?: string; // DECIMAL(15,3)
turnover_rate?: string; // numeric(15,3)
current_lead_time?: string; // numeric(15,3)
target_lead_time?: string; // numeric(15,3)
lead_time_status?: string;
reorder_qty?: number;
overstocked_amt?: string; // DECIMAL(15,3)
overstocked_amt?: string; // numeric(15,3)
}

View File

@@ -1 +1 @@
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/auth/requireauth.tsx","./src/components/dashboard/bestsellers.tsx","./src/components/dashboard/forecastmetrics.tsx","./src/components/dashboard/inventoryhealthsummary.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/keymetricscharts.tsx","./src/components/dashboard/lowstockalerts.tsx","./src/components/dashboard/overstockmetrics.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/purchasemetrics.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/replenishmentmetrics.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/salesmetrics.tsx","./src/components/dashboard/stockmetrics.tsx","./src/components/dashboard/topoverstockedproducts.tsx","./src/components/dashboard/topreplenishproducts.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/dashboard/vendorperformance.tsx","./src/components/forecasting/columns.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/productdetail.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/products/productviews.tsx","./src/components/settings/calculationsettings.tsx","./src/components/settings/configuration.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/performancemetrics.tsx","./src/components/settings/stockmanagement.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/command.tsx","./src/components/ui/date-range-picker-narrow.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/categories.tsx","./src/pages/dashboard.tsx","./src/pages/forecasting.tsx","./src/pages/login.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx","./src/pages/vendors.tsx","./src/routes/forecasting.tsx","./src/types/products.ts"],"version":"5.6.3"}
{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/auth/requireauth.tsx","./src/components/dashboard/bestsellers.tsx","./src/components/dashboard/forecastmetrics.tsx","./src/components/dashboard/inventoryhealthsummary.tsx","./src/components/dashboard/inventorystats.tsx","./src/components/dashboard/keymetricscharts.tsx","./src/components/dashboard/lowstockalerts.tsx","./src/components/dashboard/overstockmetrics.tsx","./src/components/dashboard/overview.tsx","./src/components/dashboard/purchasemetrics.tsx","./src/components/dashboard/recentsales.tsx","./src/components/dashboard/replenishmentmetrics.tsx","./src/components/dashboard/salesbycategory.tsx","./src/components/dashboard/salesmetrics.tsx","./src/components/dashboard/stockmetrics.tsx","./src/components/dashboard/topoverstockedproducts.tsx","./src/components/dashboard/topreplenishproducts.tsx","./src/components/dashboard/trendingproducts.tsx","./src/components/dashboard/vendorperformance.tsx","./src/components/forecasting/columns.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/products/productdetail.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/products/productviews.tsx","./src/components/settings/calculationsettings.tsx","./src/components/settings/configuration.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/performancemetrics.tsx","./src/components/settings/stockmanagement.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/command.tsx","./src/components/ui/date-range-picker-narrow.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/hooks/use-mobile.tsx","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/categories.tsx","./src/pages/dashboard.tsx","./src/pages/forecasting.tsx","./src/pages/login.tsx","./src/pages/orders.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx","./src/pages/vendors.tsx","./src/types/products.ts","./src/types/status-codes.ts"],"version":"5.6.3"}