diff --git a/inventory-server/src/routes/csv.js b/inventory-server/src/routes/csv.js index d71d5dd..164b7b1 100644 --- a/inventory-server/src/routes/csv.js +++ b/inventory-server/src/routes/csv.js @@ -836,4 +836,58 @@ router.get('/status/tables', async (req, res) => { } }); +// GET /status/table-counts - Get record counts for all tables +router.get('/status/table-counts', async (req, res) => { + try { + const pool = req.app.locals.pool; + const tables = [ + // Core tables + 'products', 'categories', 'product_categories', 'orders', 'purchase_orders', + // Metrics tables + 'product_metrics', 'product_time_aggregates', 'vendor_metrics', 'category_metrics', + 'vendor_time_metrics', 'category_time_metrics', 'category_sales_metrics', + 'brand_metrics', 'brand_time_metrics', 'sales_forecasts', 'category_forecasts', + // Config tables + 'stock_thresholds', 'lead_time_thresholds', 'sales_velocity_config', + 'abc_classification_config', 'safety_stock_config', 'turnover_config', + 'sales_seasonality', 'financial_calc_config' + ]; + + const counts = await Promise.all( + tables.map(table => + pool.query(`SELECT COUNT(*) as count FROM ${table}`) + .then(result => ({ + table_name: table, + count: parseInt(result.rows[0].count) + })) + .catch(err => ({ + table_name: table, + count: null, + error: err.message + })) + ) + ); + + // Group tables by type + const groupedCounts = { + core: counts.filter(c => ['products', 'categories', 'product_categories', 'orders', 'purchase_orders'].includes(c.table_name)), + metrics: counts.filter(c => [ + 'product_metrics', 'product_time_aggregates', 'vendor_metrics', 'category_metrics', + 'vendor_time_metrics', 'category_time_metrics', 'category_sales_metrics', + 'brand_metrics', 'brand_time_metrics', 'sales_forecasts', 'category_forecasts' + ].includes(c.table_name)), + config: counts.filter(c => [ + 'stock_thresholds', 'lead_time_thresholds', 'sales_velocity_config', + 'abc_classification_config', 'safety_stock_config', 'turnover_config', + 'sales_seasonality', 'financial_calc_config' + ].includes(c.table_name)) + }; + + res.json(groupedCounts); + } catch (error) { + console.error('Error fetching table counts:', error); + res.status(500).json({ error: error.message }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/inventory/src/components/settings/DataManagement.tsx b/inventory/src/components/settings/DataManagement.tsx index 27accb7..bbc799c 100644 --- a/inventory/src/components/settings/DataManagement.tsx +++ b/inventory/src/components/settings/DataManagement.tsx @@ -79,6 +79,18 @@ interface TableStatus { last_sync_timestamp: string; } +interface TableCount { + table_name: string; + count: number | null; + error?: string; +} + +interface GroupedTableCounts { + core: TableCount[]; + metrics: TableCount[]; + config: TableCount[]; +} + export function DataManagement() { const [isUpdating, setIsUpdating] = useState(false); const [isResetting, setIsResetting] = useState(false); @@ -90,6 +102,7 @@ export function DataManagement() { const [tableStatus, setTableStatus] = useState([]); const [scriptOutput, setScriptOutput] = useState([]); const [eventSource, setEventSource] = useState(null); + const [tableCounts, setTableCounts] = useState(null); // Add useRef for scroll handling const terminalRef = useRef(null); @@ -187,6 +200,7 @@ export function DataManagement() { const handleFullUpdate = async () => { setIsUpdating(true); setScriptOutput([]); + fetchHistory(); // Refresh at start try { const source = new EventSource(`${config.apiUrl}/csv/update/progress`, { @@ -207,10 +221,10 @@ export function DataManagement() { source.close(); setEventSource(null); setIsUpdating(false); + fetchHistory(); // Refresh at end if (data.status === 'complete') { toast.success("Update completed successfully"); - fetchHistory(); } else if (data.status === 'error') { toast.error(`Update failed: ${data.error || 'Unknown error'}`); } else { @@ -257,6 +271,7 @@ export function DataManagement() { const handleFullReset = async () => { setIsResetting(true); setScriptOutput([]); + fetchHistory(); // Refresh at start try { const source = new EventSource(`${config.apiUrl}/csv/reset/progress`, { @@ -277,10 +292,10 @@ export function DataManagement() { source.close(); setEventSource(null); setIsResetting(false); + fetchHistory(); // Refresh at end if (data.status === 'complete') { toast.success("Reset completed successfully"); - fetchHistory(); } else if (data.status === 'error') { toast.error(`Reset failed: ${data.error || 'Unknown error'}`); } else { @@ -359,26 +374,29 @@ export function DataManagement() { }; const fetchHistory = async () => { + let shouldSetLoading = !importHistory.length || !calculateHistory.length; try { - setIsLoading(true); + if (shouldSetLoading) setIsLoading(true); setHasError(false); - const [importRes, calcRes, moduleRes, tableRes] = await Promise.all([ + const [importRes, calcRes, moduleRes, tableRes, tableCountsRes] = await Promise.all([ fetch(`${config.apiUrl}/csv/history/import`, { credentials: 'include' }), fetch(`${config.apiUrl}/csv/history/calculate`, { credentials: 'include' }), fetch(`${config.apiUrl}/csv/status/modules`, { credentials: 'include' }), fetch(`${config.apiUrl}/csv/status/tables`, { credentials: 'include' }), + fetch(`${config.apiUrl}/csv/status/table-counts`, { credentials: 'include' }), ]); - if (!importRes.ok || !calcRes.ok || !moduleRes.ok || !tableRes.ok) { + if (!importRes.ok || !calcRes.ok || !moduleRes.ok || !tableRes.ok || !tableCountsRes.ok) { throw new Error('One or more requests failed'); } - const [importData, calcData, moduleData, tableData] = await Promise.all([ + const [importData, calcData, moduleData, tableData, tableCountsData] = await Promise.all([ importRes.json(), calcRes.json(), moduleRes.json(), tableRes.json(), + tableCountsRes.json(), ]); // Process import history to add duration_minutes if it doesn't exist @@ -405,17 +423,19 @@ export function DataManagement() { setCalculateHistory(processedCalcData); setModuleStatus(moduleData || []); setTableStatus(tableData || []); + setTableCounts(tableCountsData); setHasError(false); } catch (error) { - console.error("Error fetching history:", error); + console.error("Error fetching data:", error); setHasError(true); toast.error("Failed to load data. Please try again."); setImportHistory([]); setCalculateHistory([]); setModuleStatus([]); setTableStatus([]); + setTableCounts(null); } finally { - setIsLoading(false); + if (shouldSetLoading) setIsLoading(false); } }; @@ -528,6 +548,57 @@ export function DataManagement() { ); }; + // Add formatNumber helper + const formatNumber = (num: number | null) => { + if (num === null) return 'N/A'; + return new Intl.NumberFormat().format(num); + }; + + // Update renderTableCountsSection to match other cards' styling + const renderTableCountsSection = () => { + if (!tableCounts) return null; + + const renderTableGroup = (title: string, tables: TableCount[]) => ( +
+
+ {tables.map((table, index) => ( +
+ {table.table_name} + {table.error ? ( + {table.error} + ) : ( + {formatNumber(table.count)} + )} +
+ ))} +
+
+ ); + + return ( + + + Table Record Counts + + + {isLoading && !tableCounts.core.length ? ( +
+ +
+ ) : ( +
+
{renderTableGroup('Core Tables', tableCounts.core)}
+
{renderTableGroup('Metrics Tables', tableCounts.metrics)}
+
+ )} +
+
+ ); + }; + return (
@@ -632,7 +703,7 @@ export function DataManagement() { {(isUpdating || isResetting) && renderTerminal()} {/* History Section */} -
+

History & Status