';
// 2. Get Prices (Tab 3)
const priceArch = bdd_getFloat(`bdd-price-arch-${archType}`);
const priceColumns = bdd_getFloat("bdd-price-columns");
const priceBouquets = bdd_getFloat("bdd-price-bouquets");
const priceCenterpieces = bdd_getFloat("bdd-price-centerpieces");
const priceFoil = bdd_getFloat("bdd-price-foil");
const priceGarland = bdd_getFloat("bdd-price-garland");
const priceFee = bdd_getFloat("bdd-price-fee");
// 3. Calculate Totals
const items = [];
let subtotal = 0;
if (archType > 0) {
items.push({
name: `Balloon Arch (${archText})`,
qty: 1,
unitPrice: priceArch,
total: priceArch
});
subtotal += priceArch;
}
if (qtyColumns > 0) {
const total = qtyColumns * priceColumns;
items.push({
name: 'Balloon Columns (Pair)',
qty: qtyColumns,
unitPrice: priceColumns,
total: total
});
subtotal += total;
}
if (qtyBouquets > 0) {
const total = qtyBouquets * priceBouquets;
items.push({
name: 'Balloon Bouquets',
qty: qtyBouquets,
unitPrice: priceBouquets,
total: total
});
subtotal += total;
}
if (qtyCenterpieces > 0) {
const total = qtyCenterpieces * priceCenterpieces;
items.push({
name: 'Table Centerpieces',
qty: qtyCenterpieces,
unitPrice: priceCenterpieces,
total: total
});
subtotal += total;
}
if (qtyFoil > 0) {
const total = qtyFoil * priceFoil;
items.push({
name: 'Jumbo Foil Balloons',
qty: qtyFoil,
unitPrice: priceFoil,
total: total
});
subtotal += total;
}
if (qtyGarland > 0) {
const total = qtyGarland * priceGarland;
items.push({
name: 'Organic Garland (ft)',
qty: qtyGarland,
unitPrice: priceGarland,
total: total
});
subtotal += total;
}
const totalCost = subtotal + priceFee;
// 4. Build HTML Output
// Using tables for professional formatting (Spec II.C.2)
let html = `
Event Summary
| Event Type |
${eventType} |
| Color Palette |
${colorListHTML} |
`;
if (items.length > 0) {
html += `
Itemized Estimate
| Item |
Quantity |
Unit Price |
Total |
${items.map(item => `
| ${item.name} |
${item.qty} |
${bdd_formatCurrency(item.unitPrice)} |
${bdd_formatCurrency(item.total)} |
`).join('')}
`;
} else {
html += `
No items selected. Go to the "Design Studio" tab to add decorations.
`;
}
html += `
Cost Summary
| Subtotal |
${bdd_formatCurrency(subtotal)} |
| Setup & Delivery Fee |
${bdd_formatCurrency(priceFee)} |
| Total Estimated Cost |
${bdd_formatCurrency(totalCost)} |
`;
outputDiv.innerHTML = html;
}
/**
* PDF Download Function (Spec II.C)
*/
function bdd_downloadPdf() {
// Check for jsPDF (Spec IV.B - Robust check)
if (typeof jspdf === "undefined" || typeof jspdf.plugin.autotable === "undefined") {
alert("Error: PDF library is missing. Please refresh the page.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// --- Get Data Again (cleanly, for PDF) ---
const eventType = bdd_getString("bdd-event-type");
const archType = bdd_getFloat("bdd-item-arch");
const archText = bdd_getSelectText("bdd-item-arch");
const qtyColumns = bdd_getFloat("bdd-item-columns");
const qtyBouquets = bdd_getFloat("bdd-item-bouquets");
const qtyCenterpieces = bdd_getFloat("bdd-item-centerpieces");
const qtyFoil = bdd_getFloat("bdd-item-foil");
const qtyGarland = bdd_getFloat("bdd-item-garland");
const selectedColors = [];
colorCheckboxes.forEach(cb => {
if (cb.checked) selectedColors.push(cb.value);
});
const colorString = selectedColors.length > 0 ? selectedColors.join(', ') : 'N/A';
const priceArch = bdd_getFloat(`bdd-price-arch-${archType}`);
const priceColumns = bdd_getFloat("bdd-price-columns");
const priceBouquets = bdd_getFloat("bdd-price-bouquets");
const priceCenterpieces = bdd_getFloat("bdd-price-centerpieces");
const priceFoil = bdd_getFloat("bdd-price-foil");
const priceGarland = bdd_getFloat("bdd-price-garland");
const priceFee = bdd_getFloat("bdd-price-fee");
const itemBody = [];
let subtotal = 0;
if (archType > 0) {
itemBody.push([
`Balloon Arch (${archText})`,
1,
bdd_formatCurrency(priceArch),
bdd_formatCurrency(priceArch)
]);
subtotal += priceArch;
}
if (qtyColumns > 0) {
const total = qtyColumns * priceColumns;
itemBody.push([
'Balloon Columns (Pair)',
qtyColumns,
bdd_formatCurrency(priceColumns),
bdd_formatCurrency(total)
]);
subtotal += total;
}
if (qtyBouquets > 0) {
const total = qtyBouquets * priceBouquets;
itemBody.push([
'Balloon Bouquets',
qtyBouquets,
bdd_formatCurrency(priceBouquets),
bdd_formatCurrency(total)
]);
subtotal += total;
}
if (qtyCenterpieces > 0) {
const total = qtyCenterpieces * priceCenterpieces;
itemBody.push([
'Table Centerpieces',
qtyCenterpieces,
bdd_formatCurrency(priceCenterpieces),
bdd_formatCurrency(total)
]);
subtotal += total;
}
if (qtyFoil > 0) {
const total = qtyFoil * priceFoil;
itemBody.push([
'Jumbo Foil Balloons',
qtyFoil,
bdd_formatCurrency(priceFoil),
bdd_formatCurrency(total)
]);
subtotal += total;
}
if (qtyGarland > 0) {
const total = qtyGarland * priceGarland;
itemBody.push([
'Organic Garland (ft)',
qtyGarland,
bdd_formatCurrency(priceGarland),
bdd_formatCurrency(total)
]);
subtotal += total;
}
const totalCost = subtotal + priceFee;
// --- Build PDF Document (Spec II.C.2 - Professional Formatting) ---
doc.setFontSize(18);
doc.text("Balloon Decoration Summary", 14, 22);
// Event Details
doc.autoTable({
startY: 30,
head: [['Event Details', '']],
body: [
['Event Type', eventType],
['Color Palette', colorString],
],
theme: 'striped',
headStyles: {
fillColor: '#007bff' // Consistent color (Spec II.C.3)
},
});
// Itemized Estimate
if (itemBody.length > 0) {
doc.autoTable({
startY: doc.lastAutoTable.finalY + 10,
head: [['Item', 'Quantity', 'Unit Price', 'Total']],
body: itemBody,
theme: 'striped',
headStyles: {
fillColor: '#007bff'
},
didDrawCell: (data) => {
if (data.section === 'body' && data.column.index >= 1) {
data.cell.styles.halign = 'right';
}
}
});
}
// Cost Summary
doc.autoTable({
startY: doc.lastAutoTable.finalY + 10,
head: [['Cost Summary', 'Amount']],
body: [
['Subtotal', bdd_formatCurrency(subtotal)],
['Setup & Delivery Fee', bdd_formatCurrency(priceFee)],
['Total Estimated Cost', bdd_formatCurrency(totalCost)],
],
theme: 'striped',
headStyles: {
fillColor: '#343a40'
},
bodyStyles: {
fontStyle: 'bold'
},
didDrawCell: (data) => {
if (data.column.index === 0) {
data.cell.styles.halign = 'right';
data.cell.styles.fontStyle = 'bold';
}
if (data.column.index === 1) {
data.cell.styles.halign = 'right';
}
if(data.row.index === 2) {
data.cell.styles.fontSize = 12;
}
}
});
// Save the PDF (Spec II.C.4 - Functional Button)
doc.save("Balloon-Decoration-Summary.pdf");
}
// --- Checkbox Limitation ---
function bdd_limitColorCheckboxes() {
let checkedCount = 0;
colorCheckboxes.forEach(cb => {
if (cb.checked) checkedCount++;
});
if (checkedCount >= 4) {
colorCheckboxes.forEach(cb => {
if (!cb.checked) cb.disabled = true;
});
} else {
colorCheckboxes.forEach(cb => {
cb.disabled = false;
});
}
bdd_updateSummary(); // Update summary on check
}
// --- Event Listener Attachment (Spec IV.A) ---
// Attach to all inputs for live calculation
calcInputs.forEach((input) => {
input.addEventListener("input", bdd_updateSummary);
});
// Attach to color checkboxes
colorCheckboxes.forEach((input) => {
input.addEventListener("change", bdd_limitColorCheckboxes);
});
// Attach to event type select
if (eventTypeSelect) {
eventTypeSelect.addEventListener("change", bdd_updateSummary);
}
// Attach to PDF button
if (pdfButton) {
pdfButton.addEventListener("click", bdd_downloadPdf);
}
// --- Initial Load ---
// Open the first tab by default
const defaultTab = container.querySelector(".bdd-tab-link.bdd-active");
if (defaultTab) {
// Use the element's 'onclick' handler to open it
// This is a robust way to ensure the correct function runs
defaultTab.click();
}
// Run initial calculation
bdd_updateSummary();
bdd_limitColorCheckboxes(); // Run limit check on load
});
// --- Global Functions (required for inline 'onclick') (Spec IV.C) ---
/**
* Tab navigation function
* @param {Event} evt - The click event
* @param {string} tabName - The ID of the tab content to show
*/
function bdd_openTab(evt, tabName) {
const container = document.getElementById("bdd-container");
if (!container) return; // Guard clause
// Get all elements with class="bdd-tab-content" and hide them
const tabcontent = container.getElementsByClassName("bdd-tab-content");
for (let i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
tabcontent[i].classList.remove("bdd-active");
}
// Get all elements with class="bdd-tab-link" and remove the class "bdd-active"
const tablinks = container.getElementsByClassName("bdd-tab-link");
for (let i = 0; i < tablinks.length; i++) {
tablinks[i].classList.remove("bdd-active");
}
// Show the current tab, and add an "bdd-active" class to the button that opened the tab
const activeTab = document.getElementById(tabName);
if (activeTab) { // Guard clause (Spec IV.B)
activeTab.style.display = "block";
activeTab.classList.add("bdd-active");
}
if (evt && evt.currentTarget) {
evt.currentTarget.classList.add("bdd-active");
}
// If navigating to Tab 2, refresh the summary
if (tabName === 'bdd-tab-2') {
// Find the global 'bdd_updateSummary' function (it's defined inside DOMContentLoaded)
// To make it accessible, we need to find it on the window or redefine it globally.
// For simplicity with inline onclick, we will call the global function
// that was defined inside the DOMContentLoaded scope *if* it was attached.
// This is tricky. A better way: make bdd_updateSummary global.
// Let's redefine the update function outside the DOMContentLoaded
// and just call it from inside and from here.
// Re-thinking: The bdd_updateSummary is already called on *every input change*.
// So, when the user *clicks* the tab, the summary is already up-to-date.
// This is a better UX. No extra call needed here.
}
}
/**
* Next/Previous button navigation (Spec II.B.4)
* @param {number} direction - 1 for next, -1 for previous
*/
function bdd_navTab(direction) {
const container = document.getElementById("bdd-container");
if (!container) return; // Guard clause
const tabs = container.querySelectorAll(".bdd-tab-link");
const tabContents = container.querySelectorAll(".bdd-tab-content");
let currentIndex = -1;
// Find the current active tab index
for (let i = 0; i < tabContents.length; i++) {
if (tabContents[i].classList.contains("bdd-active")) {
currentIndex = i;
break;
}
}
if (currentIndex === -1) return; // Should not happen
// Calculate new index
let newIndex = currentIndex + direction;
// Loop around
if (newIndex >= tabs.length) {
newIndex = 0;
}
if (newIndex < 0) {
newIndex = tabs.length - 1;
}
// Simulate a click on the new tab button
tabs[newIndex].click();
}