From 6d81ff56a8b20132b3b67ed3a3668ebe11addea9 Mon Sep 17 00:00:00 2001 From: Rahaf Date: Sun, 15 Feb 2026 01:53:37 +0300 Subject: [PATCH] Edit admin page Edit home image Added properties page --- app/admin/page.js | 970 ++++----------------- app/components/NavLinks.js | 4 +- app/components/admin/AddPropertyForm.js | 346 ++++++++ app/components/admin/BookingRequests.js | 948 ++++++++++++++++++++ app/components/admin/DashboardStats.js | 139 +++ app/components/admin/LedgerBook.js | 352 ++++++++ app/components/admin/PropertiesTable.js | 157 ++++ app/components/admin/UsersList.js | 190 ++++ app/components/property/BookingCalendar.js | 162 ++++ app/contexts/PropertyContext.js | 141 +++ app/i18n/config.js | 4 +- app/layout.js | 6 +- app/page.js | 4 +- app/properties/page.js | 713 +++++++++++++++ app/property/[id]/page.js | 681 +++++++++++++++ app/utils/PropertyContext.js | 103 +++ app/utils/calculations.js | 67 ++ app/utils/constants.js | 41 + public/Home.jpg | Bin 0 -> 13588616 bytes 19 files changed, 4200 insertions(+), 828 deletions(-) create mode 100644 app/components/admin/AddPropertyForm.js create mode 100644 app/components/admin/BookingRequests.js create mode 100644 app/components/admin/DashboardStats.js create mode 100644 app/components/admin/LedgerBook.js create mode 100644 app/components/admin/PropertiesTable.js create mode 100644 app/components/admin/UsersList.js create mode 100644 app/components/property/BookingCalendar.js create mode 100644 app/contexts/PropertyContext.js create mode 100644 app/properties/page.js create mode 100644 app/property/[id]/page.js create mode 100644 app/utils/PropertyContext.js create mode 100644 app/utils/calculations.js create mode 100644 app/utils/constants.js create mode 100644 public/Home.jpg diff --git a/app/admin/page.js b/app/admin/page.js index a98dc5c..93103be 100644 --- a/app/admin/page.js +++ b/app/admin/page.js @@ -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, + Calendar, + 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); - - // احصل على اللغة الحالية من i18n - const currentLanguage = i18n.language || 'en'; - - const [stats, setStats] = useState({ - totalUsers: 0, - totalProperties: 0, - activeBookings: 0, - availableProperties: 0 - }); + const [activeTab, setActiveTab] = useState('dashboard'); + const [showAddProperty, setShowAddProperty] = useState(false); + const [notifications, setNotifications] = useState(3); - 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 ( -
- {/* Header بدون زر اختيار اللغة */} - -
-

- {t("adminDashboard")} -

-

{t("manageProperties")}

-

{t("pricesInSYP")}

-
-
- - {/* إحصائيات */} - + +
-
-
- +
+
+

+ {t('adminDashboard')} +

+

+ إدارة العقارات، الحجوزات، والحسابات المالية +

