`;
resortGrid.appendChild(card);
});
}
/**
* Populates the location filter dropdown with unique locations from data.
*/
function populateFilters() {
const locations = [...new Set(resortsData.map(r => r.location))];
locationFilter.innerHTML = '';
locations.forEach(location => {
locationFilter.innerHTML += ``;
});
}
/**
* Renders the side-by-side comparison table.
*/
function renderComparisonTable() {
if (comparisonList.length === 0) {
comparisonTableContainer.innerHTML = '
';
comparisonTableContainer.innerHTML = tableHTML;
}
// --- EVENT HANDLERS & PUBLIC FUNCTIONS ---
/**
* Applies the selected filters and sorting, then re-renders the grid.
*/
window.applyFilters = function() {
const location = document.getElementById('filter-location').value;
const maxPrice = parseFloat(document.getElementById('filter-price').value);
const sortBy = document.getElementById('sort-by').value;
let filteredResorts = [...resortsData];
// Apply location filter
if (location !== 'all') {
filteredResorts = filteredResorts.filter(r => r.location === location);
}
// Apply price filter
if (!isNaN(maxPrice) && maxPrice > 0) {
filteredResorts = filteredResorts.filter(r => r.pricePerNight <= maxPrice);
}
// Apply sorting
switch (sortBy) {
case 'price-asc':
filteredResorts.sort((a, b) => a.pricePerNight - b.pricePerNight);
break;
case 'price-desc':
filteredResorts.sort((a, b) => b.pricePerNight - a.pricePerNight);
break;
case 'rating-desc':
filteredResorts.sort((a, b) => b.rating - a.rating);
break;
}
renderResortGrid(filteredResorts);
openTab(null, 'dashboard'); // Switch back to dashboard to show results
}
/**
* Adds or removes a resort from the comparison list.
* @param {number} resortId - The ID of the resort to toggle.
*/
window.toggleCompare = function(resortId) {
const index = comparisonList.indexOf(resortId);
if (index > -1) {
comparisonList.splice(index, 1); // Remove if exists
} else {
comparisonList.push(resortId); // Add if not
}
applyFilters(); // Re-render grid to update button state
renderComparisonTable(); // Update comparison table
}
/**
* Handles tab switching logic.
* @param {Event | null} evt - The click event, or null for programmatic call.
* @param {string} tabName - The ID of the tab content to show.
*/
window.openTab = function(evt, tabName) {
const tabContents = document.getElementsByClassName("tab-content");
Array.from(tabContents).forEach(tab => tab.style.display = "none");
const tabButtons = document.getElementsByClassName("tab-btn");
Array.from(tabButtons).forEach(btn => btn.classList.remove("active"));
const tabToShow = document.getElementById(tabName);
if (tabToShow) {
tabToShow.style.display = "block";
}
if (evt) {
evt.currentTarget.classList.add("active");
} else {
// Programmatic call: find and activate the correct button
const btnToActivate = Array.from(tabButtons).find(btn => btn.getAttribute('onclick').includes(`'${tabName}'`));
if (btnToActivate) btnToActivate.classList.add("active");
}
updateNavButtons();
}
/**
* Handles Next/Previous button clicks.
* @param {string} direction - 'next' or 'prev'.
*/
window.navigateTabs = function(direction) {
const tabs = Array.from(document.querySelectorAll('.tab-btn'));
const activeTabIndex = tabs.findIndex(tab => tab.classList.contains('active'));
let newIndex = (direction === 'next')
? (activeTabIndex + 1) % tabs.length
: (activeTabIndex - 1 + tabs.length) % tabs.length;
tabs[newIndex].click();
}
/**
* Updates the visibility of Next/Previous buttons based on the active tab.
*/
function updateNavButtons() {
const tabs = Array.from(document.querySelectorAll('.tab-btn'));
const activeTabIndex = tabs.findIndex(tab => tab.classList.contains('active'));
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
if (!prevBtn || !nextBtn) return;
prevBtn.style.visibility = activeTabIndex === 0 ? 'hidden' : 'visible';
nextBtn.style.visibility = activeTabIndex === tabs.length - 1 ? 'hidden' : 'visible';
}
/**
* PDF Download Functionality.
*/
downloadPdfBtn.addEventListener('click', function() {
const { jsPDF } = window.jspdf;
const contentToDownload = document.getElementById('comparison-content-to-download');
if (!contentToDownload) {
console.error("PDF content area not found.");
return;
}
// Temporarily remove buttons from the clone to exclude them from PDF
const contentClone = contentToDownload.cloneNode(true);
const buttonsInClone = contentClone.querySelectorAll('button');
buttonsInClone.forEach(btn => btn.remove());
html2canvas(contentToDownload, { scale: 2, useCORS: true })
.then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'l', unit: 'mm', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const imgWidth = pdfWidth - 20; // 10mm margin on each side
const imgHeight = (imgProps.height * imgWidth) / imgProps.width;
let heightLeft = imgHeight;
let position = 10; // Top margin
pdf.setFont('helvetica', 'bold');
pdf.setFontSize(18);
pdf.text('All-Inclusive Resort Comparison', pdfWidth / 2, 15, { align: 'center' });
pdf.addImage(imgData, 'PNG', 10, position + 10, imgWidth, imgHeight);
heightLeft -= (pdfHeight - position - 20);
while (heightLeft > 0) {
position = -heightLeft - 10;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
heightLeft -= pdfHeight;
}
pdf.save('Resort-Comparison.pdf');
}).catch(err => {
console.error("Error generating PDF:", err);
});
});
// --- INITIALIZATION ---
function initializeTool() {
populateFilters();
applyFilters(); // Initial render of all resorts
renderComparisonTable();
updateNavButtons();
}
initializeTool();
});
Add resorts from the dashboard to compare them side-by-side.
'; return; } const resortsToCompare = resortsData.filter(r => comparisonList.includes(r.id)); const amenities = ['pool', 'beachAccess', 'spa', 'gym', 'fineDining', 'kidsClub']; const amenityLabels = { pool: 'Swimming Pool', beachAccess: 'Beach Access', spa: 'Spa Services', gym: 'Fitness Center', fineDining: 'Fine Dining', kidsClub: 'Kids Club' }; let tableHTML = '| Feature | '; resortsToCompare.forEach(r => { tableHTML += `${r.name} ${r.location} | `;
});
tableHTML += '
|---|---|
| Price per Night | '; resortsToCompare.forEach(r => { tableHTML += `$${r.pricePerNight} | `; }); tableHTML += '
| Rating | '; resortsToCompare.forEach(r => { tableHTML += `${r.rating} / 5.0 | `; }); tableHTML += '
| ${amenityLabels[amenity]} | `; resortsToCompare.forEach(r => { tableHTML += `${r.amenities[amenity] ? '' : '—'} | `; }); tableHTML += '
