One-Time Costs
No one-time costs added for this property.
`;
propertiesContainer.appendChild(propDiv);
setupEscalationVisibility(propDiv);
propDiv.querySelector('.orl-remove-property').addEventListener('click', function() {
this.closest('.orl-property-section').remove();
checkEmptyState('#orl-properties-container', 'orl-no-items-text');
});
propDiv.querySelectorAll('.orl-add-other-recurring').forEach(btn => btn.addEventListener('click', handleAddSubItem));
propDiv.querySelectorAll('.orl-add-one-time').forEach(btn => btn.addEventListener('click', handleAddSubItem));
propDiv.querySelector(`#prop-area-unit-${propertyIdCounter}`).addEventListener('input', function() {
const suffix = this.closest('.orl-property-section').querySelector('.orl-property-area-unit-suffix');
if (suffix) suffix.textContent = this.value || defaultAreaUnitInput.value || 'unit';
});
checkEmptyState('#orl-properties-container', 'orl-no-items-text');
updateDynamicTextElements(); // To apply currency to new elements
}
function setupEscalationVisibility(parentElement) {
parentElement.querySelectorAll('.orl-esc-type-select').forEach(select => {
const valueGroup = select.closest('div').parentElement.querySelector('.orl-esc-value-group');
const afterYearGroup = select.closest('div').parentElement.querySelector('.orl-esc-after-year-group');
const escUnitSpan = valueGroup ? valueGroup.querySelector('.orl-esc-unit') : null;
function toggleEscFields() {
if (select.value === 'None') {
if (valueGroup) valueGroup.style.display = 'none';
if (afterYearGroup) afterYearGroup.style.display = 'none';
} else {
if (valueGroup) valueGroup.style.display = 'flex';
if (afterYearGroup) afterYearGroup.style.display = 'flex';
if (escUnitSpan) escUnitSpan.textContent = select.value === 'Percentage' ? '%' : (currencySymbolInput.value || '$');
}
}
select.addEventListener('change', toggleEscFields);
toggleEscFields(); // Initial call
});
}
function handleAddSubItem(event) {
const targetContainerId = event.target.dataset.targetContainer;
const container = document.getElementById(targetContainerId);
const isOneTime = event.target.classList.contains('orl-add-one-time');
const currency = currencySymbolInput.value || '$';
const subItemId = Date.now(); // Unique ID
const itemDiv = document.createElement('div');
itemDiv.classList.add('orl-repeatable-item');
if (isOneTime) {
itemDiv.innerHTML = `
`;
} else { // Other Recurring Cost
itemDiv.innerHTML = `
`;
setupEscalationVisibility(itemDiv); // Setup for the new recurring item
}
container.appendChild(itemDiv);
itemDiv.querySelector('.orl-remove-button').addEventListener('click', function() {
this.closest('.orl-repeatable-item').remove();
checkEmptyState(`#${container.id}`, 'orl-no-items-text');
});
checkEmptyState(`#${container.id}`, 'orl-no-items-text');
updateDynamicTextElements();
}
// --- Summary Generation ---
function getNum(value) { return parseFloat(value) || 0; }
function calculateAnnualCost(initialAmount, frequency, escalationType, escalationValue, escalationAfterYear, currentYear, area, amountType) {
let annualCost = initialAmount;
if (amountType === 'PerAreaUnitPerPeriod') {
annualCost = initialAmount * area;
}
if (frequency === 'Monthly') {
annualCost *= 12;
}
if (currentYear > escalationAfterYear && escalationType !== 'None') {
const prevYearsToEscalate = currentYear - escalationAfterYear;
let effectiveEscalatedAnnualCost = annualCost; // Start with the initial annual cost for escalation base
// To correctly calculate compounded escalation, we need the *true* initial annual cost
// before any escalations. This is tricky if we only pass currentYear cost.
// Let's assume `initialAmount` is the *true* unescalated periodic amount.
let baseForEsc = (amountType === 'PerAreaUnitPerPeriod' ? initialAmount * area : initialAmount) * (frequency === 'Monthly' ? 12 : 1);
for (let i = 1; i <= prevYearsToEscalate; i++) {
if (escalationType === 'Percentage') {
baseForEsc *= (1 + getNum(escalationValue) / 100);
} else if (escalationType === 'Fixed') {
baseForEsc += getNum(escalationValue); // Assuming escalationValue is annual fixed increase
}
}
return baseForEsc; // This is the cost for the currentYear
}
return annualCost; // If currentYear <= escalationAfterYear or no escalation
}
function generateLeaseSummary() {
const currency = currencySymbolInput.value || '$';
const budgetHorizonYears = parseInt(document.getElementById('orl-budget-horizon').value);
const summaryOutput = document.getElementById('orl-summary-output');
let html = '';
let grandTotalByYear = Array(budgetHorizonYears).fill(0);
let grandTotalAllYears = 0;
const properties = document.querySelectorAll('.orl-property-section');
if (properties.length === 0) {
summaryOutput.innerHTML = '
No properties added. Please add properties in Tab 2.
';
downloadPdfButton.disabled = true;
return;
}
properties.forEach(prop => {
const propId = prop.dataset.propertyId;
const propName = prop.querySelector('.orl-prop-name').value || `Property ${propId}`;
const propArea = getNum(prop.querySelector('.orl-prop-area').value);
const propAreaUnit = prop.querySelector('.orl-prop-area-unit').value || defaultAreaUnitInput.value || 'unit';
html += `
${propName} (Area: ${propArea} ${propAreaUnit})
`;
html += `
| Year | Base Rent | Add. Rent/CAM | Other Recurring | One-Time Costs | Annual Total |
`;
let propTotalAllYears = 0;
// Collect one-time costs for this property
let propertyOneTimeCostsTotal = 0;
prop.querySelectorAll(`#orl-one-time-container-${propId} .orl-repeatable-item`).forEach(otcItem => {
propertyOneTimeCostsTotal += getNum(otcItem.querySelector('.orl-otc-amount').value);
});
for (let year = 1; year <= budgetHorizonYears; year++) {
let annualBaseRent = calculateAnnualCost(
getNum(prop.querySelector('.orl-br-amount').value),
prop.querySelector('.orl-br-freq').value,
prop.querySelector('.orl-br-esc-type').value,
getNum(prop.querySelector('.orl-br-esc-val').value),
getNum(prop.querySelector('.orl-br-esc-after').value),
year, propArea, 'TotalPerPeriod' // Base rent is usually total
);
let annualCam = calculateAnnualCost(
getNum(prop.querySelector('.orl-cam-amount').value),
prop.querySelector('.orl-cam-freq').value,
prop.querySelector('.orl-cam-esc-type').value,
getNum(prop.querySelector('.orl-cam-esc-val').value),
getNum(prop.querySelector('.orl-cam-esc-after').value),
year, propArea, prop.querySelector('.orl-cam-amount-type').value
);
let annualOtherRecurring = 0;
prop.querySelectorAll(`#orl-other-recurring-container-${propId} .orl-repeatable-item`).forEach(orcItem => {
annualOtherRecurring += calculateAnnualCost(
getNum(orcItem.querySelector('.orl-orc-amount').value),
orcItem.querySelector('.orl-orc-freq').value,
orcItem.querySelector('.orl-orc-esc-type').value,
getNum(orcItem.querySelector('.orl-orc-esc-val').value),
getNum(orcItem.querySelector('.orl-orc-esc-after').value),
year, propArea, 'TotalPerPeriod' // Assuming other recurring are total amounts
);
});
let yearOneTimeCosts = (year === 1) ? propertyOneTimeCostsTotal : 0;
let annualTotalForPropertyYear = annualBaseRent + annualCam + annualOtherRecurring + yearOneTimeCosts;
html += `
| Year ${year} |
${currency}${annualBaseRent.toFixed(2)} |
${currency}${annualCam.toFixed(2)} |
${currency}${annualOtherRecurring.toFixed(2)} |
${currency}${yearOneTimeCosts.toFixed(2)} |
${currency}${annualTotalForPropertyYear.toFixed(2)} |
`;
propTotalAllYears += annualTotalForPropertyYear;
grandTotalByYear[year-1] += annualTotalForPropertyYear;
}
html += `| Total for ${propName} over ${budgetHorizonYears} Years: | ${currency}${propTotalAllYears.toFixed(2)} |
`;
html += `
`;
grandTotalAllYears += propTotalAllYears;
});
html += `
Overall Summary - All Properties
`;
html += `
| Year | Total Cost All Properties |
`;
for (let year = 0; year < budgetHorizonYears; year++) {
html += `| Year ${year+1} | ${currency}${grandTotalByYear[year].toFixed(2)} |
`;
}
html += `| Grand Total Over ${budgetHorizonYears} Years: | ${currency}${grandTotalAllYears.toFixed(2)} |
`;
html += `
`;
summaryOutput.innerHTML = html;
downloadPdfButton.disabled = properties.length === 0;
}
// PDF Download
downloadPdfButton.addEventListener('click', function () {
if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library is not loaded.'); return;
}
const { jsPDF } = jspdf;
const doc = new jsPDF('p'); // Portrait
const currency = currencySymbolInput.value || '$';
const budgetHorizonYears = parseInt(document.getElementById('orl-budget-horizon').value);
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--orl-primary-color').trim();
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--orl-secondary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--orl-text-color').trim();
const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--orl-button-text-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text('Office Rent & Lease Budget Plan', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(textColor);
doc.text(`Business Name: ${document.getElementById('orl-business-name').value || 'N/A'}`, 14, 30);
doc.text(`Budget Horizon: ${budgetHorizonYears} Years`, 14, 37);
doc.text(`Currency: ${currency}`, 14, 44);
doc.text(`Default Area Unit: ${defaultAreaUnitInput.value || 'unit'}`, 14, 51);
let lastY = 60;
let grandTotalAllYearsPdf = 0;
const grandTotalByYearPdf = Array(budgetHorizonYears).fill(0);
document.querySelectorAll('.orl-property-section').forEach(prop => {
if (lastY > 250) { doc.addPage(); lastY = 20; } // Add new page if content gets too long
const propId = prop.dataset.propertyId;
const propName = prop.querySelector('.orl-prop-name').value || `Property ${propId}`;
const propArea = getNum(prop.querySelector('.orl-prop-area').value);
const propAreaUnit = prop.querySelector('.orl-prop-area-unit').value || defaultAreaUnitInput.value || 'unit';
doc.setFontSize(14);
doc.setTextColor(primaryColor);
doc.text(`${propName} (Area: ${propArea} ${propAreaUnit})`, 14, lastY);
lastY += 7;
const head = [['Year', 'Base Rent', 'Add. Rent/CAM', 'Other Recurring', 'One-Time Costs', 'Annual Total']];
const body = [];
let propTotalAllYearsPdf = 0;
let propertyOneTimeCostsTotal = 0;
prop.querySelectorAll(`#orl-one-time-container-${propId} .orl-repeatable-item`).forEach(otcItem => {
propertyOneTimeCostsTotal += getNum(otcItem.querySelector('.orl-otc-amount').value);
});
// Add One-Time Costs summary for this property
if (propertyOneTimeCostsTotal > 0) {
const otcData = [];
prop.querySelectorAll(`#orl-one-time-container-${propId} .orl-repeatable-item`).forEach(otcItem => {
otcData.push([ otcItem.querySelector('.orl-otc-name').value || 'Unnamed Cost', `${currency}${getNum(otcItem.querySelector('.orl-otc-amount').value).toFixed(2)}` ]);
});
if (otcData.length > 0) {
doc.setFontSize(10);
doc.setTextColor(textColor);
doc.text('One-Time Costs:', 14, lastY);
lastY += 5;
doc.autoTable({
startY: lastY, theme: 'grid',
head: [['Cost Name', 'Amount']], body: otcData,
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 9 },
styles: { fontSize: 8, cellPadding: 1.5 }
});
lastY = doc.lastAutoTable.finalY + 7;
}
}
for (let year = 1; year <= budgetHorizonYears; year++) {
let annualBaseRent = calculateAnnualCost(
getNum(prop.querySelector('.orl-br-amount').value), prop.querySelector('.orl-br-freq').value,
prop.querySelector('.orl-br-esc-type').value, getNum(prop.querySelector('.orl-br-esc-val').value),
getNum(prop.querySelector('.orl-br-esc-after').value), year, propArea, 'TotalPerPeriod'
);
let annualCam = calculateAnnualCost(
getNum(prop.querySelector('.orl-cam-amount').value), prop.querySelector('.orl-cam-freq').value,
prop.querySelector('.orl-cam-esc-type').value, getNum(prop.querySelector('.orl-cam-esc-val').value),
getNum(prop.querySelector('.orl-cam-esc-after').value), year, propArea, prop.querySelector('.orl-cam-amount-type').value
);
let annualOtherRecurring = 0;
prop.querySelectorAll(`#orl-other-recurring-container-${propId} .orl-repeatable-item`).forEach(orcItem => {
annualOtherRecurring += calculateAnnualCost(
getNum(orcItem.querySelector('.orl-orc-amount').value), orcItem.querySelector('.orl-orc-freq').value,
orcItem.querySelector('.orl-orc-esc-type').value, getNum(orcItem.querySelector('.orl-orc-esc-val').value),
getNum(orcItem.querySelector('.orl-orc-esc-after').value), year, propArea, 'TotalPerPeriod'
);
});
let yearOneTimeCosts = (year === 1) ? propertyOneTimeCostsTotal : 0;
let annualTotalForPropertyYear = annualBaseRent + annualCam + annualOtherRecurring + yearOneTimeCosts;
body.push([
`Year ${year}`, `${currency}${annualBaseRent.toFixed(2)}`, `${currency}${annualCam.toFixed(2)}`,
`${currency}${annualOtherRecurring.toFixed(2)}`, `${currency}${yearOneTimeCosts.toFixed(2)}`,
`${currency}${annualTotalForPropertyYear.toFixed(2)}`
]);
propTotalAllYearsPdf += annualTotalForPropertyYear;
grandTotalByYearPdf[year-1] += annualTotalForPropertyYear;
}
// Property Total Row
body.push([
{ content: `Total for ${propName}`, colSpan: 5, styles: { halign: 'right', fontStyle: 'bold' } },
{ content: `${currency}${propTotalAllYearsPdf.toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } }
]);
grandTotalAllYearsPdf += propTotalAllYearsPdf;
doc.autoTable({
startY: lastY, head: head, body: body, theme: 'grid',
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 9 },
styles: { fontSize: 8, cellPadding: 1.5 },
columnStyles: { // Align numbers to right in PDF table
1: { halign: 'right' }, 2: { halign: 'right' }, 3: { halign: 'right' }, 4: { halign: 'right' }, 5: { halign: 'right' }
}
});
lastY = doc.lastAutoTable.finalY + 10;
});
if (lastY > 250) { doc.addPage(); lastY = 20; }
doc.setFontSize(14);
doc.setTextColor(primaryColor);
doc.text('Overall Summary - All Properties', 14, lastY);
lastY += 7;
const summaryHead = [['Year', 'Total Cost All Properties']];
const summaryBody = [];
for (let year = 0; year < budgetHorizonYears; year++) {
summaryBody.push([`Year ${year+1}`, `${currency}${grandTotalByYearPdf[year].toFixed(2)}`]);
}
summaryBody.push([
{ content: `Grand Total Over ${budgetHorizonYears} Years:`, styles: { fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } },
{ content: `${currency}${grandTotalAllYearsPdf.toFixed(2)}`, styles: { fontStyle: 'bold', halign: 'right', fillColor: primaryColor, textColor: buttonTextColor } }
]);
doc.autoTable({
startY: lastY, head: summaryHead, body: summaryBody, theme: 'grid',
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 10 },
styles: { fontSize: 9 },
columnStyles: { 1: { halign: 'right' } }
});
doc.save(`Office_Lease_Budget_${document.getElementById('orl-business-name').value.replace(/\s+/g, '_') || 'Report'}.pdf`);
});
checkEmptyState('#orl-properties-container', 'orl-no-items-text');
showTab(0);
updateDynamicTextElements();
});