diff --git a/src/Fonctions/Incident_fonction.js b/src/Fonctions/Incident_fonction.js index e5a58f7..2a4ecb6 100644 --- a/src/Fonctions/Incident_fonction.js +++ b/src/Fonctions/Incident_fonction.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { config } from "config"; import { useParams, useHistory, useLocation } from "react-router-dom"; import "video-react/dist/video-react.css"; @@ -318,11 +318,12 @@ export const IncidentData = () => { }; // Récupérer les structures sensibles à proximité via Overpass - const fetchNearbySensitiveStructures = async (latitude, longitude) => { - try { - const radius = 250; - const overpassUrl = "https://overpass-api.de/api/interpreter"; - const overpassQuery = ` + const fetchNearbySensitiveStructures = useCallback( + async (latitude, longitude) => { + try { + const radius = 250; + const overpassUrl = "https://overpass-api.de/api/interpreter"; + const overpassQuery = ` [out:json]; ( // Infrastructures urbaines et autres @@ -387,78 +388,86 @@ export const IncidentData = () => { out center; `; - const response = await axios.post( - overpassUrl, - `data=${encodeURIComponent(overpassQuery)}` - ); - const nearbyElements = response.data.elements; + const response = await axios.post( + overpassUrl, + `data=${encodeURIComponent(overpassQuery)}` + ); + const nearbyElements = response.data.elements; - // Traduction en français - const translatedElements = nearbyElements.map((element) => { - const tags = element.tags; - if (tags.highway) return "Route"; - if (tags.amenity === "hospital") return "Hôpital"; - if (tags.amenity === "school") return "École"; - if (tags.amenity === "public_building") - return "Bâtiment public"; - if (tags.man_made === "sewer") return "Égout"; - if (tags.man_made === "drain") return "Drain"; - if (tags.waterway === "river") return "Rivière"; - if (tags.waterway === "stream") return "Ruisseau"; - if (tags.waterway === "canal") return "Canal"; - if (tags.waterway === "drain") return "Drain"; - if (tags.waterway === "ditch") return "Fossé"; - if (tags.natural === "water") return "Plan d'eau"; - if (tags.natural === "wetland") return "Zone humide"; - if (tags.waterway === "riverbank") return "Berge de rivière"; - if (tags.landuse === "reservoir") return "Réservoir"; - if (tags.landuse === "basin") return "Bassin"; - if (tags.man_made === "reservoir_covered") - return "Réservoir couvert"; - if (tags.landuse === "forest") return "Forêt"; - if (tags.leisure === "park") return "Parc"; - if (tags.landuse === "residential") return "Zone résidentielle"; - if (tags.building === "residential") - return "Bâtiment résidentiel"; - if (tags.amenity === "marketplace") return "Marché"; - if (tags.landuse === "farmland") return "Terre agricole"; - if (tags.landuse === "orchard") return "Verger"; - if (tags.boundary === "protected_area") return "Zone protégée"; - if (tags.landuse === "industrial") return "Zone industrielle"; - if (tags.landuse === "commercial") return "Zone commerciale"; - if (tags.amenity === "waste_disposal") - return "Site de gestion des déchets"; - if (tags.amenity === "recycling") return "Site de recyclage"; - if (tags.amenity === "drinking_water") - return "Point d'eau potable"; - if (tags.man_made === "water_well") return "Puits"; - if (tags.man_made === "wastewater_plant") - return "Station d'épuration"; - if (tags.power === "plant") return "Centrale électrique"; - if (tags.power === "substation") - return "Sous-station électrique"; - if (tags.power === "line") return "Ligne électrique"; - if (tags.highway === "bus_stop") return "Arrêt de bus"; - if (tags.railway === "station") return "Gare"; - if (tags.hazard === "flood") return "Zone inondable"; - return "Autre"; - }); + // Traduction en français + const translatedElements = nearbyElements.map((element) => { + const tags = element.tags; + if (tags.highway) return "Route"; + if (tags.amenity === "hospital") return "Hôpital"; + if (tags.amenity === "school") return "École"; + if (tags.amenity === "public_building") + return "Bâtiment public"; + if (tags.man_made === "sewer") return "Égout"; + if (tags.man_made === "drain") return "Drain"; + if (tags.waterway === "river") return "Rivière"; + if (tags.waterway === "stream") return "Ruisseau"; + if (tags.waterway === "canal") return "Canal"; + if (tags.waterway === "drain") return "Drain"; + if (tags.waterway === "ditch") return "Fossé"; + if (tags.natural === "water") return "Plan d'eau"; + if (tags.natural === "wetland") return "Zone humide"; + if (tags.waterway === "riverbank") + return "Berge de rivière"; + if (tags.landuse === "reservoir") return "Réservoir"; + if (tags.landuse === "basin") return "Bassin"; + if (tags.man_made === "reservoir_covered") + return "Réservoir couvert"; + if (tags.landuse === "forest") return "Forêt"; + if (tags.leisure === "park") return "Parc"; + if (tags.landuse === "residential") + return "Zone résidentielle"; + if (tags.building === "residential") + return "Bâtiment résidentiel"; + if (tags.amenity === "marketplace") return "Marché"; + if (tags.landuse === "farmland") return "Terre agricole"; + if (tags.landuse === "orchard") return "Verger"; + if (tags.boundary === "protected_area") + return "Zone protégée"; + if (tags.landuse === "industrial") + return "Zone industrielle"; + if (tags.landuse === "commercial") + return "Zone commerciale"; + if (tags.amenity === "waste_disposal") + return "Site de gestion des déchets"; + if (tags.amenity === "recycling") + return "Site de recyclage"; + if (tags.amenity === "drinking_water") + return "Point d'eau potable"; + if (tags.man_made === "water_well") return "Puits"; + if (tags.man_made === "wastewater_plant") + return "Station d'épuration"; + if (tags.power === "plant") return "Centrale électrique"; + if (tags.power === "substation") + return "Sous-station électrique"; + if (tags.power === "line") return "Ligne électrique"; + if (tags.highway === "bus_stop") return "Arrêt de bus"; + if (tags.railway === "station") return "Gare"; + if (tags.hazard === "flood") return "Zone inondable"; + return "Autre"; + }); - return translatedElements; - } catch (error) { - console.error( - "Error fetching structures and natural resources from Overpass API:", - error - ); - return []; - } - }; + return translatedElements; + } catch (error) { + console.error( + "Error fetching structures and natural resources from Overpass API:", + error + ); + return []; + } + }, + [latitude, longitude] + ); // Envoi des prédictions vers FastAPI - const sendPrediction = async () => { + const sendPrediction = useCallback(async () => { try { const fastapiUrl = config.url2; - if (!incident.photo) { + if (!incident?.photo) { console.error( "Incident photo is undefined, skipping prediction." ); @@ -476,7 +485,7 @@ export const IncidentData = () => { sensitive_structures: sensitiveStructures, incident_id: incidentId, user_id: userId, - zone: incident.zone, + zone: incident?.zone || "", latitude: latitude, longitude: longitude, }; @@ -484,7 +493,7 @@ export const IncidentData = () => { console.log("Payload being sent:", payload); // Envoi vers FastAPI - await axios.post(fastapiUrl, payload); + await axios.post(fastapiUrl, payload, { timeout: 120000 }); } catch (error) { console.error( "Error sending prediction (Axios error):", @@ -494,7 +503,14 @@ export const IncidentData = () => { error.response?.data?.detail || "Error during API call" ); } - }; + }, [ + incident, + latitude, + longitude, + incidentId, + userId, + fetchNearbySensitiveStructures, + ]); return { handleChangeStatus, diff --git a/src/views/Dashboard/analyze.jsx b/src/views/Dashboard/analyze.jsx index 43ed6dc..4b44dfe 100644 --- a/src/views/Dashboard/analyze.jsx +++ b/src/views/Dashboard/analyze.jsx @@ -1,6 +1,6 @@ // Analyze.jsx -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; import { Box, Button, @@ -66,7 +66,8 @@ export default function Analyze() { const [isLoadingContext, setIsLoadingContext] = useState(true); // State to track context loading const [predictionError, setPredictionError] = useState(null); // State to track prediction errors const predictionSentRef = useRef(false); // Ref to track if prediction has been sent - const refreshTimerRef = useRef(null); // Ref to store the refresh timer + const [isPolling, setIsPolling] = useState(false); // Add state for polling status + const pollingIntervalRef = useRef(null); // Ref to store polling interval ID const { isOpen, onOpen, onClose } = useDisclosure(); @@ -74,159 +75,233 @@ export default function Analyze() { setExpanded(!expanded); }; - // Function to handle page refresh - const refreshPage = () => { - console.log("Auto-refreshing page to check for prediction data..."); - window.location.reload(); - }; - - // Function to fetch predictions by incident ID - const fetchPredictionsByIncidentId = async (incidentId) => { + // Function to fetch predictions by incident ID (useCallback for stability) + const fetchPredictionsByIncidentId = useCallback(async (id) => { try { const response = await fetch( - `${config.url}/MapApi/Incidentprediction/${incidentId}` + `${config.url}/MapApi/Incidentprediction/${id}` ); if (!response.ok) { - throw new Error("Failed to fetch predictions"); + if (response.status === 404) { + console.log( + `Prediction for incident ${id} not found (404). Still polling.` + ); + return null; + } + throw new Error( + `Failed to fetch predictions (status: ${response.status})` + ); } const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { - const data = await response.json(); // Parse the JSON response - return data; // Return the prediction data + const data = await response.json(); + const predictionExists = + (Array.isArray(data) && data.length > 0) || + (typeof data === "object" && + data !== null && + Object.keys(data).length > 0); + if (predictionExists) { + console.log(`Prediction found for incident ${id}:`, data); + return Array.isArray(data) ? data[0] : data; + } else { + console.log( + `Prediction for incident ${id} exists but is empty. Still polling.` + ); + return null; + } } else { - throw new Error("Received non-JSON response"); + console.warn( + `Received non-JSON response when fetching prediction for incident ${id}` + ); + return null; } } catch (error) { - console.error("Error fetching predictions:", error); - return null; // Return null if there is an error + console.error( + `Error fetching prediction for incident ${id}:`, + error + ); + return null; } - }; + }, []); // Add this function to determine if we should show the report const shouldShowReport = () => { return type_incident !== "Aucun problème environnemental"; }; - // Modify the useEffect for fetching predictions + // Modify the useEffect for fetching/sending predictions useEffect(() => { + let isMounted = true; + const fetchData = async () => { + if (!incidentId || !shouldShowReport()) { + if (isMounted) setIsLoadingContext(false); + return; + } + try { - // Only fetch prediction if we should show the report - if (shouldShowReport()) { - const existingPrediction = await fetchPredictionsByIncidentId( - incidentId + console.log(`Checking prediction for incident: ${incidentId}`); + const existingPrediction = await fetchPredictionsByIncidentId( + incidentId + ); + + if (existingPrediction && isMounted) { + console.log( + "Setting existing prediction:", + existingPrediction ); + setPrediction(existingPrediction); + setPredictionError(null); + setIsLoadingContext(false); + setIsPolling(false); + predictionSentRef.current = true; + } else if (imgUrl && !predictionSentRef.current && isMounted) { + console.log( + `No prediction found for ${incidentId}, attempting to send.` + ); + predictionSentRef.current = true; - console.log("Existing prediction:", existingPrediction); - - const predictionExists = - (Array.isArray(existingPrediction) && - existingPrediction.length > 0) || - (typeof existingPrediction === "object" && - existingPrediction !== null && - Object.keys(existingPrediction).length > 0); - - if (predictionExists) { - const validPrediction = Array.isArray( - existingPrediction - ) - ? existingPrediction[0] - : existingPrediction; - setPrediction(validPrediction); - setPredictionError(null); // Clear any previous errors - - // Clear any pending refresh timer - if (refreshTimerRef.current) { - clearTimeout(refreshTimerRef.current); - refreshTimerRef.current = null; - } - } else if ( - imgUrl && - !predictionSentRef.current && - incidentId - ) { - // Only try to send prediction once and only if we have both an image URL and an incident ID - try { - await sendPrediction(); - predictionSentRef.current = true; - - // Set a refresh timer to check for new prediction data after 30 seconds - refreshTimerRef.current = setTimeout( - refreshPage, - 30000 - ); - } catch (error) { - console.error("Failed to send prediction:", error); - // Set error state with meaningful message + try { + await sendPrediction(); + console.log( + `Prediction request sent for incident ${incidentId}. Starting polling.` + ); + if (isMounted) setIsPolling(true); + } catch (error) { + console.error("Failed to send prediction:", error); + if (isMounted) { setPredictionError( "Échec de l'envoi de la prédiction. Problème de connexion au serveur d'analyse." ); - // Mark as sent even on error to prevent retries - predictionSentRef.current = true; - } - } else if ( - Array.isArray(existingPrediction) && - existingPrediction.length === 0 - ) { - // If the API returned an empty array and we're not in the process of sending a prediction, - // set a refresh timer to check again after 30 seconds - if ( - !refreshTimerRef.current && - !predictionSentRef.current - ) { - console.log( - "No predictions found, setting refresh timer..." - ); - refreshTimerRef.current = setTimeout( - refreshPage, - 30000 - ); + setIsLoadingContext(false); } } + } else if ( + !existingPrediction && + predictionSentRef.current && + isMounted + ) { + console.log( + `Prediction sent for ${incidentId}, but no data yet. Ensuring polling is active.` + ); + if (!isPolling) setIsPolling(true); + } else if (!imgUrl && isMounted) { + console.log("Image URL not available yet."); + } else { + if (isMounted) setIsLoadingContext(false); } - setIsLoadingContext(false); } catch (error) { - console.error("Error fetching or sending prediction:", error); - setPredictionError( - "Erreur lors de la récupération des données d'analyse." + console.error("Error in main fetchData effect:", error); + if (isMounted) { + setPredictionError( + "Erreur lors de la récupération des données d'analyse." + ); + setIsLoadingContext(false); + } + } + }; + + fetchData(); + + return () => { + isMounted = false; + console.log( + "Unmounting analyze component or dependencies changed." + ); + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current); + pollingIntervalRef.current = null; + } + }; + }, [incidentId, imgUrl, sendPrediction, fetchPredictionsByIncidentId]); + + useEffect(() => { + let isMounted = true; + const maxPollingTime = 5 * 60 * 1000; + const pollingStartTime = Date.now(); + + const poll = async () => { + if (!incidentId) return; + + console.log(`Polling check for incident ${incidentId}...`); + + if (Date.now() - pollingStartTime > maxPollingTime) { + console.error( + `Polling timed out for incident ${incidentId} after 5 minutes.` ); + if (isMounted) { + setPredictionError( + "L'analyse prend plus de temps que prévu. Veuillez vérifier à nouveau plus tard." + ); + setIsLoadingContext(false); + setIsPolling(false); + } + return; + } + + const fetchedPrediction = await fetchPredictionsByIncidentId( + incidentId + ); + + if (fetchedPrediction && isMounted) { + console.log(`Polling successful for incident ${incidentId}.`); + setPrediction(fetchedPrediction); + setPredictionError(null); setIsLoadingContext(false); - // Mark as sent on error to prevent infinite retries - predictionSentRef.current = true; + setIsPolling(false); + } else { + console.log( + `Prediction still not available for ${incidentId}. Will poll again.` + ); } }; - // Only run fetchData if predictionSentRef is false - if (!predictionSentRef.current) { - fetchData(); + if (isPolling && incidentId) { + console.log( + `Starting polling interval for incident ${incidentId}.` + ); + if (pollingIntervalRef.current) { + clearInterval(pollingIntervalRef.current); + } + pollingIntervalRef.current = setInterval(poll, 15000); + + poll(); + } else { + if (pollingIntervalRef.current) { + console.log( + `Stopping polling interval for incident ${incidentId}.` + ); + clearInterval(pollingIntervalRef.current); + pollingIntervalRef.current = null; + } } - // Cleanup function to clear timer on unmount return () => { - if (refreshTimerRef.current) { - clearTimeout(refreshTimerRef.current); + isMounted = false; + if (pollingIntervalRef.current) { + console.log( + `Clearing polling interval on effect cleanup for incident ${incidentId}.` + ); + clearInterval(pollingIntervalRef.current); + pollingIntervalRef.current = null; } }; - }, [incident, incidentId, imgUrl]); + }, [isPolling, incidentId, fetchPredictionsByIncidentId]); - // Modified console logging to avoid repetitive messages useEffect(() => { if (prediction) { if (prediction.ndvi_heatmap) { console.log("NDVI Heatmap URL:", prediction.ndvi_heatmap); } else { - // Log only once console.log( "Prediction found but ndvi_heatmap is not available." ); } } - // No else case to avoid repetitive logs }, [prediction]); - // Create a custom marker icon based on the incident state const iconHTML = ReactDOMServer.renderToString( { @@ -258,10 +332,8 @@ export default function Analyze() { h2: (props) => , h3: (props) => , p: (props) => , - // Add more custom components as needed }; - // Settings for the carousel const sliderSettings = { dots: true, infinite: true, @@ -277,7 +349,6 @@ export default function Analyze() { templateRows={{ lg: "repeat(2, auto)" }} gap="20px" > - {/* Card for the Incident Report */} - L'analyse est en cours et le rapport - sera fourni dans quelques instants... + {predictionError + ? "Erreur d'Analyse" + : "L'analyse est en cours et le rapport sera fourni dans quelques instants..."} - - - - + {predictionError ? ( + + {predictionError} + + ) : ( + + + + )} + {!predictionError && } ) : type_incident === "Aucun problème environnemental" ? ( @@ -338,7 +416,6 @@ export default function Analyze() { ) : ( - // Existing report display code Rapport d'Analyse @@ -367,11 +444,6 @@ export default function Analyze() { Erreur d'analyse {predictionError} - - Veuillez réessayer plus tard - ou contacter le support - technique. - ) : expanded ? ( <> @@ -393,7 +465,6 @@ export default function Analyze() { ) : ( - // Show a snippet of the context with an option to expand @@ -500,7 +571,6 @@ export default function Analyze() { - {/* Card for Interactive Map */} - {/* Card for Incident Image */}