"use client" import { IUnclusteredPointLayerProps } from "@/app/_utils/types/map" import { useEffect, useCallback } from "react" export default function UnclusteredPointLayer({ visible = true, map, crimes = [], filterCategory = "all", focusedDistrictId, }: IUnclusteredPointLayerProps) { // Konversi crimes ke GeoJSON FeatureCollection const geojsonData = { type: "FeatureCollection" as const, features: crimes.flatMap((crime) => crime.crime_incidents .filter( (incident) => (filterCategory === "all" || incident.crime_categories.name === filterCategory) && incident.locations && typeof incident.locations.longitude === "number" && typeof incident.locations.latitude === "number" ) .map((incident) => ({ type: "Feature" as const, geometry: { type: "Point" as const, coordinates: [incident.locations.longitude, incident.locations.latitude], }, properties: { id: incident.id, district: crime.districts.name, category: incident.crime_categories.name, incidentType: incident.crime_categories.type || "", description: incident.description, status: incident.status || "", timestamp: incident.timestamp ? incident.timestamp.toString() : "", }, })) ), } const handleIncidentClick = useCallback( (e: any) => { if (!map) return const features = map.queryRenderedFeatures(e.point, { layers: ["unclustered-point"] }) if (!features || features.length === 0) return const incident = features[0] if (!incident.properties) return e.originalEvent.stopPropagation() e.preventDefault() const incidentDetails = { id: incident.properties.id, district: incident.properties.district, category: incident.properties.category, type: incident.properties.incidentType, description: incident.properties.description, status: incident.properties?.status || "Unknown", longitude: (incident.geometry as any).coordinates[0], latitude: (incident.geometry as any).coordinates[1], timestamp: new Date(incident.properties.timestamp || Date.now()), } console.log("Incident clicked:", incidentDetails) // Create a custom event with incident details const customEvent = new CustomEvent("incident_click", { detail: incidentDetails, bubbles: true, }) // Dispatch the event on both the map canvas and document to ensure it's captured if (map.getCanvas()) { map.getCanvas().dispatchEvent(customEvent) } document.dispatchEvent(customEvent) // Also trigger a fly-to event to zoom to the incident const flyToEvent = new CustomEvent("mapbox_fly_to", { detail: { longitude: incidentDetails.longitude, latitude: incidentDetails.latitude, zoom: 15, bearing: 0, pitch: 45, duration: 1000, }, bubbles: true, }) if (map.getCanvas()) { map.getCanvas().dispatchEvent(flyToEvent) } else { document.dispatchEvent(flyToEvent) } }, [map], ) useEffect(() => { if (!map || !visible) return const setupLayerAndSource = () => { try { // First check if source exists and update it if (map.getSource("crime-incidents")) { (map.getSource("crime-incidents") as any).setData(geojsonData); } else { // If not, add source map.addSource("crime-incidents", { type: "geojson", data: geojsonData, }); } // Get layers to find first symbol layer const layers = map.getStyle().layers; let firstSymbolId: string | undefined; for (const layer of layers) { if (layer.type === "symbol") { firstSymbolId = layer.id; break; } } // Check if layer exists if (!map.getLayer("unclustered-point")) { map.addLayer( { id: "unclustered-point", type: "circle", source: "crime-incidents", filter: ["!", ["has", "point_count"]], paint: { "circle-color": "#11b4da", "circle-radius": 8, "circle-stroke-width": 1, "circle-stroke-color": "#fff", }, layout: { visibility: focusedDistrictId ? "none" : "visible", }, }, firstSymbolId, ); map.on("mouseenter", "unclustered-point", () => { map.getCanvas().style.cursor = "pointer"; }); map.on("mouseleave", "unclustered-point", () => { map.getCanvas().style.cursor = ""; }); } else { // Update visibility based on focused district map.setLayoutProperty("unclustered-point", "visibility", focusedDistrictId ? "none" : "visible"); } // Always ensure click handler is properly registered map.off("click", "unclustered-point", handleIncidentClick); map.on("click", "unclustered-point", handleIncidentClick); } catch (error) { console.error("Error setting up unclustered point layer:", error); } }; // Check if style is loaded and set up layer accordingly if (map.isStyleLoaded()) { setupLayerAndSource(); } else { // Add event listener for style loading completion const onStyleLoad = () => { setupLayerAndSource(); }; map.once("style.load", onStyleLoad); // Also wait a bit and try again as a fallback setTimeout(() => { if (map.isStyleLoaded()) { setupLayerAndSource(); } }, 500); } return () => { if (map) { map.off("click", "unclustered-point", handleIncidentClick); } }; }, [map, visible, focusedDistrictId, handleIncidentClick, crimes, filterCategory, geojsonData]); return null; }