'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 { formatPrice } from '@/lib/currency'; import { PROJECT_STATUS_LABELS, type ProjectSummary, } from '@/lib/du-an-api'; interface ProjectMapProps { projects: ProjectSummary[]; className?: string; } const DEFAULT_CENTER: [number, number] = [106.6297, 10.8231]; // HCMC const DEFAULT_ZOOM = 12; export function ProjectMap({ projects, className }: ProjectMapProps) { const mapContainerRef = React.useRef(null); const mapRef = React.useRef(null); const markersRef = React.useRef([]); const geoProjects = React.useMemo( () => projects.filter((p) => p.latitude != null && p.longitude != null), [projects], ); 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: 'mapbox://styles/mapbox/streets-v12', 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; }; }, []); React.useEffect(() => { const map = mapRef.current; if (!map) return; markersRef.current.forEach((m) => m.remove()); markersRef.current = []; if (geoProjects.length === 0) return; const bounds = new mapboxgl.LngLatBounds(); geoProjects.forEach((project) => { const el = document.createElement('div'); el.className = 'project-map-marker'; el.style.cssText = ` background: white; border-radius: 8px; padding: 4px 8px; font-size: 11px; font-weight: 600; box-shadow: 0 2px 6px rgba(0,0,0,0.15); white-space: nowrap; cursor: pointer; border-left: 3px solid hsl(142.1, 76.2%, 36.3%); transition: transform 0.15s; max-width: 160px; overflow: hidden; text-overflow: ellipsis; `; el.textContent = project.name; el.addEventListener('mouseenter', () => { el.style.transform = 'scale(1.05)'; }); el.addEventListener('mouseleave', () => { el.style.transform = 'scale(1)'; }); const statusLabel = PROJECT_STATUS_LABELS[project.status]; const priceText = project.minPrice ? formatPrice(project.minPrice) : 'Liên hệ'; const popup = new mapboxgl.Popup({ offset: 15, maxWidth: '240px', closeButton: false }) .setHTML( `

${project.name}

${project.district}, ${project.city}

${statusLabel} ${priceText}

Xem chi tiết →
`, ); const marker = new mapboxgl.Marker({ element: el, anchor: 'left' }) .setLngLat([project.longitude!, project.latitude!]) .setPopup(popup) .addTo(map); markersRef.current.push(marker); bounds.extend([project.longitude!, project.latitude!]); }); if (geoProjects.length > 1) { map.fitBounds(bounds, { padding: 60, maxZoom: 15 }); } else { map.flyTo({ center: [geoProjects[0]!.longitude!, geoProjects[0]!.latitude!], zoom: 14 }); } }, [geoProjects]); 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 đồ

)}
{geoProjects.length} dự án trên bản đồ
); }