import React, { useState, useEffect, useRef } from 'react'; import { MapContainer, TileLayer, Marker, Polyline, Circle, useMap } from 'react-leaflet'; import { Navigation, ShieldAlert, MapPin, Volume2, Share2, Activity, Clock, } from 'lucide-react'; import { motion } from 'framer-motion'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8000'; // --- ICONS: user blue dot --- const createBlueDotIcon = () => L.divIcon({ className: 'custom-div-icon', html: `
`, iconSize: [20, 20], iconAnchor: [10, 10], }); // --- ICONS: radar marker for zones (danger/crowded/safe) --- const createRadarIcon = (type) => { const color = type === 'danger' ? '#EF4444' : type === 'crowded' ? '#EAB308' : '#22C55E'; return L.divIcon({ className: 'custom-radar', html: `
`, iconSize: [20, 20], iconAnchor: [10, 10], }); }; // --- Map controller: smoothly fly to center + zoom --- const MapController = ({ center, zoom }) => { const map = useMap(); useEffect(() => { if (center) { map.flyTo(center, zoom || 13, { animate: true, duration: 1.5 }); } }, [center, zoom, map]); return null; }; const MapScreen = () => { // --- STATIC USER LOCATION (for demo) --- // Change STATIC_POS if you want a different default user location const STATIC_POS = [24.7150, 46.6800]; const [userPos] = useState(STATIC_POS); const [mapCenter, setMapCenter] = useState(STATIC_POS); const [mapZoom, setMapZoom] = useState(12); // --- BACKEND DATA --- const [zones, setZones] = useState([]); const [routeLine, setRouteLine] = useState([]); const [targetZone, setTargetZone] = useState(null); const [distanceKm, setDistanceKm] = useState(null); // --- AI STATE (reason + step-by-step instructions) --- const [aiReason, setAiReason] = useState(''); const [instructionsList, setInstructionsList] = useState([]); const [currentInstIndex, setCurrentInstIndex] = useState(0); const [etaMinutes, setEtaMinutes] = useState(null); // --- AUDIO STATE --- const [isPlaying, setIsPlaying] = useState(false); const audioRef = useRef(null); // --- UI STATE --- const [isOpen, setIsOpen] = useState(true); // bottom sheet open/closed const [isSharing, setIsSharing] = useState(false); // SOS button loading const [sosData, setSosData] = useState(null); // SOS toast data const [isNavigating, setIsNavigating] = useState(false); // mini-nav mode // --- Fetch zones from backend (fire expansion, etc.) --- useEffect(() => { const fetchZones = async () => { try { const res = await fetch(`${API_BASE}/map-data`); const data = await res.json(); setZones(data); } catch (e) { console.error('Error loading zones:', e); } }; fetchZones(); // Refresh zones every 5 seconds to reflect dynamic hazard changes const interval = setInterval(fetchZones, 5000); return () => clearInterval(interval); }, []); // --- Rotate instructions (top banner) every 6 seconds --- useEffect(() => { if (instructionsList.length > 0) { const interval = setInterval(() => { setCurrentInstIndex((prev) => (prev + 1) % instructionsList.length); }, 6000); return () => clearInterval(interval); } }, [instructionsList]); // --- Handle audio ended --- useEffect(() => { if (audioRef.current) { audioRef.current.onended = () => setIsPlaying(false); } }, [audioRef.current]); // --- Start navigation once on mount (using static user position) --- useEffect(() => { startNavigation(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // --- Call /navigate to get safest target zone + route + AI explanation + TTS --- const startNavigation = async () => { try { setAiReason('جاري تحليل الموقف...'); const response = await fetch(`${API_BASE}/navigate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lat: userPos[0], lng: userPos[1] }), }); const data = await response.json(); setTargetZone(data.target_zone); setRouteLine(data.route || []); setDistanceKm(data.meta?.distance_km ?? null); if (data.ai_response) { setAiReason(data.ai_response.reasoning || ''); setInstructionsList(data.ai_response.instructions_list || []); setEtaMinutes(data.ai_response.eta_minutes || null); } setIsOpen(true); // Prepare audio from base64 if available if (data.audio_base64) { const audioSrc = `data:audio/mpeg;base64,${data.audio_base64}`; audioRef.current = new Audio(audioSrc); audioRef.current.onended = () => setIsPlaying(false); console.log('✅ Audio ready'); } else { console.warn('⚠️ No audio_base64 from backend'); } } catch (error) { console.error(error); alert('خطأ في الاتصال مع خادم الإخلاء'); } }; // --- Toggle audio play/pause from Volume icon --- const toggleAudio = (e) => { e.stopPropagation(); if (!audioRef.current) { console.warn('⚠️ Audio not ready yet'); return; } if (isPlaying) { audioRef.current.pause(); setIsPlaying(false); } else { audioRef.current .play() .then(() => setIsPlaying(true)) .catch((err) => console.error('Audio play error:', err)); } }; // --- Start navigation: collapse sheet to mini-nav, zoom to user, play audio --- const handleStart = (e) => { e.stopPropagation(); setIsOpen(false); setIsNavigating(true); // Focus map on user location setMapCenter(userPos); setMapZoom(16); // Play from the beginning if (audioRef.current) { audioRef.current.currentTime = 0; audioRef.current .play() .then(() => setIsPlaying(true)) .catch((err) => console.error('Audio play error:', err)); } else { console.warn('⚠️ Audio not ready when pressing start'); } }; // --- Stop navigation: return to full bottom sheet, stop audio --- const handleStopNavigation = (e) => { e.stopPropagation(); setIsNavigating(false); setIsOpen(true); if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; setIsPlaying(false); } }; // --- Send SOS to /alert-911 (Fire Dept / Command Center) --- const handleShare = async (e) => { e.stopPropagation(); setIsSharing(true); try { const response = await fetch(`${API_BASE}/alert-911`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lat: userPos[0], lng: userPos[1] }), }); const data = await response.json(); setSosData(data); setTimeout(() => setSosData(null), 5000); } catch (error) { console.error('SOS error:', error); } finally { setIsSharing(false); } }; // --- Distance text for bottom sheet --- const distanceText = distanceKm != null ? `يبعد تقريبًا ${distanceKm.toFixed(1)} كم عن موقعك` : ''; // Distance as plain number for mini navigation UI const distance = distanceKm != null ? distanceKm.toFixed(1) : ''; // Current time (for mini navigation view) const getCurrentTime = () => { const now = new Date(); return now.toLocaleTimeString('ar-SA', { hour: '2-digit', minute: '2-digit', hour12: true, }); }; // --- Expanding hazard zones (animated red auras around specific ids) --- const dangerZones = zones.filter((z) => z.type === 'danger'); const expandingZones = dangerZones.filter((z) => ['z_danger_1', 'z_danger_2', 'z_danger_3'].includes(z.id) ); return (
{/* MAP LAYER */}
{/* Expanding hazard circles for key danger zones */} {targetZone && expandingZones.map((zone) => { let expansionMultiplier = 1.0; if (zone.id === 'z_danger_1') expansionMultiplier = 1.0; else if (zone.id === 'z_danger_2') expansionMultiplier = 0.7; else if (zone.id === 'z_danger_3') expansionMultiplier = 1.05; const currentRadius = (zone.radius || 1) * expansionMultiplier; return ( ); })} {/* Zone markers */} {zones.map((zone) => ( ))} {/* Evacuation route polyline */} {routeLine.length > 0 && ( )} {/* Static user location marker */} {/* Map controller for smooth fly-to transitions */}
{/* TOP: Emergency banner + rotating instructions */}

