Klim Mikhailov
Add application file
d6fb204
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;
}