'use client'; import * as React from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import type { ListingDetail } from '@/lib/listings-api'; function formatPrice(priceVND: string): string { const num = Number(priceVND); if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)} tỷ`; if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(0)} tr`; return num.toLocaleString('vi-VN'); } interface ListingMapProps { listings: ListingDetail[]; onMarkerClick?: (listing: ListingDetail) => void; className?: string; } interface MapMarker { listing: ListingDetail; lat: number; lng: number; } export function ListingMap({ listings, onMarkerClick, className }: ListingMapProps) { const [selectedMarker, setSelectedMarker] = React.useState(null); const mapRef = React.useRef(null); // Parse listings with valid coordinates const markers = React.useMemo(() => { return listings .filter((l) => { // ListingDetail doesn't expose lat/lng directly, but the property might have it // For now we'll use a simple city-based mapping as fallback return true; }) .map((listing, index) => { // Generate approximate coordinates based on city/district for demo // In production, these would come from the API const cityCoords: Record = { 'Hồ Chí Minh': [10.8231, 106.6297], 'Hà Nội': [21.0285, 105.8542], 'Đà Nẵng': [16.0544, 108.2022], 'Nha Trang': [12.2388, 109.1967], 'Cần Thơ': [10.0452, 105.7469], }; const base = cityCoords[listing.property.city] || [10.8231, 106.6297]; // Add small random offset per listing for visual spread const seed = listing.id.charCodeAt(0) + index; const lat = base[0] + ((seed % 100) - 50) * 0.001; const lng = base[1] + ((seed % 73) - 36) * 0.001; return { listing, lat, lng }; }); }, [listings]); const handleMarkerClick = (marker: MapMarker) => { setSelectedMarker(marker); onMarkerClick?.(marker.listing); }; // CSS-based map visualization (no Mapbox dependency required) // Uses a relative coordinate system to position markers const bounds = React.useMemo(() => { if (markers.length === 0) return { minLat: 10, maxLat: 22, minLng: 102, maxLng: 110 }; const lats = markers.map((m) => m.lat); const lngs = markers.map((m) => m.lng); const padding = 0.01; return { minLat: Math.min(...lats) - padding, maxLat: Math.max(...lats) + padding, minLng: Math.min(...lngs) - padding, maxLng: Math.max(...lngs) + padding, }; }, [markers]); return (
{/* Grid lines for visual reference */}
{/* Markers */} {markers.map((marker) => { const x = ((marker.lng - bounds.minLng) / (bounds.maxLng - bounds.minLng)) * 100; const y = ((bounds.maxLat - marker.lat) / (bounds.maxLat - bounds.minLat)) * 100; const isSelected = selectedMarker?.listing.id === marker.listing.id; return ( ); })} {/* Selected marker popup */} {selectedMarker && (
{selectedMarker.listing.property.media.length > 0 && ( {selectedMarker.listing.property.title} )}

{formatPrice(selectedMarker.listing.priceVND)} VNĐ

{selectedMarker.listing.property.title}

{selectedMarker.listing.property.district}, {selectedMarker.listing.property.city}

{selectedMarker.listing.property.areaM2} m² {selectedMarker.listing.property.bedrooms != null && ( {selectedMarker.listing.property.bedrooms} PN )}
Xem chi tiết
)} {/* Map controls */}
{markers.length} bất động sản trên bản đồ
{/* Empty state */} {markers.length === 0 && (

Không có bất động sản để hiển thị trên bản đồ

)}
); }