Update frontend to launch relevant scripts and show script history + output
This commit is contained in:
@@ -2,6 +2,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const db = require('../utils/db');
|
||||||
|
|
||||||
// Debug middleware MUST be first
|
// Debug middleware MUST be first
|
||||||
router.use((req, res, next) => {
|
router.use((req, res, next) => {
|
||||||
@@ -9,9 +10,11 @@ router.use((req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store active import process and its progress
|
// Store active processes and their progress
|
||||||
let activeImport = null;
|
let activeImport = null;
|
||||||
let importProgress = null;
|
let importProgress = null;
|
||||||
|
let activeFullUpdate = null;
|
||||||
|
let activeFullReset = null;
|
||||||
|
|
||||||
// SSE clients for progress updates
|
// SSE clients for progress updates
|
||||||
const updateClients = new Set();
|
const updateClients = new Set();
|
||||||
@@ -19,17 +22,16 @@ const importClients = new Set();
|
|||||||
const resetClients = new Set();
|
const resetClients = new Set();
|
||||||
const resetMetricsClients = new Set();
|
const resetMetricsClients = new Set();
|
||||||
const calculateMetricsClients = new Set();
|
const calculateMetricsClients = new Set();
|
||||||
|
const fullUpdateClients = new Set();
|
||||||
|
const fullResetClients = new Set();
|
||||||
|
|
||||||
// Helper to send progress to specific clients
|
// Helper to send progress to specific clients
|
||||||
function sendProgressToClients(clients, progress) {
|
function sendProgressToClients(clients, data) {
|
||||||
const data = typeof progress === 'string' ? { progress } : progress;
|
// If data is a string, send it directly
|
||||||
|
// If it's an object, convert it to JSON
|
||||||
// Ensure we have a status field
|
const message = typeof data === 'string'
|
||||||
if (!data.status) {
|
? `data: ${data}\n\n`
|
||||||
data.status = 'running';
|
: `data: ${JSON.stringify(data)}\n\n`;
|
||||||
}
|
|
||||||
|
|
||||||
const message = `data: ${JSON.stringify(data)}\n\n`;
|
|
||||||
|
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
try {
|
try {
|
||||||
@@ -45,115 +47,118 @@ function sendProgressToClients(clients, progress) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to run a script and stream progress
|
||||||
|
function runScript(scriptPath, type, clients) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Kill any existing process of this type
|
||||||
|
let activeProcess;
|
||||||
|
switch (type) {
|
||||||
|
case 'update':
|
||||||
|
if (activeFullUpdate) {
|
||||||
|
try { activeFullUpdate.kill(); } catch (e) { }
|
||||||
|
}
|
||||||
|
activeProcess = activeFullUpdate;
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
if (activeFullReset) {
|
||||||
|
try { activeFullReset.kill(); } catch (e) { }
|
||||||
|
}
|
||||||
|
activeProcess = activeFullReset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = spawn('node', [scriptPath], {
|
||||||
|
stdio: ['inherit', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'update':
|
||||||
|
activeFullUpdate = child;
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
activeFullReset = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
child.stdout.on('data', (data) => {
|
||||||
|
const text = data.toString();
|
||||||
|
output += text;
|
||||||
|
// Send raw output directly
|
||||||
|
sendProgressToClients(clients, text);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
const text = data.toString();
|
||||||
|
console.error(text);
|
||||||
|
// Send stderr output directly too
|
||||||
|
sendProgressToClients(clients, text);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'update':
|
||||||
|
activeFullUpdate = null;
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
activeFullReset = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
const error = `Script ${scriptPath} exited with code ${code}`;
|
||||||
|
sendProgressToClients(clients, error);
|
||||||
|
reject(new Error(error));
|
||||||
|
} else {
|
||||||
|
sendProgressToClients(clients, `${type} completed successfully`);
|
||||||
|
resolve({ output });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (err) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'update':
|
||||||
|
activeFullUpdate = null;
|
||||||
|
break;
|
||||||
|
case 'reset':
|
||||||
|
activeFullReset = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sendProgressToClients(clients, err.message);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Progress endpoints
|
// Progress endpoints
|
||||||
router.get('/update/progress', (req, res) => {
|
router.get('/:type/progress', (req, res) => {
|
||||||
res.writeHead(200, {
|
const { type } = req.params;
|
||||||
'Content-Type': 'text/event-stream',
|
if (!['update', 'reset'].includes(type)) {
|
||||||
'Cache-Control': 'no-cache',
|
return res.status(400).json({ error: 'Invalid operation type' });
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
|
||||||
'Access-Control-Allow-Credentials': 'true'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send an initial message to test the connection
|
|
||||||
res.write('data: {"status":"running","operation":"Initializing connection..."}\n\n');
|
|
||||||
|
|
||||||
// Add this client to the update set
|
|
||||||
updateClients.add(res);
|
|
||||||
|
|
||||||
// Remove client when connection closes
|
|
||||||
req.on('close', () => {
|
|
||||||
updateClients.delete(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/import/progress', (req, res) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
|
||||||
'Access-Control-Allow-Credentials': 'true'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send an initial message to test the connection
|
|
||||||
res.write('data: {"status":"running","operation":"Initializing connection..."}\n\n');
|
|
||||||
|
|
||||||
// Add this client to the import set
|
|
||||||
importClients.add(res);
|
|
||||||
|
|
||||||
// Remove client when connection closes
|
|
||||||
req.on('close', () => {
|
|
||||||
importClients.delete(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/reset/progress', (req, res) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
|
||||||
'Access-Control-Allow-Credentials': 'true'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send an initial message to test the connection
|
|
||||||
res.write('data: {"status":"running","operation":"Initializing connection..."}\n\n');
|
|
||||||
|
|
||||||
// Add this client to the reset set
|
|
||||||
resetClients.add(res);
|
|
||||||
|
|
||||||
// Remove client when connection closes
|
|
||||||
req.on('close', () => {
|
|
||||||
resetClients.delete(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add reset-metrics progress endpoint
|
|
||||||
router.get('/reset-metrics/progress', (req, res) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
|
||||||
'Access-Control-Allow-Credentials': 'true'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send an initial message to test the connection
|
|
||||||
res.write('data: {"status":"running","operation":"Initializing connection..."}\n\n');
|
|
||||||
|
|
||||||
// Add this client to the reset-metrics set
|
|
||||||
resetMetricsClients.add(res);
|
|
||||||
|
|
||||||
// Remove client when connection closes
|
|
||||||
req.on('close', () => {
|
|
||||||
resetMetricsClients.delete(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add calculate-metrics progress endpoint
|
|
||||||
router.get('/calculate-metrics/progress', (req, res) => {
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
|
||||||
'Access-Control-Allow-Credentials': 'true'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send current progress if it exists
|
|
||||||
if (importProgress) {
|
|
||||||
res.write(`data: ${JSON.stringify(importProgress)}\n\n`);
|
|
||||||
} else {
|
|
||||||
res.write('data: {"status":"running","operation":"Initializing connection..."}\n\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this client to the calculate-metrics set
|
res.writeHead(200, {
|
||||||
calculateMetricsClients.add(res);
|
'Content-Type': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Access-Control-Allow-Origin': req.headers.origin || '*',
|
||||||
|
'Access-Control-Allow-Credentials': 'true'
|
||||||
|
});
|
||||||
|
|
||||||
// Remove client when connection closes
|
// Add this client to the correct set
|
||||||
|
const clients = type === 'update' ? fullUpdateClients : fullResetClients;
|
||||||
|
clients.add(res);
|
||||||
|
|
||||||
|
// Send initial connection message
|
||||||
|
sendProgressToClients(new Set([res]), JSON.stringify({
|
||||||
|
status: 'running',
|
||||||
|
operation: 'Initializing connection...'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Handle client disconnect
|
||||||
req.on('close', () => {
|
req.on('close', () => {
|
||||||
calculateMetricsClients.delete(res);
|
clients.delete(res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,7 +179,6 @@ router.get('/status', (req, res) => {
|
|||||||
|
|
||||||
// Add calculate-metrics status endpoint
|
// Add calculate-metrics status endpoint
|
||||||
router.get('/calculate-metrics/status', (req, res) => {
|
router.get('/calculate-metrics/status', (req, res) => {
|
||||||
console.log('Calculate metrics status endpoint hit');
|
|
||||||
const calculateMetrics = require('../../scripts/calculate-metrics');
|
const calculateMetrics = require('../../scripts/calculate-metrics');
|
||||||
const progress = calculateMetrics.getProgress();
|
const progress = calculateMetrics.getProgress();
|
||||||
|
|
||||||
@@ -371,49 +375,35 @@ router.post('/import', async (req, res) => {
|
|||||||
|
|
||||||
// Route to cancel active process
|
// Route to cancel active process
|
||||||
router.post('/cancel', (req, res) => {
|
router.post('/cancel', (req, res) => {
|
||||||
if (!activeImport) {
|
let killed = false;
|
||||||
return res.status(404).json({ error: 'No active process to cancel' });
|
|
||||||
|
// Get the operation type from the request
|
||||||
|
const { type } = req.query;
|
||||||
|
const clients = type === 'update' ? fullUpdateClients : fullResetClients;
|
||||||
|
const activeProcess = type === 'update' ? activeFullUpdate : activeFullReset;
|
||||||
|
|
||||||
|
if (activeProcess) {
|
||||||
|
try {
|
||||||
|
activeProcess.kill('SIGTERM');
|
||||||
|
if (type === 'update') {
|
||||||
|
activeFullUpdate = null;
|
||||||
|
} else {
|
||||||
|
activeFullReset = null;
|
||||||
|
}
|
||||||
|
killed = true;
|
||||||
|
sendProgressToClients(clients, JSON.stringify({
|
||||||
|
status: 'cancelled',
|
||||||
|
operation: 'Operation cancelled'
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error killing ${type} process:`, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (killed) {
|
||||||
// If it's the prod import module, call its cancel function
|
|
||||||
if (typeof activeImport.cancelImport === 'function') {
|
|
||||||
activeImport.cancelImport();
|
|
||||||
} else {
|
|
||||||
// Otherwise it's a child process
|
|
||||||
activeImport.kill('SIGTERM');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the operation type from the request
|
|
||||||
const { operation } = req.query;
|
|
||||||
|
|
||||||
// Send cancel message only to the appropriate client set
|
|
||||||
const cancelMessage = {
|
|
||||||
status: 'cancelled',
|
|
||||||
operation: 'Operation cancelled'
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (operation) {
|
|
||||||
case 'update':
|
|
||||||
sendProgressToClients(updateClients, cancelMessage);
|
|
||||||
break;
|
|
||||||
case 'import':
|
|
||||||
sendProgressToClients(importClients, cancelMessage);
|
|
||||||
break;
|
|
||||||
case 'reset':
|
|
||||||
sendProgressToClients(resetClients, cancelMessage);
|
|
||||||
break;
|
|
||||||
case 'calculate-metrics':
|
|
||||||
sendProgressToClients(calculateMetricsClients, cancelMessage);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} else {
|
||||||
// Even if there's an error, try to clean up
|
res.status(404).json({ error: 'No active process to cancel' });
|
||||||
activeImport = null;
|
|
||||||
importProgress = null;
|
|
||||||
res.status(500).json({ error: 'Failed to cancel process' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -552,20 +542,6 @@ router.post('/reset-metrics', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add calculate-metrics status endpoint
|
|
||||||
router.get('/calculate-metrics/status', (req, res) => {
|
|
||||||
const calculateMetrics = require('../../scripts/calculate-metrics');
|
|
||||||
const progress = calculateMetrics.getProgress();
|
|
||||||
|
|
||||||
// Only consider it active if both the process is running and we have progress
|
|
||||||
const isActive = !!activeImport && !!progress;
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
active: isActive,
|
|
||||||
progress: isActive ? progress : null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add calculate-metrics endpoint
|
// Add calculate-metrics endpoint
|
||||||
router.post('/calculate-metrics', async (req, res) => {
|
router.post('/calculate-metrics', async (req, res) => {
|
||||||
if (activeImport) {
|
if (activeImport) {
|
||||||
@@ -711,4 +687,96 @@ router.post('/import-from-prod', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /csv/full-update - Run full update script
|
||||||
|
router.post('/full-update', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const scriptPath = path.join(__dirname, '../../scripts/full-update.js');
|
||||||
|
runScript(scriptPath, 'update', fullUpdateClients)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Update failed:', error);
|
||||||
|
});
|
||||||
|
res.status(202).json({ message: 'Update started' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /csv/full-reset - Run full reset script
|
||||||
|
router.post('/full-reset', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const scriptPath = path.join(__dirname, '../../scripts/full-reset.js');
|
||||||
|
runScript(scriptPath, 'reset', fullResetClients)
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Reset failed:', error);
|
||||||
|
});
|
||||||
|
res.status(202).json({ message: 'Reset started' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /history/import - Get recent import history
|
||||||
|
router.get('/history/import', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = req.app.locals.pool;
|
||||||
|
const [rows] = await pool.query(`
|
||||||
|
SELECT * FROM import_history
|
||||||
|
ORDER BY start_time DESC
|
||||||
|
LIMIT 20
|
||||||
|
`);
|
||||||
|
res.json(rows || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching import history:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /history/calculate - Get recent calculation history
|
||||||
|
router.get('/history/calculate', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = req.app.locals.pool;
|
||||||
|
const [rows] = await pool.query(`
|
||||||
|
SELECT * FROM calculate_history
|
||||||
|
ORDER BY start_time DESC
|
||||||
|
LIMIT 20
|
||||||
|
`);
|
||||||
|
res.json(rows || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching calculate history:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /status/modules - Get module calculation status
|
||||||
|
router.get('/status/modules', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = req.app.locals.pool;
|
||||||
|
const [rows] = await pool.query(`
|
||||||
|
SELECT module_name, last_calculation_timestamp
|
||||||
|
FROM calculate_status
|
||||||
|
ORDER BY module_name
|
||||||
|
`);
|
||||||
|
res.json(rows || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching module status:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /status/tables - Get table sync status
|
||||||
|
router.get('/status/tables', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = req.app.locals.pool;
|
||||||
|
const [rows] = await pool.query(`
|
||||||
|
SELECT table_name, last_sync_timestamp
|
||||||
|
FROM sync_status
|
||||||
|
ORDER BY table_name
|
||||||
|
`);
|
||||||
|
res.json(rows || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching table status:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -133,6 +133,10 @@ export function PerformanceMetrics() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getCategoryName(_cat_id: number): import("react").ReactNode {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-[700px] space-y-4">
|
<div className="max-w-[700px] space-y-4">
|
||||||
{/* Lead Time Thresholds Card */}
|
{/* Lead Time Thresholds Card */}
|
||||||
@@ -205,11 +209,11 @@ export function PerformanceMetrics() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Category</TableHead>
|
<TableCell>Category</TableCell>
|
||||||
<TableHead>Vendor</TableHead>
|
<TableCell>Vendor</TableCell>
|
||||||
<TableHead className="text-right">A Threshold</TableHead>
|
<TableCell className="text-right">A Threshold</TableCell>
|
||||||
<TableHead className="text-right">B Threshold</TableHead>
|
<TableCell className="text-right">B Threshold</TableCell>
|
||||||
<TableHead className="text-right">Period Days</TableHead>
|
<TableCell className="text-right">Period Days</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -242,10 +246,10 @@ export function PerformanceMetrics() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Category</TableHead>
|
<TableCell>Category</TableCell>
|
||||||
<TableHead>Vendor</TableHead>
|
<TableCell>Vendor</TableCell>
|
||||||
<TableHead className="text-right">Period Days</TableHead>
|
<TableCell className="text-right">Period Days</TableCell>
|
||||||
<TableHead className="text-right">Target Rate</TableHead>
|
<TableCell className="text-right">Target Rate</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { Table, TableBody, TableCell, TableHeader, TableRow } from "@/components/ui/table";
|
|
||||||
|
|
||||||
interface StockThreshold {
|
interface StockThreshold {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -244,54 +243,6 @@ export function StockManagement() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Category</TableHead>
|
|
||||||
<TableHead>Vendor</TableHead>
|
|
||||||
<TableHead className="text-right">Critical Days</TableHead>
|
|
||||||
<TableHead className="text-right">Reorder Days</TableHead>
|
|
||||||
<TableHead className="text-right">Overstock Days</TableHead>
|
|
||||||
<TableHead className="text-right">Low Stock</TableHead>
|
|
||||||
<TableHead className="text-right">Min Reorder</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{stockThresholds.map((threshold) => (
|
|
||||||
<TableRow key={`${threshold.cat_id}-${threshold.vendor}`}>
|
|
||||||
<TableCell>{threshold.cat_id ? getCategoryName(threshold.cat_id) : 'Global'}</TableCell>
|
|
||||||
<TableCell>{threshold.vendor || 'All Vendors'}</TableCell>
|
|
||||||
<TableCell className="text-right">{threshold.critical_days}</TableCell>
|
|
||||||
<TableCell className="text-right">{threshold.reorder_days}</TableCell>
|
|
||||||
<TableCell className="text-right">{threshold.overstock_days}</TableCell>
|
|
||||||
<TableCell className="text-right">{threshold.low_stock_threshold}</TableCell>
|
|
||||||
<TableCell className="text-right">{threshold.min_reorder_quantity}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Category</TableHead>
|
|
||||||
<TableHead>Vendor</TableHead>
|
|
||||||
<TableHead className="text-right">Coverage Days</TableHead>
|
|
||||||
<TableHead className="text-right">Service Level</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{safetyStockConfigs.map((config) => (
|
|
||||||
<TableRow key={`${config.cat_id}-${config.vendor}`}>
|
|
||||||
<TableCell>{config.cat_id ? getCategoryName(config.cat_id) : 'Global'}</TableCell>
|
|
||||||
<TableCell>{config.vendor || 'All Vendors'}</TableCell>
|
|
||||||
<TableCell className="text-right">{config.coverage_days}</TableCell>
|
|
||||||
<TableCell className="text-right">{config.service_level}%</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user