`;
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 = '
`;
grandTotalAllYears += propTotalAllYears;
});
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();
});
${currency}
${currency}
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 |
|---|---|---|---|---|---|
| Year ${year} | ${currency}${annualBaseRent.toFixed(2)} | ${currency}${annualCam.toFixed(2)} | ${currency}${annualOtherRecurring.toFixed(2)} | ${currency}${yearOneTimeCosts.toFixed(2)} | ${currency}${annualTotalForPropertyYear.toFixed(2)} |
| Total for ${propName} over ${budgetHorizonYears} Years: | ${currency}${propTotalAllYears.toFixed(2)} | ||||
Overall Summary - All Properties
`; html += `| Year | Total Cost All Properties |
|---|---|
| Year ${year+1} | ${currency}${grandTotalByYear[year].toFixed(2)} |
| Grand Total Over ${budgetHorizonYears} Years: | ${currency}${grandTotalAllYears.toFixed(2)} |
