// Vaccine Catalog Interactive Functionality - FIXED VERSION // Addresses data loading issues and button selector problems class VaccineCatalog { constructor() { this.data = []; this.filteredData = []; this.searchQuery = ''; this.statusFilter = ''; this.sortColumn = ''; this.sortDirection = 'asc'; this.apiEndpoint = null; this.isInitialized = false; // Wait for DOM to be ready before initializing if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.init()); } else { this.init(); } } init() { console.log('๐Ÿš€ VaccineCatalog initializing...'); // Verify DOM elements exist before binding if (!this.verifyDOMElements()) { console.error('โŒ Required DOM elements not found, retrying in 500ms...'); setTimeout(() => this.init(), 500); return; } this.bindEvents(); this.loadData(); this.showLoadingState(); this.isInitialized = true; console.log('โœ… VaccineCatalog initialized successfully'); } verifyDOMElements() { const requiredElements = [ 'searchInput', 'statusFilter', 'clearFilters', 'vaccineTable', 'vaccineTableBody', 'resultsCounter', 'loadingState', 'emptyState' ]; const missing = requiredElements.filter(id => !document.getElementById(id)); if (missing.length > 0) { console.warn('Missing DOM elements:', missing); return false; } return true; } setApiEndpoint(url) { this.apiEndpoint = url; console.log('๐Ÿ“ก API endpoint set:', url); } bindEvents() { console.log('๐Ÿ”— Binding events...'); const searchInput = document.getElementById('searchInput'); const statusFilter = document.getElementById('statusFilter'); const clearFilters = document.getElementById('clearFilters'); const sortHeaders = document.querySelectorAll('[data-sort]'); if (searchInput) { searchInput.addEventListener('input', this.debounce((e) => { this.searchQuery = e.target.value.trim().toLowerCase(); this.filterData(); }, 300)); console.log('โœ… Search input event bound'); } if (statusFilter) { statusFilter.addEventListener('change', (e) => { this.statusFilter = e.target.value; this.filterData(); }); console.log('โœ… Status filter event bound'); } if (clearFilters) { clearFilters.addEventListener('click', () => { this.clearFilters(); }); console.log('โœ… Clear filters event bound'); } // Bind sorting events sortHeaders.forEach(header => { header.addEventListener('click', (e) => { const column = e.currentTarget.getAttribute('data-sort'); this.sortData(column); }); }); console.log(`โœ… ${sortHeaders.length} sort headers bound`); // Better button selectors using more specific approach this.bindActionButtons(); } bindActionButtons() { // Find buttons by their text content and context (more reliable) const buttons = document.querySelectorAll('button'); buttons.forEach(button => { const buttonText = button.textContent.trim(); if (buttonText.includes('Export to CSV')) { button.addEventListener('click', () => this.exportToCSV()); console.log('โœ… Export button event bound'); } else if (buttonText.includes('Refresh Data')) { button.addEventListener('click', () => this.loadData()); console.log('โœ… Refresh button event bound'); } else if (buttonText.includes('Add New Vaccine')) { button.addEventListener('click', () => this.showAddVaccineModal()); console.log('โœ… Add vaccine button event bound'); } }); } showAddVaccineModal() { alert('Add New Vaccine functionality would open a modal here.\n\nThis would connect to your database to add new entries.'); } debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } async loadData() { console.log('๐Ÿ“Š Loading vaccine data...'); this.showLoadingState(); try { let data; if (this.apiEndpoint) { console.log(`๐Ÿ“ก Fetching from API: ${this.apiEndpoint}`); const response = await fetch(this.apiEndpoint, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' } }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } data = await response.json(); if (!Array.isArray(data)) { throw new Error('API response is not an array'); } console.log(`โœ… Loaded ${data.length} vaccines from API`); this.showApiStatus(true, `API data loaded: ${data.length} vaccines`); } else { console.log('๐Ÿ“‚ Using local sample data (no API configured)'); data = this.getSampleData(); this.showApiStatus(false, `Local sample data: ${data.length} vaccines`); } this.data = data; this.filteredData = [...this.data]; this.hideLoadingState(); this.renderTable(); this.updateResultsCounter(); console.log(`โœ… Data loading complete: ${this.data.length} vaccines loaded`); } catch (error) { console.error('โŒ Error loading vaccine data:', error); this.showApiError(error.message); // Fallback to local sample data console.log('๐Ÿ”„ Falling back to sample data...'); this.data = this.getSampleData(); this.filteredData = [...this.data]; this.hideLoadingState(); this.renderTable(); this.updateResultsCounter(); this.showApiStatus(false, `API failed - using fallback data (${this.data.length} vaccines)`); } } showApiStatus(isSuccess, message) { const counter = document.getElementById('resultsCounter'); if (counter) { const statusIcon = isSuccess ? '' : ''; setTimeout(() => { counter.innerHTML = `${statusIcon}${message}`; console.log('๐Ÿ“Š Status updated:', message); }, 100); } } showApiError(errorMessage) { const resultsCounter = document.getElementById('resultsCounter'); if (resultsCounter) { resultsCounter.innerHTML = ` Connection failed: ${errorMessage} `; console.error('๐Ÿšจ API Error displayed:', errorMessage); } } getSampleData() { // Enhanced sample data with more realistic examples return [ { vaccine_uid: 'VAC-COVID-001', vaccine_common_name: 'COVID-19 mRNA', disease_target: 'SARS-CoV-2', product_name_brand: 'Comirnaty', platform: 'mRNA', manufacturer_primary: 'Pfizer-BioNTech', first_public_use_date: '2020-12-11', earliest_trial_start_date: '2020-04-29', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '125742', ema_procedure_number: 'EMEA/H/C/005735' }, { vaccine_uid: 'VAC-COVID-002', vaccine_common_name: 'COVID-19 mRNA', disease_target: 'SARS-CoV-2', product_name_brand: 'Spikevax', platform: 'mRNA', manufacturer_primary: 'Moderna', first_public_use_date: '2020-12-18', earliest_trial_start_date: '2020-07-14', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '125663', ema_procedure_number: 'EMEA/H/C/005791' }, { vaccine_uid: 'VAC-COVID-003', vaccine_common_name: 'COVID-19 Viral Vector', disease_target: 'SARS-CoV-2', product_name_brand: 'Vaxzevria', platform: 'viral_vector', manufacturer_primary: 'AstraZeneca', first_public_use_date: '2021-01-04', earliest_trial_start_date: '2020-04-23', status_current_use: 'withdrawn', reg_authority_first: 'EMA', who_prequalified: true, fda_bla_number: null, ema_procedure_number: 'EMEA/H/C/005675' }, { vaccine_uid: 'VAC-FLU-001', vaccine_common_name: 'Seasonal Influenza', disease_target: 'Influenza A/B', product_name_brand: 'Fluzone Quadrivalent', platform: 'inactivated', manufacturer_primary: 'Sanofi Pasteur', first_public_use_date: '1945-01-01', earliest_trial_start_date: '1943-01-01', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '103851', ema_procedure_number: null }, { vaccine_uid: 'VAC-MMR-001', vaccine_common_name: 'Measles, Mumps, Rubella', disease_target: 'Measles/Mumps/Rubella', product_name_brand: 'M-M-R II', platform: 'live_attenuated', manufacturer_primary: 'Merck & Co.', first_public_use_date: '1971-04-01', earliest_trial_start_date: '1969-01-01', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '103516', ema_procedure_number: null }, { vaccine_uid: 'VAC-HPV-001', vaccine_common_name: 'Human Papillomavirus', disease_target: 'HPV Types 16,18,6,11,31,33,45,52,58', product_name_brand: 'Gardasil 9', platform: 'protein_subunit', manufacturer_primary: 'Merck & Co.', first_public_use_date: '2014-12-10', earliest_trial_start_date: '2007-09-01', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '125508', ema_procedure_number: 'EMEA/H/C/003852' }, { vaccine_uid: 'VAC-RSV-001', vaccine_common_name: 'Respiratory Syncytial Virus', disease_target: 'RSV', product_name_brand: 'Arexvy', platform: 'protein_subunit', manufacturer_primary: 'GSK', first_public_use_date: '2023-05-03', earliest_trial_start_date: '2017-03-01', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: false, fda_bla_number: '125769', ema_procedure_number: 'EMEA/H/C/005993' }, { vaccine_uid: 'VAC-SHINGLES-001', vaccine_common_name: 'Zoster (Shingles)', disease_target: 'Varicella-Zoster Virus', product_name_brand: 'Shingrix', platform: 'protein_subunit', manufacturer_primary: 'GSK', first_public_use_date: '2017-10-20', earliest_trial_start_date: '2010-03-15', status_current_use: 'in_use', reg_authority_first: 'FDA', who_prequalified: true, fda_bla_number: '125259', ema_procedure_number: 'EMEA/H/C/004336' }, { vaccine_uid: 'VAC-EXP-001', vaccine_common_name: 'Experimental Cancer Vaccine', disease_target: 'Multiple Cancer Types', product_name_brand: 'mRNA-4157', platform: 'mRNA', manufacturer_primary: 'Moderna', first_public_use_date: null, earliest_trial_start_date: '2019-08-01', status_current_use: 'investigational', reg_authority_first: null, who_prequalified: false, fda_bla_number: null, ema_procedure_number: null }, { vaccine_uid: 'VAC-OLD-001', vaccine_common_name: 'Oral Polio Vaccine', disease_target: 'Poliovirus', product_name_brand: 'OPV', platform: 'live_attenuated', manufacturer_primary: 'Various', first_public_use_date: '1961-01-01', earliest_trial_start_date: '1957-01-01', status_current_use: 'withdrawn', reg_authority_first: 'FDA', who_prequalified: false, fda_bla_number: null, ema_procedure_number: null }, { vaccine_uid: 'VAC-DISC-001', vaccine_common_name: 'Anthrax Vaccine Adsorbed', disease_target: 'Bacillus anthracis', product_name_brand: 'BioThrax', platform: 'protein_subunit', manufacturer_primary: 'Emergent BioSolutions', first_public_use_date: '1970-01-01', earliest_trial_start_date: '1954-01-01', status_current_use: 'discontinued', reg_authority_first: 'FDA', who_prequalified: false, fda_bla_number: '103432', ema_procedure_number: null } ]; } filterData() { const loadingIndicator = document.getElementById('loadingIndicator'); if (loadingIndicator) { loadingIndicator.classList.remove('hidden'); } // Use shorter timeout for better responsiveness setTimeout(() => { this.filteredData = this.data.filter(vaccine => { const searchText = [ vaccine.vaccine_common_name, vaccine.disease_target, vaccine.product_name_brand, vaccine.platform, vaccine.manufacturer_primary, vaccine.reg_authority_first, vaccine.fda_bla_number, vaccine.ema_procedure_number ].filter(Boolean).join(' ').toLowerCase(); const matchesSearch = !this.searchQuery || searchText.includes(this.searchQuery); const matchesStatus = !this.statusFilter || vaccine.status_current_use === this.statusFilter; return matchesSearch && matchesStatus; }); this.renderTable(); this.updateResultsCounter(); if (loadingIndicator) { loadingIndicator.classList.add('hidden'); } console.log(`๐Ÿ” Filtered results: ${this.filteredData.length} of ${this.data.length}`); }, 100); } sortData(column) { if (this.sortColumn === column) { this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.sortColumn = column; this.sortDirection = 'asc'; } this.filteredData.sort((a, b) => { let aVal, bVal; switch (column) { case 'name': aVal = a.vaccine_common_name || ''; bVal = b.vaccine_common_name || ''; break; case 'disease': aVal = a.disease_target || ''; bVal = b.disease_target || ''; break; case 'platform': aVal = a.platform || ''; bVal = b.platform || ''; break; case 'manufacturer': aVal = a.manufacturer_primary || ''; bVal = b.manufacturer_primary || ''; break; case 'status': aVal = a.status_current_use || ''; bVal = b.status_current_use || ''; break; case 'first_use': aVal = a.first_public_use_date || ''; bVal = b.first_public_use_date || ''; break; case 'trial_start': aVal = a.earliest_trial_start_date || ''; bVal = b.earliest_trial_start_date || ''; break; default: return 0; } const comparison = aVal.localeCompare(bVal); return this.sortDirection === 'asc' ? comparison : -comparison; }); this.updateSortIcons(column); this.renderTable(); console.log(`๐Ÿ”„ Sorted by ${column} (${this.sortDirection})`); } updateSortIcons(activeColumn) { const headers = document.querySelectorAll('[data-sort]'); headers.forEach(header => { const icon = header.querySelector('i'); const column = header.getAttribute('data-sort'); if (column === activeColumn) { icon.className = `fas fa-sort-${this.sortDirection === 'asc' ? 'up' : 'down'} ml-1 text-xs`; } else { icon.className = 'fas fa-sort ml-1 text-xs'; } }); } renderTable() { const tbody = document.getElementById('vaccineTableBody'); const emptyState = document.getElementById('emptyState'); const tableContainer = document.querySelector('#vaccineTable').parentElement; if (!tbody) { console.error('โŒ Table body element not found'); return; } if (this.filteredData.length === 0) { tbody.innerHTML = ''; if (emptyState) emptyState.classList.remove('hidden'); if (tableContainer) tableContainer.style.display = 'none'; console.log('๐Ÿ“‹ Empty state displayed'); return; } if (emptyState) emptyState.classList.add('hidden'); if (tableContainer) tableContainer.style.display = 'block'; tbody.innerHTML = this.filteredData.map((vaccine, index) => { const rowClass = index % 2 === 0 ? 'bg-white' : 'bg-gray-50'; const displayName = vaccine.vaccine_common_name + (vaccine.product_name_brand ? ` โ€” ${vaccine.product_name_brand}` : ''); return ` ${displayName} ${vaccine.disease_target || '-'} ${vaccine.platform || '-'} ${vaccine.manufacturer_primary || '-'} ${this.getStatusBadge(vaccine.status_current_use)} ${vaccine.first_public_use_date || '-'} ${vaccine.earliest_trial_start_date || '-'} ${vaccine.reg_authority_first || '-'} ${this.getWhoPqBadge(vaccine.who_prequalified)} ${vaccine.fda_bla_number || '-'} ${vaccine.ema_procedure_number || '-'} `; }).join(''); console.log(`๐Ÿ“‹ Table rendered with ${this.filteredData.length} rows`); } getStatusBadge(status) { const badges = { 'in_use': 'In use', 'investigational': 'Investigational', 'withdrawn': 'Withdrawn', 'discontinued': 'Discontinued' }; return badges[status] || '-'; } getWhoPqBadge(isPrequalified) { if (isPrequalified === true) { return 'Yes'; } else if (isPrequalified === false) { return 'No'; } return '-'; } updateResultsCounter() { const counter = document.getElementById('resultsCounter'); if (counter) { const total = this.data.length; const filtered = this.filteredData.length; if (this.searchQuery || this.statusFilter) { counter.innerHTML = `Showing ${filtered} of ${total} vaccines`; } else { // Don't override the API status message initially if (!counter.innerHTML.includes('fa-check-circle') && !counter.innerHTML.includes('fa-database')) { counter.innerHTML = `${total} vaccines loaded`; } } } } clearFilters() { this.searchQuery = ''; this.statusFilter = ''; const searchInput = document.getElementById('searchInput'); const statusFilter = document.getElementById('statusFilter'); if (searchInput) searchInput.value = ''; if (statusFilter) statusFilter.value = ''; this.filteredData = [...this.data]; this.renderTable(); this.updateResultsCounter(); console.log('๐Ÿงน Filters cleared'); } showLoadingState() { const loadingState = document.getElementById('loadingState'); const emptyState = document.getElementById('emptyState'); const tableContainer = document.querySelector('#vaccineTable')?.parentElement; if (loadingState) loadingState.classList.remove('hidden'); if (emptyState) emptyState.classList.add('hidden'); if (tableContainer) tableContainer.style.display = 'none'; console.log('โณ Loading state shown'); } hideLoadingState() { const loadingState = document.getElementById('loadingState'); const tableContainer = document.querySelector('#vaccineTable')?.parentElement; if (loadingState) loadingState.classList.add('hidden'); if (tableContainer) tableContainer.style.display = 'block'; console.log('โœ… Loading state hidden'); } exportToCSV() { console.log('๐Ÿ“„ Exporting to CSV...'); const headers = [ 'Vaccine UID', 'Common Name', 'Disease Target', 'Product Brand', 'Platform', 'Manufacturer', 'First Public Use', 'Earliest Trial', 'Status', 'Reg Authority', 'WHO Prequalified', 'FDA BLA', 'EMA Procedure' ]; const csvContent = [ headers.join(','), ...this.filteredData.map(vaccine => [ vaccine.vaccine_uid, `"${vaccine.vaccine_common_name}"`, `"${vaccine.disease_target}"`, `"${vaccine.product_name_brand || ''}"`, vaccine.platform || '', `"${vaccine.manufacturer_primary || ''}"`, vaccine.first_public_use_date || '', vaccine.earliest_trial_start_date || '', vaccine.status_current_use || '', vaccine.reg_authority_first || '', vaccine.who_prequalified ? 'Yes' : 'No', vaccine.fda_bla_number || '', vaccine.ema_procedure_number || '' ].join(',')) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `vaccine-catalog-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); console.log('โœ… CSV export completed'); } // Public method to check if catalog is working getStatus() { return { initialized: this.isInitialized, dataCount: this.data.length, filteredCount: this.filteredData.length, apiEndpoint: this.apiEndpoint, hasData: this.data.length > 0 }; } } // Global vaccine catalog instance let vaccineCatalog = null; function init() { console.log('๐ŸŒŸ Initializing Vaccine Catalog...'); try { vaccineCatalog = new VaccineCatalog(); // ๐Ÿš€ TO CONNECT YOUR API, UNCOMMENT ONE OF THESE: // For deployed Next.js app: // vaccineCatalog.setApiEndpoint('https://your-app-name.vercel.app/api/vaccines'); // For local development with CORS: // vaccineCatalog.setApiEndpoint('http://localhost:3000/api/vaccines'); // Debug helper - check status in console setTimeout(() => { console.log('๐Ÿ“Š Vaccine Catalog Status:', vaccineCatalog.getStatus()); }, 2000); } catch (error) { console.error('โŒ Failed to initialize Vaccine Catalog:', error); } } function teardown() { console.log('๐Ÿงน Cleaning up Vaccine Catalog...'); if (vaccineCatalog) { vaccineCatalog = null; } } // Make functions available globally for debugging window.vaccineCatalogDebug = { getCatalog: () => vaccineCatalog, getStatus: () => vaccineCatalog?.getStatus(), reload: () => vaccineCatalog?.loadData() }; // Export for module system if (typeof module !== 'undefined' && module.exports) { module.exports = { init, teardown }; }