"use client"; import { useEffect, useState, useRef } from 'react'; import mapboxgl from 'mapbox-gl'; import { createRoot } from 'react-dom/client'; import { Clock, FileText, MapPin } from 'lucide-react'; import { Badge } from '@/app/_components/ui/badge'; import { formatDistanceToNow } from 'date-fns'; import { CustomAnimatedPopup } from '@/app/_utils/map/custom-animated-popup'; import { incident_logs } from '@prisma/client'; import { IIncidentLogs } from '@/app/_utils/types/crimes'; // export interface ICrimeIncident { // id: string; // category: string; // location: { // latitude: number; // longitude: number; // address: string; // district: string; // }; // timestamp: string; // description: string; // severity: 'high' | 'medium' | 'low'; // reportedBy: string; // } interface RecentCrimesLayerProps { map: mapboxgl.Map | null; incidents: IIncidentLogs[]; visible: boolean; onIncidentClick?: (incident: IIncidentLogs) => void; } export default function RecentCrimesLayer({ map, incidents, visible, onIncidentClick, }: RecentCrimesLayerProps) { const markersRef = useRef>(new Map()); // Filter incidents to only show those from the last 24 hours const recentIncidents = incidents.filter(incident => { const incidentDate = new Date(incident.timestamp); const now = new Date(); const timeDiff = now.getTime() - incidentDate.getTime(); const hoursDiff = timeDiff / (1000 * 60 * 60); return hoursDiff <= 24; }); // Create markers for each incident useEffect(() => { if (!map || !visible) { // Remove all markers if layer is not visible markersRef.current.forEach(marker => marker.remove()); return; } // Track existing incident IDs to avoid recreating markers const existingIds = new Set(Array.from(markersRef.current.keys())); // Add markers for each recent incident recentIncidents.forEach(incident => { existingIds.delete(incident.id); if (!markersRef.current.has(incident.id)) { // Create marker element const el = document.createElement('div'); el.className = 'crime-marker'; // Style based on severity const colors = { high: 'bg-red-500', medium: 'bg-amber-500', low: 'bg-blue-500' }; const markerRoot = createRoot(el); markerRoot.render(
); // Create popup content const popupEl = document.createElement('div'); const popupRoot = createRoot(popupEl); popupRoot.render(
{incident.category} {formatDistanceToNow(new Date(incident.timestamp), { addSuffix: true })}

{incident.district}

{incident.address}

{incident.description}

ID: {incident.id.substring(0, 8)}...
); // Create popup const popup = new CustomAnimatedPopup({ closeButton: false, maxWidth: '300px', offset: 15 }).setDOMContent(popupEl); // Create and add marker const marker = new mapboxgl.Marker(el) .setLngLat([incident.longitude, incident.latitude]) .setPopup(popup) .addTo(map); // Add click handler for the entire marker el.addEventListener('click', () => { if (onIncidentClick) { onIncidentClick(incident); } }); markersRef.current.set(incident.id, marker); } }); // Remove any markers that are no longer in the recent incidents list existingIds.forEach(id => { const marker = markersRef.current.get(id); if (marker) { marker.remove(); markersRef.current.delete(id); } }); // Clean up on unmount return () => { markersRef.current.forEach(marker => marker.remove()); markersRef.current.clear(); }; }, [map, recentIncidents, visible, onIncidentClick]); return null; // This is a functional component with no visual rendering }