Files
kleinanzeigen-boosted/web/js/ui.js
2025-11-26 11:17:12 +01:00

229 lines
7.5 KiB
JavaScript

// UI management functions
function showStatus(message, type = 'loading') {
const statusBar = document.getElementById('statusBar');
statusBar.className = `status-bar visible ${type}`;
if (type === 'loading') {
statusBar.innerHTML = `<span class="loading-spinner"></span>${message}`;
} else {
statusBar.textContent = message;
}
if (type !== 'loading') {
setTimeout(() => {
statusBar.classList.remove('visible');
}, 3000);
}
}
function updateProgress(current, total) {
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Mobile progress bar
const progressInfo = document.getElementById('progressInfo');
const progressFill = progressInfo.querySelector('.progress-fill-mobile');
const progressText = progressInfo.querySelector('.progress-text-mobile');
if (total === 0) {
progressInfo.classList.remove('active');
return;
}
progressInfo.classList.add('active');
const percentage = (current / total) * 100;
progressFill.style.width = percentage + '%';
progressText.textContent = `${current}/${total} Inserate`;
} else {
// Desktop progress bar
const progressInfo = document.getElementById('progressInfoDesktop');
const progressFill = progressInfo.querySelector('.progress-fill');
const progressText = progressInfo.querySelector('.progress-text');
const etaText = progressInfo.querySelector('.eta-text');
if (total === 0) {
progressInfo.classList.remove('active');
return;
}
progressInfo.classList.add('active');
const percentage = (current / total) * 100;
progressFill.style.width = percentage + '%';
progressText.textContent = `Inserate werden geladen: ${current}/${total}`;
// Calculate ETA
if (AppState.scrapeStartTime && current > 0) {
const elapsed = (Date.now() - AppState.scrapeStartTime) / 1000;
const avgTimePerListing = elapsed / current;
const remaining = total - current;
const etaSeconds = Math.round(avgTimePerListing * remaining);
const minutes = Math.floor(etaSeconds / 60);
const seconds = etaSeconds % 60;
if (minutes > 0) {
etaText.textContent = `Noch ca. ${minutes}m ${seconds}s`;
} else {
etaText.textContent = `Noch ca. ${seconds}s`;
}
}
}
}
function highlightSelectedListing() {
document.querySelectorAll('.result-item').forEach(item => {
const itemId = parseInt(item.dataset.id);
if (itemId === AppState.selectedListingId) {
item.classList.add('selected');
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} else {
item.classList.remove('selected');
}
});
}
function formatDate(dateString) {
if (!dateString) return 'Unbekanntes Datum';
const date = new Date(dateString);
return date.toLocaleDateString('de-DE');
}
function renderResults(listings) {
// Only render on desktop
if (window.innerWidth < 768) return;
const resultsList = document.getElementById('resultsList');
const resultsCount = document.querySelector('.results-count');
if (listings.length === 0) {
resultsList.innerHTML = '<div class="no-results">Keine Inserate gefunden</div>';
resultsCount.textContent = 'Keine Ergebnisse';
return;
}
resultsCount.textContent = `${listings.length} Inserat${listings.length !== 1 ? 'e' : ''}`;
resultsList.innerHTML = listings.map(listing => `
<div class="result-item" data-id="${listing.id}">
${listing.image ? `<img src="${listing.image}" class="result-image" alt="${listing.title}">` : '<div class="result-image"></div>'}
<div class="result-content">
<div class="result-title">${listing.title}</div>
<div class="result-price">${listing.price} €</div>
<div class="result-meta">
<div class="result-location">
<span>📍</span>
<span>${listing.address || listing.zip_code}</span>
</div>
<div class="result-date">${formatDate(listing.date_added)}</div>
</div>
</div>
</div>
`).join('');
// Add click handlers
document.querySelectorAll('.result-item').forEach(item => {
item.addEventListener('click', () => {
const id = parseInt(item.dataset.id);
const listing = listings.find(l => l.id === id);
if (listing) {
AppState.selectedListingId = id;
highlightSelectedListing();
if (listing.lat && listing.lon) {
AppState.map.setView([listing.lat, listing.lon], 13);
const marker = AppState.markers.find(m =>
m.getLatLng().lat === listing.lat &&
m.getLatLng().lng === listing.lon
);
if (marker) {
marker.openPopup();
}
}
window.open(listing.url, '_blank');
}
});
});
}
function sortListings(listings, sortBy) {
const sorted = [...listings];
switch (sortBy) {
case 'price-asc':
sorted.sort((a, b) => a.price - b.price);
break;
case 'price-desc':
sorted.sort((a, b) => b.price - a.price);
break;
case 'date-asc':
sorted.sort((a, b) => new Date(a.date_added || 0) - new Date(b.date_added || 0));
break;
case 'date-desc':
sorted.sort((a, b) => new Date(b.date_added || 0) - new Date(a.date_added || 0));
break;
}
return sorted;
}
// Privacy modal
function initPrivacyModal() {
const modal = document.getElementById('privacyModal');
const link = document.getElementById('privacyLink');
const close = document.querySelector('.modal-close');
link.addEventListener('click', (e) => {
e.preventDefault();
modal.classList.add('show');
});
close.addEventListener('click', () => {
modal.classList.remove('show');
});
window.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('show');
}
});
}
// Mobile popup
function initMobilePopup() {
const closeBtn = document.getElementById('mobilePopupClose');
const popup = document.getElementById('mobilePopup');
closeBtn.addEventListener('click', () => {
popup.classList.remove('show');
});
}
// Format price inputs with € suffix
function initPriceInputs() {
const minPrice = document.getElementById('minPrice');
const maxPrice = document.getElementById('maxPrice');
function formatPriceInput(input) {
input.addEventListener('blur', (e) => {
if (e.target.value) {
// Store the numeric value
const value = e.target.value.replace(/[^\d]/g, '');
if (value) {
e.target.dataset.value = value;
}
}
});
input.addEventListener('focus', (e) => {
// Show only numeric value when focused
if (e.target.dataset.value) {
e.target.value = e.target.dataset.value;
}
});
}
formatPriceInput(minPrice);
formatPriceInput(maxPrice);
}