hhhh
This commit is contained in:
@@ -52,110 +52,54 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Events from backend (injected on page); fallback to empty
|
||||
const events = Array.isArray(window.__events) ? window.__events : [];
|
||||
|
||||
const state = { topic: 'all', age: '', region: '', query: '', geo: null };
|
||||
const el = {
|
||||
list: document.getElementById('eventList'),
|
||||
chips: document.querySelectorAll('[data-filter]'),
|
||||
locInput: document.getElementById('locInput'),
|
||||
topicSelect: document.getElementById('topicSelect'),
|
||||
ageSelect: document.getElementById('ageSelect'),
|
||||
btnSearch: document.getElementById('btnSearch'),
|
||||
btnGeo: document.getElementById('btnGeo'),
|
||||
sliderTrack: document.getElementById('eventSlider'),
|
||||
sliderViewport: document.querySelector('.slider__viewport'),
|
||||
sliderPrev: document.querySelector('[data-slider-prev]'),
|
||||
sliderNext: document.querySelector('[data-slider-next]'),
|
||||
modal: document.getElementById('eventModal'),
|
||||
modalBody: document.getElementById('eventModalBody'),
|
||||
modalTitle: document.getElementById('eventModalTitle'),
|
||||
quickForm: document.getElementById('quickSearchForm'),
|
||||
quickLoc: document.getElementById('qsLoc'),
|
||||
quickQuery: document.getElementById('qsQuery'),
|
||||
quickLat: document.getElementById('qsLat'),
|
||||
quickLng: document.getElementById('qsLng'),
|
||||
quickGeo: document.getElementById('quickGeo'),
|
||||
};
|
||||
const searchState = { active: false };
|
||||
|
||||
const fmtDate = (iso) => {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
const haversine = (lat1, lon1, lat2, lon2) => {
|
||||
const toRad = (d) => d * Math.PI / 180;
|
||||
const R = 6371e3;
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLon = toRad(lon2 - lon1);
|
||||
const a = Math.sin(dLat/2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon/2) ** 2;
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||
return R * c;
|
||||
};
|
||||
|
||||
const tag = (label) => `<span class="badge">${label}</span>`;
|
||||
const smallTag = (label) => `<span class="badge">${label}</span>`;
|
||||
|
||||
const renderCard = (item) => {
|
||||
const guest = !isLoggedIn;
|
||||
const access = item.visibility === 'members' && guest ? '<div class="event__access">Nur für Mitglieder</div>' : '';
|
||||
const desc = `<p class="muted">${item.teaser}</p>`;
|
||||
const contact = !guest ? `<div class="muted small">Kontakt: ${item.contact}</div>` : '';
|
||||
const kids = item.allowKids ? 'Mit Kindern' : 'Ohne Kinder';
|
||||
const tags = [
|
||||
tag(item.topic === 'kaffee' ? 'Kaffee & Austausch' : item.topic.charAt(0).toUpperCase() + item.topic.slice(1)),
|
||||
tag(kids),
|
||||
item.ageGroup ? tag(`Alter: ${item.ageGroup}`) : '',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
return `
|
||||
<article class="card">
|
||||
<div class="event__body">
|
||||
${access}
|
||||
<div class="event__meta">
|
||||
<span>${fmtDate(item.startsAt)}</span>
|
||||
<span>📍 ${item.region || item.city}</span>
|
||||
<span>${item.visibility === 'public' ? 'Öffentlich' : 'Mitglieder'}</span>
|
||||
</div>
|
||||
<h3>${item.title}</h3>
|
||||
<p class="muted">${guest ? item.teaser : item.description.slice(0, 140) + '...'}</p>
|
||||
${guest ? '' : desc}
|
||||
<div class="event__tags">${tags}</div>
|
||||
${contact}
|
||||
<div class="flex gap-12">
|
||||
<button class="btn ghost" data-event-detail="${item.id}">Details</button>
|
||||
${guest ? '' : '<button class="btn">Teilnehmen</button>'}
|
||||
</div>
|
||||
<article class="card event-card-small">
|
||||
<div class="muted small">${fmtDate(item.startsAt)}</div>
|
||||
<h3>${item.title}</h3>
|
||||
<p class="muted">${item.teaser}</p>
|
||||
<div class="muted small">📍 ${item.city || item.region || ''}</div>
|
||||
<div class="event__tags">${smallTag(kids)} ${item.visibility === 'public' ? smallTag('Öffentlich') : smallTag('Mitglieder')}</div>
|
||||
<div class="flex gap-8" style="margin-top:8px;">
|
||||
<button class="btn ghost" data-event-detail="${item.id}">Details</button>
|
||||
${guest ? '' : '<button class="btn">Teilnehmen</button>'}
|
||||
</div>
|
||||
</article>`;
|
||||
};
|
||||
|
||||
const matchesFilter = (ev) => {
|
||||
const topicOk = state.topic === 'all' || state.topic === ev.topic || state.topic === '';
|
||||
const ageOk = !state.age || ev.ageGroup === state.age || (state.age === 'baby' && ev.ageGroup === 'kids'); // simple fallback
|
||||
const regionField = (ev.region || '').toLowerCase();
|
||||
const cityField = (ev.city || '').toLowerCase();
|
||||
const zipField = (ev.zip || '');
|
||||
const queryHaystack = (ev.title + ev.teaser + ev.description + (ev.city || '') + (ev.region || '')).toLowerCase();
|
||||
const regionOk = !state.region || regionField.includes(state.region) || cityField.includes(state.region) || zipField.startsWith(state.region);
|
||||
const queryOk = !state.query || queryHaystack.includes(state.query);
|
||||
return topicOk && ageOk && regionOk && queryOk;
|
||||
};
|
||||
|
||||
const renderEvents = () => {
|
||||
if (!el.list) return;
|
||||
let filtered = events.filter(matchesFilter);
|
||||
filtered.sort((a,b) => new Date(a.startsAt) - new Date(b.startsAt));
|
||||
|
||||
if (state.geo && !searchState.active) {
|
||||
const withCoords = filtered.filter(ev => ev.lat !== null && ev.lng !== null);
|
||||
withCoords.forEach(ev => { ev._distance = haversine(state.geo.lat, state.geo.lng, ev.lat, ev.lng); });
|
||||
withCoords.sort((a,b) => (a._distance || Infinity) - (b._distance || Infinity));
|
||||
const near = withCoords.filter(ev => ev._distance <= 5000).slice(0,5);
|
||||
const fallback = withCoords.slice(0,5);
|
||||
filtered = (near.length ? near : fallback).map(ev => {
|
||||
const copy = { ...ev };
|
||||
if (ev._distance) {
|
||||
copy.teaser = `${ev.teaser} · ca. ${(ev._distance/1000).toFixed(1)} km entfernt`;
|
||||
}
|
||||
return copy;
|
||||
});
|
||||
} else if (!searchState.active) {
|
||||
filtered = filtered.slice(0,5);
|
||||
const renderSlider = () => {
|
||||
if (!el.sliderTrack) return;
|
||||
if (!events.length) {
|
||||
el.sliderTrack.innerHTML = '<p class="muted">Keine Events vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
el.list.innerHTML = filtered.map(renderCard).join('') || '<p class="muted">Keine Events gefunden.</p>';
|
||||
|
||||
// wire detail buttons
|
||||
el.list.querySelectorAll('[data-event-detail]').forEach(btn => {
|
||||
el.sliderTrack.innerHTML = events.slice(0, 10).map(renderCard).join('');
|
||||
el.sliderTrack.querySelectorAll('[data-event-detail]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = parseInt(btn.getAttribute('data-event-detail'), 10);
|
||||
const ev = events.find(e => e.id === id);
|
||||
@@ -174,41 +118,61 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
};
|
||||
|
||||
if (el.chips.length > 0) {
|
||||
el.chips[0].classList.add('active');
|
||||
}
|
||||
const scrollSlider = (dir) => {
|
||||
if (!el.sliderViewport) return;
|
||||
const amount = dir === 'next' ? 320 : -320;
|
||||
el.sliderViewport.scrollBy({ left: amount, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
el.chips.forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
el.chips.forEach(c => c.classList.remove('active'));
|
||||
chip.classList.add('active');
|
||||
state.topic = chip.dataset.filter;
|
||||
renderEvents();
|
||||
});
|
||||
el.sliderPrev?.addEventListener('click', () => scrollSlider('prev'));
|
||||
el.sliderNext?.addEventListener('click', () => scrollSlider('next'));
|
||||
|
||||
// Quick search with geocoding
|
||||
const geocode = async (query) => {
|
||||
const url = 'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=' + encodeURIComponent(query);
|
||||
const res = await fetch(url, { headers: { 'Accept-Language': 'de', 'User-Agent': 'papa-kind-treff/1.0' } });
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data) && data[0]) {
|
||||
return { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon) };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
el.quickGeo?.addEventListener('click', () => {
|
||||
if (!navigator.geolocation) {
|
||||
alert('Geolocation wird nicht unterstützt.');
|
||||
return;
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
if (el.quickLat && el.quickLng) {
|
||||
el.quickLat.value = pos.coords.latitude.toFixed(6);
|
||||
el.quickLng.value = pos.coords.longitude.toFixed(6);
|
||||
if (el.quickLoc) el.quickLoc.value = 'Mein Standort';
|
||||
}
|
||||
},
|
||||
() => alert('Standort konnte nicht ermittelt werden.')
|
||||
);
|
||||
});
|
||||
|
||||
if (el.btnSearch) {
|
||||
el.btnSearch.addEventListener('click', () => {
|
||||
const q = (el.locInput?.value || '').trim();
|
||||
const url = '/search' + (q ? ('?q=' + encodeURIComponent(q)) : '');
|
||||
window.location.href = url;
|
||||
});
|
||||
}
|
||||
|
||||
if (el.btnGeo) {
|
||||
el.btnGeo.addEventListener('click', () => {
|
||||
if (!navigator.geolocation) {
|
||||
alert('Geolocation wird nicht unterstützt.');
|
||||
return;
|
||||
if (el.quickForm) {
|
||||
el.quickForm.addEventListener('submit', async (e) => {
|
||||
if (!el.quickLoc || !el.quickLat || !el.quickLng) return;
|
||||
const hasCoords = el.quickLat.value && el.quickLng.value;
|
||||
const locVal = (el.quickLoc.value || '').trim();
|
||||
if (hasCoords || locVal === '') return;
|
||||
e.preventDefault();
|
||||
try {
|
||||
const coords = await geocode(locVal);
|
||||
if (coords) {
|
||||
el.quickLat.value = coords.lat.toFixed(6);
|
||||
el.quickLng.value = coords.lng.toFixed(6);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
} finally {
|
||||
el.quickForm.submit();
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
state.geo = { lat: pos.coords.latitude, lng: pos.coords.longitude };
|
||||
searchState.active = false;
|
||||
renderEvents();
|
||||
},
|
||||
() => alert('Standort konnte nicht ermittelt werden.')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -218,5 +182,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
el.modal?.addEventListener('click', (e) => { if (e.target === el.modal) el.modal.classList.remove('open'); });
|
||||
|
||||
renderEvents();
|
||||
renderSlider();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user