'use client'; import { useState, useRef, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import Image from 'next/image'; import dynamic from 'next/dynamic'; import 'leaflet/dist/leaflet.css'; import { ArrowLeft, MapPin, Camera, X, Home, Building, Bed, Bath, Square, DollarSign, Calendar, Clock, CheckCircle, AlertCircle, Info, ChevronRight, ChevronLeft, Loader2, Upload, FileText, Shield, HelpCircle, Search, Navigation, Wifi, Zap, Flame, Droplets, Cigarette, Dog, Music, Star, Sofa, DoorOpen, Warehouse, Layers, Plus, Minus, Save, Wind, Move, Trees } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; import { addRentProperty, addSaleProperty, getCurrencies, uploadPicture } from '../../../utils/api'; import { BuildingType, RentPropertyCondition, RentPropertyType, RentType, PropertyService, PropertyServiceLabels, PropertyServicesList, PropertyTerm, PropertyTermLabels, PropertyTermsList, Currency, CurrencyLabels } from '../../../enums'; const MapContainer = dynamic(() => import('react-leaflet').then(mod => mod.MapContainer), { ssr: false }); const TileLayer = dynamic(() => import('react-leaflet').then(mod => mod.TileLayer), { ssr: false }); const Marker = dynamic(() => import('react-leaflet').then(mod => mod.Marker), { ssr: false }); const Popup = dynamic(() => import('react-leaflet').then(mod => mod.Popup), { ssr: false }); import { useMapEvents } from 'react-leaflet'; function MapClickHandler({ onMapClick }) { const map = useMapEvents({ click: (e) => { const { lat, lng } = e.latlng; onMapClick([lat, lng]); }, }); return null; } export default function AddPropertyPage() { const router = useRouter(); const searchParams = useSearchParams(); const purpose = searchParams.get('purpose') || 'rent'; const [step, setStep] = useState(1); const totalSteps = purpose === 'sale' ? 4 : 4; const [formData, setFormData] = useState({ propertyType: 'apartment', furnished: false, bedrooms: 1, bathrooms: 1, livingRooms: 1, floorNumber: '', salons: '', balconies: '', space: '', services: { [PropertyService.ELECTRICITY]: false, [PropertyService.INTERNET]: false, [PropertyService.HEATING]: false, [PropertyService.WATER]: false, [PropertyService.CENTRAL_AIR_CONDITIONING]: false, [PropertyService.PARKING]: false, [PropertyService.ELEVATOR]: false }, serviceDetails: {}, terms: { [PropertyTerm.NO_SMOKING]: false, [PropertyTerm.NO_ANIMALS]: false, [PropertyTerm.NO_PARTIES]: false }, offerType: 'daily', dailyPrice: '', monthlyPrice: '', deposit: '', city: '', district: '', address: '', lat: null, lng: null, description: '', images: [], nearbySchool: '', nearbyHospital: '', nearbyRestaurant: '', nearbyUniversity: '', nearbyPark: '', nearbyMall: '', roomAreaType: 'Private room', roomPeopleAllowed: '', roomFurniture: '', roomEntrance: 'Shared entrance', roomBathroom: 'Shared', roomKitchen: 'Not available', roomRestrictedAreas: false, roomResidents: '', roomGender: 'Family', roomLanguage: '', roomChildren: false, roomPets: false, roomDedicatedTo: 'Everyone', roomVisitors: true, roomQuietTimes: false, roomQuietTimesDetails: '', }); const [imagePreviews, setImagePreviews] = useState([]); const [uploadedImagePaths, setUploadedImagePaths] = useState([]); const [customTerms, setCustomTerms] = useState([]); const [customTermInput, setCustomTermInput] = useState(''); const [selectedLocation, setSelectedLocation] = useState(null); const [mapCenter, setMapCenter] = useState([33.5138, 36.2765]); const [mapZoom, setMapZoom] = useState(13); const [searchQuery, setSearchQuery] = useState(''); const [mapLoaded, setMapLoaded] = useState(false); const [currencies, setCurrencies] = useState([]); const [selectedCurrencyId, setSelectedCurrencyId] = useState(Currency.SYP); const [errors, setErrors] = useState({}); const [isLoading, setIsLoading] = useState(false); const fileInputRef = useRef(null); const propertyTypes = [ { id: 'apartment', label: 'شقة', icon: Building }, { id: 'villa', label: 'فيلا', icon: Home }, { id: 'sweet', label: 'سويت', icon: Sofa }, { id: 'room', label: 'غرفة', icon: DoorOpen }, { id: 'studio', label: 'استوديو', icon: Sofa }, { id: 'office', label: 'مكتب', icon: Building }, { id: 'farms', label: 'مزرعة', icon: Trees }, { id: 'shop', label: 'متجر', icon: Warehouse }, { id: 'warehouse', label: 'مستودع', icon: Warehouse }, ]; const serviceList = [ { id: PropertyService.ELECTRICITY, label: PropertyServiceLabels[PropertyService.ELECTRICITY], icon: Zap }, { id: PropertyService.INTERNET, label: PropertyServiceLabels[PropertyService.INTERNET], icon: Wifi }, { id: PropertyService.HEATING, label: PropertyServiceLabels[PropertyService.HEATING], icon: Flame }, { id: PropertyService.WATER, label: PropertyServiceLabels[PropertyService.WATER], icon: Droplets }, { id: PropertyService.CENTRAL_AIR_CONDITIONING, label: PropertyServiceLabels[PropertyService.CENTRAL_AIR_CONDITIONING], icon: Wind }, { id: PropertyService.PARKING, label: PropertyServiceLabels[PropertyService.PARKING], icon: Warehouse }, { id: PropertyService.ELEVATOR, label: PropertyServiceLabels[PropertyService.ELEVATOR], icon: Layers }, ]; const termsList = [ { id: PropertyTerm.NO_SMOKING, label: PropertyTermLabels[PropertyTerm.NO_SMOKING], icon: Cigarette }, { id: PropertyTerm.NO_ANIMALS, label: PropertyTermLabels[PropertyTerm.NO_ANIMALS], icon: Dog }, { id: PropertyTerm.NO_PARTIES, label: PropertyTermLabels[PropertyTerm.NO_PARTIES], icon: Music }, ]; const offerTypes = [ { id: 'daily', label: 'إيجار يومي', icon: Clock }, { id: 'monthly', label: 'إيجار شهري', icon: Calendar }, { id: 'both', label: 'إيجار يومي وشهري', icon: Calendar }, ].filter(Boolean); useEffect(() => { if (typeof window !== 'undefined') { const L = require('leaflet'); 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', }); } setMapLoaded(true); // Fetch available currencies getCurrencies().then((data) => { if (Array.isArray(data) && data.length > 0) { setCurrencies(data); console.log('[AddProperty] Currencies loaded:', data); } }).catch((err) => { console.warn('[AddProperty] Failed to load currencies:', err); }); }, []); const handleSearch = async () => { if (!searchQuery) return; toast.loading('جاري البحث...', { id: 'search' }); try { const response = await fetch( `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery)}&limit=1&accept-language=ar` ); const data = await response.json(); if (data && data.length > 0) { const result = data[0]; const lat = parseFloat(result.lat); const lng = parseFloat(result.lon); setMapCenter([lat, lng]); setMapZoom(18); const addressResponse = await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar` ); const addressData = await addressResponse.json(); setSelectedLocation({ lat: lat, lng: lng, address: addressData.display_name || result.display_name }); toast.success('تم العثور على الموقع', { id: 'search' }); } else { toast.error('لم يتم العثور على العنوان', { id: 'search' }); } } catch (error) { console.error('خطأ في البحث:', error); toast.error('حدث خطأ في البحث', { id: 'search' }); } }; const handleGeolocation = () => { if (!navigator.geolocation) { toast.error('المتصفح لا يدعم تحديد الموقع'); return; } toast.loading('جاري تحديد موقعك...', { id: 'geolocation' }); navigator.geolocation.getCurrentPosition( async (position) => { const { latitude, longitude } = position.coords; setMapCenter([latitude, longitude]); setMapZoom(18); try { const response = await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar` ); const data = await response.json(); setSelectedLocation({ lat: latitude, lng: longitude, address: data.display_name || 'موقعك الحالي' }); toast.success('تم تحديد موقعك', { id: 'geolocation' }); } catch (error) { setSelectedLocation({ lat: latitude, lng: longitude, address: 'موقعك الحالي' }); toast.success('تم تحديد موقعك', { id: 'geolocation' }); } }, (error) => { toast.error('فشل في تحديد الموقع', { id: 'geolocation' }); } ); }; const handleMapClick = async (coords) => { try { const [lat, lng] = coords; toast.loading('جاري تحديد الموقع...', { id: 'location' }); const response = await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar` ); const data = await response.json(); setSelectedLocation({ lat: lat, lng: lng, address: data.display_name || 'موقع محدد' }); setMapZoom(18); toast.success('تم تحديد الموقع بنجاح!', { id: 'location' }); } catch (error) { console.error('خطأ في تحديد الموقع:', error); const [lat, lng] = coords; setSelectedLocation({ lat: lat, lng: lng, address: 'موقع محدد' }); setMapZoom(18); toast.success('تم تحديد الموقع', { id: 'location' }); } }; const confirmLocation = () => { if (selectedLocation) { setFormData({ ...formData, lat: selectedLocation.lat, lng: selectedLocation.lng, address: selectedLocation.address }); toast.success('تم تأكيد الموقع بنجاح'); } }; const resetLocation = () => { setSelectedLocation(null); setFormData({ ...formData, lat: null, lng: null, address: '' }); setMapZoom(15); toast.info('تم إلغاء تحديد الموقع'); }; const handleImageUpload = async (files) => { const newImages = Array.from(files); console.log('[AddProperty] handleImageUpload called with', newImages.length, 'files'); if (formData.images.length + newImages.length > 10) { toast.error('يمكنك رفع 10 صور كحد أقصى'); return; } for (const file of newImages) { if (!file.type.startsWith('image/')) { toast.error('الرجاء اختيار صور صالحة فقط'); continue; } if (file.size > 5 * 1024 * 1024) { toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت'); continue; } // Show preview const reader = new FileReader(); reader.onloadend = () => { setImagePreviews(prev => [...prev, reader.result]); }; reader.readAsDataURL(file); setFormData(prev => ({ ...prev, images: [...prev.images, file] })); // Upload to server immediately try { const path = await uploadPicture(file); setUploadedImagePaths(prev => [...prev, path]); console.log('[AddProperty] Image uploaded:', path); } catch (err) { console.error('[AddProperty] Image upload failed:', err); toast.error('فشل رفع الصورة: ' + file.name); } } }; const removeImage = (index) => { const newImages = [...formData.images]; newImages.splice(index, 1); const newPreviews = [...imagePreviews]; newPreviews.splice(index, 1); const newPaths = [...uploadedImagePaths]; newPaths.splice(index, 1); setFormData(prev => ({ ...prev, images: newImages })); setImagePreviews(newPreviews); setUploadedImagePaths(newPaths); }; const toggleService = (serviceId) => { setFormData(prev => { const services = { ...prev.services }; services[serviceId] = !services[serviceId]; return { ...prev, services }; }); }; const updateServiceDetail = (serviceId, value) => { setFormData(prev => ({ ...prev, serviceDetails: { ...prev.serviceDetails, [serviceId]: value } })); }; const toggleTerm = (termId) => { setFormData(prev => { const terms = { ...prev.terms }; terms[termId] = !terms[termId]; return { ...prev, terms }; }); }; const addCustomTerm = () => { const val = customTermInput.trim(); if (!val) return; if (customTerms.includes(val)) return; setCustomTerms(prev => [...prev, val]); setCustomTermInput(''); }; const removeCustomTerm = (term) => { setCustomTerms(prev => prev.filter(t => t !== term)); }; const incrementBedrooms = () => { setFormData({ ...formData, bedrooms: formData.bedrooms + 1 }); }; const decrementBedrooms = () => { if (formData.bedrooms > 1) { setFormData({ ...formData, bedrooms: formData.bedrooms - 1 }); } }; const incrementBathrooms = () => { setFormData({ ...formData, bathrooms: formData.bathrooms + 1 }); }; const decrementBathrooms = () => { if (formData.bathrooms > 1) { setFormData({ ...formData, bathrooms: formData.bathrooms - 1 }); } }; const incrementLivingRooms = () => { setFormData({ ...formData, livingRooms: formData.livingRooms + 1 }); }; const decrementLivingRooms = () => { if (formData.livingRooms > 1) { setFormData({ ...formData, livingRooms: formData.livingRooms - 1 }); } }; const validateStep = () => { const newErrors = {}; switch(step) { case 1: if (!formData.propertyType) { newErrors.propertyType = 'نوع العقار مطلوب'; } break; case 2: if (!formData.bedrooms) { newErrors.bedrooms = 'عدد الغرف مطلوب'; } if (!formData.bathrooms) { newErrors.bathrooms = 'عدد الحمامات مطلوب'; } if (!formData.livingRooms) { newErrors.livingRooms = 'عدد الصالونات مطلوب'; } break; case 3: if (purpose === 'sale') { if (!formData.salePrice) newErrors.salePrice = 'سعر البيع مطلوب'; } else { if (formData.offerType === 'daily' && !formData.dailyPrice) { newErrors.dailyPrice = 'السعر اليومي مطلوب'; } if (formData.offerType === 'monthly' && !formData.monthlyPrice) { newErrors.monthlyPrice = 'السعر الشهري مطلوب'; } if (formData.offerType === 'both') { if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب'; if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب'; } } break; case 4: if (!formData.lat || !formData.lng) { newErrors.location = 'الرجاء تحديد موقع العقار على الخريطة'; } if (formData.images.length === 0) { newErrors.images = 'يجب رفع صورة واحدة على الأقل'; } break; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleNext = () => { if (validateStep()) { setStep(step + 1); window.scrollTo({ top: 0, behavior: 'smooth' }); } }; const handleBack = () => { setStep(step - 1); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleSubmit = async () => { if (!validateStep()) return; setIsLoading(true); // Map UI property type to API BuildingType enum const buildingTypeMap = { apartment: BuildingType.APARTMENT, villa: BuildingType.VILLA, sweet: BuildingType.SWEET, suite: BuildingType.SWEET, room: BuildingType.ROOM, studio: BuildingType.STUDIO, office: BuildingType.OFFICE, farms: BuildingType.FARMS, shop: BuildingType.SHOP, warehouse: BuildingType.WAREHOUSE }; const selectedServices = Object.entries(formData.services) .filter(([, v]) => v) .map(([k]) => k); const selectedTerms = Object.entries(formData.terms) .filter(([, v]) => v) .map(([k]) => k); const allTerms = [...new Set([...selectedTerms, ...customTerms])]; const details = { description: formData.description || '', services: selectedServices, serviceDetails: selectedServices.reduce((acc, s) => ({ ...acc, [s]: formData.serviceDetails[s] || 'in general' }), {}), terms: allTerms.reduce((acc, k) => ({ ...acc, [k]: true }), {}), displayType: formData.offerType === 'both' ? 'Both' : formData.offerType === 'daily' ? 'Daily' : 'Monthly', propertyCondition: formData.furnished ? 'WithFurniture' : 'WithoutFurniture', floorNumber: parseInt(formData.floorNumber) || 0, numberOfSalons: parseInt(formData.salons) || 0, numberOfBalconies: parseInt(formData.balconies) || 0, nearbyDistances: { school: formData.nearbySchool || '', hospital: formData.nearbyHospital || '', restaurant: formData.nearbyRestaurant || '', university: formData.nearbyUniversity || '', park: formData.nearbyPark || '', mall: formData.nearbyMall || '', }, }; if (formData.propertyType === 'room') { details.room = { areaType: formData.roomAreaType || 'Private room', peopleAllowed: formData.roomPeopleAllowed || String(formData.bedrooms), furnitureDetails: formData.roomFurniture || '', entranceType: formData.roomEntrance || 'Shared entrance', bathroomType: formData.roomBathroom || 'Shared', kitchenType: formData.roomKitchen || 'Not available', hasRestrictedOwnerAreas: formData.roomRestrictedAreas || false, homeResidentsCount: formData.roomResidents || '', currentPopulationGender: formData.roomGender || 'Family', languageDialect: formData.roomLanguage || '', hasChildren: formData.roomChildren || false, hasPets: formData.roomPets || false, dedicatedTo: formData.roomDedicatedTo || 'Everyone', visitorsAllowed: formData.roomVisitors ?? true, quietTimesEnabled: formData.roomQuietTimes ?? false, quietTimes: formData.roomQuietTimesDetails || '', }; } const detailsJSON = JSON.stringify(details); const propInfo = { CordsX: formData.lat ? String(formData.lat) : '', CordsY: formData.lng ? String(formData.lng) : '', Images: uploadedImagePaths, Address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(), Description: formData.description || '', NumberOfBathRooms: formData.bathrooms || 0, NumberOfRooms: formData.bedrooms || 0, NumberOfBedRooms: formData.bedrooms || 0, Space: parseFloat(formData.space) || 0, DetailsJSON: detailsJSON, BuildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT, Status: 0, PropertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE, }; try { if (purpose === 'sale') { const payload = { propInfo, price: parseFloat(formData.salePrice) || 0, currencyId: selectedCurrencyId, }; console.log('[AddProperty] Sale payload:', JSON.stringify(payload, null, 2)); const res = await addSaleProperty(payload); console.log('[AddProperty] Sale API response:', res); toast.success('تم إضافة عقار للبيع بنجاح!'); } else { const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY }; const rentBody = { PropertyInformation: propInfo, Deposit: parseFloat(formData.deposit) || 0, MonthlyRent: parseFloat(formData.monthlyPrice) || 0, DailyRent: parseFloat(formData.dailyPrice) || 0, Rating: 1, CurrencyId: selectedCurrencyId, RentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY, Type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED, AllowedPaymentPeriod: '01:00:00:00', }; const payload = { rentPropertyDto: rentBody }; console.log('[AddProperty] Rent payload:', JSON.stringify(payload, null, 2)); const res = await addRentProperty(payload); console.log('[AddProperty] Rent API response:', res); toast.success('تم إضافة عقار للإيجار بنجاح!'); } setTimeout(() => { router.push('/owner/properties'); }, 1500); } catch (err) { console.error('[AddProperty] API error:', err); toast.error(err.message || 'فشل في إضافة العقار'); } finally { setIsLoading(false); } }; const fadeInUp = { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.5 } }; return (
اختر نوع العقار والحالة
{errors.propertyType}
)}أدخل التفاصيل والخدمات المتاحة
{errors.bedrooms}
)}{errors.bathrooms}
)}{errors.livingRooms}
)}إضافة شرط مخصص
حدد سعر البيع والعملة
{errors.salePrice}
}اختر نوع العرض وحدد السعر المناسب
{errors.dailyPrice}
)}{errors.monthlyPrice}
)}حدد موقع العقار وأضف الصور
موقع العقار
{selectedLocation.address}
{formData.address}
{errors.location}
)}اضغط لرفع الصور
JPEG, PNG, JPG • حتى 5MB • 800x600 بكسل • حد أقصى 10 صور
{errors.images}
)} {imagePreviews.length > 0 && (