let chart; let currentAction = null; document.addEventListener("DOMContentLoaded", function () { const discountRate = parseFloat(document.getElementById('discountPeriod').value) / 100; const analysisPeriod = parseInt(document.getElementById('analysisPeriod').value) || 0; const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; const terminalSalvageValue = parseFloat(document.getElementById('terminalSalvageValue').value) || 0; updateTableRows(); }); function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); const activeTab = document.getElementById(tabId); activeTab.classList.add('active'); document.querySelectorAll('.tab').forEach(tab => { tab.classList.remove('active'); }); const activeTabElement = document.querySelector(`.tab[onclick*="${tabId}"]`); if (activeTabElement) { activeTabElement.classList.add('active'); } } let projectName = ''; function openProjectPopup() { document.getElementById('overlay').classList.add('active'); document.getElementById('popup').classList.add('active'); document.getElementById('projectInput').value = projectName; } function confirmProjectAction() { const newProjectName = document.getElementById('projectInput').value.trim(); if (!newProjectName) { alert("Please, enter a name for your project."); return; } projectName = newProjectName; document.getElementById('projectDisplay').textContent = projectName; closeProjectPopup(); } function closeProjectPopup() { document.getElementById('overlay').classList.remove('active'); document.getElementById('popup').classList.remove('active'); document.getElementById('projectInput').value = ''; } function printProject() { const projectDisplay = document.getElementById('projectDisplay').innerText; const terminalSalvageValue = document.getElementById('terminalSalvageValue').value; const analysisPeriod = document.getElementById('analysisPeriod').value; const discountPeriod = document.getElementById('discountPeriod').value; const pvbR = document.getElementById('pvbR').innerText; const pvc = document.getElementById('pvc').innerText; const fyrr = document.getElementById('fyrr').innerText; const npv = document.getElementById('npv').innerText; const npvToPvc = document.getElementById('npvToPvc').innerText; const bcr = document.getElementById('bcr').innerText; const irr = document.getElementById('irr').innerText; const { jsPDF } = window.jspdf; const pdf = new jsPDF(); pdf.setFontSize(18); pdf.text('ECONOMIC ASSESSMENT', 10, 10); pdf.setFontSize(10); pdf.text(`Project Name: ${projectDisplay}`, 10, 25); pdf.text(`Terminal Salvage Value: ${terminalSalvageValue}`, 10, 30); pdf.text(`Analysis Period: ${analysisPeriod} year(s)`, 10, 35); pdf.text(`Discount Rate: ${discountPeriod}%`, 10, 40); pdf.setFontSize(8); let startY = 50; const headers = ['Year', 'Initial Costs', 'Maintenance', 'Accidents', 'Time', 'Environment', 'Other Benefits', 'Other Costs']; const columnWidth = 24; for (let i = 0; i < headers.length; i++) { pdf.rect(10 + (i * columnWidth), startY, columnWidth, 20); pdf.text(headers[i], 10 + (i * columnWidth) + 2, startY + 6); } const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; const rows = tbody.getElementsByTagName('tr'); for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { const row = rows[rowIndex]; startY += 10; const cells = row.getElementsByTagName('td'); pdf.rect(10, startY, columnWidth, 10); pdf.text((rowIndex + 1).toString(), 10 + 2, startY + 7); for (let i = 1; i < cells.length; i++) { const input = cells[i].querySelector('input'); const value = input ? input.value : ''; pdf.rect(10 + (i * columnWidth), startY, columnWidth, 10); pdf.text(value, 10 + (i * columnWidth) + 2, startY + 7); } } pdf.setFontSize(10); startY += 20; pdf.text('Results', 10, startY); pdf.text(`Discounted Benefits (PVB): ${pvbR}`, 10, startY + 5); pdf.text(`Discounted Costs (PVC): ${pvc}`, 10, startY + 10); pdf.text(`FYRR: ${fyrr}`, 10, startY + 15); pdf.text(`NPV: ${npv}`, 10, startY + 20); pdf.text(`NPV / PVC: ${npvToPvc}`, 10, startY + 25); pdf.text(`BCR: ${bcr}`, 10, startY + 30); pdf.text(`IRR: ${irr}`, 10, startY + 35); const canvas = document.getElementById('resultsChart'); html2canvas(canvas).then((canvas) => { const imgData = canvas.toDataURL('image/png'); const imgWidth = 90; const imgHeight = (canvas.height * imgWidth) / canvas.width; const xPosition = (pdf.internal.pageSize.getWidth() - imgWidth) / 2; pdf.addImage(imgData, 'PNG', xPosition, startY + 50, imgWidth, imgHeight); pdf.save('Economic_Assessment_Report.pdf'); }).catch((error) => { console.error('Error capturing canvas:', error); }); } let tableValues = {}; function updateTableRows() { const period = document.getElementById('analysisPeriod').value; const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; tbody.innerHTML = ''; const maintenanceVisible = document.getElementById('maintenance1').checked || document.getElementById('maintenance2').checked; const accidentsVisible = document.getElementById('accidents1').checked || document.getElementById('accidents2').checked; const timeVisible = document.getElementById('time1').checked || document.getElementById('time2').checked; const environmentVisible = document.getElementById('environment1').checked || document.getElementById('environment2').checked; toggleHeaderVisibility('maintenanceCostHeader', maintenanceVisible); toggleHeaderVisibility('accidentsHeader', accidentsVisible); toggleHeaderVisibility('timeCostHeader', timeVisible); toggleHeaderVisibility('environmentCostHeader', environmentVisible); toggleHeaderVisibility('other1Header', document.getElementById('other1').checked); toggleHeaderVisibility('other2Header', document.getElementById('other2').checked); const tableVisible = maintenanceVisible || accidentsVisible || timeVisible || environmentVisible; const tableContainer = document.querySelector('.table1Ea'); tableContainer.style.display = tableVisible ? 'block' : 'none'; if (!tableVisible) return; for (let i = 1; i <= period; i++) { const row = tbody.insertRow(); const yearCell = row.insertCell(0); const costsCell = row.insertCell(1); yearCell.textContent = i; costsCell.innerHTML = ``; insertCell(row, maintenanceVisible, 'maintenance', i); insertCell(row, accidentsVisible, 'accidents', i); insertCell(row, timeVisible, 'time', i); insertCell(row, environmentVisible, 'environment', i); insertCell(row, document.getElementById('other1').checked, 'other1', i); insertCell(row, document.getElementById('other2').checked, 'other2', i); } updateChart(); calculateResults(); } function toggleHeaderVisibility(headerId, isVisible) { const header = document.getElementById(headerId); header.classList.toggle('hidden', !isVisible); } function insertCell(row, isVisible, type, period) { if (isVisible) { const cell = row.insertCell(); const value = getValue(period, type); cell.innerHTML = ``; } } function getValue(period, type) { return (tableValues[period] && tableValues[period][type]) || 0; } function storeValue(input, type, period) { if (!tableValues[period]) { tableValues[period] = {}; } tableValues[period][type] = parseFloat(input.value) || 0; } function clearValues() { tableValues = {}; } function calculateResults() { const discountRate = parseFloat(document.getElementById('discountPeriod').value) / 100; const terminalSalvageValue = parseFloat(document.getElementById('terminalSalvageValue').value) || 0; const analysisPeriod = parseInt(document.getElementById('analysisPeriod').value) || 0; const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; const rows = tbody.rows; let pvb = 0; let pvc = 0; let pvbR = 0; let initialCostsSum = 0; for (let i = 0; i < rows.length; i++) { const row = rows[i]; let benefitRowSum = 0; if (document.getElementById('maintenance1').checked) { benefitRowSum += parseFloat(row.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents1').checked) { benefitRowSum += parseFloat(row.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time1').checked) { benefitRowSum += parseFloat(row.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment1').checked) { benefitRowSum += parseFloat(row.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other1').checked) { benefitRowSum += parseFloat(row.cells[6]?.children[0]?.value) || 0; } if (document.getElementById('discountYear1').checked) { multiplier = 1 / (1 + discountRate) ** (i + 1); } else { multiplier = 1 / (1 + discountRate) ** i; } if (document.getElementById('discountYear1').checked) { salvage = terminalSalvageValue * (1 / (1 + discountRate) ** analysisPeriod); } else { salvage = terminalSalvageValue * (1 / (1 + discountRate) ** (analysisPeriod - 1)); } pvb += Math.ceil(benefitRowSum * multiplier); pvbR = pvb + salvage; } for (let i = 0; i < rows.length; i++) { const row = rows[i]; let costRowSum = 0; costRowSum += parseFloat(row.cells[1].children[0]?.value) || 0; if (document.getElementById('maintenance2').checked) { costRowSum += parseFloat(row.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents2').checked) { costRowSum += parseFloat(row.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time2').checked) { costRowSum += parseFloat(row.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment2').checked) { costRowSum += parseFloat(row.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other2').checked) { costRowSum += parseFloat(row.cells[7]?.children[0]?.value) || 0; } if (document.getElementById('discountYear1').checked) { multiplier = 1 / (1 + discountRate) ** (i + 1); } else { multiplier = 1 / (1 + discountRate) ** i; } pvc += costRowSum * multiplier; } for (let i = 0; i < rows.length; i++) { const row = rows[i]; const initialCostValue = parseFloat(row.cells[1]?.children[0]?.value) || 0; const multiplierC = 1 / (1 + discountRate) ** i; initialCostsSum += initialCostValue * multiplierC; } let fyrr = 0; if (document.getElementById('discountYear1').checked) { let benefitRowSumYear1 = 0; const row1 = rows[0]; if (document.getElementById('maintenance1').checked) { benefitRowSumYear1 += parseFloat(row1.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents1').checked) { benefitRowSumYear1 += parseFloat(row1.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time1').checked) { benefitRowSumYear1 += parseFloat(row1.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment1').checked) { benefitRowSumYear1 += parseFloat(row1.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other1').checked) { benefitRowSumYear1 += parseFloat(row1.cells[6]?.children[0]?.value) || 0; } const sumInitialCosts = initialCostsSum; fyrr = sumInitialCosts !== 0 ? (benefitRowSumYear1 / sumInitialCosts) * 100 : 0; } else { let benefitRowSumYear2 = 0; const row2 = rows[1]; if (row2) { if (document.getElementById('maintenance1').checked) { benefitRowSumYear2 += parseFloat(row2.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents1').checked) { benefitRowSumYear2 += parseFloat(row2.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time1').checked) { benefitRowSumYear2 += parseFloat(row2.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment1').checked) { benefitRowSumYear2 += parseFloat(row2.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other1').checked) { benefitRowSumYear2 += parseFloat(row2.cells[6]?.children[0]?.value) || 0; } } else { benefitRowSumYear2 = 0; } const sumInitialCosts = initialCostsSum; fyrr = sumInitialCosts !== 0 ? (benefitRowSumYear2 / sumInitialCosts) * 100 : 0; } const npv = pvbR - pvc; const npvToPvc = npv / pvc; const bcr = pvbR / pvc; const irr = calculateIRR(); document.getElementById('pvbR').textContent = pvbR.toFixed(0); document.getElementById('pvc').textContent = pvc.toFixed(0); document.getElementById('fyrr').textContent = fyrr.toFixed(2); document.getElementById('npv').textContent = npv.toFixed(0); document.getElementById('npvToPvc').textContent = npvToPvc.toFixed(2); document.getElementById('bcr').textContent = bcr.toFixed(2); document.getElementById('irr').textContent = (irr * 100).toFixed(2) + '%'; updateChart(); } function irr(cashFlow, guess = 0.1) { const maxIteration = 1000; const precision = 1e-7; let rate = guess; for (let i = 0; i < maxIteration; i++) { let npv = 0; let derivative = 0; for (let t = 0; t < cashFlow.length; t++) { npv += cashFlow[t] / (1 + rate) ** t; derivative += -t * cashFlow[t] / (1 + rate) ** (t + 1); } const newRate = rate - npv / derivative; if (Math.abs(newRate - rate) < precision) { return newRate; } rate = newRate; } return rate; } function calculateIRR() { const cashFlow = []; const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; const rows = tbody.rows; const terminalSalvageValue = parseFloat(document.getElementById('terminalSalvageValue').value) || 0; for (let i = 0; i < rows.length; i++) { const row = rows[i]; let benefitRowSum = 0; let costRowSum = 0; if (document.getElementById('maintenance1').checked) { benefitRowSum += parseFloat(row.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents1').checked) { benefitRowSum += parseFloat(row.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time1').checked) { benefitRowSum += parseFloat(row.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment1').checked) { benefitRowSum += parseFloat(row.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other1').checked) { benefitRowSum += parseFloat(row.cells[6]?.children[0]?.value) || 0; } costRowSum += parseFloat(row.cells[1].children[0]?.value) || 0; if (document.getElementById('maintenance2').checked) { costRowSum += parseFloat(row.cells[2]?.children[0]?.value) || 0; } if (document.getElementById('accidents2').checked) { costRowSum += parseFloat(row.cells[3]?.children[0]?.value) || 0; } if (document.getElementById('time2').checked) { costRowSum += parseFloat(row.cells[4]?.children[0]?.value) || 0; } if (document.getElementById('environment2').checked) { costRowSum += parseFloat(row.cells[5]?.children[0]?.value) || 0; } if (document.getElementById('other2').checked) { costRowSum += parseFloat(row.cells[7]?.children[0]?.value) || 0; } cashFlow.push(benefitRowSum - costRowSum); } if (cashFlow.length > 0) { cashFlow[cashFlow.length - 1] += terminalSalvageValue; } const irrValue = irr(cashFlow); return irrValue; } function drawChart(discountRate, analysisPeriod, tbody, terminalSalvageValue) { const labels = [ "Initial costs", "TSV", "Maint. & oper.", "Accidents", "Time", "Environment", "Other benefits", "Other costs" ]; const dataValues = new Array(labels.length).fill(0); let initialCostsSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const initialCostValue = parseFloat(rows[i].cells[1]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); initialCostsSum += initialCostValue * multiplier; } dataValues[0] = -initialCostsSum; dataValues[1] = terminalSalvageValue * (1 / Math.pow(1 + discountRate, analysisPeriod)); dataValues[2] = calculateMaintenanceValue(tbody, discountRate, analysisPeriod, 2, 'maintenance'); dataValues[3] = calculateAccidentsValue(tbody, discountRate, analysisPeriod, 3, 'accidents'); dataValues[4] = calculateTimeValue(tbody, discountRate, analysisPeriod, 4, 'time'); dataValues[5] = calculateEnvironmentValue(tbody, discountRate, analysisPeriod, 5, 'environment'); dataValues[6] = calculateOtherBenefits(tbody, discountRate, analysisPeriod, 6, 'other1'); dataValues[7] = -calculateOtherCosts(tbody, discountRate, analysisPeriod, 7, 'other2'); const ctx = document.getElementById('resultsChart').getContext('2d'); if (chart) { chart.destroy(); } chart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Benefits and costs', data: dataValues, backgroundColor: [ 'darkgreen', // Initial costs 'yellow', // TSV 'red', // Maint. & oper. 'lightgreen', // Accidents 'lightyellow', // Time 'gray', // Environment 'white', // Other benefits 'black' // Other costs ], borderColor: 'black', borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { display: true, labels: { color: 'black', font: { size: 12, }, }, }, }, scales: { x: { beginAtZero: true, ticks: { autoSkip: false, maxRotation: 90, minRotation: 90, callback: function (value, index) { return labels[index]; }, color: 'black' } }, y: { ticks: { color: 'black', }, grid: { color: 'black', lineWidth: 1, drawBorder: false, zeroLineColor: 'black', zeroLineWidth: 1, zeroLineDash: [] } } } } }); } function calculateMaintenanceValue(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'maintenance') { if (document.getElementById('maintenance1').checked) { valueToAdd = rowValue * multiplier; } else if (document.getElementById('maintenance2').checked) { valueToAdd = -rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function calculateAccidentsValue(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'accidents') { if (document.getElementById('accidents1').checked) { valueToAdd = rowValue * multiplier; } else if (document.getElementById('accidents2').checked) { valueToAdd = -rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function calculateTimeValue(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'time') { if (document.getElementById('time1').checked) { valueToAdd = rowValue * multiplier; } else if (document.getElementById('time2').checked) { valueToAdd = -rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function calculateEnvironmentValue(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'environment') { if (document.getElementById('environment1').checked) { valueToAdd = rowValue * multiplier; } else if (document.getElementById('environment2').checked) { valueToAdd = -rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function calculateOtherBenefits(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'other1') { if (document.getElementById('other1').checked) { valueToAdd = rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function calculateOtherCosts(tbody, discountRate, analysisPeriod, columnIndex, type) { let totalSum = 0; const rows = tbody.rows; for (let i = 0; i < rows.length; i++) { const rowValue = parseFloat(rows[i].cells[columnIndex]?.children[0]?.value) || 0; const isDiscountYear1 = document.getElementById(`discountYear1`).checked; const multiplier = isDiscountYear1 ? 1 / Math.pow(1 + discountRate, i + 1) : 1 / Math.pow(1 + discountRate, i); let valueToAdd = 0; if (type === 'other2') { if (document.getElementById('other2').checked) { valueToAdd = rowValue * multiplier; } } totalSum += valueToAdd; } return totalSum; } function updateChart() { const discountRate = parseFloat(document.getElementById('discountPeriod').value) / 100; const analysisPeriod = parseInt(document.getElementById('analysisPeriod').value) || 0; const tbody = document.getElementById('benefitsCostsTable').getElementsByTagName('tbody')[0]; const terminalSalvageValue = parseFloat(document.getElementById('terminalSalvageValue').value) || 0; drawChart(discountRate, analysisPeriod, tbody, terminalSalvageValue); }