'use client'; /* eslint-disable import-x/no-named-as-default-member */ import mapboxgl from 'mapbox-gl'; import * as React from 'react'; import 'mapbox-gl/dist/mapbox-gl.css'; import { type IndustrialParkListItem, PARK_STATUS_LABELS, PARK_STATUS_COLORS, } from '@/lib/khu-cong-nghiep-api'; import { useMapboxStyle } from '@/lib/mapbox-style'; interface ParkMapProps { parks: IndustrialParkListItem[]; className?: string; } const DEFAULT_CENTER: [number, number] = [106.6297, 10.8231]; // HCMC const DEFAULT_ZOOM = 6; export function ParkMap({ parks, className }: ParkMapProps) { const mapContainerRef = React.useRef(null); const mapRef = React.useRef(null); const markersRef = React.useRef([]); const mapStyle = useMapboxStyle(); const geoParks = React.useMemo( () => parks.filter((p) => p.latitude != null && p.longitude != null), [parks], ); React.useEffect(() => { if (!mapContainerRef.current) return; const token = process.env['NEXT_PUBLIC_MAPBOX_TOKEN']; if (!token) return; mapboxgl.accessToken = token; const map = new mapboxgl.Map({ container: mapContainerRef.current, style: mapStyle, center: DEFAULT_CENTER, zoom: DEFAULT_ZOOM, attributionControl: false, }); map.addControl(new mapboxgl.NavigationControl(), 'top-right'); map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right'); mapRef.current = map; return () => { map.remove(); mapRef.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); React.useEffect(() => { const map = mapRef.current; if (!map) return; map.setStyle(mapStyle); }, [mapStyle]); React.useEffect(() => { const map = mapRef.current; if (!map) return; markersRef.current.forEach((m) => m.remove()); markersRef.current = []; if (geoParks.length === 0) return; const bounds = new mapboxgl.LngLatBounds(); geoParks.forEach((park) => { // Mapbox owns `transform: translate(...)` on the marker element. // Apply hover scale to an inner wrapper so we don't clobber it. const el = document.createElement('div'); el.className = 'park-map-marker'; const inner = document.createElement('div'); inner.style.cssText = ` background: hsl(var(--card)); color: hsl(var(--card-foreground)); border-radius: 8px; padding: 4px 8px; font-size: 11px; font-weight: 600; box-shadow: 0 2px 6px rgba(0,0,0,0.3); white-space: nowrap; cursor: pointer; border-left: 3px solid hsl(var(--primary)); transition: transform 0.15s; transform: scale(1); max-width: 160px; overflow: hidden; text-overflow: ellipsis; `; inner.textContent = park.name; el.appendChild(inner); el.addEventListener('mouseenter', () => { inner.style.transform = 'scale(1.05)'; }); el.addEventListener('mouseleave', () => { inner.style.transform = 'scale(1)'; }); const statusLabel = PARK_STATUS_LABELS[park.status]; const statusColorClass = PARK_STATUS_COLORS[park.status]; const bgColor = statusColorClass.includes('green') ? '#dcfce7' : statusColorClass.includes('amber') ? '#fef3c7' : statusColorClass.includes('red') ? '#fee2e2' : '#dbeafe'; const textColor = statusColorClass.includes('green') ? '#166534' : statusColorClass.includes('amber') ? '#92400e' : statusColorClass.includes('red') ? '#991b1b' : '#1e40af'; const rentText = park.landRentUsdM2Year ? `$${park.landRentUsdM2Year}/m²/năm` : 'Liên hệ'; const popup = new mapboxgl.Popup({ offset: 15, maxWidth: '260px', closeButton: false }) .setHTML( `

${park.name}

${park.province} · ${park.totalAreaHa.toLocaleString()} ha

${statusLabel} ${rentText}

Lấp đầy: ${park.occupancyRate}% · ${park.tenantCount} DN

Xem chi tiết →
`, ); const marker = new mapboxgl.Marker({ element: el, anchor: 'left' }) .setLngLat([park.longitude, park.latitude]) .setPopup(popup) .addTo(map); markersRef.current.push(marker); bounds.extend([park.longitude, park.latitude]); }); if (geoParks.length > 1) { map.fitBounds(bounds, { padding: 60, maxZoom: 13 }); } else { map.flyTo({ center: [geoParks[0]!.longitude, geoParks[0]!.latitude], zoom: 14 }); } }, [geoParks]); const hasToken = typeof process !== 'undefined' && process.env['NEXT_PUBLIC_MAPBOX_TOKEN']; return (
{!hasToken && (

Thiết lập NEXT_PUBLIC_MAPBOX_TOKEN để hiển thị bản đồ

)}
{geoParks.length} KCN trên bản đồ
); }