"use client" import { BASE_BEARING, BASE_PITCH, BASE_ZOOM } from "@/app/_utils/const/map" import { createFillColorExpression, processDistrictFeature } from "@/app/_utils/map" import { IDistrictLayerProps } from "@/app/_utils/types/map" import { useEffect } from "react" export default function DistrictFillLineLayer({ visible = true, map, onClick, onDistrictClick, // Add the new prop year, month, filterCategory = "all", crimes = [], tilesetId, focusedDistrictId, setFocusedDistrictId, crimeDataByDistrict, showFill = true, activeControl, }: IDistrictLayerProps & { onDistrictClick?: (district: any) => void }) { // Extend the type inline useEffect(() => { if (!map || !visible) return const handleDistrictClick = (e: any) => { const incidentFeatures = map.queryRenderedFeatures(e.point, { layers: ["unclustered-point", "clusters"], }) if (incidentFeatures && incidentFeatures.length > 0) { return } if (!map || !e.features || e.features.length === 0) return const feature = e.features[0] const districtId = feature.properties.kode_kec // If clicking the same district, deselect it if (focusedDistrictId === districtId) { // Add null check for setFocusedDistrictId if (setFocusedDistrictId) { setFocusedDistrictId(null) } // Reset pitch and bearing with animation map.easeTo({ zoom: BASE_ZOOM, pitch: BASE_PITCH, bearing: BASE_BEARING, duration: 1500, easing: (t) => t * (2 - t), // easeOutQuad }) // Restore fill color for all districts when unfocusing const fillColorExpression = createFillColorExpression(null, crimeDataByDistrict) map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any) // Show all clusters again when unfocusing if (map.getLayer("clusters")) { map.setLayoutProperty("clusters", "visibility", "visible") } if (map.getLayer("unclustered-point")) { map.setLayoutProperty("unclustered-point", "visibility", "visible") } return } else if (focusedDistrictId) { // If we're already focusing on a district and clicking a different one, // we need to reset the current one and move to the new one if (setFocusedDistrictId) { setFocusedDistrictId(null) } // Wait a moment before selecting the new district to ensure clean transitions setTimeout(() => { const district = processDistrictFeature(feature, e, districtId, crimeDataByDistrict, crimes, year, month) if (!district || !setFocusedDistrictId) return setFocusedDistrictId(district.id) // Fly to the new district map.flyTo({ center: [district.longitude, district.latitude], zoom: 12.5, pitch: 75, bearing: 0, duration: 1500, easing: (t) => t * (2 - t), // easeOutQuad }) // Use onDistrictClick if available, otherwise fall back to onClick if (onDistrictClick) { onDistrictClick(district) } else if (onClick) { onClick(district) } }, 100) return } const district = processDistrictFeature(feature, e, districtId, crimeDataByDistrict, crimes, year, month) if (!district) return // Set the fill color expression immediately to show the focus const focusedFillColorExpression = createFillColorExpression(district.id, crimeDataByDistrict) map.setPaintProperty("district-fill", "fill-color", focusedFillColorExpression as any) // Add null check for setFocusedDistrictId if (setFocusedDistrictId) { setFocusedDistrictId(district.id) } // Hide clusters when focusing on a district if (map.getLayer("clusters")) { map.setLayoutProperty("clusters", "visibility", "none") } if (map.getLayer("unclustered-point")) { map.setLayoutProperty("unclustered-point", "visibility", "none") } // Animate to a pitched view focused on the district map.flyTo({ center: [district.longitude, district.latitude], zoom: 12.5, pitch: 75, bearing: 0, duration: 1500, easing: (t) => t * (2 - t), // easeOutQuad }) // Use onDistrictClick if available, otherwise fall back to onClick if (onDistrictClick) { onDistrictClick(district) } else if (onClick) { onClick(district) } } const onStyleLoad = () => { if (!map) return try { if (!map.getSource("districts")) { const layers = map.getStyle().layers let firstSymbolId: string | undefined for (const layer of layers) { if (layer.type === "symbol") { firstSymbolId = layer.id break } } map.addSource("districts", { type: "vector", url: `mapbox://${tilesetId}`, }) const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict) // Determine fill opacity based on active control const fillOpacity = getFillOpacity(activeControl, showFill); if (!map.getLayer("district-fill")) { map.addLayer( { id: "district-fill", type: "fill", source: "districts", "source-layer": "Districts", paint: { "fill-color": fillColorExpression as any, "fill-opacity": fillOpacity, }, }, firstSymbolId, ) } if (!map.getLayer("district-line")) { map.addLayer( { id: "district-line", type: "line", source: "districts", "source-layer": "Districts", paint: { "line-color": "#ffffff", "line-width": 1, "line-opacity": 0.5, }, }, firstSymbolId, ) } map.on("mouseenter", "district-fill", () => { map.getCanvas().style.cursor = "pointer" }) map.on("mouseleave", "district-fill", () => { map.getCanvas().style.cursor = "" }) map.off("click", "district-fill", handleDistrictClick) map.on("click", "district-fill", handleDistrictClick) } else { if (map.getLayer("district-fill")) { const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict) map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any) // Update fill opacity when active control changes const fillOpacity = getFillOpacity(activeControl, showFill); map.setPaintProperty("district-fill", "fill-opacity", fillOpacity); } } } catch (error) { console.error("Error adding district layers:", error) } } if (map.isStyleLoaded()) { onStyleLoad() } else { map.once("style.load", onStyleLoad) } return () => { if (map) { map.off("click", "district-fill", handleDistrictClick) } } }, [ map, visible, tilesetId, crimes, filterCategory, year, month, focusedDistrictId, crimeDataByDistrict, onClick, onDistrictClick, // Add to dependency array setFocusedDistrictId, showFill, activeControl, ]) // Add an effect to update the fill color and opacity whenever relevant props change useEffect(() => { if (!map || !map.getLayer("district-fill")) return; try { const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict) map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any) // Update fill opacity when active control changes const fillOpacity = getFillOpacity(activeControl, showFill); map.setPaintProperty("district-fill", "fill-opacity", fillOpacity); } catch (error) { console.error("Error updating district fill colors or opacity:", error) } }, [map, focusedDistrictId, crimeDataByDistrict, activeControl, showFill]) return null } // Helper function to determine fill opacity based on active control function getFillOpacity(activeControl?: string, showFill?: boolean): number { if (!showFill) return 0; // Full opacity for incidents and clusters if (activeControl === "incidents" || activeControl === "clusters") { return 0.6; } // Low opacity for timeline to show markers but still see district boundaries if (activeControl === "timeline") { return 0.1; } // No fill for other controls, but keep boundaries visible return 0; }