229 lines
7.5 KiB
JavaScript
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);
|
|
} |