"use client" import { useEffect, useCallback, useRef, useState } from "react" import type { IIncidentLogs } from "@/app/_utils/types/crimes" import { BASE_BEARING, BASE_DURATION, BASE_PITCH, ZOOM_3D } from "@/app/_utils/const/map" import IncidentLogsPopup from "../pop-up/incident-logs-popup" interface RecentIncidentsLayerProps { visible?: boolean map: any incidents?: IIncidentLogs[] } export default function RecentIncidentsLayer({ visible = false, map, incidents = [] }: RecentIncidentsLayerProps) { const isInteractingWithMarker = useRef(false) const animationFrameRef = useRef(null) const [selectedIncident, setSelectedIncident] = useState(null) // Filter incidents from the last 24 hours const recentIncidents = incidents.filter((incident) => { if (!incident.timestamp) return false const incidentDate = new Date(incident.timestamp) const now = new Date() const timeDiff = now.getTime() - incidentDate.getTime() // 86400000 = 24 hours in milliseconds return timeDiff <= 86400000 }) // Split incidents into very recent (2 hours) and regular recent const twoHoursInMs = 2 * 60 * 60 * 1000 // 2 hours in milliseconds const handleIncidentClick = useCallback( (e: any) => { if (!map) return const features = map.queryRenderedFeatures(e.point, { layers: ["recent-incidents"] }) if (!features || features.length === 0) return // Stop event propagation e.originalEvent.stopPropagation() e.preventDefault() isInteractingWithMarker.current = true const incident = features[0] if (!incident.properties) return e.originalEvent.stopPropagation() e.preventDefault() const incidentDetails = { id: incident.properties.id, description: incident.properties.description, status: incident.properties?.status || "Active", verified: incident.properties?.status, longitude: (incident.geometry as any).coordinates[0], latitude: (incident.geometry as any).coordinates[1], timestamp: new Date(incident.properties.timestamp || Date.now()), category: incident.properties.category, address: incident.properties.address, district: incident.properties.district, severity: incident.properties.severity, source: incident.properties.source, user_id: incident.properties.user_id, name: incident.properties.name, email: incident.properties.email, phone: incident.properties.telephone, avatar: incident.properties.avatar, role_id: incident.properties.role_id, role: incident.properties.role, isVeryRecent: incident.properties.isVeryRecent, } // Fly to the incident location map.flyTo({ center: [incidentDetails.longitude, incidentDetails.latitude], zoom: ZOOM_3D, bearing: BASE_BEARING, pitch: BASE_PITCH, duration: BASE_DURATION, }) // Set selected incident for the popup setSelectedIncident(incidentDetails) // Reset the flag after a delay setTimeout(() => { isInteractingWithMarker.current = false }, 5000) }, [map], ) // Handle popup close const handleClosePopup = useCallback(() => { setSelectedIncident(null) }, []) useEffect(() => { if (!map || !visible) return // Convert incidents to GeoJSON with an additional property for recency const now = new Date().getTime() const recentData = { type: "FeatureCollection" as const, features: recentIncidents.map((incident) => { const timestamp = incident.timestamp ? new Date(incident.timestamp).getTime() : now const timeDiff = now - timestamp const isVeryRecent = timeDiff <= twoHoursInMs return { type: "Feature" as const, geometry: { type: "Point" as const, coordinates: [incident.longitude, incident.latitude], }, properties: { id: incident.id, role_id: incident.role_id, user_id: incident.user_id, name: incident.name, email: incident.email, telephone: incident.phone, avatar: incident.avatar, role: incident.role, address: incident.address, description: incident.description, timestamp: incident.timestamp ? incident.timestamp.toString() : new Date().toString(), category: incident.category, district: incident.district, severity: incident.severity, status: incident.verified, source: incident.source, isVeryRecent: isVeryRecent, // Add this property to identify very recent incidents timeDiff: timeDiff, // Time difference in milliseconds }, } }), } const setupLayerAndSource = () => { try { // Check if source exists and update it if (map.getSource("recent-incidents-source")) { ; (map.getSource("recent-incidents-source") as any).setData(recentData) } else { // If not, add source map.addSource("recent-incidents-source", { type: "geojson", data: recentData, }) } // Find first symbol layer for proper layering const layers = map.getStyle().layers let firstSymbolId: string | undefined for (const layer of layers) { if (layer.type === "symbol") { firstSymbolId = layer.id break } } // Add the pulsing glow layer for very recent incidents (2 hours or less) if (!map.getLayer("very-recent-incidents-pulse")) { map.addLayer( { id: "very-recent-incidents-pulse", type: "circle", source: "recent-incidents-source", filter: ["==", ["get", "isVeryRecent"], true], paint: { "circle-color": "#FF0000", "circle-radius": [ "interpolate", ["linear"], ["zoom"], 7, 8, 12, 16, 15, 24, ], "circle-opacity": 0.3, "circle-stroke-width": 2, "circle-stroke-color": "#FF0000", "circle-stroke-opacity": 0.5, }, layout: { visibility: visible ? "visible" : "none", }, }, firstSymbolId, ) } else { map.setLayoutProperty("very-recent-incidents-pulse", "visibility", visible ? "visible" : "none") } // Add regular recent incidents glow if (!map.getLayer("recent-incidents-glow")) { map.addLayer( { id: "recent-incidents-glow", type: "circle", source: "recent-incidents-source", paint: { "circle-color": "#FF5252", "circle-radius": ["interpolate", ["linear"], ["zoom"], 7, 6, 12, 12, 15, 18], "circle-opacity": 0.2, "circle-blur": 1, }, layout: { visibility: visible ? "visible" : "none", }, }, "very-recent-incidents-pulse", ) } else { map.setLayoutProperty("recent-incidents-glow", "visibility", visible ? "visible" : "none") } // Check if layer exists already for the main marker dots if (!map.getLayer("recent-incidents")) { map.addLayer( { id: "recent-incidents", type: "circle", source: "recent-incidents-source", paint: { "circle-color": [ "case", ["==", ["get", "isVeryRecent"], true], "#FF0000", // Bright red for very recent "#FF5252", // Standard red for older incidents ], "circle-radius": [ "interpolate", ["linear"], ["zoom"], 7, 4, 12, 8, 15, 12, ], "circle-stroke-width": 2, "circle-stroke-color": "#FFFFFF", "circle-opacity": 0.8, }, layout: { visibility: visible ? "visible" : "none", }, }, "recent-incidents-glow", ) // Add mouse events map.on("mouseenter", "recent-incidents", () => { map.getCanvas().style.cursor = "pointer" }) map.on("mouseleave", "recent-incidents", () => { map.getCanvas().style.cursor = "" }) } else { // Update existing layer visibility map.setLayoutProperty("recent-incidents", "visibility", visible ? "visible" : "none") } // Create animation for very recent incidents const animatePulse = () => { if (!map || !map.getLayer("very-recent-incidents-pulse")) { return } // Create a pulsing effect by changing the size and opacity const pulseSize = (Date.now() % 2000) / 2000 // Values from 0 to 1 every 2 seconds const pulseOpacity = 0.7 - pulseSize * 0.5 // Opacity oscillates between 0.2 and 0.7 const scaleFactor = 1 + pulseSize * 0.5 // Size oscillates between 1x and 1.5x map.setPaintProperty("very-recent-incidents-pulse", "circle-opacity", pulseOpacity) map.setPaintProperty("very-recent-incidents-pulse", "circle-radius", [ "interpolate", ["linear"], ["zoom"], 7, 8 * scaleFactor, 12, 16 * scaleFactor, 15, 24 * scaleFactor, ]) // Continue animation animationFrameRef.current = requestAnimationFrame(animatePulse) } // Start animation if visible if (visible) { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current) } animationFrameRef.current = requestAnimationFrame(animatePulse) } // Ensure click handler is properly registered map.off("click", "recent-incidents", handleIncidentClick) map.on("click", "recent-incidents", handleIncidentClick) } catch (error) { console.error("Error setting up recent incidents layer:", error) } } // Check if style is loaded and set up layer accordingly if (map.isStyleLoaded()) { setupLayerAndSource() } else { map.once("style.load", setupLayerAndSource) // Fallback setTimeout(() => { if (map.isStyleLoaded()) { setupLayerAndSource() } else { console.warn("Map style still not loaded after timeout") } }, 1000) } return () => { if (map) { map.off("click", "recent-incidents", handleIncidentClick) } if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current) } } }, [map, visible, recentIncidents, handleIncidentClick]) // Close popup when layer becomes invisible useEffect(() => { if (!visible) { setSelectedIncident(null) } }, [visible]) return ( <> {/* Popup component */} {selectedIncident && ( )} ) }