import { useEffect, useRef } from 'react';
import L from 'leaflet';
import { api } from '../api.js';
export default function MapView({ members, groups, sos, selected, onSelect }) {
const elRef = useRef(null);
const mapRef = useRef(null);
const markersRef = useRef(new Map());
const trailRef = useRef(null);
const didFit = useRef(false);
useEffect(() => {
const map = L.map(elRef.current).setView([48.8566, 2.3522], 12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19,
}).addTo(map);
mapRef.current = map;
return () => { map.remove(); mapRef.current = null; };
}, []);
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const sosIds = new Set(sos.filter((a) => a.state !== 'resolved').map((a) => a.member_id));
const colorOf = (gid) => groups.find((g) => g.id === gid)?.color || '#64748b';
const positioned = members.filter((m) => m.lat != null && m.lng != null);
const seen = new Set();
for (const m of positioned) {
seen.add(m.id);
const cls = `pin ${m.status === 'online' ? 'online' : 'offline'} ${sosIds.has(m.id) ? 'sos' : ''}`;
const icon = L.divIcon({
className: 'pin-wrap',
html: `
${m.name.slice(0, 2).toUpperCase()}
`,
iconSize: [26, 26],
iconAnchor: [13, 13],
});
let mk = markersRef.current.get(m.id);
if (!mk) {
mk = L.marker([m.lat, m.lng], { icon }).addTo(map);
mk.on('click', () => onSelect(m.id));
markersRef.current.set(m.id, mk);
} else {
mk.setLatLng([m.lat, m.lng]);
mk.setIcon(icon);
}
mk.bindPopup(
`${m.name}${m.callsign ? ` · ${m.callsign}` : ''} · ${m.group_name || 'no group'}
` +
`${m.status} · battery ${m.battery != null ? m.battery + '%' : '–'}
` +
`${m.lat.toFixed(5)}, ${m.lng.toFixed(5)}`
);
}
for (const [id, mk] of markersRef.current) {
if (!seen.has(id)) {
map.removeLayer(mk);
markersRef.current.delete(id);
}
}
if (!didFit.current && positioned.length) {
didFit.current = true;
map.fitBounds(L.latLngBounds(positioned.map((m) => [m.lat, m.lng])).pad(0.3));
}
}, [members, groups, sos, onSelect]);
// GPS trail of the selected member
useEffect(() => {
const map = mapRef.current;
if (!map) return;
trailRef.current?.remove();
trailRef.current = null;
if (!selected) return;
let cancelled = false;
api.get(`/members/${selected}/trail`).then((rows) => {
if (cancelled || rows.length < 2) return;
trailRef.current = L.polyline(rows.map((r) => [r.lat, r.lng]), { color: '#4fc3f7', weight: 3, opacity: 0.7 }).addTo(map);
}).catch(() => {});
return () => { cancelled = true; };
}, [selected]);
return ;
}