Production Yield Dashboard
Rolled Throughput Yield
100%
Total Units Produced
0
Total Units Lost
0
Worst Stage
N/A
Production Yield Funnel
Stage-by-Stage Yield %
Units Lost per Stage
Define Production Stages
Input Production Data
Add stages to begin entering data.
Add stages to begin entering data.
'; } }; dataInputContainer.addEventListener('change', (event) => { if(event.target.tagName === 'INPUT' && event.target.type === 'number') { const id = parseInt(event.target.dataset.id); const type = event.target.dataset.type; const value = parseInt(event.target.value) || 0; const stageIndex = stages.findIndex(s => s.id === id); if(stageIndex > -1) { if (type === 'in') stages[stageIndex].unitsIn = value; if (type === 'out') { stages[stageIndex].unitsOut = value; // Auto-fill next stage's input if(stageIndex + 1 < stages.length) { stages[stageIndex + 1].unitsIn = value; document.getElementById(`in-${stages[stageIndex + 1].id}`).value = value; } } } } }); const updateDashboard = () => { saveData(); let rolledThroughputYield = 1; let worstStage = { name: 'N/A', yield: 100 }; stages.forEach(stage => { stage.yield = stage.unitsIn > 0 ? (stage.unitsOut / stage.unitsIn) : 1; stage.lost = stage.unitsIn - stage.unitsOut; rolledThroughputYield *= stage.yield; if (stage.yield < worstStage.yield) { worstStage = { name: stage.name, yield: stage.yield * 100 }; } }); kpiElements.rty.textContent = `${(rolledThroughputYield * 100).toFixed(2)}%`; kpiElements.rty.className = `py-kpi-value ${rolledThroughputYield >= 0.9 ? 'success' : rolledThroughputYield >= 0.8 ? 'warning' : 'danger'}`; const finalUnits = stages.length > 0 ? stages[stages.length - 1].unitsOut : 0; kpiElements.finalUnits.textContent = finalUnits.toLocaleString(); const startUnits = stages.length > 0 ? stages[0].unitsIn : 0; kpiElements.lostUnits.textContent = (startUnits - finalUnits).toLocaleString(); kpiElements.worstStage.textContent = worstStage.name; updateFunnelChart(); updateStageYieldChart(); updateUnitsLostChart(); }; const updateFunnelChart = () => { const data = { labels: ['Input', ...stages.map(s => s.name)], datasets: [{ label: 'Units Remaining', data: [stages.length > 0 ? stages[0].unitsIn : 0, ...stages.map(s => s.unitsOut)], backgroundColor: ['#2c3e50', '#2980b9', '#3498db', '#9b59b6', '#8e44ad', '#f1c40f', '#f39c12'] }] }; renderChart('py-funnelChart', 'bar', data, { indexAxis: 'y', scales: { y: { reverse: true } }, plugins: { legend: { display: false } } }); }; const updateStageYieldChart = () => { const data = { labels: stages.map(s => s.name), datasets: [{ label: 'Stage Yield (%)', data: stages.map(s => s.yield * 100), backgroundColor: stages.map(s => s.yield >= 0.95 ? 'var(--secondary-color)' : s.yield >= 0.9 ? 'var(--warning-color)' : 'var(--danger-color)'), }] }; renderChart('py-stageYieldChart', 'bar', data, { scales: { y: { min: 0, max: 100 } } }); }; const updateUnitsLostChart = () => { const data = { labels: stages.map(s => s.name), datasets: [{ label: 'Units Lost', data: stages.map(s => s.lost), backgroundColor: 'var(--danger-color)', }] }; renderChart('py-unitsLostChart', 'bar', data, {}); }; const renderChart = (canvasId, type, data, options) => { const ctx = document.getElementById(canvasId); if (!ctx) return; const chartContainer = ctx.closest('.py-chart-container'); if(chartContainer) chartContainer.innerHTML = ``; const newCtx = document.getElementById(canvasId); if (charts[canvasId]) charts[canvasId].destroy(); charts[canvasId] = new Chart(newCtx, { type, data, options: { responsive: true, maintainAspectRatio: false, ...options } }); }; const downloadPDF = () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(18); doc.text("Production Yield Report", 14, 22); const kpiData = [ ['Rolled Throughput Yield', kpiElements.rty.textContent], ['Total Units Produced', kpiElements.finalUnits.textContent], ['Total Units Lost', kpiElements.lostUnits.textContent], ['Worst Stage', kpiElements.worstStage.textContent] ]; doc.autoTable({ startY: 30, head: [['Key Metric', 'Value']], body: kpiData, headStyles: { fillColor: [44, 62, 80] } }); const stageData = stages.map(s => [s.name, s.unitsIn, s.unitsOut, `${(s.yield * 100).toFixed(2)}%`, s.lost]); doc.autoTable({ startY: doc.lastAutoTable.finalY + 10, head: [['Stage', 'Units In', 'Units Out', 'Yield %', 'Units Lost']], body: stageData, headStyles: { fillColor: [39, 174, 96] } }); doc.save(`Production-Yield-Report.pdf`); }; const saveData = () => localStorage.setItem('py_stages', JSON.stringify(stages)); const loadData = () => stages = JSON.parse(localStorage.getItem('py_stages')) || []; let currentTabIndex = 0; window.py_changeTab = (event, tabName) => { currentTabIndex = Array.from(tabs).findIndex(t => t === event.currentTarget); tabContents.forEach(c => c.classList.remove('active')); tabs.forEach(t => t.classList.remove('active')); document.getElementById(tabName).classList.add('active'); event.currentTarget.classList.add('active'); updateNavButtons(); }; const updateNavButtons = () => { prevBtn.style.display = currentTabIndex === 0 ? 'none' : 'inline-block'; nextBtn.style.display = currentTabIndex === tabs.length - 1 ? 'none' : 'inline-block'; }; const navigateTabs = (direction) => { const newIndex = currentTabIndex + direction; if (newIndex >= 0 && newIndex < tabs.length) tabs[newIndex].click(); }; const showConfirmation = (message) => { confirmationMessage.textContent = message; confirmationMessage.classList.add('visible'); setTimeout(() => confirmationMessage.classList.remove('visible'), 2500); }; addStageBtn.addEventListener('click', addStage); updateDashboardBtn.addEventListener('click', () => { updateDashboard(); showConfirmation('Dashboard updated with the latest data.'); }); downloadPdfBtn.addEventListener('click', downloadPDF); prevBtn.addEventListener('click', () => navigateTabs(-1)); nextBtn.addEventListener('click', () => navigateTabs(1)); loadData(); renderDataInputUI(); updateDashboard(); updateNavButtons(); });