diff --git a/frontend/src/components/HUDleftPoints.jsx b/frontend/src/components/HUDleftPoints.jsx
index 8902e64..abc2016 100644
--- a/frontend/src/components/HUDleftPoints.jsx
+++ b/frontend/src/components/HUDleftPoints.jsx
@@ -26,7 +26,11 @@ function LogoBlock() {
const [showSortMenu, setShowSortMenu] = useState(false);
const [unit, setUnit] = useState('C');
const [showFilterModal, setShowFilterModal] = useState(false);
- const [tempFilter, setTempFilter] = useState({ min: '', max: '' });
+ const [tempFilter, setTempFilter] = useState({
+ min: '', max: '',
+ distanceMin: '', distanceMax: '',
+ maxAge: ''
+ });
// Add new state for user location
const [userLocation, setUserLocation] = useState(null);
@@ -303,19 +307,30 @@ function LogoBlock() {
// NEW: dispatch filterchange event
const handleApplyFilter = () => {
- const min = parseFloat(tempFilter.min);
- const max = parseFloat(tempFilter.max);
+ const min = parseFloat(tempFilter.min);
+ const max = parseFloat(tempFilter.max);
+ const distanceMax = parseFloat(tempFilter.distanceMax);
+ const maxAge = parseFloat(tempFilter.maxAge);
window.dispatchEvent(new CustomEvent('filterchange', {
- detail: { min: isNaN(min) ? NaN : min, max: isNaN(max) ? NaN : max }
+ detail: {
+ min: isNaN(min) ? NaN : min,
+ max: isNaN(max) ? NaN : max,
+ distanceMax:isNaN(distanceMax)? NaN : distanceMax,
+ maxAge: isNaN(maxAge) ? NaN : maxAge
+ }
}));
setShowFilterModal(false);
};
// NEW: reset filter AND dispatch
const handleResetFilter = () => {
- setTempFilter({ min: '', max: '' });
+ setTempFilter({ min:'', max:'', distanceMax:'', maxAge:'' });
window.dispatchEvent(new CustomEvent('filterchange', {
- detail: { min: NaN, max: NaN }
+ detail: {
+ min: NaN, max: NaN,
+ distanceMax: NaN,
+ maxAge: NaN
+ }
}));
setShowFilterModal(false);
};
@@ -323,19 +338,35 @@ function LogoBlock() {
// Listen for filterchange events and update the side‐panel list
useEffect(() => {
function handleFilterChange(e) {
- const { min, max } = e.detail;
- setFilteredList(
- locaList.filter(item =>
- (isNaN(min) || item.temp >= min) &&
- (isNaN(max) || item.temp <= max)
- )
- );
+ const { min, max, distanceMax, maxAge } = e.detail;
+ const now = Date.now();
+ setFilteredList(locaList.filter(item => {
+ const tempOK = (isNaN(min) || item.temp >= min) &&
+ (isNaN(max) || item.temp <= max);
+
+ let distOK = true;
+ if (!isNaN(distanceMax) && userLocation) {
+ const d = calculateDistance(
+ userLocation.latitude,
+ userLocation.longitude,
+ item.lat, item.lng || item.lon
+ );
+ if (d > distanceMax) distOK = false;
+ }
+
+ let ageOK = true;
+ if (!isNaN(maxAge) && item.timestamp) {
+ const ageDays = (now - new Date(item.timestamp).getTime())
+ / (1000*60*60*24);
+ if (ageDays > maxAge) ageOK = false;
+ }
+
+ return tempOK && distOK && ageOK;
+ }));
}
window.addEventListener('filterchange', handleFilterChange);
- return () => {
- window.removeEventListener('filterchange', handleFilterChange);
- };
- }, [locaList]);
+ return () => window.removeEventListener('filterchange', handleFilterChange);
+ }, [locaList, userLocation]);
return (
UnitManager.getUnit());
+ const userLocationRef = useRef(null);
// Temperature color function - works with both Celsius and Fahrenheit
function getTemperatureColor(temp, unit = 'C', mode = 'light') {
@@ -724,7 +725,7 @@ export default function MapComponent() {
}, 100);
});
- markersRef.current.push({ marker, tempC: t, name, lat, lon });
+ markersRef.current.push({ marker, tempC: t, name, lat, lon, timestamp: ts });
}
}
@@ -1122,6 +1123,79 @@ export default function MapComponent() {
};
}, []);
+ // new single useEffect to handle ALL filters
+ useEffect(() => {
+ function calculateDistance(lat1, lon1, lat2, lon2) {
+ const R = 6371;
+ const dLat = (lat2 - lat1)*Math.PI/180;
+ const dLon = (lon2 - lon1)*Math.PI/180;
+ const a = Math.sin(dLat/2)**2 +
+ Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180) *
+ Math.sin(dLon/2)**2;
+ return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ }
+
+ const handleFilter = e => {
+ const { min, max, distanceMax, maxAge } = e.detail;
+ const now = Date.now();
+
+ // If distance filter is requested but no location, try to get it
+ if (!isNaN(distanceMax) && !userLocationRef.current) {
+ console.log('🔄 Distance filter requested, attempting to get location...');
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ userLocationRef.current = {
+ lat: position.coords.latitude,
+ lon: position.coords.longitude
+ };
+ console.log('📍 Got user location for filtering:', userLocationRef.current);
+ // Re-trigger the filter with location now available
+ handleFilter(e);
+ },
+ (error) => {
+ console.warn('❌ Could not get location for distance filter:', error.message);
+ alert('Location access is required for distance filtering. Please enable location permissions and try again.');
+ }
+ );
+ return; // Exit early, will re-run once location is obtained
+ } else {
+ alert('Geolocation is not supported by this browser.');
+ return;
+ }
+ }
+
+ markersRef.current.forEach(({ marker, tempC, lat, lon, timestamp }) => {
+ let keep = true;
+ if (!isNaN(min) && tempC < min) keep = false;
+ if (!isNaN(max) && tempC > max) keep = false;
+
+ if (!isNaN(distanceMax) && userLocationRef.current) {
+ const d = calculateDistance(
+ userLocationRef.current.lat,
+ userLocationRef.current.lon,
+ lat, lon
+ );
+ if (d > distanceMax) keep = false;
+ }
+
+ if (!isNaN(maxAge) && timestamp) {
+ const ageDays = (now - timestamp)/(1000*60*60*24);
+ if (ageDays > maxAge) keep = false;
+ }
+
+ if (keep) {
+ marker.addTo(mapInstanceRef.current);
+ } else {
+ mapInstanceRef.current.removeLayer(marker);
+ }
+ });
+ };
+
+ window.addEventListener('filterchange', handleFilter);
+ return () => window.removeEventListener('filterchange', handleFilter);
+ }, []);
+
return (
{/* Map container */}
diff --git a/frontend/src/components/TempFilterModal.jsx b/frontend/src/components/TempFilterModal.jsx
index 712267d..643d39f 100644
--- a/frontend/src/components/TempFilterModal.jsx
+++ b/frontend/src/components/TempFilterModal.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { createPortal } from 'react-dom';
-function TempFilterModal({
+export default function TempFilterModal({
show,
onClose,
theme,
@@ -80,7 +80,7 @@ function TempFilterModal({
+
+ {/* only Max Distance now */}
+
+
+ setTempFilter(f => ({ ...f, distanceMax: e.target.value }))}
+ style={{
+ width: '100%',
+ padding: '0.5rem',
+ borderRadius: '0.5rem',
+ border: theme === 'light'
+ ? '1px solid rgba(0,0,0,0.1)'
+ : '1px solid rgba(255,255,255,0.2)',
+ backgroundColor: theme === 'light' ? '#fff' : 'rgba(255,255,255,0.1)',
+ color: theme === 'light' ? '#000' : '#fff',
+ fontSize: '1rem',
+ outline: 'none',
+ transition: 'border-color 0.2s ease',
+ boxSizing: 'border-box'
+ }}
+ onFocus={e => e.target.style.borderColor = theme === 'light' ? '#007AFF' : '#0A84FF'}
+ onBlur={e => e.target.style.borderColor = theme === 'light' ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.2)'}
+ />
+
+
+
+
+ setTempFilter(f => ({ ...f, maxAge: e.target.value }))}
+ style={{
+ width: '100%',
+ padding: '0.5rem',
+ borderRadius: '0.5rem',
+ border: theme === 'light'
+ ? '1px solid rgba(0,0,0,0.1)'
+ : '1px solid rgba(255,255,255,0.2)',
+ backgroundColor: theme === 'light'
+ ? '#fff'
+ : 'rgba(255,255,255,0.1)',
+ color: theme === 'light' ? '#000' : '#fff',
+ fontSize: '1rem',
+ outline: 'none',
+ transition: 'border-color 0.2s ease',
+ boxSizing: 'border-box'
+ }}
+ onFocus={e => e.target.style.borderColor = theme === 'light' ? '#007AFF' : '#0A84FF'}
+ onBlur={e => e.target.style.borderColor = theme === 'light' ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.2)'}
+ />
+
,
document.body
) : null;
-}
-
-export default TempFilterModal;
\ No newline at end of file
+}
\ No newline at end of file