حالة طوارئ نشطة

{instructionsList.length > 0 ? instructionsList[currentInstIndex] : 'إخلاء قيد التحليل...'}

{/* SOS TOAST */} {sosData && (

تم إرسال البلاغ

{sosData.message}

)} {/* BOTTOM SHEET */}
!isNavigating && setIsOpen(!isOpen)} className={`bg-white/98 backdrop-blur-xl rounded-t-[28px] shadow-[0_-4px_24px_rgba(0,0,0,0.12)] px-5 py-4 h-full border-t-2 border-gray-100 ${ !isNavigating ? 'cursor-pointer' : '' }`} > {/* handle for dragging */} {!isNavigating && (
)} {targetZone ? ( isNavigating ? ( // --- MINI NAVIGATION VIEW (compact mode while walking) ---
{/* Time & ETA */}
{etaMinutes ?? '--'} دقيقة
{distance && ( <> {distance} كم )} {getCurrentTime()}
{/* Direction arrow placeholder (could be replaced by heading) */} {/* AUDIO TOGGLE BUTTON */} {/* STOP NAV BUTTON */}
) : ( // --- FULL EXPANDED VIEW (before starting navigation) --- <>

{targetZone.name}

المنطقة الآمنة المختارة

{distanceText && (

{distanceText}

)} {etaMinutes && (

الوقت التقديري للوصول: {etaMinutes} دقيقة

)}
{/* AUDIO TOGGLE BUTTON */}
{/* Reason / explanation card */}

سبب اختيار المسار

{aiReason || 'يتم الآن توليد تفسير المسار الآمن...'}

{/* Actions: SOS + Start */}
) ) : ( // --- LOADING STATE: waiting for /navigate ---

جاري تحليل مسار الإخلاء

يعمل المساعد الذكي "إخلاء" الآن على اختيار أفضل مسار آمن لك...

)}
); }; export default MapScreen;