Termine entdecken
-
Upcoming Events in deiner Nähe
+
Die nächsten anstehenden Events
Gäste sehen nur Basisinfos. Als Mitglied siehst du vollständige Details, kannst zusagen und neue Treffen anlegen.
@@ -187,3 +219,6 @@ $flash = $app->flash()->get();
+
diff --git a/public/assets/js/app.js b/public/assets/js/app.js
index 3e396ad..a49214d 100644
--- a/public/assets/js/app.js
+++ b/public/assets/js/app.js
@@ -49,75 +49,10 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // Demo-Events
- const events = [
- {
- id: 1,
- title: 'Spielplatzrunde im Park',
- teaser: 'Lockeres Treffen mit Kaffee, Sandspielzeug und Picknick.',
- description: 'Wir treffen uns am großen Spielplatz im Kiez. Bringt Snacks mit, wir teilen. Für alle Altersstufen offen.',
- city: 'Berlin',
- zip: '10437',
- region: 'Prenzlauer Berg',
- topic: 'outdoor',
- startsAt: '2025-08-10T10:00:00',
- allowKids: true,
- ageGroup: 'kids',
- visibility: 'public',
- contact: 'papa-berlin@example.com',
- locationLabel: 'Mauerpark',
- },
- {
- id: 2,
- title: 'Väter-Kaffee & Austausch',
- teaser: 'Elternzeit, Job, Schlaf – wir reden über alles.',
- description: 'Reservierter Tisch im Café. Fokus auf Austausch unter Vätern, Kinder optional.',
- city: 'Hamburg',
- zip: '22767',
- region: 'Altona',
- topic: 'kaffee',
- startsAt: '2025-08-12T19:00:00',
- allowKids: false,
- ageGroup: '',
- visibility: 'members',
- contact: 'altona-dads@example.com',
- locationLabel: 'Café Elbseite',
- },
- {
- id: 3,
- title: 'Sport & Spiel im Park',
- teaser: 'Ballspiele und leichtes Workout, Kinder toben mit.',
- description: 'Wir bringen Bälle und Slackline. Warm-up, danach freies Spiel. Bitte Wasser mitbringen.',
- city: 'München',
- zip: '80804',
- region: 'Schwabing',
- topic: 'sport',
- startsAt: '2025-08-15T11:00:00',
- allowKids: true,
- ageGroup: 'school',
- visibility: 'public',
- contact: 'schwabing-sport@example.com',
- locationLabel: 'Luitpoldpark',
- },
- {
- id: 4,
- title: 'Workshop: Erste Hilfe für Kids',
- teaser: 'Erste Hilfe Basics speziell für Kinder-Unfälle.',
- description: 'Zertifizierter Trainer, kleiner Materialbeitrag. Kinder können mitgebracht werden.',
- city: 'Köln',
- zip: '50667',
- region: 'Innenstadt',
- topic: 'workshop',
- startsAt: '2025-08-20T18:30:00',
- allowKids: true,
- ageGroup: 'kids',
- visibility: 'members',
- contact: 'koeln-workshop@example.com',
- locationLabel: 'Familienzentrum Mitte',
- },
- ];
+ // Events from backend (injected on page); fallback to empty
+ const events = Array.isArray(window.__events) ? window.__events : [];
- const state = { topic: 'all', age: '', region: '', query: '' };
+ const state = { topic: 'all', age: '', region: '', query: '', geo: null };
const el = {
list: document.getElementById('eventList'),
chips: document.querySelectorAll('[data-filter]'),
@@ -133,6 +68,16 @@ document.addEventListener('DOMContentLoaded', () => {
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) => `
${label}`;
const renderCard = (item) => {
@@ -170,16 +115,39 @@ document.addEventListener('DOMContentLoaded', () => {
};
const matchesFilter = (ev) => {
- const topicOk = state.topic === 'all' || state.topic === ev.topic;
+ 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 regionOk = !state.region || ev.region.toLowerCase().includes(state.region) || ev.city.toLowerCase().includes(state.region) || ev.zip.startsWith(state.region);
- const queryOk = !state.query || (ev.title + ev.teaser + ev.description + ev.city + ev.region).toLowerCase().includes(state.query);
+ 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;
- const filtered = events.filter(matchesFilter);
+ let filtered = events.filter(matchesFilter);
+ filtered.sort((a,b) => new Date(a.startsAt) - new Date(b.startsAt));
+
+ if (state.geo) {
+ 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 {
+ filtered = filtered.slice(0,5);
+ }
+
el.list.innerHTML = filtered.map(renderCard).join('') || '
Keine Events gefunden.
';
};
@@ -212,8 +180,8 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
navigator.geolocation.getCurrentPosition(
- () => {
- state.region = 'nähe';
+ (pos) => {
+ state.geo = { lat: pos.coords.latitude, lng: pos.coords.longitude };
renderEvents();
},
() => alert('Standort konnte nicht ermittelt werden.')