diff --git a/.idea/misc.xml b/.idea/misc.xml
index cd5457df..e8311ab3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,9 +3,4 @@
Recycling map
-
- © 2025 Sortify. All rights reserved. + © 2025 Sortify.
diff --git a/frontend/sortify/components/map.css b/frontend/sortify/components/map.css new file mode 100644 index 00000000..691a9c83 --- /dev/null +++ b/frontend/sortify/components/map.css @@ -0,0 +1,131 @@ +/* map.css */ + +.container { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 800px; + position: relative; +} + +/* Wrap map and filters horizontally */ +.map-and-filters { + display: flex; + flex-direction: row; + width: 100%; +} + +.filter-panel { + background-color: #DBBC99; + padding: 20px; + border-radius: 15px; + border: 2px solid #3E4739; /* Dark green border for consistency */ + max-width: 180px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 10px; + font-size: 14px; + color: #3E4739; + margin-left: 20px; /* Space between map and panel */ + height: 100%; + margin-top: 12px; +} + +/* Optional: Style for each filter checkbox line */ +.filter-panel label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} + +/* Adjust map width to allow space for filter panel */ +.map { + flex-grow: 1; + height: 500px; +} + +/* Route button with .signupButton styles */ +.route-button { + display: inline-block; + background-color: #D9996B; + color: #3E4739; + padding: 12px 24px; + border-radius: 30px; + text-decoration: none; + font-weight: 600; + text-transform: uppercase; + font-size: 14px; + transition: all 0.3s ease; + cursor: pointer; + box-sizing: border-box; + border: 2px solid #3E4739; +} + +/* Hover effect for the route button */ +.route-button:hover { + background-color: #B6CBBC; +} + +/* Disabled route button style */ +.route-button:disabled { + background-color: #ddd; + cursor: not-allowed; + border: 2px solid #ccc; +} + +/* Container for the info box and button */ +.info-box-container { + background-color: #DBBC99; /* Warm beige color for the container */ + padding: 20px; + border-radius: 8px; + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 10px; + max-width: 220px; /* Limit the width of the container */ + width: 100%; /* Makes sure it is responsive */ + margin: 0 auto; /* Centers the container horizontally */ +} + +/* Styling for the info box */ +.info-box { + border: 2px solid #3E4739; /* Dark green border for contrast */ + border-radius: 8px; + padding: 10px; + min-width: 150px; + background: #f9f9f9; /* Light background */ + font-size: 14px; + color: #3E4739; /* Text color to match the border */ +} + +/* Info box text spacing */ +.info-box div { + margin-bottom: 8px; +} + +/* Button inside the info box */ +.info-box button { + background-color: #D9996B; /* Same background color as route button */ + color: #3E4739; /* Text color */ + padding: 6px 12px; + cursor: pointer; + font-size: 14px; + border-radius: 30px; + border: 2px solid #3E4739; /* Matching border */ + font-weight: 600; + transition: all 0.3s ease; +} + +/* Hover effect for info box button */ +.info-box button:hover { + background-color: #B6CBBC; /* Lighter color on hover */ +} + +/* Disabled button in info box */ +.info-box button:disabled { + background-color: #ddd; + cursor: not-allowed; + border: 2px solid #ccc; +} diff --git a/frontend/sortify/components/map.jsx b/frontend/sortify/components/map.jsx index f54041e6..e4d30b32 100644 --- a/frontend/sortify/components/map.jsx +++ b/frontend/sortify/components/map.jsx @@ -3,10 +3,12 @@ import { useEffect, useRef, useState } from "react"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; +import './map.css'; import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'; import markerIcon from 'leaflet/dist/images/marker-icon.png'; import markerShadow from 'leaflet/dist/images/marker-shadow.png'; +import {useSearch} from "@/app/context/searchContext"; delete L.Icon.Default.prototype._getIconUrl; @@ -16,7 +18,7 @@ L.Icon.Default.mergeOptions({ shadowUrl: markerShadow.src ?? markerShadow, }); -export default function Map({filter}) { +export default function Map() { // Store references to map and marker instances const mapRef = useRef(null); const markerRef = useRef(null); @@ -27,24 +29,41 @@ export default function Map({filter}) { const routeLayerRef = useRef(null); const [routeVisible, setRouteVisible] = useState(false); + const [routeInfo, setRouteInfo] = useState(null); + + const { search, setSearch } = useSearch(); + const skipNextSearchEffect = useRef(false); + + const [filters, setFilters] = useState({ + "Plast": true, + "Restavfall": false, + "Matavfall": true, + "Papp og papir": false, + "El-avfall": false, + "Klær": false, + "Farlig avfall": false, + "Glass og metall": false, + "Hageavfall": false, + "Brennbart avfall": false + }); - console.log("filter: " + filter) + const toggleFilter = (type) => { + setFilters(prev => ({ + ...prev, + [type]: !prev[type] + })); + }; // Initialize map only once on component mount useEffect(() => { - console.log("Initializing Map") - if (typeof window === 'undefined') return; - const map = L.map('map').setView([60.39, 5.32], 11); mapRef.current = map; - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap', }).addTo(map); - return () => map.remove(); }, []); @@ -55,33 +74,72 @@ export default function Map({filter}) { navigator.geolocation.getCurrentPosition( (position) => { console.log("coords",position.coords) - setUserLocation({ + const newLocation = { lat: position.coords.latitude, lon: position.coords.longitude, - }); + }; + setUserLocation(newLocation); + // Create the user marker immediately with the new location + createUserMarker(mapRef.current, newLocation); }, (error) => console.error("Error getting location:", error.message) ); }, []); - // When both map and location are available, update the map view + // When both map and location are available, mark and zoom onto the users geolocation useEffect(() => { - - console.log("Trying to display locations") - - if (!mapRef.current || !userLocation) { - console.log("Could not find map or userLocation") + if (!mapRef.current || !userLocation) return; + updateUserMarker(mapRef.current, userLocation) + + fetch(`http://localhost:9876/api/locations/sorted?lat=${userLocation.lat}&lon=${userLocation.lon}`) + .then(res => res.json()) + .then(data => { + setLocations(data); + }) + .catch(err => console.error("Failed to fetch locations:", err)); + }, [userLocation]); + + // Update the displayed users location and the displayed locations, if they have changed + useEffect(() => { + if (!mapRef.current || !userLocation || locations.length === 0) return; + + let filtered = [...locations]; // Start with all locations + + // Debug + console.log(locations) + + // Apply search filter if there's a search term + console.log(search) + if (search) { + filtered = filtered.filter(loc => loc.wasteTypes.includes(search)); + const updatedFilters = Object.fromEntries( + Object.keys(filters).map(type => [type, type === search]) + ); + setFilters(updatedFilters); // Update the filters state + setSearch(""); // Reset search after applying + } else { + // Apply checkbox filters + const selected = Object.entries(filters) + .filter(([_, checked]) => checked) + .map(([key]) => key); + if (selected.length) { + filtered = filtered.filter(loc => + loc.wasteTypes.some(type => selected.includes(type)) + ); + } + } + // Check if there are any filtered locations + if (filtered.length === 0) { + // Handle case where no locations match the filters (optional) + setNearestLocation(null); + addLocationMarkers(mapRef.current, []); // Clear any existing markers return; } - updateUserMarker(mapRef.current, userLocation); - fetchAndDisplayLocations( - mapRef.current, - userLocation, - setLocations, - setNearestLocation, - filter - ); - }, [userLocation, filter]); + + // Set the nearest location + setNearestLocation(filtered[0]); + addLocationMarkers(mapRef.current, filtered); + }, [search, filters, locations, userLocation]); // Functionality to toggle the route on or off const toggleRoute = async () => { @@ -101,6 +159,18 @@ export default function Map({filter}) { const routeData = await routeRes.json(); if (routeData.routes?.length) { + + const distanceMeters = routeData.routes[0].distance; + const durationSeconds = routeData.routes[0].duration; + + setRouteInfo({ + distance: (distanceMeters / 1000).toFixed(2), + duration: (durationSeconds / 60).toFixed(2), + }); + + console.log(`Distance to nearest: ${(distanceMeters / 1000).toFixed(2)} km`); + console.log(`ETA: ${(durationSeconds / 60).toFixed(2)} minutes`); + const geoJSON = routeData.routes[0].geometry; const routeLine = L.geoJSON(geoJSON, { style: { color: 'blue', weight: 4 }, @@ -109,6 +179,7 @@ export default function Map({filter}) { routeLayerRef.current = routeLine; setRouteVisible(true); map.fitBounds(routeLine.getBounds()); + } } catch (err) { console.error("Error drawing route:", err); @@ -119,68 +190,143 @@ export default function Map({filter}) { // Map component // Button for toggling the route on or off return ( - <> - - - > +setSearch(item.type)}>{item.name}
+{item.name}