Spaces:
Sleeping
Sleeping
| document.getElementById('searchForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| await search(); | |
| }); | |
| async function search() { | |
| const products = document.getElementById('products').value | |
| .split('\n') | |
| .map(p => p.trim()) | |
| .filter(p => p); | |
| if (products.length === 0) { | |
| showError('Please enter at least one product'); | |
| return; | |
| } | |
| const shops = Array.from(document.querySelectorAll('input[name="shops"]:checked')) | |
| .map(cb => cb.value) | |
| .join(','); | |
| if (!shops) { | |
| showError('Please select at least one supermarket'); | |
| return; | |
| } | |
| const minPrice = document.getElementById('minPrice').value; | |
| const threshold = document.getElementById('threshold').value; | |
| showLoading(true); | |
| clearError(); | |
| try { | |
| const allResults = []; | |
| for (const product of products) { | |
| const query = new URLSearchParams({ | |
| query: product, | |
| shops: shops, | |
| min_price: minPrice, | |
| threshold: threshold | |
| }); | |
| const response = await fetch(`/search?${query}`); | |
| if (!response.ok) throw new Error('API request failed'); | |
| const data = await response.json(); | |
| allResults.push(...data); | |
| } | |
| displayResults(allResults); | |
| } catch (error) { | |
| showError('Error searching: ' + error.message); | |
| } finally { | |
| showLoading(false); | |
| } | |
| } | |
| let currentSortColumn = null; | |
| let currentSortDirection = 'asc'; | |
| let currentResults = []; | |
| function displayResults(results) { | |
| const resultsDiv = document.getElementById('results'); | |
| const resultsTable = document.getElementById('resultsTable'); | |
| const resultCount = document.getElementById('resultCount'); | |
| if (results.length === 0) { | |
| resultsTable.innerHTML = '<div class="no-results">No products found. Try adjusting your filters.</div>'; | |
| resultCount.textContent = '0'; | |
| resultsDiv.style.display = 'block'; | |
| return; | |
| } | |
| currentResults = results; | |
| currentSortColumn = null; | |
| currentSortDirection = 'asc'; | |
| resultCount.textContent = results.length; | |
| renderTable(results); | |
| attachSortListeners(); | |
| resultsDiv.style.display = 'block'; | |
| resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } | |
| function attachSortListeners() { | |
| const headers = document.querySelectorAll('.results-table th.sortable'); | |
| headers.forEach((header, index) => { | |
| header.removeEventListener('click', handleSort); | |
| header.addEventListener('click', function() { | |
| handleSort.call(this, index); | |
| }); | |
| }); | |
| } | |
| function handleSort(columnIndex) { | |
| sortTable(columnIndex); | |
| } | |
| function renderTable(results) { | |
| const resultsTable = document.getElementById('resultsTable'); | |
| const html = ` | |
| <table class="results-table"> | |
| <thead> | |
| <tr> | |
| <th class="sortable">Product</th> | |
| <th class="sortable">Shop</th> | |
| <th class="sortable">Price</th> | |
| <th class="sortable">Size</th> | |
| <th class="sortable">Match</th> | |
| <th>Link</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${results.map(r => ` | |
| <tr> | |
| <td><strong>${escapeHtml(r.match)}</strong></td> | |
| <td><span class="shop-badge">${r.shop.toUpperCase()}</span></td> | |
| <td><span class="price">€${r.price ? r.price.toFixed(2) : 'N/A'}</span></td> | |
| <td>${r.size || '—'}</td> | |
| <td><span class="score">${r.score.toFixed(0)}%</span></td> | |
| <td>${r.url ? `<a href="${r.url}" target="_blank" class="url-link">View →</a>` : '—'}</td> | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| `; | |
| resultsTable.innerHTML = html; | |
| } | |
| function sortTable(columnIndex) { | |
| const columnNames = ['match', 'shop', 'price', 'size', 'score']; | |
| const columnName = columnNames[columnIndex]; | |
| // Toggle sort direction if clicking the same column | |
| if (currentSortColumn === columnIndex) { | |
| currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; | |
| } else { | |
| currentSortColumn = columnIndex; | |
| currentSortDirection = 'asc'; | |
| } | |
| // Sort results | |
| const sorted = [...currentResults].sort((a, b) => { | |
| let aVal = a[columnName]; | |
| let bVal = b[columnName]; | |
| // Handle numeric values | |
| if (columnIndex === 2) { // Price column | |
| aVal = aVal || 0; | |
| bVal = bVal || 0; | |
| } else if (columnIndex === 4) { // Match column (percentage) | |
| aVal = aVal || 0; | |
| bVal = bVal || 0; | |
| } | |
| // String comparison | |
| if (typeof aVal === 'string') { | |
| aVal = aVal.toLowerCase(); | |
| bVal = bVal.toLowerCase(); | |
| return currentSortDirection === 'asc' | |
| ? aVal.localeCompare(bVal) | |
| : bVal.localeCompare(aVal); | |
| } | |
| // Numeric comparison | |
| if (currentSortDirection === 'asc') { | |
| return aVal > bVal ? 1 : aVal < bVal ? -1 : 0; | |
| } else { | |
| return aVal < bVal ? 1 : aVal > bVal ? -1 : 0; | |
| } | |
| }); | |
| // Update header styling | |
| const headers = document.querySelectorAll('.results-table th.sortable'); | |
| headers.forEach((h, i) => { | |
| h.classList.remove('sort-asc', 'sort-desc'); | |
| if (i === columnIndex) { | |
| h.classList.add(currentSortDirection === 'asc' ? 'sort-asc' : 'sort-desc'); | |
| } | |
| }); | |
| renderTable(sorted); | |
| attachSortListeners(); | |
| } | |
| function showLoading(show) { | |
| document.getElementById('loading').style.display = show ? 'block' : 'none'; | |
| } | |
| function showError(message) { | |
| const errorDiv = document.getElementById('error'); | |
| errorDiv.innerHTML = `<div class="error">${escapeHtml(message)}</div>`; | |
| errorDiv.style.display = 'block'; | |
| } | |
| function clearError() { | |
| document.getElementById('error').innerHTML = ''; | |
| } | |
| function clearForm() { | |
| document.getElementById('products').value = ''; | |
| document.getElementById('minPrice').value = '0'; | |
| document.getElementById('threshold').value = '80'; | |
| document.getElementById('results').style.display = 'none'; | |
| clearError(); | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |