Edit admin page

Edit home image
Added properties page
This commit is contained in:
Rahaf
2026-02-15 01:53:37 +03:00
parent 61c16f6cec
commit 6d81ff56a8
19 changed files with 4200 additions and 828 deletions

View File

@ -1,847 +1,179 @@
'use client';
import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import '../i18n/config';
import {
Users,
Home,
Calendar,
User,
Clock,
Users,
DollarSign,
PlusCircle,
Edit,
Trash2,
CheckCircle,
XCircle,
Search,
Filter,
Download,
Eye,
MapPin,
Bed,
Bath,
Square,
Star,
Phone,
Mail,
CalendarDays
TrendingUp,
Bell
} from 'lucide-react';
import DashboardStats from '../components/admin/DashboardStats';
import PropertiesTable from '../components/admin/PropertiesTable';
import BookingRequests from '../components/admin/BookingRequests';
import UsersList from '../components/admin/UsersList';
import LedgerBook from '../components/admin/LedgerBook';
import AddPropertyForm from '../components/admin/AddPropertyForm';
import { PropertyProvider } from '../contexts/PropertyContext';
import '../i18n/config';
export default function AdminPage() {
const { t, i18n } = useTranslation();
const [activeTab, setActiveTab] = useState('properties');
const [searchQuery, setSearchQuery] = useState('');
const [selectedUser, setSelectedUser] = useState(null);
const [activeTab, setActiveTab] = useState('dashboard');
const [showAddProperty, setShowAddProperty] = useState(false);
const [notifications, setNotifications] = useState(3);
// احصل على اللغة الحالية من i18n
const currentLanguage = i18n.language || 'en';
const [stats, setStats] = useState({
totalUsers: 0,
totalProperties: 0,
activeBookings: 0,
availableProperties: 0
});
const [properties, setProperties] = useState([
{
id: 1,
name: "luxuryVillaDamascus",
type: "villa",
price: 500000,
location: "Damascus, Al-Mazzeh",
bedrooms: 5,
bathrooms: 4,
area: 450,
status: "available",
images: [],
features: ["swimmingPool", "privateGarden", "parking", "superLuxFinish"]
},
{
id: 2,
name: "modernApartmentAleppo",
type: "apartment",
price: 250000,
location: "Aleppo, Al-Shahba",
bedrooms: 3,
bathrooms: 2,
area: 180,
status: "booked",
images: [],
features: ["equippedKitchen", "centralHeating", "balcony", "securitySystem"]
},
{
id: 3,
name: "familyHouseHoms",
type: "house",
price: 350000,
location: "Homs, Baba Amr",
bedrooms: 4,
bathrooms: 3,
area: 300,
status: "available",
images: [],
features: ["largeGarden", "receptionHall", "maidRoom", "garage"]
},
{
id: 4,
name: "seasideApartmentLatakia",
type: "apartment",
price: 300000,
location: "Latakia, Blue Beach",
bedrooms: 3,
bathrooms: 2,
area: 200,
status: "available",
images: [],
features: ["seaView", "centralHeating", "centralAC", "parking"]
},
{
id: 5,
name: "villaDaraa",
type: "villa",
price: 400000,
location: "Daraa, Doctors District",
bedrooms: 4,
bathrooms: 3,
area: 350,
status: "booked",
images: [],
features: ["fruitGarden", "highWall", "advancedSecurity", "storage"]
}
]);
const [users, setUsers] = useState([
{
id: 1,
name: "Ahmed Mohamed",
email: "ahmed@example.com",
phone: "+963 123 456 789",
joinDate: "2024-01-15",
activeBookings: 1,
totalBookings: 3,
currentBooking: {
propertyId: 2,
propertyName: "modernApartmentAleppo",
startDate: "2024-02-01",
endDate: "2024-08-01",
duration: "6 months",
totalAmount: 1500000
}
},
{
id: 2,
name: "Sara Ahmed",
email: "sara@example.com",
phone: "+963 987 654 321",
joinDate: "2024-02-10",
activeBookings: 0,
totalBookings: 2,
currentBooking: null
},
{
id: 3,
name: "Mohammed Al-Halabi",
email: "mohammed@example.com",
phone: "+963 555 123 456",
joinDate: "2024-01-25",
activeBookings: 1,
totalBookings: 1,
currentBooking: {
propertyId: 5,
propertyName: "villaDaraa",
startDate: "2024-02-15",
endDate: "2024-05-15",
duration: "3 months",
totalAmount: 1200000
}
}
]);
const [bookingRequests, setBookingRequests] = useState([
{
id: "B001",
userId: 2,
userName: "Sara Ahmed",
propertyId: 1,
propertyName: "luxuryVillaDamascus",
startDate: "2024-03-01",
endDate: "2024-06-01",
duration: "3 months",
totalAmount: 1500000,
status: "pending",
requestDate: "2024-02-20"
},
{
id: "B002",
userId: 1,
userName: "Ahmed Mohamed",
propertyId: 4,
propertyName: "seasideApartmentLatakia",
startDate: "2024-03-15",
endDate: "2024-05-15",
duration: "2 months",
totalAmount: 600000,
status: "pending",
requestDate: "2024-02-18"
}
]);
const formatNumber = (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const fadeInUp = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 }
}
};
const staggerContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
};
const cardHover = {
rest: {
scale: 1,
y: 0,
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.05)"
},
hover: {
scale: 1.02,
y: -5,
boxShadow: "0 10px 25px rgba(0, 0, 0, 0.1)",
transition: {
type: "spring",
stiffness: 300
}
}
};
const buttonHover = {
rest: { scale: 1 },
hover: { scale: 1.05 },
tap: { scale: 0.98 }
};
useEffect(() => {
const totalBookings = users.reduce((sum, user) => sum + user.activeBookings, 0);
const availableProps = properties.filter(p => p.status === "available").length;
setStats({
totalUsers: users.length,
totalProperties: properties.length,
activeBookings: totalBookings,
availableProperties: availableProps
});
}, [users, properties]);
const handleBookingAction = (bookingId, action) => {
setBookingRequests(prev =>
prev.map(booking =>
booking.id === bookingId
? { ...booking, status: action === 'accept' ? 'approved' : 'rejected' }
: booking
)
);
};
const handlePropertyAction = (propertyId, action) => {
if (action === 'delete') {
setProperties(prev => prev.filter(p => p.id !== propertyId));
}
};
const showUserDetails = (user) => {
setSelectedUser(user);
};
const getLocation = (location) => {
const [city, district] = location.split(",").map(s => s.trim());
// تحويل القيم إلى مفاتيح ترجمة
const cityKey = city.toLowerCase();
const districtKey = district.replace(/\s+/g, '');
return `${t(cityKey)}, ${t(districtKey)}`;
};
const tabs = [
{ id: 'dashboard', label: 'لوحة التحكم', icon: Home },
{ id: 'properties', label: 'العقارات', icon: Home },
{ id: 'bookings', label: 'طلبات الحجز', icon: Calendar, badge: notifications },
{ id: 'users', label: 'المستخدمين', icon: Users },
{ id: 'ledger', label: 'دفتر الحسابات', icon: DollarSign },
{ id: 'reports', label: 'التقارير', icon: TrendingUp }
];
return (
<div className={`min-h-screen bg-gray-50 p-4 md:p-6 font-sans ${currentLanguage === 'ar' ? 'text-right' : 'text-left'}`}>
{/* Header بدون زر اختيار اللغة */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-8"
>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 mb-2 tracking-tight">
{t("adminDashboard")}
</h1>
<p className="text-gray-600 text-base mb-1">{t("manageProperties")}</p>
<p className="text-gray-500 text-sm">{t("pricesInSYP")}</p>
</div>
</motion.div>
{/* إحصائيات */}
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8"
variants={staggerContainer}
initial="hidden"
animate="visible"
>
<PropertyProvider>
<div className={`min-h-screen bg-gray-50 p-4 md:p-6 ${i18n.language === 'ar' ? 'text-right' : 'text-left'}`}>
<motion.div
variants={fadeInUp}
whileHover="hover"
variants={cardHover}
className="bg-gradient-to-br from-blue-600 to-blue-700 text-white rounded-xl shadow p-5"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className={`flex items-center justify-between mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="p-3 bg-white/20 rounded-lg">
<Users className="w-6 h-6" />
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 mb-2">
{t('adminDashboard')}
</h1>
<p className="text-gray-600">
إدارة العقارات، الحجوزات، والحسابات المالية
</p>
</div>
<div className={currentLanguage === 'ar' ? 'text-left' : 'text-right'}>
<div className="text-2xl font-bold mb-1">{stats.totalUsers}</div>
<div className="text-blue-100 text-sm">{t("totalUsers")}</div>
</div>
</div>
<div className="text-xs text-blue-100 mt-3 pt-3 border-t border-blue-400/30">
{users.filter(u => u.activeBookings > 0).length} {t("usersWithActiveBookings")}
<button className="relative p-2 hover:bg-gray-100 rounded-lg">
<Bell className="w-6 h-6 text-gray-600" />
{notifications > 0 && (
<span className="absolute top-0 right-0 w-4 h-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
{notifications}
</span>
)}
</button>
</div>
</motion.div>
<motion.div
variants={fadeInUp}
whileHover="hover"
variants={cardHover}
className="bg-gradient-to-br from-emerald-600 to-emerald-700 text-white rounded-xl shadow p-5"
>
<div className={`flex items-center justify-between mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="p-3 bg-white/20 rounded-lg">
<Home className="w-6 h-6" />
</div>
<div className={currentLanguage === 'ar' ? 'text-left' : 'text-right'}>
<div className="text-2xl font-bold mb-1">{stats.totalProperties}</div>
<div className="text-emerald-100 text-sm">{t("totalProperties")}</div>
</div>
</div>
<div className="text-xs text-emerald-100 mt-3 pt-3 border-t border-emerald-400/30">
{stats.availableProperties} {t("propertiesAvailable")}
</div>
</motion.div>
<motion.div
variants={fadeInUp}
whileHover="hover"
variants={cardHover}
className="bg-gradient-to-br from-purple-600 to-purple-700 text-white rounded-xl shadow p-5"
>
<div className={`flex items-center justify-between mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="p-3 bg-white/20 rounded-lg">
<Calendar className="w-6 h-6" />
</div>
<div className={currentLanguage === 'ar' ? 'text-left' : 'text-right'}>
<div className="text-2xl font-bold mb-1">{stats.activeBookings}</div>
<div className="text-purple-100 text-sm">{t("activeBookings")}</div>
</div>
</div>
<div className="text-xs text-purple-100 mt-3 pt-3 border-t border-purple-400/30">
{bookingRequests.filter(b => b.status === 'pending').length} {t("bookingRequestsPending")}
</div>
</motion.div>
<motion.div
variants={fadeInUp}
whileHover="hover"
variants={cardHover}
className="bg-gradient-to-br from-amber-600 to-amber-700 text-white rounded-xl shadow p-5"
>
<div className={`flex items-center justify-between mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="p-3 bg-white/20 rounded-lg">
<Home className="w-6 h-6" />
</div>
<div className={currentLanguage === 'ar' ? 'text-left' : 'text-right'}>
<div className="text-2xl font-bold mb-1">{stats.availableProperties}</div>
<div className="text-amber-100 text-sm">{t("availableProperties")}</div>
</div>
</div>
<div className="text-xs text-amber-100 mt-3 pt-3 border-t border-amber-400/30">
{properties.filter(p => p.status === 'available').length} {t("propertiesReadyForRent")}
</div>
</motion.div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mb-6"
>
<div className={`flex flex-wrap gap-2 border-b border-gray-200 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<button
onClick={() => setActiveTab('properties')}
className={`px-4 py-3 font-medium text-sm rounded-t-lg transition-all ${activeTab === 'properties' ? 'bg-white border-t border-x border-gray-300 text-blue-700' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-100'}`}
>
<div className="flex items-center gap-2">
<Home className="w-4 h-4" />
<span>{t("properties")}</span>
</div>
</button>
<button
onClick={() => setActiveTab('bookings')}
className={`px-4 py-3 font-medium text-sm rounded-t-lg transition-all ${activeTab === 'bookings' ? 'bg-white border-t border-x border-gray-300 text-blue-700' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-100'}`}
>
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
<span>{t("bookingRequests")}</span>
</div>
</button>
<button
onClick={() => setActiveTab('users')}
className={`px-4 py-3 font-medium text-sm rounded-t-lg transition-all ${activeTab === 'users' ? 'bg-white border-t border-x border-gray-300 text-blue-700' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-100'}`}
>
<div className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span>{t("users")}</span>
</div>
</button>
</div>
</motion.div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
{activeTab === 'properties' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className={`flex flex-col md:flex-row md:items-center justify-between mb-6 ${currentLanguage === 'ar' ? 'md:flex-row-reverse' : ''}`}>
<div>
<h2 className="text-xl font-bold text-gray-900 mb-1">{t("propertiesManagement")}</h2>
<p className="text-gray-600 text-sm">{t("addEditDeleteProperties")}</p>
</div>
<motion.button
variants={buttonHover}
whileHover="hover"
whileTap="tap"
className="mt-3 md:mt-0 bg-blue-700 hover:bg-blue-800 text-white px-5 py-3 rounded-lg flex items-center gap-2 text-sm"
>
<PlusCircle className="w-4 h-4" />
{t("addNewProperty")}
</motion.button>
</div>
<div className={`mb-6 flex flex-col md:flex-row gap-3 ${currentLanguage === 'ar' ? 'md:flex-row-reverse' : ''}`}>
<div className="relative flex-1">
<Search className={`absolute ${currentLanguage === 'ar' ? 'right-3' : 'left-3'} top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500`} />
<input
type="text"
placeholder={t("searchProperties")}
className={`w-full ${currentLanguage === 'ar' ? 'pr-3 pl-10' : 'pl-3 pr-10'} py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm placeholder:text-gray-400`}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<div className={`flex gap-2 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<motion.button
variants={buttonHover}
whileHover="hover"
className="px-4 py-2.5 border border-gray-300 rounded-lg hover:bg-gray-50 flex items-center gap-2 text-sm"
>
<Filter className="w-4 h-4" />
{t("filter")}
</motion.button>
<motion.button
variants={buttonHover}
whileHover="hover"
className="px-4 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg flex items-center gap-2 text-sm"
>
<Download className="w-4 h-4" />
{t("export")}
</motion.button>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
{properties.map((property) => (
<motion.div
key={property.id}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3 }}
whileHover={{ y: -4 }}
className="border border-gray-200 rounded-lg overflow-hidden hover:shadow-md transition-all duration-200"
>
<div className="p-5">
<div className={`flex justify-between items-start mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div>
<h3 className="text-base font-bold text-gray-900 mb-1">{t(property.name)}</h3>
<div className="flex items-center gap-2 text-gray-600 text-xs">
<MapPin className="w-3 h-3" />
<span>{getLocation(property.location)}</span>
</div>
</div>
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${property.status === 'available' ? 'bg-emerald-100 text-emerald-800' : 'bg-red-100 text-red-800'}`}>
{property.status === 'available' ? t("available") : t("booked")}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="text-center bg-gray-50 p-3 rounded-lg">
<div className="flex items-center justify-center gap-1 text-gray-700 mb-1 text-xs">
<Bed className="w-3 h-3" />
<span>{t("bedrooms")}</span>
</div>
<div className="font-bold text-gray-900 text-sm">{property.bedrooms}</div>
</div>
<div className="text-center bg-gray-50 p-3 rounded-lg">
<div className="flex items-center justify-center gap-1 text-gray-700 mb-1 text-xs">
<Bath className="w-3 h-3" />
<span>{t("bathrooms")}</span>
</div>
<div className="font-bold text-gray-900 text-sm">{property.bathrooms}</div>
</div>
<div className="text-center bg-gray-50 p-3 rounded-lg">
<div className="flex items-center justify-center gap-1 text-gray-700 mb-1 text-xs">
<Square className="w-3 h-3" />
<span>{t("area")}</span>
</div>
<div className="font-bold text-gray-900 text-sm">{property.area} </div>
</div>
<div className="text-center bg-gray-50 p-3 rounded-lg">
<div className="flex items-center justify-center gap-1 text-gray-700 mb-1 text-xs">
<DollarSign className="w-3 h-3" />
<span>{t("price")}</span>
</div>
<div className="font-bold text-gray-900 text-sm">{formatNumber(property.price)} SYP/{t("month")}</div>
</div>
</div>
<div className="mb-5">
<h4 className="font-bold text-gray-900 text-sm mb-2">{t("features")}:</h4>
<div className="flex flex-wrap gap-2">
{property.features.map((feature, idx) => (
<span key={idx} className="px-2.5 py-1 bg-blue-50 text-blue-800 rounded-full text-xs font-medium">
{t(feature)}
</span>
))}
</div>
</div>
<div className={`flex justify-end gap-2 pt-4 border-t border-gray-100 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-3 py-2 bg-blue-100 text-blue-800 hover:bg-blue-200 rounded-lg flex items-center gap-2 text-xs"
>
<Eye className="w-3 h-3" />
{t("viewDetails")}
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-3 py-2 bg-amber-100 text-amber-800 hover:bg-amber-200 rounded-lg flex items-center gap-2 text-xs"
>
<Edit className="w-3 h-3" />
{t("edit")}
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => handlePropertyAction(property.id, 'delete')}
className="px-3 py-2 bg-red-100 text-red-800 hover:bg-red-200 rounded-lg flex items-center gap-2 text-xs"
>
<Trash2 className="w-3 h-3" />
{t("delete")}
</motion.button>
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
)}
{activeTab === 'bookings' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="mb-6">
<h2 className="text-xl font-bold text-gray-900 mb-1">{t("bookingRequests")}</h2>
<p className="text-gray-600 text-sm">{t("manageBookingRequests")}</p>
</div>
<div className="space-y-5">
{bookingRequests.map((request) => (
<motion.div
key={request.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
className="border border-gray-200 rounded-lg p-5 hover:shadow-sm transition-shadow"
>
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div className="flex-1">
<div className={`flex items-center justify-between mb-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<h3 className="text-base font-bold text-gray-900">{t("bookingRequest")} #{request.id}</h3>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${request.status === 'pending' ? 'bg-yellow-100 text-yellow-800' : request.status === 'approved' ? 'bg-emerald-100 text-emerald-800' : 'bg-red-100 text-red-800'}`}>
{request.status === 'pending' ? t("pending") : request.status === 'approved' ? t("approved") : t("rejected")}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 mb-4">
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-xs text-gray-600 mb-1">{t("user")}</div>
<div className="font-bold text-gray-900 text-sm">{request.userName}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-xs text-gray-600 mb-1">{t("property")}</div>
<div className="font-bold text-gray-900 text-sm">{t(request.propertyName)}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-xs text-gray-600 mb-1">{t("duration")}</div>
<div className="font-bold text-gray-900 text-sm">{request.duration}</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<div className="text-xs text-gray-600 mb-1">{t("totalAmount")}</div>
<div className="font-bold text-gray-900 text-sm">{formatNumber(request.totalAmount)} SYP</div>
</div>
</div>
<div className="text-xs text-gray-600 space-y-2">
<div className="flex items-center gap-2">
<CalendarDays className="w-3 h-3" />
<span>{t("from")} <span className="font-medium">{request.startDate}</span> {t("to")} <span className="font-medium">{request.endDate}</span></span>
</div>
<div className="flex items-center gap-2">
<Clock className="w-3 h-3" />
<span>{t("requestDate")}: <span className="font-medium">{request.requestDate}</span></span>
</div>
</div>
</div>
{request.status === 'pending' && (
<div className={`flex gap-2 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<motion.button
variants={buttonHover}
whileHover="hover"
whileTap="tap"
onClick={() => handleBookingAction(request.id, 'accept')}
className="px-4 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg flex items-center gap-2 text-sm"
>
<CheckCircle className="w-4 h-4" />
{t("accept")}
</motion.button>
<motion.button
variants={buttonHover}
whileHover="hover"
whileTap="tap"
onClick={() => handleBookingAction(request.id, 'reject')}
className="px-4 py-2.5 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center gap-2 text-sm"
>
<XCircle className="w-4 h-4" />
{t("reject")}
</motion.button>
</div>
)}
</div>
</motion.div>
))}
</div>
</motion.div>
)}
{activeTab === 'users' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="mb-6">
<h2 className="text-xl font-bold text-gray-900 mb-1">{t("users")}</h2>
<p className="text-gray-600 text-sm">{t("viewUserDetails")}</p>
</div>
<div className="space-y-5">
{users.map((user) => (
<motion.div
key={user.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="border border-gray-200 rounded-lg overflow-hidden hover:shadow-sm transition-all"
>
<div className="p-5">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-5">
<div className={`flex items-center gap-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
<User className="w-6 h-6 text-blue-700" />
</div>
<div className={currentLanguage === 'ar' ? 'text-right' : 'text-left'}>
<h3 className="text-base font-bold text-gray-900 mb-1">{user.name}</h3>
<div className="flex flex-wrap gap-3 text-xs">
<div className="flex items-center gap-1 text-gray-700">
<Mail className="w-3 h-3" />
<span>{user.email}</span>
</div>
<div className="flex items-center gap-1 text-gray-700">
<Phone className="w-3 h-3" />
<span>{user.phone}</span>
</div>
</div>
</div>
</div>
<div className={`flex gap-4 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<div className="text-center bg-blue-50 p-3 rounded-lg">
<div className="text-lg font-bold text-blue-700">{user.activeBookings}</div>
<div className="text-gray-700 text-xs">{t("activeBookings")}</div>
</div>
<div className="text-center bg-emerald-50 p-3 rounded-lg">
<div className="text-lg font-bold text-emerald-700">{user.totalBookings}</div>
<div className="text-gray-700 text-xs">{t("totalBookings")}</div>
</div>
</div>
</div>
{user.currentBooking ? (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-5">
<h4 className="font-bold text-blue-900 text-sm mb-3 flex items-center gap-2">
<Calendar className="w-4 h-4" />
{t("currentActiveBooking")}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
<div className="bg-white p-3 rounded">
<div className="text-xs text-blue-700 mb-1">{t("property")}</div>
<div className="font-bold text-blue-900 text-sm">{t(user.currentBooking.propertyName)}</div>
</div>
<div className="bg-white p-3 rounded">
<div className="text-xs text-blue-700 mb-1">{t("duration")}</div>
<div className="font-bold text-blue-900 text-sm">{user.currentBooking.duration}</div>
</div>
<div className="bg-white p-3 rounded">
<div className="text-xs text-blue-700 mb-1">{t("totalAmount")}</div>
<div className="font-bold text-blue-900 text-sm">{formatNumber(user.currentBooking.totalAmount)} SYP</div>
</div>
<div className="bg-white p-3 rounded">
<div className="text-xs text-blue-700 mb-1">{t("bookingPeriod")}</div>
<div className="font-bold text-blue-900 text-sm">
{user.currentBooking.startDate} {t("to")} {user.currentBooking.endDate}
</div>
</div>
</div>
</div>
) : (
<div className="bg-gray-100 border border-gray-300 rounded-lg p-4 mb-5 text-center">
<div className="text-gray-600 text-sm">{t("noActiveBookings")}</div>
</div>
)}
<div className="flex justify-end">
<motion.button
variants={buttonHover}
whileHover="hover"
whileTap="tap"
onClick={() => showUserDetails(user)}
className="px-4 py-2.5 bg-blue-700 hover:bg-blue-800 text-white rounded-lg flex items-center gap-2 text-sm"
>
<Eye className="w-4 h-4" />
{t("viewFullDetails")}
</motion.button>
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
)}
</div>
{selectedUser && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="bg-white rounded-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto"
>
<div className="p-6">
<div className={`flex justify-between items-center mb-6 ${currentLanguage === 'ar' ? 'flex-row-reverse' : ''}`}>
<h3 className="text-lg font-bold text-gray-900">{t("userDetails")}: {selectedUser.name}</h3>
<div className="mb-6 border-b border-gray-200">
<div className="flex flex-wrap gap-2">
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
onClick={() => setSelectedUser(null)}
className="p-2 hover:bg-gray-100 rounded-lg"
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`
px-4 py-3 font-medium text-sm rounded-t-lg transition-all relative
${activeTab === tab.id
? 'bg-white border-t border-x border-gray-300 text-blue-700'
: 'text-gray-700 hover:text-blue-600 hover:bg-gray-100'
}
`}
>
<XCircle className="w-5 h-5 text-gray-600" />
<div className="flex items-center gap-2">
<Icon className="w-4 h-4" />
<span>{tab.label}</span>
{tab.badge && (
<span className="bg-red-500 text-white text-xs px-2 py-0.5 rounded-full">
{tab.badge}
</span>
)}
</div>
</button>
);
})}
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
{activeTab === 'dashboard' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<DashboardStats />
</motion.div>
)}
{activeTab === 'properties' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<div className="flex justify-between items-center mb-6">
<div>
<h2 className="text-xl font-bold">إدارة العقارات</h2>
<p className="text-gray-600 text-sm">إضافة وتعديل العقارات مع تحديد نسب الأرباح</p>
</div>
<button
onClick={() => setShowAddProperty(true)}
className="bg-blue-700 text-white px-4 py-2 rounded-lg hover:bg-blue-800"
>
إضافة عقار جديد
</button>
</div>
<PropertiesTable />
</motion.div>
)}
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="font-bold text-sm text-gray-800 mb-3">{t("personalInformation")}</h4>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">{t("fullName")}:</span>
<span className="font-medium">{selectedUser.name}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">{t("email")}:</span>
<span className="font-medium">{selectedUser.email}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">{t("phoneNumber")}:</span>
<span className="font-medium">{selectedUser.phone}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">{t("joinDate")}:</span>
<span className="font-medium">{selectedUser.joinDate}</span>
</div>
</div>
</div>
{activeTab === 'bookings' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<BookingRequests />
</motion.div>
)}
<div className="bg-blue-50 p-4 rounded-lg">
<h4 className="font-bold text-sm text-blue-800 mb-3">{t("bookingStatistics")}</h4>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-blue-600">{t("activeBookings")}:</span>
<span className="font-medium text-blue-800">{selectedUser.activeBookings}</span>
</div>
<div className="flex justify-between">
<span className="text-blue-600">{t("totalBookings")}:</span>
<span className="font-medium text-blue-800">{selectedUser.totalBookings}</span>
</div>
</div>
</div>
</div>
{activeTab === 'users' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<UsersList />
</motion.div>
)}
{activeTab === 'ledger' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<LedgerBook userType="admin" />
</motion.div>
)}
{activeTab === 'reports' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<div className="text-center py-12 text-gray-500">
قريباً... تقارير متقدمة
</div>
</div>
</motion.div>
</motion.div>
)}
</div>
)}
</div>
{showAddProperty && (
<AddPropertyForm
onClose={() => setShowAddProperty(false)}
onSuccess={() => {
setShowAddProperty(false);
}}
/>
)}
</div>
</PropertyProvider>
);
}

View File

@ -4,7 +4,7 @@ import Link from 'next/link';
export function NavLink({ href, children }) {
const pathname = usePathname();
const isActive = pathname === href;
const isActive = pathname === href || pathname.startsWith(href + '/');
return (
<Link
href={href}
@ -24,7 +24,7 @@ export function NavLink({ href, children }) {
export function MobileNavLink({ href, children, onClick }) {
const pathname = usePathname();
const isActive = pathname === href;
const isActive = pathname === href || pathname.startsWith(href + '/');
return (
<Link
href={href}

View File

@ -0,0 +1,346 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { useProperties } from '@/app/contexts/PropertyContext';
import { COMMISSION_TYPE, CITIES } from '@/app/utils/constants';
import { X, MapPin, Home, DollarSign, Percent } from 'lucide-react';
export default function AddPropertyForm({ onClose, onSuccess }) {
const { addProperty } = useProperties();
const [formData, setFormData] = useState({
title: '',
description: '',
city: '',
district: '',
address: '',
latitude: '',
longitude: '',
type: 'apartment',
bedrooms: 1,
bathrooms: 1,
area: 0,
floor: 1,
dailyPrice: 0,
commissionRate: 5,
commissionType: COMMISSION_TYPE.FROM_OWNER,
securityDeposit: 0,
images: [],
features: [],
status: 'available'
});
const [selectedFeatures, setSelectedFeatures] = useState([]);
const featuresList = [
'swimmingPool', 'privateGarden', 'parking', 'superLuxFinish',
'equippedKitchen', 'centralHeating', 'balcony', 'securitySystem',
'largeGarden', 'receptionHall', 'maidRoom', 'garage',
'seaView', 'centralAC', 'fruitGarden', 'storage'
];
const handleSubmit = async (e) => {
e.preventDefault();
const propertyData = {
...formData,
features: selectedFeatures,
priceDisplay: {
daily: formData.dailyPrice,
monthly: formData.dailyPrice * 30,
withCommission: calculateCommissionPrice(formData)
},
location: {
lat: formData.latitude,
lng: formData.longitude,
address: formData.address
}
};
try {
await addProperty(propertyData);
onSuccess?.();
onClose();
} catch (error) {
console.error('Error adding property:', error);
}
};
const calculateCommissionPrice = (data) => {
const { dailyPrice, commissionRate, commissionType } = data;
const commission = (dailyPrice * commissionRate) / 100;
switch(commissionType) {
case COMMISSION_TYPE.FROM_TENANT:
return dailyPrice + commission;
case COMMISSION_TYPE.FROM_OWNER:
return dailyPrice;
case COMMISSION_TYPE.FROM_BOTH:
return dailyPrice + (commission / 2);
default:
return dailyPrice;
}
};
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"
>
<motion.div
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
className="bg-white rounded-xl w-full max-w-4xl max-h-[90vh] overflow-y-auto"
>
<div className="sticky top-0 bg-white border-b p-4 flex justify-between items-center">
<h2 className="text-xl font-bold">إضافة عقار جديد</h2>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-lg">
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6">
<div className="bg-blue-50 p-4 rounded-lg">
<h3 className="font-semibold mb-3 flex items-center gap-2">
<MapPin className="w-4 h-4" />
موقع العقار (سيظهر على الخريطة)
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">المدينة</label>
<select
value={formData.city}
onChange={(e) => setFormData({...formData, city: e.target.value})}
className="w-full p-2 border rounded-lg"
required
>
<option value="">اختر المدينة</option>
{Object.values(CITIES).map(city => (
<option key={city} value={city}>{city}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">الحي</label>
<input
type="text"
value={formData.district}
onChange={(e) => setFormData({...formData, district: e.target.value})}
className="w-full p-2 border rounded-lg"
required
/>
</div>
<div className="col-span-2">
<label className="block text-sm font-medium mb-1">العنوان بالتفصيل</label>
<input
type="text"
value={formData.address}
onChange={(e) => setFormData({...formData, address: e.target.value})}
className="w-full p-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">خط العرض (Latitude)</label>
<input
type="number"
step="any"
value={formData.latitude}
onChange={(e) => setFormData({...formData, latitude: e.target.value})}
className="w-full p-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">خط الطول (Longitude)</label>
<input
type="number"
step="any"
value={formData.longitude}
onChange={(e) => setFormData({...formData, longitude: e.target.value})}
className="w-full p-2 border rounded-lg"
required
/>
</div>
</div>
</div>
<div className="bg-amber-50 p-4 rounded-lg">
<h3 className="font-semibold mb-3 flex items-center gap-2">
<DollarSign className="w-4 h-4" />
السعر ونسبة الربح
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">
السعر اليومي (ل.س)
</label>
<input
type="number"
value={formData.dailyPrice}
onChange={(e) => setFormData({...formData, dailyPrice: Number(e.target.value)})}
className="w-full p-2 border rounded-lg"
required
min="0"
/>
<p className="text-xs text-gray-500 mt-1">
هذا السعر سيظهر على الخريطة
</p>
</div>
<div>
<label className="block text-sm font-medium mb-1">
نسبة ربح المنصة (%)
</label>
<div className="flex items-center gap-2">
<input
type="number"
value={formData.commissionRate}
onChange={(e) => setFormData({...formData, commissionRate: Number(e.target.value)})}
className="w-full p-2 border rounded-lg"
min="0"
max="100"
step="0.1"
required
/>
<Percent className="w-4 h-4 text-gray-400" />
</div>
</div>
<div className="col-span-2">
<label className="block text-sm font-medium mb-2">
مصدر العمولة (بموافقة الأدمن)
</label>
<div className="grid grid-cols-3 gap-3">
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="radio"
name="commissionType"
value={COMMISSION_TYPE.FROM_OWNER}
checked={formData.commissionType === COMMISSION_TYPE.FROM_OWNER}
onChange={(e) => setFormData({...formData, commissionType: e.target.value})}
/>
<span>من المالك</span>
</label>
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="radio"
name="commissionType"
value={COMMISSION_TYPE.FROM_TENANT}
checked={formData.commissionType === COMMISSION_TYPE.FROM_TENANT}
onChange={(e) => setFormData({...formData, commissionType: e.target.value})}
/>
<span>من المستأجر</span>
</label>
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="radio"
name="commissionType"
value={COMMISSION_TYPE.FROM_BOTH}
checked={formData.commissionType === COMMISSION_TYPE.FROM_BOTH}
onChange={(e) => setFormData({...formData, commissionType: e.target.value})}
/>
<span>من الاثنين</span>
</label>
</div>
</div>
<div className="col-span-2 bg-white p-3 rounded-lg">
<h4 className="font-medium mb-2">تفاصيل السعر بعد العمولة:</h4>
<div className="grid grid-cols-3 gap-4 text-sm">
<div>
<span className="text-gray-600">السعر الأصلي:</span>
<span className="block font-bold">{formData.dailyPrice} ل.س</span>
</div>
<div>
<span className="text-gray-600">العمولة:</span>
<span className="block font-bold">
{(formData.dailyPrice * formData.commissionRate / 100)} ل.س
</span>
</div>
<div>
<span className="text-gray-600">السعر النهائي:</span>
<span className="block font-bold text-green-600">
{calculateCommissionPrice(formData)} ل.س
</span>
</div>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">نوع العقار</label>
<select
value={formData.type}
onChange={(e) => setFormData({...formData, type: e.target.value})}
className="w-full p-2 border rounded-lg"
>
<option value="apartment">شقة</option>
<option value="house">بيت</option>
<option value="villa">فيلا</option>
<option value="studio">استوديو</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">مبلغ الضمان (ل.س)</label>
<input
type="number"
value={formData.securityDeposit}
onChange={(e) => setFormData({...formData, securityDeposit: Number(e.target.value)})}
className="w-full p-2 border rounded-lg"
min="0"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-2">المميزات</label>
<div className="grid grid-cols-3 gap-2">
{featuresList.map(feature => (
<label key={feature} className="flex items-center gap-2 p-2 border rounded-lg">
<input
type="checkbox"
checked={selectedFeatures.includes(feature)}
onChange={(e) => {
if (e.target.checked) {
setSelectedFeatures([...selectedFeatures, feature]);
} else {
setSelectedFeatures(selectedFeatures.filter(f => f !== feature));
}
}}
/>
<span className="text-sm">{feature}</span>
</label>
))}
</div>
</div>
<div className="flex gap-3 pt-4 border-t">
<button
type="submit"
className="flex-1 bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
>
إضافة العقار
</button>
<button
type="button"
onClick={onClose}
className="px-6 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
إلغاء
</button>
</div>
</form>
</motion.div>
</motion.div>
);
}

View File

@ -0,0 +1,948 @@
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
CheckCircle,
XCircle,
Clock,
User,
Home,
Calendar,
DollarSign,
AlertCircle,
Key,
DoorOpen,
Shield,
Phone,
Mail,
MessageCircle,
ChevronDown,
ChevronUp,
FileText,
Download,
Printer,
History
} from 'lucide-react';
const ReasonDialog = ({ isOpen, onClose, onConfirm, title, defaultReason = '' }) => {
const [reason, setReason] = useState(defaultReason);
const [otherReason, setOtherReason] = useState('');
const commonReasons = [
'أعمال صيانة في العقار',
'العقار غير متاح في هذه التواريخ',
'مشكلة في وثائق المستأجر',
'المالك غير متاح للتسليم',
'تأخر في دفع الضمان',
'سبب آخر'
];
if (!isOpen) return null;
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
className="bg-white rounded-2xl w-full max-w-md p-6 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="text-center mb-4">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-3">
<AlertCircle className="w-8 h-8 text-red-600" />
</div>
<h3 className="text-xl font-bold text-gray-900">{title}</h3>
<p className="text-sm text-gray-500 mt-1">يرجى تحديد سبب الرفض</p>
</div>
<div className="space-y-3">
{commonReasons.map((r) => (
<button
key={r}
onClick={() => {
if (r === 'سبب آخر') {
} else {
onConfirm(r);
}
}}
className="w-full text-right p-3 border rounded-xl hover:bg-gray-50 transition-colors"
>
{r}
</button>
))}
<textarea
placeholder="اكتب سبباً آخر..."
value={otherReason}
onChange={(e) => setOtherReason(e.target.value)}
className="w-full p-3 border rounded-xl resize-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
rows="3"
/>
<div className="flex gap-3 pt-3">
<button
onClick={() => onConfirm(otherReason)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-colors"
disabled={!otherReason.trim()}
>
تأكيد الرفض
</button>
<button
onClick={onClose}
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
إلغاء
</button>
</div>
</div>
</motion.div>
</motion.div>
);
};
const RequestDetailsDialog = ({ request, isOpen, onClose }) => {
if (!isOpen || !request) return null;
const formatCurrency = (amount) => {
return amount?.toLocaleString() + ' ل.س';
};
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
onClick={onClose}
>
<motion.div
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
className="bg-white rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="sticky top-0 bg-white border-b p-4 flex justify-between items-center">
<h2 className="text-xl font-bold flex items-center gap-2">
<FileText className="w-5 h-5 text-blue-600" />
تفاصيل الطلب #{request.id}
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
>
<XCircle className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-6">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-blue-800">
<User className="w-4 h-4" />
معلومات المستأجر
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">الاسم الكامل</label>
<div className="font-medium">{request.user}</div>
</div>
<div>
<label className="text-xs text-gray-500">نوع الهوية</label>
<div className="font-medium">
{request.userType === 'syrian' ? '🇸🇾 هوية سورية' : '🛂 جواز سفر'}
<span className="text-xs text-gray-500 mr-2">{request.identityNumber}</span>
</div>
</div>
<div>
<label className="text-xs text-gray-500">البريد الإلكتروني</label>
<div className="font-medium flex items-center gap-1">
<Mail className="w-3 h-3 text-gray-400" />
{request.userEmail}
</div>
</div>
<div>
<label className="text-xs text-gray-500">رقم الهاتف</label>
<div className="font-medium flex items-center gap-1">
<Phone className="w-3 h-3 text-gray-400" />
{request.userPhone}
</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-green-50 to-emerald-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-green-800">
<Home className="w-4 h-4" />
معلومات العقار
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">العقار</label>
<div className="font-medium">{request.property}</div>
</div>
<div>
<label className="text-xs text-gray-500">السعر اليومي</label>
<div className="font-medium text-green-600">{formatCurrency(request.dailyPrice)}</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-amber-50 to-orange-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-amber-800">
<Calendar className="w-4 h-4" />
تفاصيل الحجز
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">تاريخ البداية</label>
<div className="font-medium">{request.startDate}</div>
</div>
<div>
<label className="text-xs text-gray-500">تاريخ النهاية</label>
<div className="font-medium">{request.endDate}</div>
</div>
<div>
<label className="text-xs text-gray-500">عدد الأيام</label>
<div className="font-medium">{request.days} يوم</div>
</div>
<div>
<label className="text-xs text-gray-500">المبلغ الإجمالي</label>
<div className="font-medium text-green-600">{formatCurrency(request.totalAmount)}</div>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-purple-50 to-pink-50 p-4 rounded-xl">
<h3 className="font-bold mb-3 flex items-center gap-2 text-purple-800">
<DollarSign className="w-4 h-4" />
المعلومات المالية
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs text-gray-500">سلفة الضمان</label>
<div className="font-medium text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
<div>
<label className="text-xs text-gray-500">نسبة العمولة</label>
<div className="font-medium">{request.commissionRate}%</div>
</div>
<div>
<label className="text-xs text-gray-500">نوع العمولة</label>
<div className="font-medium text-amber-600">{request.commissionType}</div>
</div>
<div>
<label className="text-xs text-gray-500">قيمة العمولة</label>
<div className="font-medium text-amber-600">{formatCurrency(request.commissionAmount)}</div>
</div>
</div>
</div>
<div className="border-t pt-4">
<h3 className="font-bold mb-3 flex items-center gap-2">
<History className="w-4 h-4" />
سجل الإجراءات
</h3>
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-gray-600">تم إنشاء الطلب: {request.requestDate}</span>
</div>
{request.ownerApproved && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span className="text-gray-600">موافقة المالك</span>
</div>
)}
{request.adminApproved && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-gray-600">موافقة الإدارة</span>
</div>
)}
{request.ownerDelivered && (
<div className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 bg-purple-500 rounded-full"></div>
<span className="text-gray-600">تم تسليم المفتاح من المالك</span>
</div>
)}
{request.notes && (
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
<p className="text-sm text-gray-600">{request.notes}</p>
</div>
)}
</div>
</div>
</div>
<div className="sticky bottom-0 bg-gray-50 border-t p-4 flex gap-3">
<button
onClick={() => window.print()}
className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-medium hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
>
<Printer className="w-5 h-5" />
طباعة التفاصيل
</button>
<button
className="flex-1 bg-green-600 text-white py-3 rounded-xl font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-2"
>
<Download className="w-5 h-5" />
تصدير PDF
</button>
</div>
</motion.div>
</motion.div>
);
};
const RequestCard = ({ request, onAction, onViewDetails }) => {
const [expanded, setExpanded] = useState(false);
const formatCurrency = (amount) => {
return amount?.toLocaleString() + ' ل.س';
};
const getStatusColor = (status) => {
switch(status) {
case 'pending': return 'border-yellow-400 bg-yellow-50';
case 'owner_approved': return 'border-blue-400 bg-blue-50';
case 'admin_approved': return 'border-green-400 bg-green-50';
case 'active': return 'border-purple-400 bg-purple-50';
case 'completed': return 'border-gray-400 bg-gray-50';
case 'rejected': return 'border-red-400 bg-red-50';
default: return 'border-gray-200 bg-white';
}
};
const getStatusBadge = (status) => {
const styles = {
pending: 'bg-yellow-500 text-white',
owner_approved: 'bg-blue-500 text-white',
admin_approved: 'bg-green-500 text-white',
active: 'bg-purple-500 text-white',
completed: 'bg-gray-500 text-white',
rejected: 'bg-red-500 text-white'
};
const labels = {
pending: ' بانتظار الموافقة',
owner_approved: ' موافقة المالك',
admin_approved: ' موافقة الإدارة',
active: ' إيجار نشط',
completed: ' منتهي',
rejected: ' مرفوض'
};
return (
<span className={`px-3 py-1 rounded-full text-xs font-medium shadow-sm ${styles[status]}`}>
{labels[status]}
</span>
);
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className={`bg-white border-2 rounded-2xl overflow-hidden transition-all hover:shadow-xl ${getStatusColor(request.status)}`}
>
<div className="p-4 cursor-pointer" onClick={() => setExpanded(!expanded)}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-lg font-bold text-gray-900">طلب #{request.id}</span>
{getStatusBadge(request.status)}
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">{request.requestDate}</span>
{expanded ? (
<ChevronUp className="w-5 h-5 text-gray-400" />
) : (
<ChevronDown className="w-5 h-5 text-gray-400" />
)}
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-3">
<div className="flex items-center gap-2 text-sm">
<User className="w-4 h-4 text-gray-400" />
<span className="truncate">{request.user}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Home className="w-4 h-4 text-gray-400" />
<span className="truncate">{request.property}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<Calendar className="w-4 h-4 text-gray-400" />
<span>{request.days} أيام</span>
</div>
<div className="flex items-center gap-2 text-sm">
<DollarSign className="w-4 h-4 text-gray-400" />
<span className="font-bold text-green-600">{formatCurrency(request.totalAmount)}</span>
</div>
</div>
</div>
<AnimatePresence>
{expanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="border-t bg-white p-4"
>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-4">
<div className="bg-blue-50 p-2 rounded-lg">
<div className="text-xs text-gray-500">سلفة ضمان</div>
<div className="font-bold text-blue-600">{formatCurrency(request.securityDeposit)}</div>
</div>
<div className="bg-amber-50 p-2 rounded-lg">
<div className="text-xs text-gray-500">العمولة</div>
<div className="font-bold text-amber-600">{request.commissionRate}% ({request.commissionType})</div>
</div>
<div className="bg-purple-50 p-2 rounded-lg">
<div className="text-xs text-gray-500">مدة الإيجار</div>
<div className="font-bold text-purple-600">{request.startDate} إلى {request.endDate}</div>
</div>
</div>
{(request.ownerApproved || request.adminApproved) && (
<div className="bg-green-50 p-3 rounded-lg mb-4">
<h4 className="font-bold text-sm mb-2 flex items-center gap-2 text-green-800">
<Phone className="w-4 h-4" />
معلومات الاتصال
</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex items-center gap-1">
<Mail className="w-3 h-3 text-gray-500" />
{request.userEmail}
</div>
<div className="flex items-center gap-1">
<Phone className="w-3 h-3 text-gray-500" />
{request.userPhone}
</div>
</div>
</div>
)}
<div className="space-y-3">
{request.status === 'pending' && (
<div className="flex gap-3">
<button
onClick={() => onAction('owner_approve', request.id)}
className="flex-1 bg-green-600 text-white py-3 rounded-xl font-medium hover:bg-green-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<CheckCircle className="w-5 h-5" />
موافقة المالك
</button>
<button
onClick={() => onAction('owner_reject', request.id)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<XCircle className="w-5 h-5" />
رفض
</button>
<button
onClick={() => onAction('view_details', request)}
className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
<FileText className="w-5 h-5" />
</button>
</div>
)}
{request.status === 'owner_approved' && (
<div className="flex gap-3">
<button
onClick={() => onAction('admin_approve', request.id)}
className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-medium hover:bg-blue-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<CheckCircle className="w-5 h-5" />
موافقة الإدارة
</button>
<button
onClick={() => onAction('admin_reject', request.id)}
className="flex-1 bg-red-600 text-white py-3 rounded-xl font-medium hover:bg-red-700 transition-all transform hover:scale-105 flex items-center justify-center gap-2"
>
<XCircle className="w-5 h-5" />
رفض إداري
</button>
<button
onClick={() => onAction('view_details', request)}
className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
>
<FileText className="w-5 h-5" />
</button>
</div>
)}
{request.status === 'admin_approved' && (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => onAction('deliver_key', { id: request.id, type: 'owner' })}
disabled={request.ownerDelivered}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.ownerDelivered
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-blue-600 text-white hover:bg-blue-700 transform hover:scale-105'
}`}
>
<Key className="w-5 h-5" />
{request.ownerDelivered ? '✓ تم تسليم المفتاح' : 'تسليم المفتاح'}
</button>
<button
onClick={() => onAction('receive_property', { id: request.id, type: 'tenant' })}
disabled={request.tenantReceived}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.tenantReceived
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-green-600 text-white hover:bg-green-700 transform hover:scale-105'
}`}
>
<DoorOpen className="w-5 h-5" />
{request.tenantReceived ? '✓ تم الاستلام' : 'استلام العقار'}
</button>
</div>
</div>
)}
{request.status === 'active' && (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => onAction('tenant_leave', { id: request.id, type: 'tenant' })}
disabled={request.tenantLeft}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.tenantLeft
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-amber-600 text-white hover:bg-amber-700 transform hover:scale-105'
}`}
>
<DoorOpen className="w-5 h-5" />
{request.tenantLeft ? '✓ تم المغادرة' : 'مغادرة العقار'}
</button>
<button
onClick={() => onAction('owner_receive', { id: request.id, type: 'owner' })}
disabled={request.ownerReceived}
className={`py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition-all ${
request.ownerReceived
? 'bg-green-100 text-green-800 cursor-default'
: 'bg-purple-600 text-white hover:bg-purple-700 transform hover:scale-105'
}`}
>
<Key className="w-5 h-5" />
{request.ownerReceived ? '✓ تم الاستلام' : 'استلام العقار'}
</button>
</div>
{request.actualStartDate && (
<div className="bg-gray-100 p-3 rounded-lg">
<div className="flex justify-between text-sm mb-1">
<span>بدأ الإيجار: {request.actualStartDate}</span>
<span>المدة: {request.days} يوم</span>
</div>
<div className="w-full bg-gray-300 rounded-full h-2">
<div
className="bg-purple-600 h-2 rounded-full transition-all duration-500"
style={{ width: '45%' }}
/>
</div>
</div>
)}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};
export default function BookingRequests() {
const [requests, setRequests] = useState([
{
id: 'REQ001',
user: 'أحمد محمد',
userEmail: 'ahmed@example.com',
userPhone: '0938123456',
userType: 'syrian',
identityNumber: '123456789',
property: 'فيلا فاخرة في دمشق',
propertyId: 1,
startDate: '2024-03-01',
endDate: '2024-03-10',
days: 10,
totalAmount: 5000000,
dailyPrice: 500000,
commissionRate: 5,
commissionType: 'من المالك',
commissionAmount: 250000,
securityDeposit: 500000,
status: 'pending',
requestDate: '2024-02-25',
ownerApproved: false,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ002',
user: 'سارة أحمد',
userEmail: 'sara@example.com',
userPhone: '0945123789',
userType: 'passport',
identityNumber: 'AB123456',
property: 'شقة حديثة في حلب',
propertyId: 2,
startDate: '2024-03-05',
endDate: '2024-03-15',
days: 10,
totalAmount: 2500000,
dailyPrice: 250000,
commissionRate: 7,
commissionType: 'من المستأجر',
commissionAmount: 175000,
securityDeposit: 250000,
status: 'owner_approved',
requestDate: '2024-02-24',
ownerApproved: true,
adminApproved: false,
ownerDelivered: false,
tenantReceived: false,
tenantLeft: false,
ownerReceived: false,
securityDepositReturned: null,
contractSigned: false,
notes: '',
actualStartDate: null,
actualEndDate: null
},
{
id: 'REQ003',
user: 'محمد الحلبي',
userEmail: 'mohammed@example.com',
userPhone: '0956123456',
userType: 'syrian',
identityNumber: '987654321',
property: 'شقة بجانب البحر في اللاذقية',
propertyId: 3,
startDate: '2024-02-20',
endDate: '2024-03-20',
days: 30,
totalAmount: 9000000,
dailyPrice: 300000,
commissionRate: 5,
commissionType: 'من الاثنين',
commissionAmount: 450000,
securityDeposit: 500000,
status: 'active',
requestDate: '2024-02-15',
ownerApproved: true,
adminApproved: true,
ownerDelivered: true,
tenantReceived: true,
tenantLeft: false,
ownerReceived: false,
securityDepositReturned: null,
contractSigned: true,
notes: 'عقد موقع إلكترونياً',
actualStartDate: '2024-02-20',
actualEndDate: null
}
]);
const [filter, setFilter] = useState('all');
const [reasonDialog, setReasonDialog] = useState({ isOpen: false, requestId: null, type: null });
const [detailsDialog, setDetailsDialog] = useState({ isOpen: false, request: null });
const handleAction = (action, data) => {
switch(action) {
case 'owner_approve':
handleOwnerApprove(data);
break;
case 'owner_reject':
setReasonDialog({ isOpen: true, requestId: data, type: 'owner' });
break;
case 'admin_approve':
handleAdminApprove(data);
break;
case 'admin_reject':
setReasonDialog({ isOpen: true, requestId: data, type: 'admin' });
break;
case 'deliver_key':
handleKeyDelivery(data.id, data.type);
break;
case 'receive_property':
handleKeyDelivery(data.id, data.type);
break;
case 'tenant_leave':
handleEndRental(data.id, data.type);
break;
case 'owner_receive':
handleEndRental(data.id, data.type);
break;
case 'view_details':
setDetailsDialog({ isOpen: true, request: data });
break;
default:
break;
}
};
const handleRejectWithReason = (reason) => {
const { requestId, type } = reasonDialog;
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
status: 'rejected',
[type === 'owner' ? 'ownerApproved' : 'adminApproved']: false,
rejectionReason: reason,
rejectionType: type,
notes: `${type === 'owner' ? 'رفض من المالك' : 'رفض إداري'}: ${reason}`
}
: req
)
);
setReasonDialog({ isOpen: false, requestId: null, type: null });
};
const handleOwnerApprove = (requestId) => {
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
ownerApproved: true,
status: 'owner_approved',
notes: 'تمت الموافقة من قبل المالك'
}
: req
)
);
};
const handleAdminApprove = (requestId) => {
setRequests(prev =>
prev.map(req =>
req.id === requestId
? {
...req,
adminApproved: true,
status: 'admin_approved',
notes: 'تمت الموافقة من قبل الإدارة'
}
: req
)
);
};
const handleKeyDelivery = (requestId, userType) => {
setRequests(prev =>
prev.map(req => {
if (req.id === requestId) {
const updates = {};
if (userType === 'owner') {
updates.ownerDelivered = true;
updates.notes = 'تم تسليم المفتاح من قبل المالك';
}
if (userType === 'tenant') {
updates.tenantReceived = true;
updates.notes = 'تم استلام العقار من قبل المستأجر';
}
if ((userType === 'owner' && req.tenantReceived) ||
(userType === 'tenant' && req.ownerDelivered)) {
updates.status = 'active';
updates.contractSigned = true;
updates.actualStartDate = new Date().toISOString().split('T')[0];
updates.notes = 'بدأت فترة الإيجار الفعلية';
}
return { ...req, ...updates };
}
return req;
})
);
};
const handleEndRental = (requestId, userType) => {
setRequests(prev =>
prev.map(req => {
if (req.id === requestId) {
const updates = {};
if (userType === 'tenant') {
updates.tenantLeft = true;
updates.notes = 'غادر المستأجر العقار';
}
if (userType === 'owner') {
updates.ownerReceived = true;
updates.notes = 'استلم المالك العقار';
}
if ((userType === 'tenant' && req.ownerReceived) ||
(userType === 'owner' && req.tenantLeft)) {
const actualEndDate = new Date();
const actualStartDate = new Date(req.actualStartDate || req.startDate);
const actualDays = Math.ceil((actualEndDate - actualStartDate) / (1000 * 60 * 60 * 24));
const actualAmount = req.dailyPrice * actualDays;
const damageDeduction = 0;
const refundAmount = req.securityDeposit - damageDeduction;
updates.status = 'completed';
updates.actualEndDate = actualEndDate.toISOString().split('T')[0];
updates.actualDays = actualDays;
updates.actualAmount = actualAmount;
updates.securityDepositReturned = refundAmount;
updates.damageDeduction = damageDeduction;
updates.notes = `انتهى الإيجار بعد ${actualDays} يوم - المبلغ الفعلي: ${actualAmount.toLocaleString()} ل.س - مسترد الضمان: ${refundAmount.toLocaleString()} ل.س`;
}
return { ...req, ...updates };
}
return req;
})
);
};
const filteredRequests = requests.filter(req =>
filter === 'all' ? true : req.status === filter
);
const stats = {
total: requests.length,
pending: requests.filter(r => r.status === 'pending').length,
active: requests.filter(r => r.status === 'active').length,
completed: requests.filter(r => r.status === 'completed').length
};
return (
<div className="space-y-6">
<div className="grid grid-cols-4 gap-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-gradient-to-br from-blue-600 to-blue-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.total}</div>
<div className="text-sm opacity-90">إجمالي الطلبات</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-gradient-to-br from-yellow-600 to-yellow-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.pending}</div>
<div className="text-sm opacity-90">قيد الانتظار</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-gradient-to-br from-purple-600 to-purple-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.active}</div>
<div className="text-sm opacity-90">إيجارات نشطة</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-gradient-to-br from-green-600 to-green-700 text-white rounded-2xl p-4 shadow-lg"
>
<div className="text-3xl font-bold mb-1">{stats.completed}</div>
<div className="text-sm opacity-90">منتهية</div>
</motion.div>
</div>
<div className="bg-white/80 backdrop-blur-sm rounded-2xl p-4 shadow-lg border border-white/20">
<div className="flex items-center justify-between mb-3">
<h3 className="font-bold text-gray-700">تصفية حسب الحالة</h3>
<span className="text-sm text-gray-500">{filteredRequests.length} طلب</span>
</div>
<div className="flex flex-wrap gap-2">
{[
{ id: 'all', label: 'الكل', color: 'gray' },
{ id: 'pending', label: 'قيد الانتظار', color: 'yellow' },
{ id: 'owner_approved', label: 'موافقة المالك', color: 'blue' },
{ id: 'admin_approved', label: 'موافقة الإدارة', color: 'green' },
{ id: 'active', label: 'إيجارات نشطة', color: 'purple' },
{ id: 'completed', label: 'منتهية', color: 'gray' }
].map((tab) => (
<button
key={tab.id}
onClick={() => setFilter(tab.id)}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all transform hover:scale-105 ${
filter === tab.id
? `bg-${tab.color}-600 text-white shadow-lg`
: `bg-${tab.color}-100 text-${tab.color}-800 hover:bg-${tab.color}-200`
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="space-y-4">
{filteredRequests.map((request) => (
<RequestCard
key={request.id}
request={request}
onAction={handleAction}
/>
))}
</div>
{filteredRequests.length === 0 && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-16 bg-white rounded-2xl border-2 border-dashed border-gray-300"
>
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Clock className="w-12 h-12 text-gray-400" />
</div>
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد طلبات حجز</h3>
<p className="text-gray-500">لا توجد طلبات حجز في هذه الفئة</p>
</motion.div>
)}
<ReasonDialog
isOpen={reasonDialog.isOpen}
onClose={() => setReasonDialog({ isOpen: false, requestId: null, type: null })}
onConfirm={handleRejectWithReason}
title={reasonDialog.type === 'owner' ? 'رفض من المالك' : 'رفض إداري'}
/>
<RequestDetailsDialog
request={detailsDialog.request}
isOpen={detailsDialog.isOpen}
onClose={() => setDetailsDialog({ isOpen: false, request: null })}
/>
</div>
);
}

View File

@ -0,0 +1,139 @@
'use client';
import { motion } from 'framer-motion';
import { Users, Home, Calendar, DollarSign } from 'lucide-react';
import { useEffect, useState } from 'react';
export default function DashboardStats() {
const [stats, setStats] = useState({
totalUsers: 0,
totalProperties: 0,
activeBookings: 0,
totalRevenue: 0,
pendingRequests: 0,
availableProperties: 0
});
useEffect(() => {
setStats({
totalUsers: 156,
totalProperties: 89,
activeBookings: 34,
totalRevenue: 12500000,
pendingRequests: 12,
availableProperties: 45
});
}, []);
const formatNumber = (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const formatCurrency = (amount) => {
return `${formatNumber(amount)} ل.س`;
};
const cards = [
{
title: 'إجمالي المستخدمين',
value: stats.totalUsers,
icon: Users,
color: 'from-blue-600 to-blue-700',
bgColor: 'bg-blue-100',
iconColor: 'text-blue-600'
},
{
title: 'إجمالي العقارات',
value: stats.totalProperties,
icon: Home,
color: 'from-emerald-600 to-emerald-700',
bgColor: 'bg-emerald-100',
iconColor: 'text-emerald-600'
},
{
title: 'الحجوزات النشطة',
value: stats.activeBookings,
icon: Calendar,
color: 'from-purple-600 to-purple-700',
bgColor: 'bg-purple-100',
iconColor: 'text-purple-600'
},
{
title: 'الإيرادات',
value: formatCurrency(stats.totalRevenue),
icon: DollarSign,
color: 'from-amber-600 to-amber-700',
bgColor: 'bg-amber-100',
iconColor: 'text-amber-600'
}
];
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{cards.map((card, index) => {
const Icon = card.icon;
return (
<motion.div
key={card.title}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className={`bg-gradient-to-br ${card.color} text-white rounded-xl shadow-lg p-5`}
>
<div className="flex items-center justify-between mb-4">
<div className={`p-3 ${card.bgColor} bg-opacity-20 rounded-lg`}>
<Icon className="w-6 h-6" />
</div>
<div className="text-right">
<div className="text-2xl font-bold">{card.value}</div>
<div className="text-sm opacity-90">{card.title}</div>
</div>
</div>
<div className="text-xs opacity-75">
آخر تحديث: الآن
</div>
</motion.div>
);
})}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="bg-white border rounded-lg p-4"
>
<div className="text-sm text-gray-600 mb-1">طلبات حجز معلقة</div>
<div className="text-2xl font-bold text-yellow-600">{stats.pendingRequests}</div>
<div className="text-xs text-gray-500">بحاجة لموافقة</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="bg-white border rounded-lg p-4"
>
<div className="text-sm text-gray-600 mb-1">عقارات متاحة</div>
<div className="text-2xl font-bold text-green-600">{stats.availableProperties}</div>
<div className="text-xs text-gray-500">جاهزة للإيجار</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
className="bg-white border rounded-lg p-4"
>
<div className="text-sm text-gray-600 mb-1">نسبة الإشغال</div>
<div className="text-2xl font-bold text-blue-600">
{Math.round((stats.activeBookings / stats.totalProperties) * 100)}%
</div>
<div className="text-xs text-gray-500">من إجمالي العقارات</div>
</motion.div>
</div>
</div>
);
}

View File

@ -0,0 +1,352 @@
'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import {
DollarSign,
Calendar,
User,
Home,
Download,
Filter,
Search,
TrendingUp,
TrendingDown,
Wallet,
Shield
} from 'lucide-react';
import { formatCurrency } from '@/app/utils/calculations';
export default function LedgerBook({ userType = 'admin' }) {
const [transactions, setTransactions] = useState([]);
const [filteredTransactions, setFilteredTransactions] = useState([]);
const [dateRange, setDateRange] = useState({ start: '', end: '' });
const [searchTerm, setSearchTerm] = useState('');
const [summary, setSummary] = useState({
totalRevenue: 0,
pendingPayments: 0,
securityDeposits: 0,
commissionEarned: 0
});
useEffect(() => {
loadTransactions();
}, []);
useEffect(() => {
filterTransactions();
calculateSummary();
}, [transactions, dateRange, searchTerm]);
const loadTransactions = async () => {
const mockTransactions = [
{
id: 'T001',
date: '2024-02-20',
type: 'rent_payment',
description: 'دفعة إيجار - فيلا في دمشق',
amount: 500000,
commission: 25000,
fromUser: 'أحمد محمد',
toUser: 'مالك العقار',
propertyId: 1,
propertyName: 'luxuryVillaDamascus',
status: 'completed',
paymentMethod: 'cash'
},
{
id: 'T002',
date: '2024-02-19',
type: 'security_deposit',
description: 'سلفة ضمان - شقة في حلب',
amount: 250000,
commission: 0,
fromUser: 'سارة أحمد',
toUser: 'مالك العقار',
propertyId: 2,
propertyName: 'modernApartmentAleppo',
status: 'pending_refund',
paymentMethod: 'cash'
},
{
id: 'T003',
date: '2024-02-18',
type: 'commission',
description: 'عمولة منصة - فيلا في درعا',
amount: 30000,
commission: 30000,
fromUser: 'محمد الحلبي',
toUser: 'المنصة',
propertyId: 5,
propertyName: 'villaDaraa',
status: 'completed',
paymentMethod: 'cash'
}
];
setTransactions(mockTransactions);
};
const filterTransactions = () => {
let filtered = [...transactions];
if (dateRange.start && dateRange.end) {
filtered = filtered.filter(t =>
t.date >= dateRange.start && t.date <= dateRange.end
);
}
if (searchTerm) {
filtered = filtered.filter(t =>
t.description.includes(searchTerm) ||
t.fromUser.includes(searchTerm) ||
t.toUser.includes(searchTerm)
);
}
setFilteredTransactions(filtered);
};
const calculateSummary = () => {
const summary = filteredTransactions.reduce((acc, t) => {
if (t.type === 'rent_payment' || t.type === 'commission') {
acc.totalRevenue += t.amount;
}
if (t.type === 'security_deposit' && t.status === 'pending_refund') {
acc.securityDeposits += t.amount;
}
if (t.commission) {
acc.commissionEarned += t.commission;
}
if (t.status === 'pending') {
acc.pendingPayments += t.amount;
}
return acc;
}, {
totalRevenue: 0,
pendingPayments: 0,
securityDeposits: 0,
commissionEarned: 0
});
setSummary(summary);
};
const getTransactionIcon = (type) => {
switch(type) {
case 'rent_payment':
return <Home className="w-4 h-4 text-blue-600" />;
case 'security_deposit':
return <Shield className="w-4 h-4 text-green-600" />;
case 'commission':
return <TrendingUp className="w-4 h-4 text-amber-600" />;
default:
return <DollarSign className="w-4 h-4" />;
}
};
const exportToExcel = () => {
const csvContent = [
['التاريخ', 'الوصف', 'من', 'إلى', 'المبلغ', 'العمولة', 'الحالة'],
...filteredTransactions.map(t => [
t.date,
t.description,
t.fromUser,
t.toUser,
t.amount,
t.commission,
t.status
])
].map(row => row.join(',')).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ledger_${new Date().toISOString()}.csv`;
a.click();
};
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-gradient-to-br from-blue-600 to-blue-700 text-white rounded-xl p-5"
>
<div className="flex items-center justify-between mb-3">
<Wallet className="w-8 h-8 opacity-80" />
<span className="text-sm opacity-90">إجمالي الإيرادات</span>
</div>
<div className="text-2xl font-bold">{formatCurrency(summary.totalRevenue)}</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-gradient-to-br from-amber-600 to-amber-700 text-white rounded-xl p-5"
>
<div className="flex items-center justify-between mb-3">
<TrendingUp className="w-8 h-8 opacity-80" />
<span className="text-sm opacity-90">أرباح المنصة</span>
</div>
<div className="text-2xl font-bold">{formatCurrency(summary.commissionEarned)}</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-gradient-to-br from-green-600 to-green-700 text-white rounded-xl p-5"
>
<div className="flex items-center justify-between mb-3">
<Shield className="w-8 h-8 opacity-80" />
<span className="text-sm opacity-90">سلف الضمان</span>
</div>
<div className="text-2xl font-bold">{formatCurrency(summary.securityDeposits)}</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-gradient-to-br from-red-600 to-red-700 text-white rounded-xl p-5"
>
<div className="flex items-center justify-between mb-3">
<TrendingDown className="w-8 h-8 opacity-80" />
<span className="text-sm opacity-90">المدفوعات المعلقة</span>
</div>
<div className="text-2xl font-bold">{formatCurrency(summary.pendingPayments)}</div>
</motion.div>
</div>
<div className="bg-white rounded-xl p-5 shadow-sm border">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="بحث في المعاملات..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex gap-2">
<input
type="date"
value={dateRange.start}
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
className="px-3 py-2 border rounded-lg"
/>
<span className="text-gray-500 self-center">إلى</span>
<input
type="date"
value={dateRange.end}
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
className="px-3 py-2 border rounded-lg"
/>
</div>
<button
onClick={exportToExcel}
className="px-4 py-2 bg-green-600 text-white rounded-lg flex items-center gap-2 hover:bg-green-700"
>
<Download className="w-4 h-4" />
تصدير
</button>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b">
<tr>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">التاريخ</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">الوصف</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">من</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">إلى</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">المبلغ</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">العمولة</th>
<th className="px-6 py-4 text-right text-sm font-semibold text-gray-900">الحالة</th>
</tr>
</thead>
<tbody className="divide-y">
{filteredTransactions.map((transaction, index) => (
<motion.tr
key={transaction.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
className="hover:bg-gray-50"
>
<td className="px-6 py-4 text-sm">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-gray-400" />
{transaction.date}
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
{getTransactionIcon(transaction.type)}
<span className="text-sm font-medium">{transaction.description}</span>
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-gray-400" />
<span className="text-sm">{transaction.fromUser}</span>
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-gray-400" />
<span className="text-sm">{transaction.toUser}</span>
</div>
</td>
<td className="px-6 py-4 text-sm font-bold">
{formatCurrency(transaction.amount)}
</td>
<td className="px-6 py-4 text-sm text-amber-600">
{transaction.commission ? formatCurrency(transaction.commission) : '-'}
</td>
<td className="px-6 py-4">
<span className={`px-2 py-1 rounded-full text-xs ${
transaction.status === 'completed' ? 'bg-green-100 text-green-800' :
transaction.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800'
}`}>
{transaction.status === 'completed' ? 'مكتمل' :
transaction.status === 'pending' ? 'معلق' : 'بإنتظار الرد'}
</span>
</td>
</motion.tr>
))}
</tbody>
</table>
</div>
{filteredTransactions.length === 0 && (
<div className="text-center py-12">
<Wallet className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500">لا توجد معاملات في هذه الفترة</p>
</div>
)}
</div>
{userType === 'owner' && (
<div className="bg-blue-50 rounded-xl p-5">
<h3 className="font-bold mb-4 flex items-center gap-2">
<User className="w-5 h-5" />
أرصدة المستأجرين
</h3>
<div className="space-y-3">
</div>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,157 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import {
Edit,
Trash2,
Eye,
MapPin,
Bed,
Bath,
Square,
DollarSign,
Percent,
MoreVertical
} from 'lucide-react';
export default function PropertiesTable() {
const [properties, setProperties] = useState([
{
id: 1,
title: 'luxuryVillaDamascus',
type: 'villa',
location: 'دمشق, المزة',
price: 500000,
commission: 5,
commissionType: 'من المالك',
bedrooms: 5,
bathrooms: 4,
area: 450,
status: 'available',
bookings: 3
},
{
id: 2,
title: 'modernApartmentAleppo',
type: 'apartment',
location: 'حلب, الشهباء',
price: 250000,
commission: 7,
commissionType: 'من المستأجر',
bedrooms: 3,
bathrooms: 2,
area: 180,
status: 'booked',
bookings: 1
}
]);
const formatCurrency = (amount) => {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' ل.س';
};
const getStatusBadge = (status) => {
const styles = {
available: 'bg-green-100 text-green-800',
booked: 'bg-red-100 text-red-800',
maintenance: 'bg-yellow-100 text-yellow-800'
};
const labels = {
available: 'متاح',
booked: 'محجوز',
maintenance: 'صيانة'
};
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${styles[status]}`}>
{labels[status]}
</span>
);
};
return (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b">
<tr>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">العقار</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">الموقع</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">السعر/يوم</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">العمولة</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">المصدر</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">التفاصيل</th>
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-900">الحالة</th>
<th className="px-4 py-3 text-center text-sm font-semibold text-gray-900">الإجراءات</th>
</tr>
</thead>
<tbody className="divide-y">
{properties.map((property, index) => (
<motion.tr
key={property.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
className="hover:bg-gray-50"
>
<td className="px-4 py-3">
<div className="font-medium">{property.title}</div>
<div className="text-xs text-gray-500">{property.type}</div>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-1 text-sm">
<MapPin className="w-3 h-3 text-gray-400" />
{property.location}
</div>
</td>
<td className="px-4 py-3 font-bold text-blue-600">
{formatCurrency(property.price)}
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-1">
<Percent className="w-3 h-3 text-amber-500" />
{property.commission}%
</div>
</td>
<td className="px-4 py-3 text-sm">{property.commissionType}</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2 text-xs">
<Bed className="w-3 h-3" /> {property.bedrooms}
<Bath className="w-3 h-3 mr-2" /> {property.bathrooms}
<Square className="w-3 h-3 mr-2" /> {property.area}
</div>
</td>
<td className="px-4 py-3">
{getStatusBadge(property.status)}
</td>
<td className="px-4 py-3">
<div className="flex items-center justify-center gap-2">
<button className="p-1 hover:bg-blue-100 rounded text-blue-600">
<Eye className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-amber-100 rounded text-amber-600">
<Edit className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-red-100 rounded text-red-600">
<Trash2 className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-gray-100 rounded">
<MoreVertical className="w-4 h-4" />
</button>
</div>
</td>
</motion.tr>
))}
</tbody>
</table>
{properties.length === 0 && (
<div className="text-center py-12">
<Home className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500">لا توجد عقارات مضافة بعد</p>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,190 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import {
User,
Mail,
Phone,
Calendar,
Home,
DollarSign,
Search,
Filter,
Eye
} from 'lucide-react';
export default function UsersList() {
const [users, setUsers] = useState([
{
id: 1,
name: 'أحمد محمد',
email: 'ahmed@example.com',
phone: '0938123456',
identityType: 'syrian',
identityNumber: '123456789',
joinDate: '2024-01-15',
totalBookings: 3,
activeBookings: 1,
totalSpent: 1500000
},
{
id: 2,
name: 'سارة أحمد',
email: 'sara@example.com',
phone: '0945123789',
identityType: 'passport',
identityNumber: 'AB123456',
joinDate: '2024-02-10',
totalBookings: 2,
activeBookings: 0,
totalSpent: 500000
}
]);
const [searchTerm, setSearchTerm] = useState('');
const [selectedUser, setSelectedUser] = useState(null);
const filteredUsers = users.filter(user =>
user.name.includes(searchTerm) ||
user.email.includes(searchTerm) ||
user.phone.includes(searchTerm)
);
return (
<div className="space-y-4">
<div className="flex gap-4">
<div className="flex-1 relative">
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="بحث عن مستخدم..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pr-10 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
<button className="px-4 py-2 border rounded-lg flex items-center gap-2 hover:bg-gray-50">
<Filter className="w-4 h-4" />
تصفية
</button>
</div>
<div className="space-y-3">
{filteredUsers.map((user, index) => (
<motion.div
key={user.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow"
>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
<User className="w-6 h-6 text-blue-600" />
</div>
<div>
<h3 className="font-bold">{user.name}</h3>
<div className="flex flex-wrap gap-3 mt-1 text-sm text-gray-600">
<div className="flex items-center gap-1">
<Mail className="w-3 h-3" />
{user.email}
</div>
<div className="flex items-center gap-1">
<Phone className="w-3 h-3" />
{user.phone}
</div>
</div>
</div>
</div>
<div className="flex gap-4">
<div className="text-center">
<div className="text-lg font-bold text-blue-600">{user.totalBookings}</div>
<div className="text-xs text-gray-500">إجمالي الحجوزات</div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-green-600">{user.activeBookings}</div>
<div className="text-xs text-gray-500">حجوزات نشطة</div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-amber-600">
{user.totalSpent.toLocaleString()}
</div>
<div className="text-xs text-gray-500">إجمالي المنصرف</div>
</div>
</div>
<button
onClick={() => setSelectedUser(user)}
className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm flex items-center gap-1 hover:bg-blue-700"
>
<Eye className="w-4 h-4" />
عرض التفاصيل
</button>
</div>
</motion.div>
))}
</div>
{selectedUser && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="bg-white rounded-xl w-full max-w-2xl p-6"
>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">تفاصيل المستخدم</h2>
<button
onClick={() => setSelectedUser(null)}
className="p-1 hover:bg-gray-100 rounded"
>
</button>
</div>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-gray-500">الاسم</label>
<div className="font-medium">{selectedUser.name}</div>
</div>
<div>
<label className="text-sm text-gray-500">البريد الإلكتروني</label>
<div className="font-medium">{selectedUser.email}</div>
</div>
<div>
<label className="text-sm text-gray-500">رقم الهاتف</label>
<div className="font-medium">{selectedUser.phone}</div>
</div>
<div>
<label className="text-sm text-gray-500">نوع الهوية</label>
<div className="font-medium">
{selectedUser.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
</div>
</div>
<div>
<label className="text-sm text-gray-500">رقم الهوية</label>
<div className="font-medium">{selectedUser.identityNumber}</div>
</div>
<div>
<label className="text-sm text-gray-500">تاريخ التسجيل</label>
<div className="font-medium">{selectedUser.joinDate}</div>
</div>
</div>
<div className="border-t pt-4">
<h3 className="font-bold mb-3">سجل الحجوزات</h3>
<p className="text-gray-500 text-center py-4">
لا توجد حجوزات سابقة
</p>
</div>
</div>
</motion.div>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,162 @@
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react';
export default function BookingCalendar({ property, onDateSelect }) {
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedRange, setSelectedRange] = useState({ start: null, end: null });
const [bookedDates, setBookedDates] = useState(property.bookings || []);
const daysInMonth = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + 1,
0
).getDate();
const firstDayOfMonth = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth(),
1
).getDay();
const monthNames = [
'يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو',
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
];
const isDateBooked = (date) => {
const dateStr = date.toISOString().split('T')[0];
return bookedDates.some(booking => {
const start = new Date(booking.startDate);
const end = new Date(booking.endDate);
const current = new Date(date);
return current >= start && current <= end;
});
};
const isInSelectedRange = (date) => {
if (!selectedRange.start || !selectedRange.end) return false;
const dateStr = date.toISOString().split('T')[0];
return dateStr >= selectedRange.start && dateStr <= selectedRange.end;
};
const handleDateClick = (date) => {
if (isDateBooked(date)) return;
const dateStr = date.toISOString().split('T')[0];
if (!selectedRange.start || (selectedRange.start && selectedRange.end)) {
setSelectedRange({ start: dateStr, end: null });
} else {
if (dateStr > selectedRange.start) {
setSelectedRange({ ...selectedRange, end: dateStr });
onDateSelect?.({ start: selectedRange.start, end: dateStr });
} else {
setSelectedRange({ start: dateStr, end: null });
}
}
};
const changeMonth = (direction) => {
setCurrentMonth(new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + direction,
1
));
};
const renderDays = () => {
const days = [];
const totalDays = daysInMonth + firstDayOfMonth;
for (let i = 0; i < totalDays; i++) {
if (i < firstDayOfMonth) {
days.push(<div key={`empty-${i}`} className="p-2" />);
} else {
const dayNumber = i - firstDayOfMonth + 1;
const date = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth(),
dayNumber
);
const isBooked = isDateBooked(date);
const isSelected = isInSelectedRange(date);
const isToday = date.toDateString() === new Date().toDateString();
days.push(
<motion.button
key={dayNumber}
whileHover={!isBooked ? { scale: 1.1 } : {}}
onClick={() => handleDateClick(date)}
disabled={isBooked}
className={`
p-2 rounded-lg text-center transition-all
${isBooked ? 'bg-gray-200 text-gray-400 cursor-not-allowed line-through' : 'hover:bg-amber-100 cursor-pointer'}
${isSelected ? 'bg-amber-500 text-white hover:bg-amber-600' : ''}
${isToday && !isSelected && !isBooked ? 'border-2 border-amber-500' : ''}
`}
>
{dayNumber}
</motion.button>
);
}
}
return days;
};
return (
<div className="bg-white rounded-xl p-6 shadow-lg">
<div className="flex items-center justify-between mb-6">
<button
onClick={() => changeMonth(-1)}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<ChevronRight className="w-5 h-5" />
</button>
<h3 className="text-lg font-bold flex items-center gap-2">
<CalendarIcon className="w-5 h-5 text-amber-500" />
{monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()}
</h3>
<button
onClick={() => changeMonth(1)}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<ChevronLeft className="w-5 h-5" />
</button>
</div>
<div className="grid grid-cols-7 gap-1 mb-2 text-center text-sm font-semibold text-gray-600">
<div>جمعة</div>
<div>سبت</div>
<div>أحد</div>
<div>إثنين</div>
<div>ثلاثاء</div>
<div>أربعاء</div>
<div>خميس</div>
</div>
<div className="grid grid-cols-7 gap-1">
{renderDays()}
</div>
<div className="flex gap-4 mt-6 pt-4 border-t text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-amber-500 rounded" />
<span>محدد</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-gray-200 rounded line-through" />
<span>محجوز</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-amber-500 rounded" />
<span>اليوم</span>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,141 @@
'use client';
import { createContext, useContext, useState, useCallback } from 'react';
const PropertyContext = createContext();
export const useProperties = () => {
const context = useContext(PropertyContext);
if (!context) {
throw new Error('useProperties must be used within PropertyProvider');
}
return context;
};
export const PropertyProvider = ({ children }) => {
const [properties, setProperties] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const addProperty = useCallback(async (propertyData) => {
setLoading(true);
try {
await new Promise(resolve => setTimeout(resolve, 500));
const newProperty = {
id: Date.now().toString(),
...propertyData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
bookings: [],
status: propertyData.status || 'available'
};
setProperties(prev => [...prev, newProperty]);
return newProperty;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const updateProperty = useCallback(async (id, updates) => {
setLoading(true);
try {
await new Promise(resolve => setTimeout(resolve, 500));
setProperties(prev =>
prev.map(p => p.id === id
? { ...p, ...updates, updatedAt: new Date().toISOString() }
: p
)
);
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const deleteProperty = useCallback(async (id) => {
setLoading(true);
try {
await new Promise(resolve => setTimeout(resolve, 500));
setProperties(prev => prev.filter(p => p.id !== id));
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const getProperty = useCallback((id) => {
return properties.find(p => p.id === id);
}, [properties]);
const checkAvailability = useCallback((propertyId, startDate, endDate) => {
const property = properties.find(p => p.id === propertyId);
if (!property) return false;
const checkStart = new Date(startDate);
const checkEnd = new Date(endDate);
return !property.bookings?.some(booking => {
if (booking.status === 'cancelled' || booking.status === 'rejected') {
return false;
}
const bookingStart = new Date(booking.startDate);
const bookingEnd = new Date(booking.endDate);
return (
(checkStart >= bookingStart && checkStart <= bookingEnd) ||
(checkEnd >= bookingStart && checkEnd <= bookingEnd) ||
(checkStart <= bookingStart && checkEnd >= bookingEnd)
);
});
}, [properties]);
const getPropertiesByOwner = useCallback((ownerId) => {
return properties.filter(p => p.ownerId === ownerId);
}, [properties]);
const getAvailableProperties = useCallback(() => {
return properties.filter(p => p.status === 'available');
}, [properties]);
const updatePropertyStatus = useCallback(async (id, status) => {
return updateProperty(id, { status });
}, [updateProperty]);
const addBookingToProperty = useCallback(async (propertyId, bookingData) => {
const property = getProperty(propertyId);
if (!property) throw new Error('Property not found');
const updatedBookings = [...(property.bookings || []), bookingData];
return updateProperty(propertyId, { bookings: updatedBookings });
}, [getProperty, updateProperty]);
return (
<PropertyContext.Provider value={{
properties,
loading,
error,
addProperty,
updateProperty,
deleteProperty,
getProperty,
checkAvailability,
getPropertiesByOwner,
getAvailableProperties,
updatePropertyStatus,
addBookingToProperty
}}>
{children}
</PropertyContext.Provider>
);
};

View File

@ -6,7 +6,7 @@ const resources = {
en: {
translation: {
"home": "Home",
"ourProducts": "Our Products",
"ourProducts": "Our Properties",
"admin": "Admin",
"logoAlt": "SweetHome Logo",
"brandNamePart1": "Sweet",
@ -184,7 +184,7 @@ const resources = {
translation: {
"home": "الرئيسية",
"ourProducts": "منتجاتنا",
"ourProducts": "عقاراتنا",
"admin": "الإدارة",
// "logoAlt": "شعار سويت هوم",
"brandNamePart1": "سويت",

View File

@ -104,7 +104,7 @@ export default function RootLayout({ children }) {
<NavLink href="/">
{t("home")}
</NavLink>
<NavLink href="/products">
<NavLink href="/properties">
{t("ourProducts")}
</NavLink>
<NavLink href="/admin">
@ -182,7 +182,7 @@ export default function RootLayout({ children }) {
<MobileNavLink href="/" onClick={closeMobileMenu}>
{t("home")}
</MobileNavLink>
<MobileNavLink href="/products" onClick={closeMobileMenu}>
<MobileNavLink href="/properties" onClick={closeMobileMenu}>
{t("ourProducts")}
</MobileNavLink>
<MobileNavLink href="/admin" onClick={closeMobileMenu}>
@ -229,7 +229,7 @@ export default function RootLayout({ children }) {
</Link>
</li>
<li>
<Link href="/products" className="text-gray-400 hover:text-white transition-colors block py-1">
<Link href="/properties" className="text-gray-400 hover:text-white transition-colors block py-1">
{t("ourProducts")}
</Link>
</li>

View File

@ -61,7 +61,7 @@ export default function HomePage() {
<motion.div
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: 'url(/hero.jpg)',
backgroundImage: 'url(/Home.jpg)',
}}
initial={{ scale: 1.1 }}
animate={{ scale: 1 }}
@ -176,7 +176,7 @@ export default function HomePage() {
<option value="house">{t("house")}</option>
<option value="villa">{t("villa")}</option>
<option value="studio">{t("studio")}</option>
<option value="penthouse">{t("penthouse")}</option>
{/* <option value="penthouse">{t("penthouse")}</option> */}
</select>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">

713
app/properties/page.js Normal file
View File

@ -0,0 +1,713 @@
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import {
Search,
MapPin,
Bed,
Bath,
Square,
DollarSign,
Filter,
Grid3x3,
List,
Heart,
Share2,
ChevronDown,
Star,
Camera,
Home,
Building2,
Trees,
Waves,
Warehouse,
Sparkles,
Shield,
Calendar,
Phone,
Mail,
MessageCircle
} from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
const PropertyCard = ({ property, viewMode = 'grid' }) => {
const [isFavorite, setIsFavorite] = useState(false);
const [currentImage, setCurrentImage] = useState(0);
const formatCurrency = (amount) => {
return amount?.toLocaleString() + ' ل.س';
};
const getPropertyTypeIcon = (type) => {
switch(type) {
case 'villa': return <Home className="w-4 h-4" />;
case 'apartment': return <Building2 className="w-4 h-4" />;
case 'house': return <Home className="w-4 h-4" />;
case 'studio': return <Building2 className="w-4 h-4" />;
default: return <Home className="w-4 h-4" />;
}
};
const getPropertyTypeLabel = (type) => {
switch(type) {
case 'villa': return 'فيلا';
case 'apartment': return 'شقة';
case 'house': return 'بيت';
case 'studio': return 'استوديو';
default: return type;
}
};
if (viewMode === 'list') {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden border border-gray-100"
>
<div className="flex flex-col md:flex-row">
<div className="md:w-1/3 relative h-64 md:h-auto bg-gray-100">
<Image
src={property.images[currentImage] || '/property-placeholder.jpg'}
alt={property.title}
fill
className="object-cover"
/>
{property.images.length > 1 && (
<div className="absolute bottom-2 left-2 right-2 flex justify-center gap-1">
{property.images.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentImage(idx)}
className={`w-1.5 h-1.5 rounded-full transition-all ${
idx === currentImage ? 'bg-gray-800 w-3' : 'bg-white/70'
}`}
/>
))}
</div>
)}
<div className="absolute top-2 right-2 flex gap-2">
<button
onClick={() => setIsFavorite(!isFavorite)}
className="w-8 h-8 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white transition-colors shadow-sm"
>
<Heart className={`w-4 h-4 ${isFavorite ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
</button>
</div>
{property.isNew && (
<div className="absolute top-2 left-2 bg-gray-800 text-white px-2 py-1 rounded-lg text-xs font-medium">
جديد
</div>
)}
</div>
<div className="md:w-2/3 p-6">
<div className="flex justify-between items-start mb-3">
<div>
<div className="flex items-center gap-2 mb-2">
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium flex items-center gap-1">
{getPropertyTypeIcon(property.type)}
{getPropertyTypeLabel(property.type)}
</span>
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
property.status === 'available'
? 'bg-gray-800 text-white'
: 'bg-gray-200 text-gray-600'
}`}>
{property.status === 'available' ? 'متاح' : 'محجوز'}
</span>
</div>
<h3 className="text-xl font-bold text-gray-900 mb-1">{property.title}</h3>
<div className="flex items-center gap-1 text-gray-500 text-sm mb-3">
<MapPin className="w-4 h-4" />
{property.location.city}، {property.location.district}
</div>
</div>
<div className="text-left">
<div className="text-2xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
</div>
</div>
<div className="flex flex-wrap gap-4 mb-4">
<div className="flex items-center gap-1 text-gray-600">
<Bed className="w-4 h-4" />
<span>{property.bedrooms} غرف</span>
</div>
<div className="flex items-center gap-1 text-gray-600">
<Bath className="w-4 h-4" />
<span>{property.bathrooms} حمامات</span>
</div>
<div className="flex items-center gap-1 text-gray-600">
<Square className="w-4 h-4" />
<span>{property.area} م²</span>
</div>
</div>
<p className="text-gray-600 text-sm mb-4 line-clamp-2">
{property.description}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{property.features.slice(0, 4).map((feature, idx) => (
<span key={idx} className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
{feature}
</span>
))}
{property.features.length > 4 && (
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
+{property.features.length - 4}
</span>
)}
</div>
<div className="flex gap-3">
<Link
href={`/property/${property.id}`}
className="flex-1 bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
>
عرض التفاصيل
</Link>
<button className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2">
<Phone className="w-4 h-4" />
</button>
</div>
</div>
</div>
</motion.div>
);
}
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden border border-gray-100"
>
<div className="relative h-56 bg-gray-100">
<Image
src={property.images[currentImage] || '/property-placeholder.jpg'}
alt={property.title}
fill
className="object-cover"
/>
{property.images.length > 1 && (
<div className="absolute bottom-2 left-2 right-2 flex justify-center gap-1">
{property.images.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentImage(idx)}
className={`w-1.5 h-1.5 rounded-full transition-all ${
idx === currentImage ? 'bg-gray-800 w-3' : 'bg-white/70'
}`}
/>
))}
</div>
)}
<div className="absolute top-2 right-2 flex gap-2">
<button
onClick={() => setIsFavorite(!isFavorite)}
className="w-8 h-8 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white transition-colors shadow-sm"
>
<Heart className={`w-4 h-4 ${isFavorite ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
</button>
</div>
{property.isNew && (
<div className="absolute top-2 left-2 bg-gray-800 text-white px-2 py-1 rounded-lg text-xs font-medium">
جديد
</div>
)}
</div>
<div className="p-5">
<div className="flex justify-between items-start mb-3">
<div>
<div className="flex items-center gap-2 mb-2">
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium flex items-center gap-1">
{getPropertyTypeIcon(property.type)}
{getPropertyTypeLabel(property.type)}
</span>
{property.status === 'available' && (
<span className="px-2 py-1 bg-gray-800 text-white rounded-lg text-xs font-medium">
متاح
</span>
)}
</div>
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
<MapPin className="w-3 h-3" />
<span className="line-clamp-1">{property.location.city}، {property.location.district}</span>
</div>
</div>
<div className="text-left">
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
</div>
</div>
<div className="flex justify-between items-center mb-4">
<div className="flex items-center gap-3 text-gray-600 text-sm">
<div className="flex items-center gap-1">
<Bed className="w-4 h-4" />
<span>{property.bedrooms}</span>
</div>
<div className="flex items-center gap-1">
<Bath className="w-4 h-4" />
<span>{property.bathrooms}</span>
</div>
<div className="flex items-center gap-1">
<Square className="w-4 h-4" />
<span>{property.area}م²</span>
</div>
</div>
<div className="flex items-center gap-1">
<Star className="w-4 h-4 fill-gray-400 text-gray-400" />
<span className="text-sm font-medium text-gray-700">{property.rating || 4.5}</span>
</div>
</div>
<div className="flex flex-wrap gap-2 mb-4">
{property.features.slice(0, 3).map((feature, idx) => (
<span key={idx} className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
{feature}
</span>
))}
{property.features.length > 3 && (
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
+{property.features.length - 3}
</span>
)}
</div>
<Link
href={`/property/${property.id}`}
className="block w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
>
عرض التفاصيل
</Link>
</div>
</motion.div>
);
};
const FilterBar = ({ filters, onFilterChange }) => {
const [showFilters, setShowFilters] = useState(false);
const propertyTypes = [
{ id: 'all', label: 'الكل' },
{ id: 'apartment', label: 'شقة', icon: Building2 },
{ id: 'villa', label: 'فيلا', icon: Home },
{ id: 'house', label: 'بيت', icon: Home },
{ id: 'studio', label: 'استوديو', icon: Building2 }
];
const priceRanges = [
{ id: 'all', label: 'جميع الأسعار' },
{ id: '0-500000', label: 'أقل من 500,000' },
{ id: '500000-1000000', label: '500,000 - 1,000,000' },
{ id: '1000000-2000000', label: '1,000,000 - 2,000,000' },
{ id: '2000000-5000000', label: '2,000,000 - 5,000,000' },
{ id: '5000000+', label: 'أكثر من 5,000,000' }
];
const cities = [
{ id: 'all', label: 'جميع المدن' },
{ id: 'damascus', label: 'دمشق' },
{ id: 'aleppo', label: 'حلب' },
{ id: 'homs', label: 'حمص' },
{ id: 'latakia', label: 'اللاذقية' },
{ id: 'daraa', label: 'درعا' }
];
return (
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div className="flex flex-col md:flex-row gap-3 mb-4">
<div className="flex-1 relative">
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
placeholder="ابحث عن عقار..."
className="w-full pr-12 px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 transition-all"
value={filters.search}
onChange={(e) => onFilterChange({ ...filters, search: e.target.value })}
/>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className="px-6 py-3 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2 text-gray-700"
>
<Filter className="w-5 h-5" />
فلاتر متقدمة
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
</button>
</div>
<AnimatePresence>
{showFilters && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden"
>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-gray-100">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">نوع العقار</label>
<div className="flex flex-wrap gap-2">
{propertyTypes.map((type) => {
const Icon = type.icon;
return (
<button
key={type.id}
onClick={() => onFilterChange({ ...filters, propertyType: type.id })}
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-1 ${
filters.propertyType === type.id
? 'bg-gray-800 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{Icon && <Icon className="w-4 h-4" />}
{type.label}
</button>
);
})}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">المدينة</label>
<select
value={filters.city}
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
>
{cities.map((city) => (
<option key={city.id} value={city.id}>{city.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">نطاق السعر</label>
<select
value={filters.priceRange}
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
>
{priceRanges.map((range) => (
<option key={range.id} value={range.id}>{range.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">غرف النوم</label>
<select
value={filters.bedrooms}
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
>
<option value="all">جميع الأعداد</option>
<option value="1">1+</option>
<option value="2">2+</option>
<option value="3">3+</option>
<option value="4">4+</option>
<option value="5">5+</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">المساحة (م²)</label>
<div className="flex gap-2">
<input
type="number"
placeholder="من"
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
value={filters.minArea}
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
/>
<input
type="number"
placeholder="إلى"
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
value={filters.maxArea}
onChange={(e) => onFilterChange({ ...filters, maxArea: e.target.value })}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">المميزات</label>
<div className="flex flex-wrap gap-2">
{['مسبح', 'حديقة', 'موقف سيارات', 'أمن', 'مصعد', 'تكييف'].map((feature) => (
<button
key={feature}
onClick={() => {
const newFeatures = filters.features.includes(feature)
? filters.features.filter(f => f !== feature)
: [...filters.features, feature];
onFilterChange({ ...filters, features: newFeatures });
}}
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all ${
filters.features.includes(feature)
? 'bg-gray-800 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{feature}
</button>
))}
</div>
</div>
</div>
<div className="flex gap-3 mt-4 pt-4 border-t border-gray-100">
<button
onClick={() => onFilterChange({
search: '',
propertyType: 'all',
city: 'all',
priceRange: 'all',
bedrooms: 'all',
minArea: '',
maxArea: '',
features: []
})}
className="px-6 py-2 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700"
>
إعادة تعيين
</button>
<button
onClick={() => setShowFilters(false)}
className="px-6 py-2 bg-gray-800 text-white rounded-xl font-medium hover:bg-gray-900 transition-colors"
>
تطبيق الفلاتر
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default function PropertiesPage() {
const [viewMode, setViewMode] = useState('grid');
const [sortBy, setSortBy] = useState('newest');
const [filters, setFilters] = useState({
search: '',
propertyType: 'all',
city: 'all',
priceRange: 'all',
bedrooms: 'all',
minArea: '',
maxArea: '',
features: []
});
const [properties] = useState([
{
id: 1,
title: 'فيلا فاخرة في المزة',
description: 'فيلا فاخرة مع حديقة خاصة ومسبح في أفضل أحياء دمشق.',
type: 'villa',
price: 500000,
priceUnit: 'daily',
location: { city: 'دمشق', district: 'المزة' },
bedrooms: 5,
bathrooms: 4,
area: 450,
features: ['مسبح', 'حديقة خاصة', 'موقف سيارات', 'أمن'],
images: ['/villa1.jpg'],
status: 'available',
rating: 4.8,
isNew: true
},
{
id: 2,
title: 'شقة حديثة في الشهباء',
description: 'شقة عصرية في حي الشهباء الراقي بحلب.',
type: 'apartment',
price: 250000,
priceUnit: 'daily',
location: { city: 'حلب', district: 'الشهباء' },
bedrooms: 3,
bathrooms: 2,
area: 180,
features: ['مطبخ مجهز', 'بلكونة', 'موقف سيارات', 'مصعد'],
images: ['/apartment1.jpg'],
status: 'available',
rating: 4.5,
isNew: false
},
{
id: 3,
title: 'بيت عائلي في بابا عمرو',
description: 'بيت واسع مناسب للعائلات في حمص.',
type: 'house',
price: 350000,
priceUnit: 'daily',
location: { city: 'حمص', district: 'بابا عمرو' },
bedrooms: 4,
bathrooms: 3,
area: 300,
features: ['حديقة كبيرة', 'موقف سيارات', 'مدفأة'],
images: ['/house1.jpg'],
status: 'booked',
rating: 4.3,
isNew: false
},
{
id: 4,
title: 'شقة بجانب البحر',
description: 'شقة رائعة مع إطلالة بحرية في اللاذقية.',
type: 'apartment',
price: 300000,
priceUnit: 'daily',
location: { city: 'اللاذقية', district: 'الشاطئ الأزرق' },
bedrooms: 3,
bathrooms: 2,
area: 200,
features: ['إطلالة بحرية', 'شرفة', 'تكييف'],
images: ['/seaside1.jpg'],
status: 'available',
rating: 4.9,
isNew: true
},
{
id: 5,
title: 'فيلا في درعا',
description: 'فيلا فاخرة في حي الأطباء بدرعا.',
type: 'villa',
price: 400000,
priceUnit: 'daily',
location: { city: 'درعا', district: 'حي الأطباء' },
bedrooms: 4,
bathrooms: 3,
area: 350,
features: ['حديقة مثمرة', 'أنظمة أمن', 'مسبح'],
images: ['/villa4.jpg'],
status: 'available',
rating: 4.6,
isNew: false
}
]);
const filteredProperties = properties
.filter(property => {
if (filters.search && !property.title.includes(filters.search) && !property.description.includes(filters.search)) {
return false;
}
if (filters.propertyType !== 'all' && property.type !== filters.propertyType) {
return false;
}
if (filters.city !== 'all' && property.location.city !== filters.city) {
return false;
}
if (filters.priceRange !== 'all') {
const [min, max] = filters.priceRange.split('-');
if (max) {
if (property.price < parseInt(min) || property.price > parseInt(max)) return false;
} else if (filters.priceRange.endsWith('+')) {
const min = parseInt(filters.priceRange.replace('+', ''));
if (property.price < min) return false;
}
}
if (filters.bedrooms !== 'all' && property.bedrooms < parseInt(filters.bedrooms)) {
return false;
}
if (filters.minArea && property.area < parseInt(filters.minArea)) return false;
if (filters.maxArea && property.area > parseInt(filters.maxArea)) return false;
if (filters.features.length > 0) {
if (!filters.features.every(f => property.features.includes(f))) return false;
}
return true;
})
.sort((a, b) => {
switch(sortBy) {
case 'price_asc': return a.price - b.price;
case 'price_desc': return b.price - a.price;
case 'rating': return b.rating - a.rating;
default: return b.isNew ? 1 : -1;
}
});
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-8"
>
<h1 className="text-4xl font-bold text-gray-900 mb-2">عقارات للإيجار</h1>
<p className="text-gray-500">أفضل العقارات في سوريا</p>
</motion.div>
<FilterBar filters={filters} onFilterChange={setFilters} />
<div className="flex justify-between items-center my-6">
<div className="text-gray-600">
<span className="font-bold text-gray-900">{filteredProperties.length}</span> عقار متاح
</div>
<div className="flex gap-3">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-gray-700"
>
<option value="newest">الأحدث</option>
<option value="price_asc">السعر: من الأقل</option>
<option value="price_desc">السعر: من الأعلى</option>
<option value="rating">التقييم</option>
</select>
<div className="flex gap-2">
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded-xl transition-colors ${
viewMode === 'grid' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
<Grid3x3 className="w-5 h-5" />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded-xl transition-colors ${
viewMode === 'list' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
<List className="w-5 h-5" />
</button>
</div>
</div>
</div>
<div className={viewMode === 'grid'
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'
: 'space-y-4'
}>
{filteredProperties.map((property) => (
<PropertyCard key={property.id} property={property} viewMode={viewMode} />
))}
</div>
{filteredProperties.length === 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-16"
>
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Home className="w-12 h-12 text-gray-400" />
</div>
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد عقارات</h3>
<p className="text-gray-500">جرب تغيير معايير البحث</p>
</motion.div>
)}
</div>
</div>
);
}

681
app/property/[id]/page.js Normal file
View File

@ -0,0 +1,681 @@
'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import {
MapPin,
Bed,
Bath,
Square,
DollarSign,
Heart,
Share2,
Phone,
Mail,
MessageCircle,
Calendar,
Shield,
Star,
ChevronLeft,
ChevronRight,
Check,
X,
Wifi,
Car,
Coffee,
Wind,
Thermometer,
Lock,
Camera,
Home,
Building2,
Users,
Ruler,
CalendarDays,
Clock,
Award,
FileText,
Printer,
Download,
ArrowLeft
} from 'lucide-react';
export default function PropertyDetailsPage() {
const params = useParams();
const [currentImage, setCurrentImage] = useState(0);
const [showContact, setShowContact] = useState(false);
const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
const [selectedDuration, setSelectedDuration] = useState(1);
const [property, setProperty] = useState(null);
const [loading, setLoading] = useState(true);
const propertiesData = {
1: {
id: 1,
title: 'فيلا فاخرة في المزة',
description: `تتميز هذه الفيلا الفاخرة بتصميمها العصري وموقعها المميز في أفضل أحياء دمشق. تم بناء الفيلا بأعلى المواصفات باستخدام أفضل المواد، مع مساحات واسعة وحديقة خاصة.
المميزات الرئيسية:
• موقع راقي وقريب من جميع الخدمات
• تصميم داخلي عصري مع أثاث فاخر
• إطلالة رائعة على المدينة
• خصوصية تامة وأمن على مدار الساعة
المساحات الداخلية:
• الطابق الأرضي: صالة استقبال كبيرة (80 م²)، مجلس رجال (40 م²)، مجلس نساء (35 م²)، مطبخ (25 م²)، غرفة طعام (30 م²)
• الطابق الأول: 5 غرف نوم ماستر مع حمامات خاصة (كل غرفة 35-45 م²)
• الطابق الثاني: غرفة معيشة عائلية (50 م²)، غرفة ترفيه (40 م²)، سطح مع إطلالة (100 م²)
الخدمات القريبة:
• مدارس وجامعات على بعد 5 دقائق
• مستشفيات ومراكز طبية
• مولات تجارية ومطاعم
• حدائق عامة ومسارات مشي`,
type: 'villa',
price: 500000,
priceUnit: 'daily',
location: {
city: 'دمشق',
district: 'المزة',
address: 'شارع المزة - فيلات غربية',
lat: 33.5,
lng: 36.3
},
bedrooms: 5,
bathrooms: 4,
area: 450,
features: [
{ name: 'مسبح', available: true, description: 'مسبح خاص بمساحة 40 م²' },
{ name: 'حديقة خاصة', available: true, description: 'حديقة بمساحة 200 م² مع نوافير' },
{ name: 'موقف سيارات', available: true, description: 'موقف يتسع لـ 4 سيارات' },
{ name: 'أمن 24/7', available: true, description: 'كاميرات مراقبة وحراسة' },
{ name: 'تدفئة مركزية', available: true, description: 'تدفئة مركزية لجميع الغرف' },
{ name: 'تكييف مركزي', available: true, description: 'تكييف مركزي في جميع الغرف' },
{ name: 'مطبخ مجهز', available: true, description: 'مطبخ أمريكي مجهز بالكامل' },
{ name: 'غرفة خادمة', available: true, description: 'غرفة خادمة مع حمام خاص' },
{ name: 'مصعد', available: false, description: 'قابل للتركيب' },
{ name: 'واي فاي', available: true, description: 'ألياف بصرية' }
],
images: [
'/villa1.jpg',
'/villa2.jpg',
'/villa3.jpg',
'/villa4.jpg',
'/villa5.jpg',
'/villa6.jpg'
],
status: 'available',
rating: 4.8,
reviews: 24,
reviewList: [
{ user: 'أحمد محمد', rating: 5, comment: 'فيلا رائعة ونظيفة، موقع ممتاز', date: '2024-01-15' },
{ user: 'سارة أحمد', rating: 5, comment: 'إقامة مريحة، خدمات ممتازة', date: '2024-01-10' },
{ user: 'خالد عمر', rating: 4, comment: 'مكان جميل ولكن السعر مرتفع قليلاً', date: '2023-12-20' }
],
owner: {
name: 'محمد الخالد',
phone: '0933111222',
email: 'mohamed@example.com',
rating: 4.9,
properties: 5,
memberSince: '2023',
responseRate: '98%',
responseTime: 'خلال ساعة'
},
nearby: [
{ type: 'مدرسة', distance: '500م' },
{ type: 'مستشفى', distance: '1كم' },
{ type: 'مول تجاري', distance: '2كم' },
{ type: 'مطعم', distance: '300م' },
{ type: 'جامعة', distance: '1.5كم' },
{ type: 'حديقة', distance: '800م' }
],
specifications: {
constructionYear: 2022,
floor: 'أرضي + 2',
parking: 4,
gardenArea: 200,
poolArea: 40,
furnished: true,
airConditioning: 'مركزي',
heating: 'مركزي',
electricity: '220V',
water: 'شبكة عامة'
},
rules: [
'لا يسمح بالحيوانات الأليفة',
'لا يسمح بالتدخين داخل الغرف',
'حفلات مسموحة بعد التنسيق',
'وقت المغادرة: 12:00 ظهراً'
]
},
2: {
id: 2,
title: 'شقة حديثة في الشهباء',
description: 'شقة عصرية في حي الشهباء الراقي بحلب. إطلالة رائعة وتشطيب فاخر.',
type: 'apartment',
price: 250000,
priceUnit: 'daily',
location: {
city: 'حلب',
district: 'الشهباء',
address: 'شارع النيل - بناء الرحاب',
lat: 36.2,
lng: 37.1
},
bedrooms: 3,
bathrooms: 2,
area: 180,
features: [
{ name: 'مطبخ مجهز', available: true, description: 'مطبخ أمريكي' },
{ name: 'بلكونة', available: true, description: 'بلكونة بمساحة 10 م²' },
{ name: 'موقف سيارات', available: true, description: 'موقف خاص' },
{ name: 'مصعد', available: true, description: 'مصعد حديث' }
],
images: ['/apartment1.jpg', '/apartment2.jpg'],
status: 'available',
rating: 4.5,
reviews: 12,
owner: {
name: 'أحمد حلبي',
phone: '0944222333',
email: 'ahmad@example.com',
rating: 4.7,
properties: 3,
memberSince: '2023'
},
nearby: [
{ type: 'مدرسة', distance: '300م' },
{ type: 'مستشفى', distance: '1.2كم' },
{ type: 'مول', distance: '500م' }
]
}
};
useEffect(() => {
setLoading(true);
setTimeout(() => {
setProperty(propertiesData[params.id] || propertiesData[1]);
setLoading(false);
}, 500);
}, [params.id]);
const formatCurrency = (amount) => {
return amount?.toLocaleString() + ' ل.س';
};
const calculateTotalPrice = () => {
if (!property) return 0;
const days = bookingDates.start && bookingDates.end
? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
: selectedDuration;
return property.price * (days > 0 ? days : 1);
};
const handleBooking = () => {
alert('تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً.');
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-gray-200 border-t-gray-800 rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-gray-600">جاري تحميل تفاصيل العقار...</p>
</div>
</div>
);
}
if (!property) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Home className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-gray-900 mb-2">العقار غير موجود</h2>
<p className="text-gray-600 mb-4">لم نتمكن من العثور على العقار المطلوب</p>
<Link href="/properties" className="bg-gray-800 text-white px-6 py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors">
العودة إلى العقارات
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b sticky top-16 z-40 shadow-sm">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between h-16">
<Link href="/properties" className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors">
<ArrowLeft className="w-5 h-5" />
<span>العودة إلى العقارات</span>
</Link>
<div className="flex gap-2">
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
<Heart className="w-5 h-5 text-gray-600" />
</button>
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
<Share2 className="w-5 h-5 text-gray-600" />
</button>
</div>
</div>
</div>
</div>
<div className="container mx-auto px-4 py-8">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="relative h-[500px] rounded-2xl overflow-hidden group bg-gray-100">
<Image
src={property.images[currentImage] || '/property-placeholder.jpg'}
alt={property.title}
fill
className="object-cover"
/>
{property.images.length > 1 && (
<>
<button
onClick={() => setCurrentImage(prev => Math.max(0, prev - 1))}
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
onClick={() => setCurrentImage(prev => Math.min(property.images.length - 1, prev + 1))}
className="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
>
<ChevronRight className="w-5 h-5" />
</button>
</>
)}
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
{property.images.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentImage(idx)}
className={`w-2 h-2 rounded-full transition-all ${
idx === currentImage ? 'bg-gray-800 w-4' : 'bg-white/70 hover:bg-white'
}`}
/>
))}
</div>
<div className="absolute bottom-4 right-4 bg-black/50 text-white px-3 py-1 rounded-full text-sm backdrop-blur-sm">
<Camera className="w-4 h-4 inline ml-1" />
{currentImage + 1} / {property.images.length}
</div>
</div>
<div className="grid grid-cols-2 gap-4">
{property.images.slice(1, 5).map((img, idx) => (
<div
key={idx}
onClick={() => setCurrentImage(idx + 1)}
className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100"
>
<Image src={img} alt={`${property.title} ${idx + 2}`} fill className="object-cover" />
</div>
))}
</div>
</div>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<div className="flex justify-between items-start mb-4">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">{property.title}</h1>
<div className="flex items-center gap-2 text-gray-500">
<MapPin className="w-5 h-5" />
<span>{property.location.address}</span>
</div>
</div>
<div className="text-left">
<div className="text-3xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
<div className="text-sm text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Star className="w-5 h-5 fill-gray-800 text-gray-800" />
<span className="font-bold text-gray-900">{property.rating}</span>
<span className="text-gray-500">({property.reviews} تقييم)</span>
</div>
<div className="w-px h-4 bg-gray-200" />
<span className={`font-medium ${
property.status === 'available' ? 'text-gray-800' : 'text-gray-500'
}`}>
{property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'}
</span>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">المواصفات الرئيسية</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center p-4 bg-gray-50 rounded-xl">
<Bed className="w-6 h-6 text-gray-700 mx-auto mb-2" />
<div className="font-bold text-gray-900">{property.bedrooms}</div>
<div className="text-sm text-gray-500">غرف نوم</div>
</div>
<div className="text-center p-4 bg-gray-50 rounded-xl">
<Bath className="w-6 h-6 text-gray-700 mx-auto mb-2" />
<div className="font-bold text-gray-900">{property.bathrooms}</div>
<div className="text-sm text-gray-500">حمامات</div>
</div>
<div className="text-center p-4 bg-gray-50 rounded-xl">
<Square className="w-6 h-6 text-gray-700 mx-auto mb-2" />
<div className="font-bold text-gray-900">{property.area}</div>
<div className="text-sm text-gray-500">م²</div>
</div>
<div className="text-center p-4 bg-gray-50 rounded-xl">
<Home className="w-6 h-6 text-gray-700 mx-auto mb-2" />
<div className="font-bold text-gray-900">
{property.type === 'villa' ? 'فيلا' :
property.type === 'apartment' ? 'شقة' : 'بيت'}
</div>
<div className="text-sm text-gray-500">نوع العقار</div>
</div>
</div>
{property.specifications && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="flex items-center gap-2 text-sm text-gray-600">
<Calendar className="w-4 h-4" />
<span>بناء: {property.specifications.constructionYear}</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Ruler className="w-4 h-4" />
<span>حديقة: {property.specifications.gardenArea} م²</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Car className="w-4 h-4" />
<span>موقف: {property.specifications.parking}</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Wind className="w-4 h-4" />
<span>{property.specifications.airConditioning}</span>
</div>
</div>
)}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">وصف العقار</h2>
<p className="text-gray-600 whitespace-pre-line leading-relaxed">{property.description}</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">المميزات والخدمات</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{property.features.map((feature, idx) => (
<div key={idx} className="flex items-start gap-3 p-3 bg-gray-50 rounded-xl">
<div className={`w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0 ${
feature.available ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-500'
}`}>
{feature.available ? (
<Check className="w-4 h-4" />
) : (
<X className="w-4 h-4" />
)}
</div>
<div>
<div className="flex items-center gap-2">
<span className="text-2xl">{feature.icon}</span>
<span className={`font-medium ${feature.available ? 'text-gray-900' : 'text-gray-400'}`}>
{feature.name}
</span>
</div>
{feature.description && (
<p className={`text-sm mt-1 ${feature.available ? 'text-gray-500' : 'text-gray-400'}`}>
{feature.description}
</p>
)}
</div>
</div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">القرب من الخدمات</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{property.nearby.map((item, idx) => (
<div key={idx} className="flex items-center justify-between p-3 bg-gray-50 rounded-xl">
<div className="flex items-center gap-2">
<span className="text-xl">{item.icon}</span>
<span className="text-gray-700">{item.type}</span>
</div>
<span className="font-medium text-gray-900">{item.distance}</span>
</div>
))}
</div>
</motion.div>
{property.reviewList && property.reviewList.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">تقييمات المستأجرين</h2>
<div className="space-y-4">
{property.reviewList.map((review, idx) => (
<div key={idx} className="border-b border-gray-100 last:border-0 pb-4 last:pb-0">
<div className="flex justify-between items-start mb-2">
<div>
<span className="font-bold text-gray-900">{review.user}</span>
<div className="flex items-center gap-1 mt-1">
{[...Array(5)].map((_, i) => (
<Star key={i} className={`w-4 h-4 ${
i < review.rating ? 'fill-gray-800 text-gray-800' : 'text-gray-300'
}`} />
))}
</div>
</div>
<span className="text-sm text-gray-500">{review.date}</span>
</div>
<p className="text-gray-600">{review.comment}</p>
</div>
))}
</div>
</motion.div>
)}
{property.rules && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">قوانين المنزل</h2>
<ul className="space-y-2">
{property.rules.map((rule, idx) => (
<li key={idx} className="flex items-center gap-2 text-gray-600">
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full" />
{rule}
</li>
))}
</ul>
</motion.div>
)}
</div>
<div className="space-y-6">
<div className="sticky top-28">
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 mb-6"
>
<h2 className="text-xl font-bold mb-4 text-gray-900">احجز هذا العقار</h2>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">اختر المدة (أيام)</label>
<div className="flex gap-2">
{[1, 3, 7, 14, 30].map(days => (
<button
key={days}
onClick={() => setSelectedDuration(days)}
className={`flex-1 py-2 rounded-xl text-sm font-medium transition-colors ${
selectedDuration === days
? 'bg-gray-800 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{days}
</button>
))}
</div>
</div>
<div className="space-y-4 mb-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">تاريخ البداية</label>
<input
type="date"
value={bookingDates.start}
onChange={(e) => setBookingDates({ ...bookingDates, start: e.target.value })}
className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">تاريخ النهاية</label>
<input
type="date"
value={bookingDates.end}
onChange={(e) => setBookingDates({ ...bookingDates, end: e.target.value })}
className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
/>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-xl mb-6">
<div className="flex justify-between mb-2">
<span className="text-gray-600">السعر لـ {selectedDuration} أيام</span>
<span className="font-bold text-gray-900">{formatCurrency(property.price * selectedDuration)}</span>
</div>
<div className="flex justify-between mb-2">
<span className="text-gray-600">سلفة ضمان</span>
<span className="font-bold text-gray-900">{formatCurrency(500000)}</span>
</div>
<div className="flex justify-between pt-2 border-t border-gray-200 font-bold">
<span className="text-gray-900">الإجمالي</span>
<span className="text-gray-900">{formatCurrency(property.price * selectedDuration + 500000)}</span>
</div>
</div>
<button
onClick={handleBooking}
className="w-full bg-gray-800 text-white py-4 rounded-xl font-bold text-lg hover:bg-gray-900 transition-colors mb-4"
>
تأكيد الحجز
</button>
<div className="flex items-center gap-2 text-sm text-gray-500">
<Shield className="w-4 h-4 text-gray-600" />
<span>الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد.</span>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
<h3 className="font-bold mb-4 text-gray-900">معلومات المالك</h3>
<div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-xl font-bold text-gray-700">
{property.owner.name.charAt(0)}
</span>
</div>
<div>
<div className="font-bold text-gray-900">{property.owner.name}</div>
<div className="flex items-center gap-1 text-sm text-gray-500">
<Star className="w-3 h-3 fill-gray-600 text-gray-600" />
<span>{property.owner.rating}</span>
<span>· {property.owner.properties} عقارات</span>
</div>
{property.owner.responseRate && (
<div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
<Clock className="w-3 h-3" />
<span>استجابة: {property.owner.responseRate}</span>
</div>
)}
</div>
</div>
{showContact ? (
<div className="space-y-3">
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
<Phone className="w-4 h-4 text-gray-600" />
<span className="font-medium text-gray-900">{property.owner.phone}</span>
</div>
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
<Mail className="w-4 h-4 text-gray-600" />
<span className="font-medium text-gray-900">{property.owner.email}</span>
</div>
</div>
) : (
<button
onClick={() => setShowContact(true)}
className="w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors flex items-center justify-center gap-2"
>
<Phone className="w-5 h-5" />
عرض معلومات الاتصال
</button>
)}
<button className="w-full mt-3 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
<MessageCircle className="w-5 h-5" />
مراسلة المالك
</button>
</motion.div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,103 @@
'use client';
import { createContext, useContext, useState, useCallback } from 'react';
const PropertyContext = createContext();
export const useProperties = () => {
const context = useContext(PropertyContext);
if (!context) {
throw new Error('useProperties must be used within PropertyProvider');
}
return context;
};
export const PropertyProvider = ({ children }) => {
const [properties, setProperties] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const addProperty = useCallback(async (propertyData) => {
setLoading(true);
try {
const newProperty = {
id: Date.now().toString(),
...propertyData,
createdAt: new Date().toISOString(),
status: 'available',
bookings: [],
commission: {
rate: propertyData.commissionRate || 5,
type: propertyData.commissionType || 'from_owner',
isActive: true
}
};
setProperties(prev => [...prev, newProperty]);
return newProperty;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const updateProperty = useCallback(async (id, updates) => {
setLoading(true);
try {
setProperties(prev =>
prev.map(p => p.id === id ? { ...p, ...updates } : p)
);
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const deleteProperty = useCallback(async (id) => {
setLoading(true);
try {
setProperties(prev => prev.filter(p => p.id !== id));
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}, []);
const checkAvailability = useCallback((propertyId, startDate, endDate) => {
const property = properties.find(p => p.id === propertyId);
if (!property) return false;
return !property.bookings?.some(booking => {
const bookingStart = new Date(booking.startDate);
const bookingEnd = new Date(booking.endDate);
const checkStart = new Date(startDate);
const checkEnd = new Date(endDate);
return (
(checkStart >= bookingStart && checkStart <= bookingEnd) ||
(checkEnd >= bookingStart && checkEnd <= bookingEnd) ||
(checkStart <= bookingStart && checkEnd >= bookingEnd)
);
});
}, [properties]);
return (
<PropertyContext.Provider value={{
properties,
loading,
error,
addProperty,
updateProperty,
deleteProperty,
checkAvailability
}}>
{children}
</PropertyContext.Provider>
);
};

67
app/utils/calculations.js Normal file
View File

@ -0,0 +1,67 @@
export const calculateRentWithCommission = (
dailyPrice,
numberOfDays,
commissionRate,
commissionType
) => {
const baseRent = dailyPrice * numberOfDays;
const commission = (baseRent * commissionRate) / 100;
switch(commissionType) {
case 'from_tenant':
return {
totalForTenant: baseRent + commission,
totalForOwner: baseRent,
commission: commission
};
case 'from_owner':
return {
totalForTenant: baseRent,
totalForOwner: baseRent - commission,
commission: commission
};
case 'from_both':
return {
totalForTenant: baseRent + (commission / 2),
totalForOwner: baseRent - (commission / 2),
commission: commission
};
default:
return {
totalForTenant: baseRent,
totalForOwner: baseRent,
commission: 0
};
}
};
export const calculateDaysBetween = (startDate, endDate) => {
const start = new Date(startDate);
const end = new Date(endDate);
const diffTime = Math.abs(end - start);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
export const formatCurrency = (amount) => {
return new Intl.NumberFormat('ar-SY', {
style: 'currency',
currency: 'SYP',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(amount).replace('SYP', '') + ' ل.س';
};
export const calculateTenantBalance = (bookings, securityDeposits) => {
return bookings.reduce((acc, booking) => {
const deposit = securityDeposits.find(d => d.bookingId === booking.id) || 0;
return {
totalRent: acc.totalRent + booking.totalAmount,
paidAmount: acc.paidAmount + booking.paidAmount,
securityDeposit: acc.securityDeposit + deposit.amount,
pendingAmount: (acc.pendingAmount + (booking.totalAmount - booking.paidAmount))
};
}, { totalRent: 0, paidAmount: 0, securityDeposit: 0, pendingAmount: 0 });
};

41
app/utils/constants.js Normal file
View File

@ -0,0 +1,41 @@
export const PROPERTY_STATUS = {
AVAILABLE: 'available',
BOOKED: 'booked',
MAINTENANCE: 'maintenance'
};
export const BOOKING_STATUS = {
PENDING: 'pending',
OWNER_APPROVED: 'owner_approved',
ADMIN_APPROVED: 'admin_approved',
REJECTED: 'rejected',
ACTIVE: 'active',
COMPLETED: 'completed',
CANCELLED: 'cancelled'
};
export const COMMISSION_TYPE = {
FROM_OWNER: 'from_owner',
FROM_TENANT: 'from_tenant',
FROM_BOTH: 'from_both'
};
export const IDENTITY_TYPE = {
SYRIAN: 'syrian',
PASSPORT: 'passport'
};
export const PAYMENT_METHOD = {
CASH: 'cash',
ELECTRONIC: 'electronic'
};
export const CITIES = {
DAMASCUS: 'damascus',
ALEPPO: 'aleppo',
HOMS: 'homs',
LATTAKIA: 'latakia',
DARAA: 'daraa'
};
export const DEFAULT_COMMISSION_RATE = 5;

BIN
public/Home.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB