Phase 4: Filter- und Suchfunktion
Neues Modul js/filters/ mit reiner Filterlogik (filterEngine.js: applyFilters(), getDistanceKm()) und UI (filterPanel.js) fuer alle Filter aus anforderung.md Abschnitt 3: Kategorie, Entfernung (mit "Standort verwenden"-Button), Alter der Kinder, Kosten, Indoor/Outdoor, 5 Ausstattungsmerkmale, Aufenthaltsdauer, Mindestbewertung sowie Freitextsuche ueber Name/Ort/Region/Adresse. Trefferzaehler, "Filter zuruecksetzen" und "Auf Ergebnisse zoomen" ergaenzt. Store erweitert um updateFilters()/getFilteredPlaces() (Selector). Karte und Liste rendern jetzt die gefilterte statt die volle Ausflugsziel-Liste; die Karten-Rerender-Optimierung vergleicht dafuer die Trefferliste per Id-Schluessel statt nur state.places, damit reine Filter-/Standortaenderungen ohne Places-Aenderung erkannt werden. Layout: Filter-Panel als aufklappbares <details> auf Mobile, eigener Grid-Bereich neben der Karte auf Desktop. docs/plan.md entsprechend fortgeschrieben (Phase 2-5 als umgesetzt markiert, Phase 5 Navigation war bereits Teil von Phase 2).
This commit is contained in:
+20
-9
@@ -4,7 +4,7 @@
|
||||
|
||||
(function () {
|
||||
const { DEFAULT_MAP_CENTER } = EventMap.config;
|
||||
const { subscribe, getState, setState } = EventMap.store;
|
||||
const { subscribe, setState, getFilteredPlaces } = EventMap.store;
|
||||
const { getPlaceLatLng } = EventMap.models;
|
||||
const { createMarkerForPlace } = EventMap.map;
|
||||
|
||||
@@ -12,9 +12,14 @@
|
||||
let clusterGroup = null;
|
||||
const markersByPlaceId = new Map();
|
||||
|
||||
let lastPlacesRef = null;
|
||||
let lastFilteredKey = null;
|
||||
let lastSelectedPlaceId = null;
|
||||
|
||||
/** Günstiger Vergleichsschlüssel für eine gefilterte Trefferliste (Reihenfolge + IDs). */
|
||||
function computeFilteredKey(places) {
|
||||
return places.map((place) => place.id).join('|');
|
||||
}
|
||||
|
||||
function hasValidCoords(place) {
|
||||
const { lat, lng } = getPlaceLatLng(place);
|
||||
return typeof lat === 'number' && typeof lng === 'number' && !Number.isNaN(lat) && !Number.isNaN(lng);
|
||||
@@ -101,15 +106,21 @@
|
||||
clusterGroup = L.markerClusterGroup();
|
||||
map.addLayer(clusterGroup);
|
||||
|
||||
const initialState = getState();
|
||||
renderMarkers(initialState.places);
|
||||
fitToPlaces(initialState.places);
|
||||
lastPlacesRef = initialState.places;
|
||||
const initialFiltered = getFilteredPlaces();
|
||||
renderMarkers(initialFiltered);
|
||||
fitToPlaces(initialFiltered);
|
||||
lastFilteredKey = computeFilteredKey(initialFiltered);
|
||||
|
||||
subscribe((state) => {
|
||||
if (state.places !== lastPlacesRef) {
|
||||
lastPlacesRef = state.places;
|
||||
renderMarkers(state.places);
|
||||
// Filter/Standort können sich ändern, ohne dass sich state.places
|
||||
// ändert - daher bei jeder Store-Änderung neu filtern und nur bei
|
||||
// tatsächlich geänderter Trefferliste neu rendern (vermeidet
|
||||
// unnötiges Marker-Flackern z.B. bei jedem Freitext-Tastendruck).
|
||||
const filtered = getFilteredPlaces();
|
||||
const key = computeFilteredKey(filtered);
|
||||
if (key !== lastFilteredKey) {
|
||||
lastFilteredKey = key;
|
||||
renderMarkers(filtered);
|
||||
}
|
||||
if (state.selectedPlaceId !== lastSelectedPlaceId) {
|
||||
lastSelectedPlaceId = state.selectedPlaceId;
|
||||
|
||||
Reference in New Issue
Block a user