"use client" import { useEffect, useMemo, useRef, useState } from 'react' import { Layer, Source } from "react-map-gl/mapbox" import { ICrimes } from "@/app/_utils/types/crimes" import { IUnits } from "@/app/_utils/types/units" import mapboxgl from 'mapbox-gl' import { generateCategoryColorMap, getCategoryColor } from '@/app/_utils/colors' interface UnitsLayerProps { crimes: ICrimes[] units?: IUnits[] filterCategory: string | "all" visible?: boolean map?: mapboxgl.Map | null } export default function UnitsLayer({ crimes, units = [], filterCategory, visible = false, map }: UnitsLayerProps) { const [loadedUnits, setLoadedUnits] = useState([]) const loadedUnitsRef = useRef([]) // Use either provided units or loaded units const unitsData = useMemo(() => { return units.length > 0 ? units : (loadedUnits || []) }, [units, loadedUnits]) // Extract all unique crime categories for color generation const uniqueCategories = useMemo(() => { const categories = new Set(); crimes.forEach(crime => { crime.crime_incidents.forEach(incident => { if (incident.crime_categories?.name) { categories.add(incident.crime_categories.name); } }); }); return Array.from(categories); }, [crimes]); // Generate color map for all categories const categoryColorMap = useMemo(() => { return generateCategoryColorMap(uniqueCategories); }, [uniqueCategories]); // Process units data to GeoJSON format const unitsGeoJSON = useMemo(() => { return { type: "FeatureCollection" as const, features: unitsData.map(unit => ({ type: "Feature" as const, properties: { id: unit.code_unit, name: unit.name, address: unit.address, phone: unit.phone, type: unit.type, district: unit.districts?.name || "", district_id: unit.district_id, }, geometry: { type: "Point" as const, coordinates: [unit.longitude || 0, unit.latitude || 0] } })).filter(feature => feature.geometry.coordinates[0] !== 0 && feature.geometry.coordinates[1] !== 0 ) } }, [unitsData]) // Create lines between units and incidents within their districts const connectionLinesGeoJSON = useMemo(() => { if (!unitsData.length || !crimes.length) return { type: "FeatureCollection" as const, features: [] } // Map district IDs to their units const districtUnitsMap = new Map() unitsData.forEach(unit => { if (!unit.district_id || !unit.longitude || !unit.latitude) return if (!districtUnitsMap.has(unit.district_id)) { districtUnitsMap.set(unit.district_id, []) } districtUnitsMap.get(unit.district_id)!.push(unit) }) // Create lines from units to incidents in their district const lineFeatures: any[] = [] crimes.forEach(crime => { // Get all units in this district const districtUnits = districtUnitsMap.get(crime.district_id) || [] if (!districtUnits.length) return // For each incident in this district crime.crime_incidents.forEach(incident => { // Skip incidents without location data or filtered by category if ( !incident.locations?.latitude || !incident.locations?.longitude || (filterCategory !== "all" && incident.crime_categories.name !== filterCategory) ) return // Create a line from each unit in this district to this incident districtUnits.forEach(unit => { if (!unit.longitude || !unit.latitude) return lineFeatures.push({ type: "Feature" as const, properties: { unit_id: unit.code_unit, unit_name: unit.name, incident_id: incident.id, district_id: crime.district_id, district_name: crime.districts.name, category: incident.crime_categories.name, lineColor: categoryColorMap[incident.crime_categories.name] || '#22c55e', }, geometry: { type: "LineString" as const, coordinates: [ [unit.longitude, unit.latitude], [incident.locations.longitude, incident.locations.latitude] ] } }) }) }) }) return { type: "FeatureCollection" as const, features: lineFeatures } }, [unitsData, crimes, filterCategory, categoryColorMap]) // Map click handler code and the rest remains the same... useEffect(() => { if (!map || !visible) return const handleUnitClick = (e: mapboxgl.MapMouseEvent & { features?: mapboxgl.MapboxGeoJSONFeature[] }) => { if (!e.features || e.features.length === 0) return const feature = e.features[0] const properties = feature.properties if (!properties) return // Create a popup for the unit const popup = new mapboxgl.Popup() .setLngLat(feature.geometry.type === 'Point' ? (feature.geometry as any).coordinates as [number, number] : [0, 0]) // Fallback coordinates if not a Point geometry .setHTML(`

${properties.name}

${properties.type}

${properties.address || 'No address provided'}

Staff: ${properties.staff_count || 'N/A'}

Phone: ${properties.phone || 'N/A'}

District: ${properties.district || 'N/A'}

`) .addTo(map) // Highlight the connected lines for this unit if (map.getLayer('units-connection-lines')) { map.setFilter('units-connection-lines', [ '==', ['get', 'unit_id'], properties.id ]) } // When popup closes, reset the lines filter popup.on('close', () => { if (map.getLayer('units-connection-lines')) { map.setFilter('units-connection-lines', ['has', 'unit_id']) } }) } // Define event handlers that can be referenced for both adding and removing const handleMouseEnter = () => { map.getCanvas().style.cursor = 'pointer' } const handleMouseLeave = () => { map.getCanvas().style.cursor = '' } // Add click event for units-points layer if (map.getLayer('units-points')) { map.on('click', 'units-points', handleUnitClick) // Change cursor on hover map.on('mouseenter', 'units-points', handleMouseEnter) map.on('mouseleave', 'units-points', handleMouseLeave) } return () => { if (map.getLayer('units-points')) { map.off('click', 'units-points', handleUnitClick) map.off('mouseenter', 'units-points', handleMouseEnter) map.off('mouseleave', 'units-points', handleMouseLeave) } } }, [map, visible]) if (!visible) return null return ( <> {/* Units Points */} {/* Units Symbols */} {/* Connection Lines */} ) }