`;
summaryOutput.innerHTML = html;
downloadPdfButton.disabled = grandTotal === 0 && detailedExpenses.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');
const currency = currencySymbolInput.value || '$';
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-primary-color').trim();
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-secondary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-text-color').trim();
const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--btce-button-text-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text('Business Trip Cost Estimate', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(textColor);
let lastY = 30;
doc.text(`Business Name: ${getVal('btce-business-name') || 'N/A'}`, 14, lastY); lastY += 7;
doc.text(`Trip Name/Purpose: ${getVal('btce-trip-name') || 'N/A'}`, 14, lastY); lastY += 7;
doc.text(`Destination: ${getVal('btce-destination') || 'N/A'}`, 14, lastY); lastY += 7;
doc.text(`Duration: ${getNum('btce-num-days')} Days, ${getNum('btce-num-nights')} Nights`, 14, lastY); lastY += 7;
doc.text(`Number of Travelers: ${getNum('btce-num-travelers')}`, 14, lastY); lastY += 10;
const categoryTotalsPdf = {};
const detailedExpensesPdf = [];
let grandTotalPdf = 0;
// Re-run calculation logic to ensure data consistency for PDF
// Transportation
let flightsTotal = getNum('btce-flights-cost') * getNum('btce-num-travelers');
if(flightsTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Flights', amount: flightsTotal });
const localTransType = getRadioVal('localTransType');
const localTransAmount = getNum('btce-localtrans-amount');
let localTransTotal = 0;
if (localTransType === 'perDayPerTraveler') localTransTotal = localTransAmount * getNum('btce-num-days') * getNum('btce-num-travelers');
else localTransTotal = localTransAmount;
if(localTransTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Local Transport', amount: localTransTotal });
let carRentalTotal = getNum('btce-carrental-total');
if(carRentalTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Car Rental', amount: carRentalTotal });
let fuelTotal = getNum('btce-carrental-fuel');
if(fuelTotal > 0) detailedExpensesPdf.push({ category: 'Transportation', name: 'Fuel for Rental', amount: fuelTotal });
// Accommodation
const hotelCostNight = getNum('btce-hotel-cost-night');
const hotelBasis = getRadioVal('hotelBasis');
let accommodationTotal = 0;
if (hotelBasis === 'perTraveler') accommodationTotal = hotelCostNight * getNum('btce-num-nights') * getNum('btce-num-travelers');
else accommodationTotal = hotelCostNight * getNum('btce-num-nights') * (getNum('btce-num-rooms') || 1);
if(accommodationTotal > 0) detailedExpensesPdf.push({ category: 'Accommodation', name: 'Hotel/Lodging', amount: accommodationTotal });
// Meals
const mealsType = getRadioVal('mealsType');
const mealsAmount = getNum('btce-meals-amount');
let mealsTotal = 0;
if (mealsType === 'perDayPerTraveler') mealsTotal = mealsAmount * getNum('btce-num-days') * getNum('btce-num-travelers');
else mealsTotal = mealsAmount;
if(mealsTotal > 0) detailedExpensesPdf.push({ category: 'Meals & Per Diems', name: 'Meals/Per Diems', amount: mealsTotal });
// Other Standard
let visaCostTotal = getNum('btce-visa-cost') * getNum('btce-num-travelers');
if(visaCostTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Visa Costs', amount: visaCostTotal });
const insuranceType = getRadioVal('insuranceType');
const insuranceAmount = getNum('btce-insurance-amount');
let insuranceTotal = 0;
if (insuranceType === 'perTraveler') insuranceTotal = insuranceAmount * getNum('btce-num-travelers');
else insuranceTotal = insuranceAmount;
if(insuranceTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Travel Insurance', amount: insuranceTotal });
let eventFeesTotal = getNum('btce-event-fees') * getNum('btce-num-travelers');
if(eventFeesTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Conference/Event Fees', amount: eventFeesTotal });
let entertainmentTotal = getNum('btce-entertainment-total');
if(entertainmentTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Client Entertainment', amount: entertainmentTotal });
let commsTotal = getNum('btce-communication-total');
if(commsTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Communication', amount: commsTotal });
let baggageTotal = getNum('btce-baggage-fees') * getNum('btce-num-travelers');
if(baggageTotal > 0) detailedExpensesPdf.push({ category: 'Miscellaneous', name: 'Baggage Fees', amount: baggageTotal });
// Custom Expenses
document.querySelectorAll('.btce-custom-expense-item').forEach(item => {
const name = item.querySelector('.btce-custom-name').value || 'Custom Expense';
let amount = parseFloat(item.querySelector('.btce-custom-amount').value) || 0;
const basis = item.querySelector('.btce-custom-cost-basis').value;
const type = item.querySelector('.btce-custom-cost-type').value;
let calculatedAmount = 0;
if (basis === 'perTraveler') calculatedAmount = amount * getNum('btce-num-travelers');
else calculatedAmount = amount;
if (type === 'perDay') calculatedAmount *= getNum('btce-num-days');
if(calculatedAmount > 0) detailedExpensesPdf.push({ category: 'Custom Expenses', name, amount: calculatedAmount });
});
const tableBody = [];
detailedExpensesPdf.sort((a,b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
let currentCategoryPdf = "";
detailedExpensesPdf.forEach(exp => {
if (exp.amount > 0) {
categoryTotalsPdf[exp.category] = (categoryTotalsPdf[exp.category] || 0) + exp.amount;
grandTotalPdf += exp.amount;
}
});
detailedExpensesPdf.forEach(exp => {
if (currentCategoryPdf !== exp.category) {
// Add subtotal for previous category if it existed and had items
if (currentCategoryPdf !== "" && categoryTotalsPdf[currentCategoryPdf] !== undefined) {
tableBody.push([
{ content: `Subtotal for ${currentCategoryPdf}`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold' } },
{ content: `${currency}${categoryTotalsPdf[currentCategoryPdf].toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } }
]);
}
currentCategoryPdf = exp.category;
tableBody.push([{ content: exp.category, colSpan: 3, styles: { fontStyle: 'bold', fillColor: '#f0f0f0' } }]);
}
tableBody.push(['', exp.name, `${currency}${exp.amount.toFixed(2)}`]);
});
// Add subtotal for the very last category
if (currentCategoryPdf !== "" && categoryTotalsPdf[currentCategoryPdf] !== undefined) {
tableBody.push([
{ content: `Subtotal for ${currentCategoryPdf}`, colSpan: 2, styles: { halign: 'right', fontStyle: 'bold' } },
{ content: `${currency}${categoryTotalsPdf[currentCategoryPdf].toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold' } }
]);
}
tableBody.push([
{ content: 'GRAND TOTAL ESTIMATED COST', colSpan: 2, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } },
{ content: `${currency}${grandTotalPdf.toFixed(2)}`, styles: { halign: 'right', fontStyle: 'bold', fillColor: primaryColor, textColor: buttonTextColor } }
]);
doc.autoTable({
startY: lastY,
head: [['Category/Details', 'Item', 'Estimated Cost']],
body: tableBody,
theme: 'grid',
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 10 },
styles: { fontSize: 9, cellPadding: 2 },
columnStyles: { 2: { halign: 'right' } },
didDrawCell: (data) => { // For rowspan-like appearance for category header
if (data.body[data.row.index] && data.body[data.row.index][0] && data.body[data.row.index][0].colSpan === 3) {
// Handled by autotable's colSpan
}
}
});
lastY = doc.lastAutoTable.finalY + 10;
const numTravelersPdf = getNum('btce-num-travelers');
const numDaysPdf = getNum('btce-num-days');
doc.setFontSize(10);
doc.setTextColor(textColor);
doc.text(`Average Cost Per Traveler: ${currency}${(numTravelersPdf > 0 ? grandTotalPdf / numTravelersPdf : 0).toFixed(2)}`, 14, lastY); lastY += 7;
doc.text(`Average Cost Per Day (Overall): ${currency}${(numDaysPdf > 0 ? grandTotalPdf / numDaysPdf : 0).toFixed(2)}`, 14, lastY);
doc.save(`Business_Trip_Estimate_${getVal('btce-trip-name').replace(/\s+/g, '_') || 'Report'}.pdf`);
});
checkEmptyState('#btce-custom-expenses-container', 'btce-no-items-text');
showTab(0);
updateAllCurrencyPrefixes(); // Initial call
});
