import React, { useState, useEffect } from 'react';
import { DollarSign, Package, Settings, TrendingUp, Zap, AlertCircle, CheckCircle, RefreshCw, Database, Calculator, Activity, BarChart3, FileText } from 'lucide-react';
// Mock data for demonstration
const mockPartNumbers = [
{
id: 'PN-001',
name: 'Brass Valve 1/2"',
family: 'Valves',
type: 'Brass Fitting',
bom: [
{ id: 'RM-001', material: 'Brass Bar', qty: 1.8, unit: 'kg', stdCost: 6.80, lmeLinked: true },
{ id: 'RM-002', material: 'Rubber Gasket', qty: 2, unit: 'pcs', stdCost: 0.35, lmeLinked: false },
{ id: 'RM-003', material: 'Steel Spring', qty: 0.2, unit: 'kg', stdCost: 3.20, lmeLinked: true },
],
routing: [
{ id: 'OP-010', operation: 'Cutting', workCenter: 'WC-CUT-01', cycleTime: 3.0, rate: 45.00, enabled: true },
{ id: 'OP-020', operation: 'Machining', workCenter: 'WC-MCH-02', cycleTime: 12.0, rate: 65.00, enabled: true },
{ id: 'OP-030', operation: 'Assembly', workCenter: 'WC-ASM-01', cycleTime: 8.5, rate: 38.00, enabled: true },
{ id: 'OP-040', operation: 'Painting', workCenter: 'WC-PNT-01', cycleTime: 0, rate: 42.00, enabled: false },
{ id: 'OP-050', operation: 'Packaging', workCenter: 'WC-PKG-01', cycleTime: 2.0, rate: 28.00, enabled: true },
]
},
{
id: 'PN-002',
name: 'Steel Coupling 3/4"',
family: 'Couplings',
type: 'Steel Component',
bom: [
{ id: 'RM-001', material: 'Steel Grade 304', qty: 2.5, unit: 'kg', stdCost: 3.20, lmeLinked: true },
{ id: 'RM-002', material: 'Zinc Coating Material', qty: 0.1, unit: 'kg', stdCost: 4.50, lmeLinked: false },
],
routing: [
{ id: 'OP-010', operation: 'Cutting', workCenter: 'WC-CUT-01', cycleTime: 2.5, rate: 45.00, enabled: true },
{ id: 'OP-020', operation: 'Machining', workCenter: 'WC-MCH-02', cycleTime: 15.0, rate: 65.00, enabled: true },
{ id: 'OP-030', operation: 'Assembly', workCenter: 'WC-ASM-01', cycleTime: 0, rate: 38.00, enabled: false },
{ id: 'OP-040', operation: 'Painting', workCenter: 'WC-PNT-01', cycleTime: 5.0, rate: 42.00, enabled: true },
{ id: 'OP-050', operation: 'Packaging', workCenter: 'WC-PKG-01', cycleTime: 1.5, rate: 28.00, enabled: true },
]
},
{
id: 'PN-003',
name: 'Copper Elbow 1"',
family: 'Elbows',
type: 'Copper Fitting',
bom: [
{ id: 'RM-001', material: 'Copper Sheet', qty: 1.2, unit: 'kg', stdCost: 8.50, lmeLinked: true },
{ id: 'RM-002', material: 'Solder Wire', qty: 0.05, unit: 'kg', stdCost: 12.00, lmeLinked: false },
],
routing: [
{ id: 'OP-010', operation: 'Cutting', workCenter: 'WC-CUT-01', cycleTime: 4.0, rate: 45.00, enabled: true },
{ id: 'OP-020', operation: 'Machining', workCenter: 'WC-MCH-02', cycleTime: 10.0, rate: 65.00, enabled: true },
{ id: 'OP-030', operation: 'Assembly', workCenter: 'WC-ASM-01', cycleTime: 6.0, rate: 38.00, enabled: true },
{ id: 'OP-040', operation: 'Painting', workCenter: 'WC-PNT-01', cycleTime: 0, rate: 42.00, enabled: false },
{ id: 'OP-050', operation: 'Packaging', workCenter: 'WC-PKG-01', cycleTime: 1.8, rate: 28.00, enabled: true },
]
},
{
id: 'PN-004',
name: 'Plastic Adapter 1/2"',
family: 'Adapters',
type: 'Plastic Component',
bom: [
{ id: 'RM-001', material: 'Plastic PVC', qty: 0.8, unit: 'kg', stdCost: 2.10, lmeLinked: false },
{ id: 'RM-002', material: 'Rubber O-Ring', qty: 1, unit: 'pcs', stdCost: 0.25, lmeLinked: false },
],
routing: [
{ id: 'OP-010', operation: 'Cutting', workCenter: 'WC-CUT-01', cycleTime: 1.5, rate: 45.00, enabled: true },
{ id: 'OP-020', operation: 'Machining', workCenter: 'WC-MCH-02', cycleTime: 5.0, rate: 65.00, enabled: true },
{ id: 'OP-030', operation: 'Assembly', workCenter: 'WC-ASM-01', cycleTime: 3.5, rate: 38.00, enabled: true },
{ id: 'OP-040', operation: 'Painting', workCenter: 'WC-PNT-01', cycleTime: 3.0, rate: 42.00, enabled: true },
{ id: 'OP-050', operation: 'Packaging', workCenter: 'WC-PKG-01', cycleTime: 1.2, rate: 28.00, enabled: true },
]
},
{
id: 'PN-005',
name: 'Aluminum Housing Assembly',
family: 'Housing',
type: 'Aluminum Component',
bom: [
{ id: 'RM-001', material: 'Aluminum Alloy 6061', qty: 3.2, unit: 'kg', stdCost: 2.45, lmeLinked: true },
{ id: 'RM-002', material: 'Steel Fasteners', qty: 8, unit: 'pcs', stdCost: 0.15, lmeLinked: false },
{ id: 'RM-003', material: 'Rubber Seal', qty: 1, unit: 'pcs', stdCost: 0.45, lmeLinked: false },
],
routing: [
{ id: 'OP-010', operation: 'Cutting', workCenter: 'WC-CUT-01', cycleTime: 5.5, rate: 45.00, enabled: true },
{ id: 'OP-020', operation: 'Machining', workCenter: 'WC-MCH-02', cycleTime: 20.0, rate: 65.00, enabled: true },
{ id: 'OP-030', operation: 'Assembly', workCenter: 'WC-ASM-01', cycleTime: 12.0, rate: 38.00, enabled: true },
{ id: 'OP-040', operation: 'Painting', workCenter: 'WC-PNT-01', cycleTime: 8.0, rate: 42.00, enabled: true },
{ id: 'OP-050', operation: 'Packaging', workCenter: 'WC-PKG-01', cycleTime: 3.5, rate: 28.00, enabled: true },
]
},
];
const WelspringCostSimulator = () => {
const [selectedPart, setSelectedPart] = useState(null);
const [bom, setBom] = useState([]);
const [routing, setRouting] = useState([]);
const [lmePrices, setLmePrices] = useState({
copper: 8.45,
brass: 6.75,
steel: 3.18,
aluminum: 2.45,
});
const [isRefreshing, setIsRefreshing] = useState(false);
const [costBreakdown, setCostBreakdown] = useState(null);
const [activeTab, setActiveTab] = useState('selection');
// Simulate LME API refresh
const refreshLMEPrices = () => {
setIsRefreshing(true);
setTimeout(() => {
setLmePrices({
copper: (8.45 + (Math.random() - 0.5) * 0.5).toFixed(2),
brass: (6.75 + (Math.random() - 0.5) * 0.4).toFixed(2),
steel: (3.18 + (Math.random() - 0.5) * 0.2).toFixed(2),
aluminum: (2.45 + (Math.random() - 0.5) * 0.3).toFixed(2),
});
setIsRefreshing(false);
}, 1200);
};
// Calculate costs when part, BOM, or routing changes
useEffect(() => {
if (selectedPart && bom.length > 0 && routing.length > 0) {
calculateCosts();
}
}, [selectedPart, bom, routing, lmePrices]);
const calculateCosts = () => {
// Raw Material Cost
const rmCost = bom.reduce((sum, item) => {
let itemCost = item.stdCost;
if (item.lmeLinked) {
const materialType = item.material.toLowerCase();
if (materialType.includes('copper')) itemCost = parseFloat(lmePrices.copper);
if (materialType.includes('brass')) itemCost = parseFloat(lmePrices.brass);
if (materialType.includes('steel')) itemCost = parseFloat(lmePrices.steel);
if (materialType.includes('aluminum') || materialType.includes('aluminium')) itemCost = parseFloat(lmePrices.aluminum);
}
return sum + (item.qty * itemCost);
}, 0);
// Processing Cost - only count enabled operations
const processingCost = routing.reduce((sum, op) => {
if (op.enabled) {
return sum + ((op.cycleTime / 60) * op.rate);
}
return sum;
}, 0);
const grossProductCost = rmCost + processingCost;
const laborCost = grossProductCost * 0.10;
const consumables = grossProductCost * 0.05;
const powerCost = grossProductCost * 0.05;
const maintenance = grossProductCost * 0.05;
const grossMargin = grossProductCost * 0.15;
const costOfProduction = grossProductCost + grossMargin;
setCostBreakdown({
rmCost: rmCost.toFixed(2),
processingCost: processingCost.toFixed(2),
grossProductCost: grossProductCost.toFixed(2),
laborCost: laborCost.toFixed(2),
consumables: consumables.toFixed(2),
powerCost: powerCost.toFixed(2),
maintenance: maintenance.toFixed(2),
grossMargin: grossMargin.toFixed(2),
costOfProduction: costOfProduction.toFixed(2),
rmPercentage: ((rmCost / costOfProduction) * 100).toFixed(1),
processingPercentage: ((processingCost / costOfProduction) * 100).toFixed(1),
});
};
const selectPart = (part) => {
setSelectedPart(part);
setBom(part.bom);
setRouting(part.routing);
setActiveTab('bom');
};
const updateBOMItem = (index, field, value) => {
const newBom = [...bom];
newBom[index][field] = field === 'qty' || field === 'stdCost' ? parseFloat(value) : value;
setBom(newBom);
};
const updateRoutingItem = (index, field, value) => {
const newRouting = [...routing];
if (field === 'enabled') {
newRouting[index][field] = value;
} else {
newRouting[index][field] = field === 'cycleTime' || field === 'rate' ? parseFloat(value) : value;
}
setRouting(newRouting);
};
return (
{/* Subtle grid pattern */}
{/* Header */}
WELSPRING
Cost Simulator & Strategic Pricing
{/* Main Content */}
{/* LME Price Ticker */}
London Metal Exchange - Live Commodity Prices
{Object.entries(lmePrices).map(([metal, price]) => (
{metal}
${price}
/kg
● Live Feed
))}
{/* Navigation Tabs */}
{[
{ id: 'selection', label: 'Part Selection', icon: Package },
{ id: 'bom', label: 'Bill of Materials', icon: Database },
{ id: 'routing', label: 'Routing & Operations', icon: Settings },
{ id: 'results', label: 'Cost Analysis', icon: BarChart3 }
].map(tab => {
const Icon = tab.icon;
return (
);
})}
{/* Tab Content */}
{/* Part Selection Tab */}
{activeTab === 'selection' && (
Select Part Number
{mockPartNumbers.map(part => (
selectPart(part)}
style={{
background: selectedPart?.id === part.id
? 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)'
: '#f8fafc',
border: selectedPart?.id === part.id
? '3px solid #3b82f6'
: '2px solid #e2e8f0',
borderRadius: '14px',
padding: '1.5rem',
cursor: 'pointer',
transition: 'all 0.3s ease',
position: 'relative'
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-4px)';
e.currentTarget.style.boxShadow = '0 12px 28px rgba(59, 130, 246, 0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}}
>
{selectedPart?.id === part.id && (
)}
{part.id}
{part.name}
{part.type}
{part.family}
{/* Process Steps Preview */}
Process Steps
{part.routing.filter(r => r.enabled).map(r => (
{r.operation === 'Cutting' && '✂️'}
{r.operation === 'Machining' && '⚙️'}
{r.operation === 'Assembly' && '🔧'}
{r.operation === 'Painting' && '🎨'}
{r.operation === 'Packaging' && '📦'}
{' '}{r.cycleTime}m
))}
))}
{selectedPart && (
Part Selected: {selectedPart.id}
Click "Bill of Materials" tab to configure BOM and routing
)}
)}
{/* BOM Tab */}
{activeTab === 'bom' && (
Bill of Materials
{selectedPart && (
Part: {selectedPart.id}
)}
{bom.length > 0 ? (
| Material ID |
Description |
Quantity |
Unit |
Cost ($/unit) |
Total |
LME |
{bom.map((item, index) => {
let currentCost = item.stdCost;
if (item.lmeLinked) {
const materialType = item.material.toLowerCase();
if (materialType.includes('copper')) currentCost = parseFloat(lmePrices.copper);
if (materialType.includes('brass')) currentCost = parseFloat(lmePrices.brass);
if (materialType.includes('steel')) currentCost = parseFloat(lmePrices.steel);
if (materialType.includes('aluminum') || materialType.includes('aluminium')) currentCost = parseFloat(lmePrices.aluminum);
}
const totalCost = (item.qty * currentCost).toFixed(2);
return (
| {item.id} |
{item.material} |
updateBOMItem(index, 'qty', e.target.value)}
style={{
background: '#ffffff',
border: '2px solid #cbd5e1',
color: '#0f172a',
padding: '0.6rem',
borderRadius: '8px',
width: '90px',
textAlign: 'right',
fontSize: '0.9rem',
fontWeight: 600,
transition: 'all 0.2s ease'
}}
/>
|
{item.unit} |
updateBOMItem(index, 'stdCost', e.target.value)}
disabled={item.lmeLinked}
style={{
background: item.lmeLinked ? '#d1fae5' : '#ffffff',
border: item.lmeLinked ? '2px solid #6ee7b7' : '2px solid #cbd5e1',
color: item.lmeLinked ? '#047857' : '#0f172a',
padding: '0.6rem',
borderRadius: '8px',
width: '110px',
textAlign: 'right',
fontSize: '0.9rem',
fontWeight: 700,
cursor: item.lmeLinked ? 'not-allowed' : 'text',
transition: 'all 0.2s ease'
}}
/>
|
${totalCost}
|
{item.lmeLinked && (
✓ LIVE
)}
|
);
})}
) : (
Please select a part number first
)}
)}
{/* Routing Tab */}
{activeTab === 'routing' && (
Routing & Operations
{selectedPart && (
Part: {selectedPart.id}
)}
{routing.length > 0 ? (
| Enable |
Operation |
Process Step |
Work Center |
Cycle Time (min) |
Rate ($/hr) |
Cost |
{routing.map((op, index) => {
const opCost = op.enabled ? ((op.cycleTime / 60) * op.rate).toFixed(2) : '0.00';
const isDisabled = !op.enabled;
return (
|
updateRoutingItem(index, 'enabled', e.target.checked)}
style={{
width: '20px',
height: '20px',
cursor: 'pointer',
accentColor: '#3b82f6'
}}
/>
|
{op.id} |
{op.operation === 'Cutting' && ✂️}
{op.operation === 'Machining' && ⚙️}
{op.operation === 'Assembly' && 🔧}
{op.operation === 'Painting' && 🎨}
{op.operation === 'Packaging' && 📦}
{op.operation}
|
{op.workCenter} |
updateRoutingItem(index, 'cycleTime', e.target.value)}
disabled={isDisabled}
style={{
background: isDisabled ? '#e2e8f0' : '#ffffff',
border: '2px solid #cbd5e1',
color: '#0f172a',
padding: '0.6rem',
borderRadius: '8px',
width: '90px',
textAlign: 'right',
fontSize: '0.9rem',
fontWeight: 600,
transition: 'all 0.2s ease',
cursor: isDisabled ? 'not-allowed' : 'text'
}}
/>
|
updateRoutingItem(index, 'rate', e.target.value)}
disabled={isDisabled}
style={{
background: isDisabled ? '#e2e8f0' : '#ffffff',
border: '2px solid #cbd5e1',
color: '#0f172a',
padding: '0.6rem',
borderRadius: '8px',
width: '110px',
textAlign: 'right',
fontSize: '0.9rem',
fontWeight: 600,
transition: 'all 0.2s ease',
cursor: isDisabled ? 'not-allowed' : 'text'
}}
/>
|
${opCost}
|
);
})}
{/* Total Processing Time Summary */}
Total Processing Time (Enabled Operations)
{routing.reduce((sum, op) => op.enabled ? sum + op.cycleTime : sum, 0).toFixed(1)} minutes
Total Processing Cost
${routing.reduce((sum, op) => op.enabled ? sum + ((op.cycleTime / 60) * op.rate) : sum, 0).toFixed(2)}
) : (
Please select a part number first
)}
)}
{/* Results Tab */}
{activeTab === 'results' && (
Cost Analysis & Breakdown
{costBreakdown ? (
{/* Cost Summary Cards */}
{[
{ label: 'Raw Material Cost', value: costBreakdown.rmCost, color: '#3b82f6', gradient: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', icon: Package },
{ label: 'Processing Cost', value: costBreakdown.processingCost, color: '#8b5cf6', gradient: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)', icon: Settings },
{ label: 'Gross Product Cost', value: costBreakdown.grossProductCost, color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)', icon: Calculator },
{ label: 'Cost of Production', value: costBreakdown.costOfProduction, color: '#10b981', gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', icon: DollarSign }
].map((item, index) => {
const Icon = item.icon;
return (
);
})}
{/* Detailed Breakdown */}
Detailed Cost Structure (per Welspring Model)
{[
{ label: 'Raw Material Consumption', value: costBreakdown.rmCost, percentage: costBreakdown.rmPercentage, standard: '60%', color: '#3b82f6' },
{ label: 'Labour Bills', value: costBreakdown.laborCost, percentage: '10', standard: '10%', color: '#8b5cf6' },
{ label: 'Process Consumables', value: costBreakdown.consumables, percentage: '5', standard: '5%', color: '#06b6d4' },
{ label: 'Power Bill', value: costBreakdown.powerCost, percentage: '5', standard: '5%', color: '#f59e0b' },
{ label: 'Repair & Maintenance', value: costBreakdown.maintenance, percentage: '5', standard: '5%', color: '#ef4444' },
{ label: 'Overheads (Gross Margin)', value: costBreakdown.grossMargin, percentage: '15', standard: '15%', color: '#10b981' }
].map((item, index) => (
{
e.currentTarget.style.borderColor = item.color;
e.currentTarget.style.boxShadow = `0 4px 16px ${item.color}20`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = '#e2e8f0';
e.currentTarget.style.boxShadow = 'none';
}}
>
{item.label}
Target: {item.standard}
Actual %
{item.percentage}%
${item.value}
))}
{/* Visual Breakdown */}
Cost Composition
RM {costBreakdown.rmPercentage}%
Process {costBreakdown.processingPercentage}%
Overheads 25%
) : (
Please configure part, BOM, and routing to see cost analysis
)}
)}
);
};
export default WelspringCostSimulator;