/* * Copyright (c) 2026 by Christian Kellner. * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { useSelector, useActions } from '../../services/state/store.js'; import { distanceMeters, generateCircleCoords, getBoundsFromCenter } from './mapUtils.js'; import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui-19'; import { IconFilter } from '@douyinfe/semi-icons'; import no_image from '../../assets/no_image.jpg'; import RangeSlider from 'react-range-slider-input'; import 'react-range-slider-input/dist/style.css'; import './Map.less'; const { Text } = Typography; const GERMANY_BOUNDS = [ [5.866, 47.27], // Southwest coordinates [15.042, 55.059], // Northeast coordinates ]; const STYLES = { STANDARD: 'https://tiles.openfreemap.org/styles/bright', SATELLITE: { version: 8, sources: { 'satellite-tiles': { type: 'raster', tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'], tileSize: 256, attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community', }, 'satellite-labels': { type: 'raster', tiles: [ 'https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', ], tileSize: 256, attribution: '© Esri', }, }, layers: [ { id: 'satellite-tiles', type: 'raster', source: 'satellite-tiles', minzoom: 0, maxzoom: 19, }, { id: 'satellite-labels', type: 'raster', source: 'satellite-labels', minzoom: 0, maxzoom: 19, }, ], }, }; export default function MapView() { const mapContainer = useRef(null); const map = useRef(null); const markers = useRef([]); const homeMarker = useRef(null); const actions = useActions(); const listings = useSelector((state) => state.listingsData.mapListings); const homeAddress = useSelector((state) => state.userSettings.settings.home_address); const [style, setStyle] = useState('STANDARD'); const [show3dBuildings, setShow3dBuildings] = useState(false); const jobs = useSelector((state) => state.jobsData.jobs); const [jobId, setJobId] = useState(null); const [priceRange, setPriceRange] = useState([0, 0]); const [showFilterBar, setShowFilterBar] = useState(false); const [distanceFilter, setDistanceFilter] = useState(0); useEffect(() => { setPriceRange([0, getMaxPrice()]); }, [listings]); const getMaxPrice = () => { return listings.reduce((max, item) => { const price = Number(item.price); return Number.isFinite(price) && price > max ? price : max; }, -Infinity); }; const filterListings = () => { const min = priceRange[0]; const max = priceRange[1] && priceRange[1] > 0 ? priceRange[1] : getMaxPrice(); return listings.filter((listing) => listing.price && listing.price >= min && listing.price <= max); }; useEffect(() => { if (map.current) return; map.current = new maplibregl.Map({ container: mapContainer.current, style: STYLES[style], center: [10.4515, 51.1657], // Center of Germany zoom: 4, maxBounds: GERMANY_BOUNDS, antialias: true, }); map.current.addControl( new maplibregl.NavigationControl({ showCompass: true, visualizePitch: true, visualizeRoll: true, }), 'top-right', ); map.current.addControl( new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, trackUserLocation: true, }), ); return () => { map.current.remove(); }; }, []); useEffect(() => { if (map.current) { map.current.setStyle(STYLES[style]); } }, [style]); useEffect(() => { if (show3dBuildings && style !== 'STANDARD') { setStyle('STANDARD'); } }, [show3dBuildings, style]); useEffect(() => { if (!map.current) return; map.current.setPitch(show3dBuildings ? 45 : 0); }, [show3dBuildings]); useEffect(() => { if (!map.current) return; const add3dLayer = () => { if (!map.current || !map.current.isStyleLoaded()) return; if (show3dBuildings) { if (!map.current.getSource('openfreemap')) { map.current.addSource('openfreemap', { type: 'vector', url: 'https://tiles.openfreemap.org/planet', }); } if (!map.current.getLayer('3d-buildings')) { const layers = map.current.getStyle().layers; let labelLayerId; for (let i = 0; i < layers.length; i++) { if (layers[i].type === 'symbol' && layers[i].layout?.['text-field']) { labelLayerId = layers[i].id; break; } } map.current.addLayer( { id: '3d-buildings', source: 'openfreemap', 'source-layer': 'building', type: 'fill-extrusion', minzoom: 15, filter: ['!=', ['get', 'hide_3d'], true], paint: { 'fill-extrusion-color': [ 'interpolate', ['linear'], ['get', 'render_height'], 0, 'lightgray', 200, 'royalblue', 400, 'lightblue', ], 'fill-extrusion-height': ['interpolate', ['linear'], ['zoom'], 15, 0, 16, ['get', 'render_height']], 'fill-extrusion-base': ['case', ['>=', ['get', 'zoom'], 16], ['get', 'render_min_height'], 0], 'fill-extrusion-opacity': 0.6, }, }, labelLayerId, ); } } else { if (map.current.getLayer('3d-buildings')) { map.current.removeLayer('3d-buildings'); } } }; add3dLayer(); }, [show3dBuildings, style]); const setMapStyle = (value) => { setStyle(value); if (value === 'SATELLITE') { setShow3dBuildings(false); } }; const fetchListings = async () => { actions.listingsData.getListingsForMap({ jobId, }); }; useEffect(() => { fetchListings(); }, [jobId]); useEffect(() => { if (!map.current || !homeAddress?.coords) return; // We only want to zoom/fly when distanceFilter OR homeAddress actually change, // not on every render. useEffect dependency array handles this. if (distanceFilter > 0) { const bounds = getBoundsFromCenter([homeAddress.coords.lng, homeAddress.coords.lat], distanceFilter); map.current.fitBounds(bounds, { padding: 20, maxZoom: 15, duration: 1000, }); } else { map.current.flyTo({ center: [homeAddress.coords.lng, homeAddress.coords.lat], zoom: 12, duration: 1000, }); } }, [homeAddress?.address, distanceFilter]); useEffect(() => { if (!map.current) return; markers.current.forEach((marker) => marker.remove()); markers.current = []; if (homeMarker.current) { homeMarker.current.remove(); homeMarker.current = null; } if (homeAddress?.coords) { homeMarker.current = new maplibregl.Marker({ color: 'red' }) .setLngLat([homeAddress.coords.lng, homeAddress.coords.lat]) .setPopup( new maplibregl.Popup({ offset: 25 }).setHTML( `
${homeAddress.address}