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);
}