Added map for home
This commit is contained in:
166
app/components/property/PropertyMap.js
Normal file
166
app/components/property/PropertyMap.js
Normal file
@ -0,0 +1,166 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { MapPin, DollarSign, X, Navigation } from 'lucide-react';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
delete L.Icon.Default.prototype._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
|
||||
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
||||
});
|
||||
|
||||
export default function PropertyMap({ properties, onPropertySelect }) {
|
||||
const [selectedProperty, setSelectedProperty] = useState(null);
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const mapRef = useRef(null);
|
||||
const mapInstanceRef = useRef(null);
|
||||
const markersRef = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapRef.current || mapInstanceRef.current) return;
|
||||
|
||||
const map = L.map(mapRef.current).setView([34.8021, 38.9968], 7);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
maxZoom: 19,
|
||||
}).addTo(map);
|
||||
|
||||
mapInstanceRef.current = map;
|
||||
setMapLoaded(true);
|
||||
|
||||
return () => {
|
||||
if (mapInstanceRef.current) {
|
||||
mapInstanceRef.current.remove();
|
||||
mapInstanceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapInstanceRef.current || !properties.length) return;
|
||||
markersRef.current.forEach(marker => marker.remove());
|
||||
markersRef.current = [];
|
||||
const customIcon = L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<div class="w-10 h-10 bg-amber-500 rounded-full flex items-center justify-center text-white font-bold shadow-lg border-2 border-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||
</div>`,
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 40],
|
||||
popupAnchor: [0, -40],
|
||||
});
|
||||
|
||||
properties.forEach(property => {
|
||||
if (property.location?.lat && property.location?.lng) {
|
||||
const marker = L.marker([property.location.lat, property.location.lng], { icon: customIcon })
|
||||
.addTo(mapInstanceRef.current)
|
||||
.on('click', () => {
|
||||
setSelectedProperty(property);
|
||||
onPropertySelect?.(property);
|
||||
});
|
||||
|
||||
marker.bindTooltip(property.title, {
|
||||
permanent: false,
|
||||
direction: 'top',
|
||||
offset: [0, -40],
|
||||
className: 'property-tooltip'
|
||||
});
|
||||
|
||||
markersRef.current.push(marker);
|
||||
}
|
||||
});
|
||||
|
||||
if (markersRef.current.length > 0) {
|
||||
const group = L.featureGroup(markersRef.current);
|
||||
mapInstanceRef.current.fitBounds(group.getBounds(), { padding: [50, 50] });
|
||||
}
|
||||
}, [properties, mapLoaded, onPropertySelect]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-[600px] rounded-xl overflow-hidden">
|
||||
<div ref={mapRef} className="w-full h-full z-0" />
|
||||
<AnimatePresence>
|
||||
{selectedProperty && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20, scale: 0.9 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 20, scale: 0.9 }}
|
||||
className="absolute bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-80 bg-white rounded-xl shadow-xl p-4 z-10 border border-gray-200"
|
||||
>
|
||||
<button
|
||||
onClick={() => setSelectedProperty(null)}
|
||||
className="absolute top-2 right-2 p-1 hover:bg-gray-100 rounded-full transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<h3 className="font-bold text-lg mb-2 text-gray-900">{selectedProperty.title}</h3>
|
||||
|
||||
<div className="flex items-center gap-1 text-gray-600 text-sm mb-3">
|
||||
<MapPin className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="line-clamp-1">{selectedProperty.location?.address || `${selectedProperty.location.city}، ${selectedProperty.location.district}`}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="bg-gray-50 p-2 rounded-lg text-center">
|
||||
<div className="text-xs text-gray-500">يومي</div>
|
||||
<div className="font-bold text-gray-900">
|
||||
{selectedProperty.priceDisplay?.daily?.toLocaleString() || selectedProperty.price?.toLocaleString()} ل.س
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-2 rounded-lg text-center">
|
||||
<div className="text-xs text-gray-500">شهري</div>
|
||||
<div className="font-bold text-gray-900">
|
||||
{selectedProperty.priceDisplay?.monthly?.toLocaleString() || (selectedProperty.price * 30)?.toLocaleString()} ل.س
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (selectedProperty.location?.lat && selectedProperty.location?.lng) {
|
||||
window.open(`https://www.openstreetmap.org/directions?from=&to=${selectedProperty.location.lat},${selectedProperty.location.lng}`, '_blank');
|
||||
}
|
||||
}}
|
||||
className="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center justify-center gap-2 text-sm"
|
||||
>
|
||||
<Navigation className="w-4 h-4" />
|
||||
الاتجاهات
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.location.href = `/property/${selectedProperty.id}`}
|
||||
className="flex-1 bg-gray-800 text-white py-2 rounded-lg hover:bg-gray-900 transition-colors text-sm"
|
||||
>
|
||||
عرض التفاصيل
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
mapInstanceRef.current?.setView([position.coords.latitude, position.coords.longitude], 13);
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error getting location:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="absolute top-4 right-4 bg-white p-3 rounded-full shadow-lg hover:bg-gray-50 transition-colors z-10 border border-gray-200"
|
||||
title="الموقع الحالي"
|
||||
>
|
||||
<Navigation className="w-5 h-5 text-gray-700" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user