All-Inclusive Resort Price Comparison Tool

All-Inclusive Resort Price Comparison Tool

Find Your Perfect Getaway

Side-by-Side Comparison

Customize Your Search

$${resort.pricePerNight} / night

`; 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 = '

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 = ''; resortsToCompare.forEach(r => { tableHTML += ``; }); tableHTML += ''; // Price Row tableHTML += ''; resortsToCompare.forEach(r => { tableHTML += ``; }); tableHTML += ''; // Rating Row tableHTML += ''; resortsToCompare.forEach(r => { tableHTML += ``; }); tableHTML += ''; // Amenities Rows amenities.forEach(amenity => { tableHTML += ``; resortsToCompare.forEach(r => { tableHTML += ``; }); tableHTML += ''; }); tableHTML += '
Feature${r.name}
${r.location}
Price per Night$${r.pricePerNight}
Rating${r.rating} / 5.0
${amenityLabels[amenity]}${r.amenities[amenity] ? '' : '—'}
'; 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(); });
Scroll to Top