-
-
{stats.totalUsers}
-
{t("totalUsers")}
-
-
-
- {users.filter(u => u.activeBookings > 0).length} {t("usersWithActiveBookings")} + +
- -
-
- -
-
-
{stats.totalProperties}
-
{t("totalProperties")}
-
-
-
- {stats.availableProperties} {t("propertiesAvailable")} -
-
- - -
-
- -
-
-
{stats.activeBookings}
-
{t("activeBookings")}
-
-
-
- {bookingRequests.filter(b => b.status === 'pending').length} {t("bookingRequestsPending")} -
-
- - -
-
- -
-
-
{stats.availableProperties}
-
{t("availableProperties")}
-
-
-
- {properties.filter(p => p.status === 'available').length} {t("propertiesReadyForRent")} -
-
- - - -
- - - - - -
-
- -
- - {activeTab === 'properties' && ( - -
-
-

{t("propertiesManagement")}

-

{t("addEditDeleteProperties")}

-
- - - {t("addNewProperty")} - -
- -
-
- - setSearchQuery(e.target.value)} - /> -
-
- - - {t("filter")} - - - - {t("export")} - -
-
- -
- {properties.map((property) => ( - -
-
-
-

{t(property.name)}

-
- - {getLocation(property.location)} -
-
- - {property.status === 'available' ? t("available") : t("booked")} - -
- -
-
-
- - {t("bedrooms")} -
-
{property.bedrooms}
-
- -
-
- - {t("bathrooms")} -
-
{property.bathrooms}
-
- -
-
- - {t("area")} -
-
{property.area} m²
-
- -
-
- - {t("price")} -
-
{formatNumber(property.price)} SYP/{t("month")}
-
-
- -
-

{t("features")}:

-
- {property.features.map((feature, idx) => ( - - {t(feature)} - - ))} -
-
- -
- - - {t("viewDetails")} - - - - {t("edit")} - - 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" - > - - {t("delete")} - -
-
-
- ))} -
-
- )} - - {activeTab === 'bookings' && ( - -
-

{t("bookingRequests")}

-

{t("manageBookingRequests")}

-
- -
- {bookingRequests.map((request) => ( - -
-
-
-

{t("bookingRequest")} #{request.id}

- - {request.status === 'pending' ? t("pending") : request.status === 'approved' ? t("approved") : t("rejected")} - -
- -
-
-
{t("user")}
-
{request.userName}
-
- -
-
{t("property")}
-
{t(request.propertyName)}
-
- -
-
{t("duration")}
-
{request.duration}
-
- -
-
{t("totalAmount")}
-
{formatNumber(request.totalAmount)} SYP
-
-
- -
-
- - {t("from")} {request.startDate} {t("to")} {request.endDate} -
-
- - {t("requestDate")}: {request.requestDate} -
-
-
- - {request.status === 'pending' && ( -
- 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" - > - - {t("accept")} - - 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" - > - - {t("reject")} - -
- )} -
-
- ))} -
-
- )} - - {activeTab === 'users' && ( - -
-

{t("users")}

-

{t("viewUserDetails")}

-
- -
- {users.map((user) => ( - -
-
-
-
- -
-
-

{user.name}

-
-
- - {user.email} -
-
- - {user.phone} -
-
-
-
- -
-
-
{user.activeBookings}
-
{t("activeBookings")}
-
-
-
{user.totalBookings}
-
{t("totalBookings")}
-
-
-
- - {user.currentBooking ? ( -
-

- - {t("currentActiveBooking")} -

-
-
-
{t("property")}
-
{t(user.currentBooking.propertyName)}
-
-
-
{t("duration")}
-
{user.currentBooking.duration}
-
-
-
{t("totalAmount")}
-
{formatNumber(user.currentBooking.totalAmount)} SYP
-
-
-
{t("bookingPeriod")}
-
- {user.currentBooking.startDate} {t("to")} {user.currentBooking.endDate} -
-
-
-
- ) : ( -
-
{t("noActiveBookings")}
-
- )} - -
- 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" - > - - {t("viewFullDetails")} - -
-
-
- ))} -
-
- )} -
- - {selectedUser && ( -
- -
-
-

{t("userDetails")}: {selectedUser.name}

+
+
+ {tabs.map((tab) => { + const Icon = tab.icon; + return ( + ); + })} +
+
+ +
+ {activeTab === 'dashboard' && ( + + + + )} + + {activeTab === 'properties' && ( + +
+
+

إدارة العقارات

+

إضافة وتعديل العقارات مع تحديد نسب الأرباح

+
+
- -
-
-
-

{t("personalInformation")}

-
-
- {t("fullName")}: - {selectedUser.name} -
-
- {t("email")}: - {selectedUser.email} -
-
- {t("phoneNumber")}: - {selectedUser.phone} -
-
- {t("joinDate")}: - {selectedUser.joinDate} -
-
-
- -
-

{t("bookingStatistics")}

-
-
- {t("activeBookings")}: - {selectedUser.activeBookings} -
-
- {t("totalBookings")}: - {selectedUser.totalBookings} -
-
-
-
+ + + )} + + {activeTab === 'bookings' && ( + + + + )} + + {activeTab === 'users' && ( + + + + )} + + {activeTab === 'ledger' && ( + + + + )} + + {activeTab === 'reports' && ( + +
+ قريباً... تقارير متقدمة
-
-
+ + )}
- )} -
+ + {showAddProperty && ( + setShowAddProperty(false)} + onSuccess={() => { + setShowAddProperty(false); + }} + /> + )} +
+ ); } \ No newline at end of file diff --git a/app/components/NavLinks.js b/app/components/NavLinks.js index 415151e..803389a 100644 --- a/app/components/NavLinks.js +++ b/app/components/NavLinks.js @@ -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 ( { + 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 ( + + +
+

إضافة عقار جديد

+ +
+ +
+
+

+ + موقع العقار (سيظهر على الخريطة) +

+
+
+ + +
+
+ + setFormData({...formData, district: e.target.value})} + className="w-full p-2 border rounded-lg" + required + /> +
+
+ + setFormData({...formData, address: e.target.value})} + className="w-full p-2 border rounded-lg" + required + /> +
+
+ + setFormData({...formData, latitude: e.target.value})} + className="w-full p-2 border rounded-lg" + required + /> +
+
+ + setFormData({...formData, longitude: e.target.value})} + className="w-full p-2 border rounded-lg" + required + /> +
+
+
+ +
+

+ + السعر ونسبة الربح +

+
+
+ + setFormData({...formData, dailyPrice: Number(e.target.value)})} + className="w-full p-2 border rounded-lg" + required + min="0" + /> +

+ هذا السعر سيظهر على الخريطة +

+
+ +
+ +
+ setFormData({...formData, commissionRate: Number(e.target.value)})} + className="w-full p-2 border rounded-lg" + min="0" + max="100" + step="0.1" + required + /> + +
+
+ +
+ +
+ + + +
+
+ +
+

تفاصيل السعر بعد العمولة:

+
+
+ السعر الأصلي: + {formData.dailyPrice} ل.س +
+
+ العمولة: + + {(formData.dailyPrice * formData.commissionRate / 100)} ل.س + +
+
+ السعر النهائي: + + {calculateCommissionPrice(formData)} ل.س + +
+
+
+
+
+ +
+
+ + +
+ +
+ + setFormData({...formData, securityDeposit: Number(e.target.value)})} + className="w-full p-2 border rounded-lg" + min="0" + /> +
+
+ +
+ +
+ {featuresList.map(feature => ( + + ))} +
+
+ +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/components/admin/BookingRequests.js b/app/components/admin/BookingRequests.js new file mode 100644 index 0000000..cdb94c5 --- /dev/null +++ b/app/components/admin/BookingRequests.js @@ -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 ( + + e.stopPropagation()} + > +
+
+ +
+

{title}

+

يرجى تحديد سبب الرفض

+
+ +
+ {commonReasons.map((r) => ( + + ))} + +