forked from amer2004/SweetHome
Added translation to all site with edit style
This commit is contained in:
@ -2,14 +2,12 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
DollarSign,
|
||||
TrendingUp,
|
||||
Calendar,
|
||||
Building,
|
||||
Loader2,
|
||||
ArrowLeft,
|
||||
Star,
|
||||
} from 'lucide-react';
|
||||
import toast, { Toaster } from 'react-hot-toast';
|
||||
@ -34,7 +32,7 @@ const StatCard = ({ title, value, icon: Icon, color, subtitle, isNA }) => (
|
||||
);
|
||||
|
||||
export default function OwnerAccountBookPage() {
|
||||
const router = useRouter();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [user, setUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [stats, setStats] = useState({
|
||||
@ -53,11 +51,9 @@ export default function OwnerAccountBookPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (AuthService.isGuest()) {
|
||||
router.push('/auth/choose-role');
|
||||
return;
|
||||
}
|
||||
if (!AuthService.isOwner()) {
|
||||
router.push('/');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -93,18 +89,18 @@ export default function OwnerAccountBookPage() {
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
toast.error('تعذر تحميل إحصائيات الحساب');
|
||||
toast.error(t('accountBook.loadError'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, [router]);
|
||||
}, []);
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
const num = Number(amount);
|
||||
if (isNaN(num)) return 'N/A';
|
||||
return num.toLocaleString() + ' ل.س';
|
||||
return num.toLocaleString() + ' ' + t('currency-syp-suffix');
|
||||
};
|
||||
|
||||
const formatNumber = (val) => {
|
||||
@ -126,32 +122,23 @@ export default function OwnerAccountBookPage() {
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-600">جاري تحميل بيانات الحساب...</p>
|
||||
<p className="text-gray-600">{t('accountBook.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
<div className="container mx-auto px-4 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="p-2 hover:bg-gray-200 rounded-xl transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">دفتر الحسابات</h1>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">{t('accountBook')}</h1>
|
||||
<p className="text-gray-600">{t('financialRecords')}</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
@ -159,24 +146,24 @@ export default function OwnerAccountBookPage() {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">نظرة مالية للمالك</h2>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('accountBook.ownerFinancialOverview')}</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
<StatCard
|
||||
title="الإيرادات"
|
||||
title={t('accountBook.revenue')}
|
||||
value={formatCurrency(stats.financialRevenue)}
|
||||
icon={TrendingUp}
|
||||
color="bg-emerald-500"
|
||||
isNA={isNA(stats.financialRevenue)}
|
||||
/>
|
||||
<StatCard
|
||||
title="العمولة"
|
||||
title={t('accountBook.commission')}
|
||||
value={formatCurrency(stats.financialCommission)}
|
||||
icon={DollarSign}
|
||||
color="bg-orange-500"
|
||||
isNA={isNA(stats.financialCommission)}
|
||||
/>
|
||||
<StatCard
|
||||
title="الرصيد المتبقي"
|
||||
title={t('accountBook.remainingBalance')}
|
||||
value={formatCurrency(stats.financialBalance)}
|
||||
icon={DollarSign}
|
||||
color="bg-blue-500"
|
||||
@ -191,38 +178,38 @@ export default function OwnerAccountBookPage() {
|
||||
transition={{ delay: 0.1 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">إحصائيات العقارات المباشرة</h2>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('accountBook.directPropertyStats')}</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||
<StatCard
|
||||
title="الإيرادات"
|
||||
title={t('accountBook.revenue')}
|
||||
value={formatCurrency(stats.directRevenue)}
|
||||
icon={TrendingUp}
|
||||
color="bg-emerald-500"
|
||||
isNA={isNA(stats.directRevenue)}
|
||||
/>
|
||||
<StatCard
|
||||
title="العمولة"
|
||||
title={t('accountBook.commission')}
|
||||
value={formatCurrency(stats.directCommission)}
|
||||
icon={DollarSign}
|
||||
color="bg-orange-500"
|
||||
isNA={isNA(stats.directCommission)}
|
||||
/>
|
||||
<StatCard
|
||||
title="الرصيد المتبقي"
|
||||
title={t('accountBook.remainingBalance')}
|
||||
value={formatCurrency(stats.directBalance)}
|
||||
icon={DollarSign}
|
||||
color="bg-blue-500"
|
||||
isNA={isNA(stats.directBalance)}
|
||||
/>
|
||||
<StatCard
|
||||
title="التقييم العام"
|
||||
title={t('accountBook.overallRating')}
|
||||
value={formatRating(stats.directRating)}
|
||||
icon={Star}
|
||||
color="bg-purple-500"
|
||||
isNA={isNA(stats.directRating)}
|
||||
/>
|
||||
<StatCard
|
||||
title="مرات التأجيل"
|
||||
title={t('accountBook.postponements')}
|
||||
value={formatNumber(stats.directPostponements)}
|
||||
icon={Calendar}
|
||||
color="bg-red-500"
|
||||
|
||||
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Calendar,
|
||||
Home,
|
||||
@ -37,6 +38,7 @@ import AuthService from '../../services/AuthService';
|
||||
import Image from 'next/image';
|
||||
|
||||
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||
const [hoverDate, setHoverDate] = useState(null);
|
||||
|
||||
@ -53,8 +55,13 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
).getDay();
|
||||
|
||||
const monthNames = [
|
||||
'يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو',
|
||||
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
|
||||
t('january'), t('february'), t('march'), t('april'), t('may'), t('june'),
|
||||
t('july'), t('august'), t('september'), t('october'), t('november'), t('december')
|
||||
];
|
||||
|
||||
const dayNames = [
|
||||
t('dayFull.sunday'), t('dayFull.monday'), t('dayFull.tuesday'), t('dayFull.wednesday'),
|
||||
t('dayFull.thursday'), t('dayFull.friday'), t('dayFull.saturday')
|
||||
];
|
||||
|
||||
const isDateBooked = (date) => {
|
||||
@ -134,7 +141,6 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
||||
{/* رأس التقويم */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<button
|
||||
onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1))}
|
||||
@ -156,15 +162,10 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* أيام الأسبوع */}
|
||||
<div className="grid grid-cols-7 gap-1 mb-3 text-center text-sm font-medium text-gray-500">
|
||||
<div>أحد</div>
|
||||
<div>إثنين</div>
|
||||
<div>ثلاثاء</div>
|
||||
<div>أربعاء</div>
|
||||
<div>خميس</div>
|
||||
<div>جمعة</div>
|
||||
<div>سبت</div>
|
||||
{dayNames.map((name, i) => (
|
||||
<div key={i}>{name}</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
@ -174,19 +175,19 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
<div className="flex flex-wrap gap-4 mt-6 pt-4 border-t border-gray-200 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-red-100 rounded" />
|
||||
<span className="text-gray-600">محجوز</span>
|
||||
<span className="text-gray-600">{t('booked')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-amber-500 rounded" />
|
||||
<span className="text-gray-600">محدد</span>
|
||||
<span className="text-gray-600">{t('selected')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-amber-100 rounded" />
|
||||
<span className="text-gray-600">ضمن الفترة</span>
|
||||
<span className="text-gray-600">{t('inRange')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 border-2 border-amber-500 rounded" />
|
||||
<span className="text-gray-600">اليوم</span>
|
||||
<span className="text-gray-600">{t('today')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -194,16 +195,18 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||
};
|
||||
|
||||
const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
return amount?.toLocaleString() + ' ل.س';
|
||||
return amount?.toLocaleString() + ' ' + t('syp');
|
||||
};
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
const statusConfig = {
|
||||
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
||||
confirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||
cancelled: { label: 'ملغي', color: 'bg-red-100 text-red-800', icon: XCircle },
|
||||
completed: { label: 'منتهي', color: 'bg-gray-100 text-gray-800', icon: CheckCircle }
|
||||
pending: { label: t('pending'), color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
||||
confirmed: { label: t('confirmed'), color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||
cancelled: { label: t('cancelled'), color: 'bg-red-100 text-red-800', icon: XCircle },
|
||||
completed: { label: t('completed'), color: 'bg-gray-100 text-gray-800', icon: CheckCircle }
|
||||
};
|
||||
|
||||
const config = statusConfig[status] || statusConfig.pending;
|
||||
@ -237,7 +240,7 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-lg font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</div>
|
||||
<div className="text-xs text-gray-500">إجمالي المبلغ</div>
|
||||
<div className="text-xs text-gray-500">{t('totalAmount')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -261,18 +264,18 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||
<div className="grid grid-cols-3 gap-3 mb-4 text-center">
|
||||
<div className="bg-gray-50 p-2 rounded-lg">
|
||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-xs text-gray-500">من</div>
|
||||
<div className="text-xs text-gray-500">{t('from')}</div>
|
||||
<div className="text-sm font-medium">{booking.startDate}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-2 rounded-lg">
|
||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-xs text-gray-500">إلى</div>
|
||||
<div className="text-xs text-gray-500">{t('to')}</div>
|
||||
<div className="text-sm font-medium">{booking.endDate}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-2 rounded-lg">
|
||||
<Clock className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-xs text-gray-500">المدة</div>
|
||||
<div className="text-sm font-medium">{booking.days} يوم</div>
|
||||
<div className="text-xs text-gray-500">{t('duration')}</div>
|
||||
<div className="text-sm font-medium">{booking.days} {t('days')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -282,15 +285,8 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
التفاصيل
|
||||
{t('details')}
|
||||
</button>
|
||||
{/* <button
|
||||
onClick={() => onContact(booking)}
|
||||
className="flex-1 bg-amber-500 text-white py-2 rounded-xl text-sm font-medium hover:bg-amber-600 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
تواصل
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -298,10 +294,11 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||
};
|
||||
|
||||
const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
if (!isOpen || !booking) return null;
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
return amount?.toLocaleString() + ' ل.س';
|
||||
return amount?.toLocaleString() + ' ' + t('syp');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -321,80 +318,80 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
||||
>
|
||||
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold">تفاصيل الحجز</h2>
|
||||
<h2 className="text-xl font-bold">{t('bookingDetails')}</h2>
|
||||
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
||||
<XCircle className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-amber-100 text-sm mt-1">رقم الحجز: #{booking.id}</p>
|
||||
<p className="text-amber-100 text-sm mt-1">{t('bookingId', { id: booking.id })}</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-3">معلومات العقار</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-3">{t('propertyInfo')}</h3>
|
||||
<div className="space-y-2">
|
||||
<p><span className="text-gray-500">العقار:</span> {booking.propertyTitle}</p>
|
||||
<p><span className="text-gray-500">الموقع:</span> {booking.location}</p>
|
||||
<p><span className="text-gray-500">{t('property')}:</span> {booking.propertyTitle}</p>
|
||||
<p><span className="text-gray-500">{t('location')}:</span> {booking.location}</p>
|
||||
{booking.propertyDetails && (
|
||||
<div className="flex gap-3 mt-2">
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bedrooms} غرف</span>
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bathrooms} حمامات</span>
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.area} م²</span>
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bedrooms} {t('rooms')}</span>
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bathrooms} {t('bathrooms')}</span>
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.area} {t('sqm')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-3">معلومات المستأجر</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-3">{t('tenantInfo')}</h3>
|
||||
<div className="space-y-2">
|
||||
<p><span className="text-gray-500">الاسم:</span> {booking.tenantName}</p>
|
||||
<p><span className="text-gray-500">البريد الإلكتروني:</span> {booking.tenantEmail}</p>
|
||||
<p><span className="text-gray-500">رقم الهاتف:</span> {booking.tenantPhone}</p>
|
||||
<p><span className="text-gray-500">{t('name')}</span> {booking.tenantName}</p>
|
||||
<p><span className="text-gray-500">{t('email')}</span> {booking.tenantEmail}</p>
|
||||
<p><span className="text-gray-500">{t('phone')}</span> {booking.tenantPhone}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-3">تفاصيل الحجز</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-3">{t('bookingDetails')}</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-gray-500">تاريخ البداية</p>
|
||||
<p className="text-gray-500">{t('startDate')}</p>
|
||||
<p className="font-medium">{booking.startDate}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">تاريخ النهاية</p>
|
||||
<p className="text-gray-500">{t('endDate')}</p>
|
||||
<p className="font-medium">{booking.endDate}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">عدد الأيام</p>
|
||||
<p className="font-medium">{booking.days} يوم</p>
|
||||
<p className="text-gray-500">{t('durationDays', { days: booking.days })}</p>
|
||||
<p className="font-medium">{booking.days} {t('days')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">حالة الحجز</p>
|
||||
<p className="font-medium">{booking.status === 'pending' ? 'قيد الانتظار' :
|
||||
booking.status === 'confirmed' ? 'مؤكد' :
|
||||
booking.status === 'cancelled' ? 'ملغي' : 'منتهي'}</p>
|
||||
<p className="text-gray-500">{t('bookingStatus')}</p>
|
||||
<p className="font-medium">{booking.status === 'pending' ? t('pending') :
|
||||
booking.status === 'confirmed' ? t('confirmed') :
|
||||
booking.status === 'cancelled' ? t('cancelled') : t('completed')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-amber-700 mb-3">المعلومات المالية</h3>
|
||||
<h3 className="font-bold text-amber-700 mb-3">{t('financialInfo')}</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">السعر اليومي</span>
|
||||
<span className="text-gray-600">{t('dailyPrice')}</span>
|
||||
<span className="font-medium">{formatCurrency(booking.dailyPrice)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">المدة ({booking.days} أيام)</span>
|
||||
<span className="text-gray-600">{t('durationDays', { days: booking.days })}</span>
|
||||
<span className="font-medium">{formatCurrency(booking.dailyPrice * booking.days)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">سلفة الضمان</span>
|
||||
<span className="text-gray-600">{t('securityDeposit')}</span>
|
||||
<span className="font-medium">{formatCurrency(booking.securityDeposit || 0)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between pt-2 border-t border-amber-200 font-bold">
|
||||
<span className="text-gray-900">الإجمالي</span>
|
||||
<span className="text-gray-900">{t('total')}</span>
|
||||
<span className="text-amber-600 text-lg">{formatCurrency(booking.totalAmount)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -402,7 +399,7 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
||||
|
||||
{booking.notes && (
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-2">ملاحظات</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-2">{t('notes')}</h3>
|
||||
<p className="text-gray-600">{booking.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
@ -413,6 +410,7 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
||||
};
|
||||
|
||||
export default function OwnerBookingsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState(null);
|
||||
const [bookings, setBookings] = useState([]);
|
||||
@ -449,10 +447,10 @@ export default function OwnerBookingsPage() {
|
||||
{
|
||||
id: 'BK001',
|
||||
propertyId: 1,
|
||||
propertyTitle: 'فيلا فاخرة في المزة',
|
||||
location: 'دمشق، المزة',
|
||||
propertyTitle: t('ownerBookings.mockB1Title'),
|
||||
location: t('ownerBookings.mockB1Location'),
|
||||
propertyDetails: { bedrooms: 5, bathrooms: 4, area: 450 },
|
||||
tenantName: 'أحمد محمد',
|
||||
tenantName: t('ownerBookings.mockB1Tenant'),
|
||||
tenantEmail: 'ahmed@example.com',
|
||||
tenantPhone: '0933111222',
|
||||
startDate: '2024-03-10',
|
||||
@ -463,15 +461,15 @@ export default function OwnerBookingsPage() {
|
||||
securityDeposit: 500000,
|
||||
status: 'confirmed',
|
||||
createdAt: '2024-02-25',
|
||||
notes: 'طلب الحجز من خلال الموقع'
|
||||
notes: t('ownerBookings.mockB1Notes')
|
||||
},
|
||||
{
|
||||
id: 'BK002',
|
||||
propertyId: 2,
|
||||
propertyTitle: 'شقة حديثة في الشهباء',
|
||||
location: 'حلب، الشهباء',
|
||||
propertyTitle: t('ownerBookings.mockB2Title'),
|
||||
location: t('ownerBookings.mockB2Location'),
|
||||
propertyDetails: { bedrooms: 3, bathrooms: 2, area: 180 },
|
||||
tenantName: 'سارة أحمد',
|
||||
tenantName: t('ownerBookings.mockB2Tenant'),
|
||||
tenantEmail: 'sara@example.com',
|
||||
tenantPhone: '0945123789',
|
||||
startDate: '2024-03-05',
|
||||
@ -482,15 +480,15 @@ export default function OwnerBookingsPage() {
|
||||
securityDeposit: 250000,
|
||||
status: 'pending',
|
||||
createdAt: '2024-02-24',
|
||||
notes: 'تحتاج إلى تأكيد'
|
||||
notes: t('ownerBookings.mockB2Notes')
|
||||
},
|
||||
{
|
||||
id: 'BK003',
|
||||
propertyId: 3,
|
||||
propertyTitle: 'بيت عائلي في بابا عمرو',
|
||||
location: 'حمص، بابا عمرو',
|
||||
propertyTitle: t('ownerBookings.mockB3Title'),
|
||||
location: t('ownerBookings.mockB3Location'),
|
||||
propertyDetails: { bedrooms: 4, bathrooms: 3, area: 300 },
|
||||
tenantName: 'محمد الحلبي',
|
||||
tenantName: t('ownerBookings.mockB3Tenant'),
|
||||
tenantEmail: 'mohammed@example.com',
|
||||
tenantPhone: '0956123456',
|
||||
startDate: '2024-02-20',
|
||||
@ -501,7 +499,7 @@ export default function OwnerBookingsPage() {
|
||||
securityDeposit: 500000,
|
||||
status: 'completed',
|
||||
createdAt: '2024-02-15',
|
||||
notes: 'تم إنهاء الإيجار بنجاح'
|
||||
notes: t('ownerBookings.mockB3Notes')
|
||||
}
|
||||
];
|
||||
setBookings(mockBookings);
|
||||
@ -518,7 +516,7 @@ export default function OwnerBookingsPage() {
|
||||
};
|
||||
|
||||
const handleContact = (booking) => {
|
||||
toast.success(`جاري فتح محادثة مع ${booking.tenantName}`, {
|
||||
toast.success(t('openingChat', { name: booking.tenantName }), {
|
||||
icon: '💬',
|
||||
style: { background: '#dcfce7', color: '#166534' }
|
||||
});
|
||||
@ -531,7 +529,7 @@ export default function OwnerBookingsPage() {
|
||||
setBookings(updatedBookings);
|
||||
setFilteredBookings(updatedBookings);
|
||||
localStorage.setItem('ownerBookings', JSON.stringify(updatedBookings));
|
||||
toast.success(`تم تحديث حالة الحجز بنجاح`);
|
||||
toast.success(t('statusUpdated'));
|
||||
};
|
||||
|
||||
const statusCounts = {
|
||||
@ -547,14 +545,14 @@ export default function OwnerBookingsPage() {
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-600">جاري تحميل الحجوزات...</p>
|
||||
<p className="text-gray-600">{t('loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
|
||||
<BookingDetailsModal
|
||||
@ -570,8 +568,8 @@ export default function OwnerBookingsPage() {
|
||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">حجوزاتي</h1>
|
||||
<p className="text-gray-600">مرحباً {user?.name}، لديك {bookings.length} حجز</p>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('myBookings')}</h1>
|
||||
<p className="text-gray-600">{t('welcomeBookings', { name: user?.name, count: bookings.length })}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
@ -580,12 +578,8 @@ export default function OwnerBookingsPage() {
|
||||
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Calendar className="w-5 h-5" />
|
||||
{showCalendar ? 'إخفاء التقويم' : 'عرض التقويم'}
|
||||
{showCalendar ? t('hideCalendar') : t('showCalendar')}
|
||||
</button>
|
||||
{/* <button className="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700 transition-colors flex items-center gap-2">
|
||||
<Download className="w-5 h-5" />
|
||||
تصدير التقرير
|
||||
</button> */}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@ -598,7 +592,7 @@ export default function OwnerBookingsPage() {
|
||||
onClick={() => setFilterStatus('all')}
|
||||
>
|
||||
<div className="text-2xl font-bold text-gray-900">{statusCounts.all}</div>
|
||||
<div className="text-sm text-gray-600">جميع الحجوزات</div>
|
||||
<div className="text-sm text-gray-600">{t('allBookings')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -610,7 +604,7 @@ export default function OwnerBookingsPage() {
|
||||
onClick={() => setFilterStatus('pending')}
|
||||
>
|
||||
<div className="text-2xl font-bold text-yellow-600">{statusCounts.pending}</div>
|
||||
<div className="text-sm text-gray-600">قيد الانتظار</div>
|
||||
<div className="text-sm text-gray-600">{t('pending')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -622,7 +616,7 @@ export default function OwnerBookingsPage() {
|
||||
onClick={() => setFilterStatus('confirmed')}
|
||||
>
|
||||
<div className="text-2xl font-bold text-green-600">{statusCounts.confirmed}</div>
|
||||
<div className="text-sm text-gray-600">مؤكدة</div>
|
||||
<div className="text-sm text-gray-600">{t('confirmed')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -634,7 +628,7 @@ export default function OwnerBookingsPage() {
|
||||
onClick={() => setFilterStatus('completed')}
|
||||
>
|
||||
<div className="text-2xl font-bold text-gray-600">{statusCounts.completed}</div>
|
||||
<div className="text-sm text-gray-600">منتهية</div>
|
||||
<div className="text-sm text-gray-600">{t('completed')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -646,7 +640,7 @@ export default function OwnerBookingsPage() {
|
||||
onClick={() => setFilterStatus('cancelled')}
|
||||
>
|
||||
<div className="text-2xl font-bold text-red-600">{statusCounts.cancelled}</div>
|
||||
<div className="text-sm text-gray-600">ملغية</div>
|
||||
<div className="text-sm text-gray-600">{t('cancelled')}</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@ -655,7 +649,7 @@ export default function OwnerBookingsPage() {
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="ابحث باسم العقار أو المستأجر.."
|
||||
placeholder={t('searchBookings')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
@ -667,21 +661,21 @@ export default function OwnerBookingsPage() {
|
||||
value={dateRange.start}
|
||||
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
||||
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="من تاريخ"
|
||||
placeholder={t('filterDateFrom')}
|
||||
/>
|
||||
<input
|
||||
type="date"
|
||||
value={dateRange.end}
|
||||
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
||||
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="إلى تاريخ"
|
||||
placeholder={t('filterDateTo')}
|
||||
/>
|
||||
{(dateRange.start || dateRange.end) && (
|
||||
<button
|
||||
onClick={() => setDateRange({ start: '', end: '' })}
|
||||
className="px-4 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
مسح
|
||||
{t('clear')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -708,16 +702,16 @@ export default function OwnerBookingsPage() {
|
||||
<div className="w-24 h-24 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Calendar className="w-12 h-12 text-amber-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد حجوزات</h3>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{filterStatus !== 'all' ? 'لا توجد حجوزات في هذه الفئة' : 'لم يتم استلام أي حجوزات بعد'}
|
||||
{filterStatus !== 'all' ? t('noBookingsInCategory') : t('noBookingsReceived')}
|
||||
</p>
|
||||
{filterStatus !== 'all' && (
|
||||
<button
|
||||
onClick={() => setFilterStatus('all')}
|
||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
||||
>
|
||||
عرض جميع الحجوزات
|
||||
{t('viewAllBookings')}
|
||||
</button>
|
||||
)}
|
||||
</motion.div>
|
||||
@ -736,4 +730,4 @@ export default function OwnerBookingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Calendar,
|
||||
ChevronLeft,
|
||||
@ -39,13 +40,14 @@ import toast, { Toaster } from 'react-hot-toast';
|
||||
import AuthService from '../../services/AuthService';
|
||||
|
||||
const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onPropertySelect }) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const [viewType, setViewType] = useState('grid');
|
||||
|
||||
const monthNames = [
|
||||
'يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو',
|
||||
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
|
||||
t('calendar.month1'), t('calendar.month2'), t('calendar.month3'), t('calendar.month4'), t('calendar.month5'), t('calendar.month6'),
|
||||
t('calendar.month7'), t('calendar.month8'), t('calendar.month9'), t('calendar.month10'), t('calendar.month11'), t('calendar.month12')
|
||||
];
|
||||
|
||||
const daysInMonth = new Date(
|
||||
@ -76,17 +78,17 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
||||
const totalProperties = properties.length;
|
||||
const bookedCount = properties.filter(p => isDateBookedForProperty(date, p)).length;
|
||||
|
||||
if (bookedCount === 0) return { status: 'all_available', label: 'جميع العقارات متاحة', color: 'bg-green-100 text-green-800' };
|
||||
if (bookedCount === totalProperties) return { status: 'all_booked', label: 'جميع العقارات محجوزة', color: 'bg-red-100 text-red-800' };
|
||||
return { status: 'partial', label: `${bookedCount}/${totalProperties} محجوز`, color: 'bg-yellow-100 text-yellow-800' };
|
||||
if (bookedCount === 0) return { status: 'all_available', label: t('calendar.allAvailable'), color: 'bg-green-100 text-green-800' };
|
||||
if (bookedCount === totalProperties) return { status: 'all_booked', label: t('calendar.allBooked'), color: 'bg-red-100 text-red-800' };
|
||||
return { status: 'partial', label: t('calendar.bookedCount', { count: bookedCount, total: totalProperties }), color: 'bg-yellow-100 text-yellow-800' };
|
||||
} else {
|
||||
const property = properties.find(p => p.id === selectedPropertyId);
|
||||
if (!property) return { status: 'no_property', label: 'غير متاح', color: 'bg-gray-100 text-gray-500' };
|
||||
if (!property) return { status: 'no_property', label: t('calendar.notAvailable'), color: 'bg-gray-100 text-gray-500' };
|
||||
|
||||
const isBooked = isDateBookedForProperty(date, property);
|
||||
return {
|
||||
status: isBooked ? 'booked' : 'available',
|
||||
label: isBooked ? 'محجوز' : 'متاح',
|
||||
label: isBooked ? t('calendar.booked') : t('calendar.available'),
|
||||
color: isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
||||
};
|
||||
}
|
||||
@ -171,7 +173,7 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
||||
onClick={() => setCurrentMonth(new Date())}
|
||||
className="px-4 py-2 bg-amber-500 text-white rounded-xl text-sm hover:bg-amber-600 transition-colors"
|
||||
>
|
||||
اليوم
|
||||
{t('calendar.today')}
|
||||
</button>
|
||||
<div className="flex border border-gray-200 rounded-xl overflow-hidden">
|
||||
<button
|
||||
@ -192,7 +194,7 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1 p-4 bg-gray-50 border-b border-gray-200">
|
||||
{['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'].map((day, index) => (
|
||||
{[t('calendar.day1'), t('calendar.day2'), t('calendar.day3'), t('calendar.day4'), t('calendar.day5'), t('calendar.day6'), t('calendar.day7')].map((day, index) => (
|
||||
<div key={index} className="text-center text-sm font-medium text-gray-600 py-2">
|
||||
{day}
|
||||
</div>
|
||||
@ -209,23 +211,23 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
||||
<div className="flex flex-wrap gap-4 justify-center text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-100 rounded" />
|
||||
<span className="text-gray-600">متاح</span>
|
||||
<span className="text-gray-600">{t('calendar.available')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-red-100 rounded" />
|
||||
<span className="text-gray-600">محجوز</span>
|
||||
<span className="text-gray-600">{t('calendar.booked')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-yellow-100 rounded" />
|
||||
<span className="text-gray-600">محجوز جزئياً</span>
|
||||
<span className="text-gray-600">{t('calendar.partiallyBooked')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 ring-2 ring-amber-500 rounded" />
|
||||
<span className="text-gray-600">اليوم</span>
|
||||
<span className="text-gray-600">{t('calendar.today')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 ring-2 ring-blue-500 rounded" />
|
||||
<span className="text-gray-600">محدد</span>
|
||||
<span className="text-gray-600">{t('calendar.selected')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -234,8 +236,9 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
||||
};
|
||||
|
||||
const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) => {
|
||||
const { t } = useTranslation();
|
||||
const formatCurrency = (amount) => {
|
||||
return amount?.toLocaleString() + ' ل.س';
|
||||
return amount?.toLocaleString() + ' ' + t('calendar.syp');
|
||||
};
|
||||
|
||||
const isDateBooked = (property, date) => {
|
||||
@ -264,8 +267,8 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
||||
return (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-12 text-center">
|
||||
<CalendarDays className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-bold text-gray-700 mb-2">اختر تاريخاً</h3>
|
||||
<p className="text-gray-500">اضغط على أي يوم في التقويم لعرض حالة العقارات في ذلك التاريخ</p>
|
||||
<h3 className="text-lg font-bold text-gray-700 mb-2">{t('calendar.selectDate')}</h3>
|
||||
<p className="text-gray-500">{t('calendar.clickDayHint')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -282,7 +285,7 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
||||
<div className="p-4 md:p-6 border-b border-gray-200 bg-gradient-to-r from-amber-50 to-amber-100">
|
||||
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||
<CalendarDays className="w-5 h-5 text-amber-500" />
|
||||
حالة العقارات في تاريخ: {formattedDate}
|
||||
{t('calendar.statusForDate')} {formattedDate}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@ -306,7 +309,7 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
||||
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
|
||||
isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
||||
}`}>
|
||||
{isBooked ? 'محجوز' : 'متاح'}
|
||||
{isBooked ? t('calendar.booked') : t('calendar.available')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-gray-500 text-sm mb-2">
|
||||
@ -316,26 +319,26 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
||||
<div className="flex flex-wrap gap-3 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-1">
|
||||
<Bed className="w-4 h-4" />
|
||||
<span>{property.bedrooms} غرف</span>
|
||||
<span>{property.bedrooms} {t('calendar.rooms')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Bath className="w-4 h-4" />
|
||||
<span>{property.bathrooms} حمامات</span>
|
||||
<span>{property.bathrooms} {t('calendar.bathrooms')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Square className="w-4 h-4" />
|
||||
<span>{property.area} م²</span>
|
||||
<span>{property.area} {t('calendar.sqm')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-amber-600">{formatCurrency(property.price)}</div>
|
||||
<div className="text-xs text-gray-500">/يوم</div>
|
||||
<div className="text-xs text-gray-500">{t('calendar.perDay')}</div>
|
||||
{isBooked && booking && (
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
<div>مستأجر: {booking.tenantName || 'غير معروف'}</div>
|
||||
<div>من: {booking.startDate} إلى {booking.endDate}</div>
|
||||
<div>{t('calendar.tenant')}: {booking.tenantName || t('calendar.unknown')}</div>
|
||||
<div>{t('calendar.from')} {booking.startDate} {t('calendar.to')} {booking.endDate}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -349,10 +352,11 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
||||
};
|
||||
|
||||
const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
if (!isOpen || !property) return null;
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
return amount?.toLocaleString() + ' ل.س';
|
||||
return amount?.toLocaleString() + ' ' + t('calendar.syp');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -385,7 +389,7 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
<div className="p-6 space-y-6">
|
||||
{property.images && property.images.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 mb-3">صور العقار</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-3">{t('calendar.propertyImages')}</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{property.images.slice(0, 4).map((image, index) => (
|
||||
<div key={index} className="relative h-32 rounded-lg overflow-hidden bg-gray-100">
|
||||
@ -400,28 +404,28 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<Bed className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-sm font-bold">{property.bedrooms}</div>
|
||||
<div className="text-xs text-gray-500">غرف نوم</div>
|
||||
<div className="text-xs text-gray-500">{t('calendar.bedrooms')}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<Bath className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-sm font-bold">{property.bathrooms}</div>
|
||||
<div className="text-xs text-gray-500">حمامات</div>
|
||||
<div className="text-xs text-gray-500">{t('calendar.bathrooms')}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<Square className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-sm font-bold">{property.area}</div>
|
||||
<div className="text-xs text-gray-500">م²</div>
|
||||
<div className="text-xs text-gray-500">{t('calendar.sqm')}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<DollarSign className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-sm font-bold">{formatCurrency(property.price)}</div>
|
||||
<div className="text-xs text-gray-500">/يوم</div>
|
||||
<div className="text-xs text-gray-500">{t('calendar.perDay')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{property.features && property.features.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 mb-3">المميزات</h3>
|
||||
<h3 className="font-bold text-gray-900 mb-3">{t('calendar.features')}</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{property.features.map((feature, index) => (
|
||||
<span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">
|
||||
@ -436,14 +440,14 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-amber-500" />
|
||||
الحجوزات القادمة
|
||||
{t('calendar.upcomingBookings')}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{property.bookings.slice(0, 3).map((booking, index) => (
|
||||
<div key={index} className="bg-gray-50 p-3 rounded-lg flex justify-between items-center">
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">{booking.startDate} - {booking.endDate}</p>
|
||||
<p className="text-xs text-gray-500">مستأجر: {booking.tenantName || 'غير معروف'}</p>
|
||||
<p className="text-xs text-gray-500">{t('calendar.tenant')}: {booking.tenantName || t('calendar.unknown')}</p>
|
||||
</div>
|
||||
<span className="text-sm font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</span>
|
||||
</div>
|
||||
@ -458,13 +462,13 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
href={`/owner/properties/edit?id=${property.id}`}
|
||||
className="flex-1 bg-amber-500 text-white py-3 rounded-xl text-center font-medium hover:bg-amber-600 transition-colors"
|
||||
>
|
||||
تعديل العقار
|
||||
{t('calendar.editProperty')}
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => window.location.href = `/owner/bookings?property=${property.id}`}
|
||||
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl text-center font-medium hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
عرض الحجوزات
|
||||
{t('calendar.viewBookings')}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -473,6 +477,7 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||
};
|
||||
|
||||
export default function OwnerCalendarPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState(null);
|
||||
const [properties, setProperties] = useState([]);
|
||||
@ -509,44 +514,44 @@ export default function OwnerCalendarPage() {
|
||||
const mockProperties = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'فيلا فاخرة في المزة',
|
||||
location: 'دمشق، المزة',
|
||||
title: t('calendar.mockVillaTitle'),
|
||||
location: t('calendar.mockVillaLocation'),
|
||||
bedrooms: 5,
|
||||
bathrooms: 4,
|
||||
area: 450,
|
||||
price: 500000,
|
||||
features: ['مسبح', 'حديقة خاصة', 'موقف سيارات', 'أمن 24/7'],
|
||||
features: [t('swimmingPool'), t('privateGarden'), t('parking'), t('propertyService.security247')],
|
||||
images: ['/villa1.jpg'],
|
||||
status: 'available',
|
||||
bookings: [
|
||||
{ startDate: '2024-03-10', endDate: '2024-03-15', totalAmount: 2500000, tenantName: 'أحمد محمد' },
|
||||
{ startDate: '2024-03-20', endDate: '2024-03-25', totalAmount: 2500000, tenantName: 'سارة أحمد' }
|
||||
{ startDate: '2024-03-10', endDate: '2024-03-15', totalAmount: 2500000, tenantName: t('calendar.mockTenant1') },
|
||||
{ startDate: '2024-03-20', endDate: '2024-03-25', totalAmount: 2500000, tenantName: t('calendar.mockTenant2') }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'شقة حديثة في الشهباء',
|
||||
location: 'حلب، الشهباء',
|
||||
title: t('calendar.mockApartmentTitle'),
|
||||
location: t('calendar.mockApartmentLocation'),
|
||||
bedrooms: 3,
|
||||
bathrooms: 2,
|
||||
area: 180,
|
||||
price: 250000,
|
||||
features: ['مطبخ مجهز', 'بلكونة', 'موقف سيارات', 'مصعد'],
|
||||
features: [t('equippedKitchen'), t('balcony'), t('parking'), t('propertyService.elevator')],
|
||||
images: ['/apartment1.jpg'],
|
||||
status: 'available',
|
||||
bookings: [
|
||||
{ startDate: '2024-03-05', endDate: '2024-03-08', totalAmount: 750000, tenantName: 'محمد علي' }
|
||||
{ startDate: '2024-03-05', endDate: '2024-03-08', totalAmount: 750000, tenantName: t('calendar.mockTenant3') }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'بيت عائلي في بابا عمرو',
|
||||
location: 'حمص، بابا عمرو',
|
||||
title: t('calendar.mockHouseTitle'),
|
||||
location: t('calendar.mockHouseLocation'),
|
||||
bedrooms: 4,
|
||||
bathrooms: 3,
|
||||
area: 300,
|
||||
price: 350000,
|
||||
features: ['حديقة كبيرة', 'موقف سيارات', 'مدفأة', 'كراج'],
|
||||
features: [t('largeGarden'), t('parking'), t('fireplace'), t('garage')],
|
||||
images: ['/house1.jpg'],
|
||||
status: 'booked',
|
||||
bookings: []
|
||||
@ -586,14 +591,14 @@ export default function OwnerCalendarPage() {
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-600">جاري تحميل التقويم...</p>
|
||||
<p className="text-gray-600">{t('calendar.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
|
||||
<PropertyDetailsModal
|
||||
@ -609,8 +614,8 @@ export default function OwnerCalendarPage() {
|
||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">تقويم العقارات</h1>
|
||||
<p className="text-gray-600">مرحباً {user?.name}، تتبع حالة عقاراتك عبر التقويم</p>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('calendar.pageTitle')}</h1>
|
||||
<p className="text-gray-600">{t('calendar.welcomeMessage', { name: user?.name })}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
@ -619,7 +624,7 @@ export default function OwnerCalendarPage() {
|
||||
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Filter className="w-5 h-5" />
|
||||
فلترة العقارات
|
||||
{t('calendar.filterProperties')}
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{/* <button className="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700 transition-colors flex items-center gap-2">
|
||||
@ -638,7 +643,7 @@ export default function OwnerCalendarPage() {
|
||||
>
|
||||
<Building className="w-6 h-6 text-amber-500 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-gray-900">{calendarStats.totalProperties}</div>
|
||||
<div className="text-sm text-gray-600">إجمالي العقارات</div>
|
||||
<div className="text-sm text-gray-600">{t('calendar.totalProperties')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -648,7 +653,7 @@ export default function OwnerCalendarPage() {
|
||||
>
|
||||
<CheckCircle className="w-6 h-6 text-green-500 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-green-600">{calendarStats.availableToday}</div>
|
||||
<div className="text-sm text-gray-600">متاح اليوم</div>
|
||||
<div className="text-sm text-gray-600">{t('calendar.availableToday')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -658,7 +663,7 @@ export default function OwnerCalendarPage() {
|
||||
>
|
||||
<XCircle className="w-6 h-6 text-red-500 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-red-600">{calendarStats.bookedToday}</div>
|
||||
<div className="text-sm text-gray-600">محجوز اليوم</div>
|
||||
<div className="text-sm text-gray-600">{t('calendar.bookedToday')}</div>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@ -668,7 +673,7 @@ export default function OwnerCalendarPage() {
|
||||
>
|
||||
<CalendarDays className="w-6 h-6 text-blue-500 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-blue-600">{calendarStats.upcomingBookings}</div>
|
||||
<div className="text-sm text-gray-600">حجوزات قادمة</div>
|
||||
<div className="text-sm text-gray-600">{t('calendar.upcomingBookings')}</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@ -682,13 +687,13 @@ export default function OwnerCalendarPage() {
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-4">
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<label className="text-sm font-medium text-gray-700">اختر عقاراً:</label>
|
||||
<label className="text-sm font-medium text-gray-700">{t('calendar.selectProperty')}</label>
|
||||
<select
|
||||
value={selectedPropertyId}
|
||||
onChange={(e) => setSelectedPropertyId(e.target.value)}
|
||||
className="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value="all">جميع العقارات</option>
|
||||
<option value="all">{t('calendar.allProperties')}</option>
|
||||
{properties.map((property) => (
|
||||
<option key={property.id} value={property.id}>{property.title}</option>
|
||||
))}
|
||||
@ -697,7 +702,7 @@ export default function OwnerCalendarPage() {
|
||||
onClick={() => setSelectedPropertyId('all')}
|
||||
className="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
إعادة تعيين
|
||||
{t('calendar.reset')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -729,7 +734,7 @@ export default function OwnerCalendarPage() {
|
||||
className="mt-6 text-center text-sm text-gray-500"
|
||||
>
|
||||
<AlertCircle className="w-4 h-4 inline ml-1" />
|
||||
اضغط على أي عقار لعرض التفاصيل الكاملة
|
||||
{t('calendar.clickPropertyHint')}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -298,9 +298,11 @@ import { useRouter } from 'next/navigation';
|
||||
import { Download, Loader2 } from 'lucide-react';
|
||||
import toast, { Toaster } from 'react-hot-toast';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AuthService from '@/app/services/AuthService';
|
||||
|
||||
export default function OwnerProfitsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -404,29 +406,29 @@ export default function OwnerProfitsPage() {
|
||||
const handleExportReport = () => {
|
||||
try {
|
||||
const exportData = tableData.map((row) => ({
|
||||
'العقار': row.property,
|
||||
'رقم الحجز': row.bookingNumber,
|
||||
'من تاريخ': row.fromDate,
|
||||
'حتى تاريخ': row.toDate,
|
||||
'العروض المستلم': row.amountReceived,
|
||||
'عمولة المنصة': row.platformCommission,
|
||||
'ربح المنصة (5%)': row.platformProfit,
|
||||
'المستحق للمالك': row.ownerDue,
|
||||
'تم التحويل للمالك': row.transferredToOwner,
|
||||
'رقم وصل التحويل': row.transferReceipt,
|
||||
[t('profits.colProperty')]: row.property,
|
||||
[t('profits.colBookingNumber')]: row.bookingNumber,
|
||||
[t('profits.colFromDate')]: row.fromDate,
|
||||
[t('profits.colToDate')]: row.toDate,
|
||||
[t('profits.colAmountReceived')]: row.amountReceived,
|
||||
[t('profits.colPlatformCommission')]: row.platformCommission,
|
||||
[t('profits.colPlatformProfit')]: row.platformProfit,
|
||||
[t('profits.colOwnerDue')]: row.ownerDue,
|
||||
[t('profits.colTransferred')]: row.transferredToOwner,
|
||||
[t('profits.colTransferReceipt')]: row.transferReceipt,
|
||||
}));
|
||||
|
||||
exportData.push({
|
||||
'العقار': 'الإجمالي العام',
|
||||
'رقم الحجز': '',
|
||||
'من تاريخ': '',
|
||||
'حتى تاريخ': '',
|
||||
'العروض المستلم': totals.totalAmountReceived,
|
||||
'عمولة المنصة': totals.totalCommission,
|
||||
'ربح المنصة (5%)': totals.totalPlatformProfit,
|
||||
'المستحق للمالك': totals.totalOwnerDue,
|
||||
'تم التحويل للمالك': totals.totalTransferred,
|
||||
'رقم وصل التحويل': '—',
|
||||
[t('profits.colProperty')]: t('profits.totalGeneral'),
|
||||
[t('profits.colBookingNumber')]: '',
|
||||
[t('profits.colFromDate')]: '',
|
||||
[t('profits.colToDate')]: '',
|
||||
[t('profits.colAmountReceived')]: totals.totalAmountReceived,
|
||||
[t('profits.colPlatformCommission')]: totals.totalCommission,
|
||||
[t('profits.colPlatformProfit')]: totals.totalPlatformProfit,
|
||||
[t('profits.colOwnerDue')]: totals.totalOwnerDue,
|
||||
[t('profits.colTransferred')]: totals.totalTransferred,
|
||||
[t('profits.colTransferReceipt')]: '—',
|
||||
});
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
||||
@ -445,14 +447,14 @@ export default function OwnerProfitsPage() {
|
||||
worksheet['!cols'] = colWidths;
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'أرباح المالك');
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, t('profits.sheetName'));
|
||||
|
||||
XLSX.writeFile(workbook, `تقرير_الأرباح_${new Date().toISOString().slice(0,19).replace(/:/g, '-')}.xlsx`);
|
||||
XLSX.writeFile(workbook, `${t('profits.fileName')}${new Date().toISOString().slice(0,19).replace(/:/g, '-')}.xlsx`);
|
||||
|
||||
toast.success('تم تصدير التقرير بنجاح!');
|
||||
toast.success(t('profits.exportSuccess'));
|
||||
} catch (error) {
|
||||
console.error('خطأ في التصدير:', error);
|
||||
toast.error('حدث خطأ أثناء تصدير التقرير');
|
||||
console.error(t('profits.exportErrorLog'), error);
|
||||
toast.error(t('profits.exportError'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -461,14 +463,14 @@ export default function OwnerProfitsPage() {
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-600">جاري تحميل بيانات الأرباح...</p>
|
||||
<p className="text-gray-600">{t('profits.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
<div className="container mx-auto px-4 max-w-7xl">
|
||||
<motion.div
|
||||
@ -477,9 +479,9 @@ export default function OwnerProfitsPage() {
|
||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">أرباح المالك</h1>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('profits.pageTitle')}</h1>
|
||||
<p className="text-gray-600">
|
||||
مرحباً {user?.name}
|
||||
{t('profits.greeting')} {user?.name}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@ -487,7 +489,7 @@ export default function OwnerProfitsPage() {
|
||||
className="px-5 py-2.5 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center gap-2 shadow-sm"
|
||||
>
|
||||
<Download className="w-5 h-5" />
|
||||
تصدير التقرير
|
||||
{t('profits.exportReport')}
|
||||
</button>
|
||||
</motion.div>
|
||||
|
||||
@ -500,18 +502,18 @@ export default function OwnerProfitsPage() {
|
||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead className="bg-gray-800 text-gray-100">
|
||||
<tr>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العقار</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم الحجز</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">من تاريخ</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">حتى تاريخ</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العروض المستلم</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">عمولة المنصة</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colProperty')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colBookingNumber')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colFromDate')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colToDate')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colAmountReceived')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colPlatformCommission')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider bg-amber-50 text-amber-800">
|
||||
ربح المنصة <span className="font-normal text-[11px] block">(5% من العربون)</span>
|
||||
{t('profits.colPlatformProfit')} <span className="font-normal text-[11px] block">{t('profits.colPlatformProfitSub')}</span>
|
||||
</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">المستحق للمالك</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">تم التحويل للمالك</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم وصل التحويل</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colOwnerDue')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colTransferred')}</th>
|
||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colTransferReceipt')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-100">
|
||||
@ -558,7 +560,7 @@ export default function OwnerProfitsPage() {
|
||||
<tfoot className="bg-gray-100 border-t-2 border-gray-300">
|
||||
<tr>
|
||||
<td colSpan="4" className="px-4 py-4 text-right font-bold text-gray-800">
|
||||
الإجمالي العام
|
||||
{t('profits.totalGeneral')}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
|
||||
{totals.totalAmountReceived}
|
||||
@ -582,8 +584,8 @@ export default function OwnerProfitsPage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 px-6 py-3 text-xs text-gray-500 border-t border-gray-200">
|
||||
<span className="inline-flex items-center gap-1"></span> ملاحظة:
|
||||
<strong> ربح المنصة </strong> يُحتسب تلقائياً بنسبة <strong className="text-amber-600">5%</strong> من قيمة «العروض المستلم».
|
||||
<span className="inline-flex items-center gap-1"></span> {t('profits.note')}
|
||||
<strong> {t('profits.colPlatformProfit')} </strong>{t('profits.noteMiddle')}<strong className="text-amber-600">5%</strong>{t('profits.noteEnd', { colName: t('profits.colAmountReceived') })}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@ -6,6 +6,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ArrowLeft,
|
||||
MapPin,
|
||||
@ -74,6 +75,7 @@ export default function AddPropertyPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const purpose = searchParams.get('purpose') || 'rent';
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [step, setStep] = useState(1);
|
||||
const totalSteps = purpose === 'sale' ? 4 : 4;
|
||||
@ -171,15 +173,15 @@ export default function AddPropertyPage() {
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const propertyTypes = [
|
||||
{ id: 'apartment', label: 'شقة', icon: Building },
|
||||
{ id: 'villa', label: 'فيلا', icon: Home },
|
||||
{ id: 'sweet', label: 'سويت', icon: Sofa },
|
||||
{ id: 'room', label: 'غرفة ضمن شقة (سكن مشترك)', icon: DoorOpen },
|
||||
{ id: 'studio', label: 'استوديو', icon: Sofa },
|
||||
{ id: 'office', label: 'مكتب', icon: Building },
|
||||
{ id: 'farms', label: 'مزرعة', icon: Trees },
|
||||
{ id: 'shop', label: 'متجر', icon: Warehouse },
|
||||
{ id: 'warehouse', label: 'مستودع', icon: Warehouse },
|
||||
{ id: 'apartment', label: t('buildingType.apartment'), icon: Building },
|
||||
{ id: 'villa', label: t('buildingType.villa'), icon: Home },
|
||||
{ id: 'sweet', label: t('buildingType.sweet'), icon: Sofa },
|
||||
{ id: 'room', label: t('buildingType.room'), icon: DoorOpen },
|
||||
{ id: 'studio', label: t('buildingType.studio'), icon: Sofa },
|
||||
{ id: 'office', label: t('buildingType.office'), icon: Building },
|
||||
{ id: 'farms', label: t('buildingType.farms'), icon: Trees },
|
||||
{ id: 'shop', label: t('buildingType.shop'), icon: Warehouse },
|
||||
{ id: 'warehouse', label: t('buildingType.warehouse'), icon: Warehouse },
|
||||
];
|
||||
|
||||
const serviceList = [
|
||||
@ -199,9 +201,9 @@ export default function AddPropertyPage() {
|
||||
];
|
||||
|
||||
const offerTypes = [
|
||||
{ id: 'daily', label: 'إيجار يومي', icon: Clock },
|
||||
{ id: 'monthly', label: 'إيجار شهري', icon: Calendar },
|
||||
{ id: 'both', label: 'إيجار يومي وشهري', icon: Calendar },
|
||||
{ id: 'daily', label: t('dailyRent'), icon: Clock },
|
||||
{ id: 'monthly', label: t('monthlyRent'), icon: Calendar },
|
||||
{ id: 'both', label: t('addProperty.dailyAndMonthly'), icon: Calendar },
|
||||
].filter(Boolean);
|
||||
|
||||
useEffect(() => {
|
||||
@ -218,7 +220,7 @@ export default function AddPropertyPage() {
|
||||
const handleSearch = async () => {
|
||||
if (!searchQuery) return;
|
||||
|
||||
toast.loading('جاري البحث...', { id: 'search' });
|
||||
toast.loading(t('toast.searching'), { id: 'search' });
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
@ -245,23 +247,23 @@ export default function AddPropertyPage() {
|
||||
address: addressData.display_name || result.display_name
|
||||
});
|
||||
|
||||
toast.success('تم العثور على الموقع', { id: 'search' });
|
||||
toast.success(t('toast.locationFound'), { id: 'search' });
|
||||
} else {
|
||||
toast.error('لم يتم العثور على العنوان', { id: 'search' });
|
||||
toast.error(t('toast.locationNotFound'), { id: 'search' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('خطأ في البحث:', error);
|
||||
toast.error('حدث خطأ في البحث', { id: 'search' });
|
||||
console.error(t('search.error'), error);
|
||||
toast.error(t('search.error'), { id: 'search' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleGeolocation = () => {
|
||||
if (!navigator.geolocation) {
|
||||
toast.error('المتصفح لا يدعم تحديد الموقع');
|
||||
if (!navigator.geolocation) {
|
||||
toast.error(t('toast.geolocationNotSupported'));
|
||||
return;
|
||||
}
|
||||
|
||||
toast.loading('جاري تحديد موقعك...', { id: 'geolocation' });
|
||||
|
||||
toast.loading(t('toast.geolocationLoading'), { id: 'geolocation' });
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
async (position) => {
|
||||
@ -269,30 +271,30 @@ export default function AddPropertyPage() {
|
||||
setMapCenter([latitude, longitude]);
|
||||
setMapZoom(18);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
setSelectedLocation({
|
||||
lat: latitude,
|
||||
lng: longitude,
|
||||
address: data.display_name || 'موقعك الحالي'
|
||||
});
|
||||
|
||||
toast.success('تم تحديد موقعك', { id: 'geolocation' });
|
||||
} catch (error) {
|
||||
setSelectedLocation({
|
||||
lat: latitude,
|
||||
lng: longitude,
|
||||
address: 'موقعك الحالي'
|
||||
});
|
||||
toast.success('تم تحديد موقعك', { id: 'geolocation' });
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
setSelectedLocation({
|
||||
lat: latitude,
|
||||
lng: longitude,
|
||||
address: data.display_name || t('addProperty.currentLocation')
|
||||
});
|
||||
|
||||
toast.success(t('toast.geolocationSuccess'), { id: 'geolocation' });
|
||||
} catch (error) {
|
||||
setSelectedLocation({
|
||||
lat: latitude,
|
||||
lng: longitude,
|
||||
address: t('addProperty.currentLocation')
|
||||
});
|
||||
toast.success(t('toast.geolocationSuccess'), { id: 'geolocation' });
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
toast.error('فشل في تحديد الموقع', { id: 'geolocation' });
|
||||
toast.error(t('toast.geolocationFailed'), { id: 'geolocation' });
|
||||
}
|
||||
);
|
||||
};
|
||||
@ -301,32 +303,32 @@ const handleMapClick = async (coords) => {
|
||||
try {
|
||||
const [lat, lng] = coords;
|
||||
|
||||
toast.loading('جاري تحديد الموقع...', { id: 'location' });
|
||||
toast.loading(t('addProperty.locationSelecting'), { id: 'location' });
|
||||
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
setSelectedLocation({
|
||||
lat: lat,
|
||||
lng: lng,
|
||||
address: data.display_name || 'موقع محدد'
|
||||
});
|
||||
|
||||
setMapZoom(18);
|
||||
toast.success('تم تحديد الموقع بنجاح!', { id: 'location' });
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
setSelectedLocation({
|
||||
lat: lat,
|
||||
lng: lng,
|
||||
address: data.display_name || t('addProperty.defaultAddress')
|
||||
});
|
||||
|
||||
setMapZoom(18);
|
||||
toast.success(t('toast.locationSelectedSuccess'), { id: 'location' });
|
||||
|
||||
} catch (error) {
|
||||
console.error('خطأ في تحديد الموقع:', error);
|
||||
console.error(t('addProperty.locationError'), error);
|
||||
const [lat, lng] = coords;
|
||||
setSelectedLocation({
|
||||
lat: lat,
|
||||
lng: lng,
|
||||
address: 'موقع محدد'
|
||||
address: t('addProperty.defaultAddress')
|
||||
});
|
||||
setMapZoom(18);
|
||||
toast.success('تم تحديد الموقع', { id: 'location' });
|
||||
toast.success(t('toast.locationConfirmed'), { id: 'location' });
|
||||
}
|
||||
};
|
||||
const handleMarkerDragEnd = async (lat, lng) => {
|
||||
@ -338,13 +340,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
setSelectedLocation({
|
||||
lat,
|
||||
lng,
|
||||
address: data.display_name || 'موقع محدد'
|
||||
address: data.display_name || t('addProperty.defaultAddress')
|
||||
});
|
||||
} catch (error) {
|
||||
setSelectedLocation({
|
||||
lat,
|
||||
lng,
|
||||
address: 'موقع محدد'
|
||||
address: t('addProperty.defaultAddress')
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -357,7 +359,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
address: selectedLocation.address
|
||||
});
|
||||
|
||||
toast.success('تم تأكيد الموقع بنجاح');
|
||||
toast.success(t('toast.locationConfirmed'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -370,25 +372,25 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
address: ''
|
||||
});
|
||||
setMapZoom(15);
|
||||
toast.info('تم إلغاء تحديد الموقع');
|
||||
toast.info(t('toast.clearLocation'));
|
||||
};
|
||||
|
||||
const handleImageUpload = async (files) => {
|
||||
const newImages = Array.from(files);
|
||||
|
||||
if (formData.images.length + newImages.length > 5) {
|
||||
toast.error('يمكنك رفع 5 صور كحد أقصى');
|
||||
toast.error(t('toast.maxImages'));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of newImages) {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
toast.error('الرجاء اختيار صور صالحة فقط');
|
||||
toast.error(t('toast.imageRequired'));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت');
|
||||
toast.error(t('toast.imageSize'));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -410,7 +412,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
setUploadedImagePaths(prev => [...prev, path]);
|
||||
} catch (err) {
|
||||
console.error('[AddProperty] Image upload failed:', err);
|
||||
toast.error('فشل رفع الصورة: ' + file.name);
|
||||
toast.error(`${t('toast.imageUploadFailed')}: ${file.name}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -519,48 +521,48 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
switch(step) {
|
||||
case 1:
|
||||
if (!formData.propertyType) {
|
||||
newErrors.propertyType = 'نوع العقار مطلوب';
|
||||
newErrors.propertyType = t('addProperty.propertyTypeRequired');
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (!formData.bedrooms) {
|
||||
newErrors.bedrooms = 'عدد الغرف مطلوب';
|
||||
newErrors.bedrooms = t('addProperty.bedroomsRequired');
|
||||
}
|
||||
if (!formData.bathrooms) {
|
||||
newErrors.bathrooms = 'عدد الحمامات مطلوب';
|
||||
newErrors.bathrooms = t('addProperty.bathroomsRequired');
|
||||
}
|
||||
if (!formData.livingRooms) {
|
||||
newErrors.livingRooms = 'عدد الصالونات مطلوب';
|
||||
newErrors.livingRooms = t('addProperty.livingRoomsRequired');
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (purpose === 'sale') {
|
||||
if (!formData.salePrice) newErrors.salePrice = 'سعر البيع مطلوب';
|
||||
if (!formData.salePrice) newErrors.salePrice = t('addProperty.salePriceRequired');
|
||||
} else {
|
||||
if (!formData.offerType) {
|
||||
newErrors.offerType = 'الرجاء اختيار نوع العرض';
|
||||
newErrors.offerType = t('addProperty.offerTypeRequired');
|
||||
} else if (formData.offerType === 'daily' && !formData.dailyPrice) {
|
||||
newErrors.dailyPrice = 'السعر اليومي مطلوب';
|
||||
newErrors.dailyPrice = t('addProperty.dailyPriceRequired');
|
||||
} else if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
|
||||
newErrors.monthlyPrice = 'السعر الشهري مطلوب';
|
||||
newErrors.monthlyPrice = t('addProperty.monthlyPriceRequired');
|
||||
} else if (formData.offerType === 'both') {
|
||||
if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب';
|
||||
if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب';
|
||||
if (!formData.dailyPrice) newErrors.dailyPrice = t('addProperty.dailyPriceRequired');
|
||||
if (!formData.monthlyPrice) newErrors.monthlyPrice = t('addProperty.monthlyPriceRequired');
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (!formData.lat || !formData.lng) {
|
||||
newErrors.location = 'الرجاء تحديد موقع العقار على الخريطة';
|
||||
newErrors.location = t('addProperty.locationRequired');
|
||||
}
|
||||
if (formData.images.length < 2) {
|
||||
newErrors.images = 'يجب رفع صورتين على الأقل';
|
||||
newErrors.images = t('addProperty.minImagesRequired');
|
||||
}
|
||||
if (formData.images.length > 5) {
|
||||
newErrors.images = 'يمكن رفع 5 صور كحد أقصى';
|
||||
newErrors.images = t('addProperty.maxImagesError');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -670,7 +672,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
currencyId: selectedCurrencyId,
|
||||
};
|
||||
const res = await addSaleProperty(payload);
|
||||
toast.success('تم إضافة عقار للبيع بنجاح!');
|
||||
toast.success(t('toast.salePropertySuccess'));
|
||||
} else {
|
||||
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
|
||||
const payload = {
|
||||
@ -685,14 +687,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
|
||||
};
|
||||
const res = await addRentProperty(payload);
|
||||
toast.success('تم إضافة عقار للإيجار بنجاح!');
|
||||
toast.success(t('toast.rentPropertySuccess'));
|
||||
}
|
||||
setTimeout(() => {
|
||||
router.push('/owner/properties');
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
console.error('[AddProperty] API error:', err);
|
||||
toast.error(err.message || 'فشل في إضافة العقار');
|
||||
toast.error(err.message || t('toast.propertyAddFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -713,15 +715,15 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="container mx-auto px-4 max-w-4xl">
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Link
|
||||
href="/owner/properties"
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>العودة للعقارات</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/owner/properties"
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>{t('addProperty.backToProperties')}</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-amber-600">خطوة {step} من {totalSteps}</span>
|
||||
<span className="text-sm font-medium text-amber-600">{t('addProperty.stepCounter', { step, total: totalSteps })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -738,10 +740,10 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-2 text-xs text-gray-500">
|
||||
<span>معلومات العقار</span>
|
||||
<span>التفاصيل والخدمات</span>
|
||||
<span>{purpose === 'sale' ? 'سعر البيع' : 'السعر'}</span>
|
||||
<span>الموقع والصور</span>
|
||||
<span>{t('addProperty.stepInfo')}</span>
|
||||
<span>{t('addProperty.stepDetails')}</span>
|
||||
<span>{purpose === 'sale' ? t('addProperty.stepSalePrice') : t('addProperty.stepRentPrice')}</span>
|
||||
<span>{t('addProperty.stepLocation')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -759,14 +761,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<Home className="w-10 h-10 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">معلومات العقار</h2>
|
||||
<p className="text-gray-600">اختر نوع العقار والحالة</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.stepInfo')}</h2>
|
||||
<p className="text-gray-600">{t('addProperty.propertyInfoSubtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
نوع العقار <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
{t('addProperty.propertyTypeLabel')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{propertyTypes.map((type) => {
|
||||
const Icon = type.icon;
|
||||
@ -794,7 +796,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
حالة العقار
|
||||
{t('addProperty.furnishedStatus')}
|
||||
</label>
|
||||
<div className="flex gap-4">
|
||||
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
||||
@ -805,7 +807,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
onChange={() => setFormData({...formData, furnished: true})}
|
||||
className="w-4 h-4 text-amber-500"
|
||||
/>
|
||||
<span className="text-gray-700">مفروش</span>
|
||||
<span className="text-gray-700">{t('addProperty.furnished')}</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
||||
<input
|
||||
@ -815,21 +817,21 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
onChange={() => setFormData({...formData, furnished: false})}
|
||||
className="w-4 h-4 text-amber-500"
|
||||
/>
|
||||
<span className="text-gray-700">غير مفروش</span>
|
||||
<span className="text-gray-700">{t('addProperty.unfurnished')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
وصف إضافي (اختياري)
|
||||
{t('addProperty.additionalDescription')}
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({...formData, description: e.target.value})}
|
||||
rows="4"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="أضف وصفاً إضافياً للعقار..."
|
||||
placeholder={t('addProperty.descriptionPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -841,14 +843,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<Layers className="w-10 h-10 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">تفاصيل العقار</h2>
|
||||
<p className="text-gray-600">أدخل التفاصيل والخدمات المتاحة</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.propertyDetailsTitle')}</h2>
|
||||
<p className="text-gray-600">{t('addProperty.propertyDetailsSubtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
المساحة (م²)
|
||||
{t('addProperty.space')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Square className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
@ -858,14 +860,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.space}
|
||||
onChange={(e) => setFormData({...formData, space: e.target.value})}
|
||||
className="w-full pr-10 pl-3 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 120"
|
||||
placeholder={t('addProperty.spacePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
عدد الغرف <span className="text-red-500">*</span>
|
||||
{t('addProperty.bedrooms')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@ -893,7 +895,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
عدد الحمامات <span className="text-red-500">*</span>
|
||||
{t('addProperty.bathrooms')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@ -921,7 +923,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
عدد الصالونات <span className="text-red-500">*</span>
|
||||
{t('addProperty.livingRooms')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@ -950,41 +952,41 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">رقم الطابق</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.floorNumber')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.floorNumber}
|
||||
onChange={(e) => setFormData({...formData, floorNumber: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 3"
|
||||
placeholder={t('addProperty.floorPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الصالونات</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.salons')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.salons}
|
||||
onChange={(e) => setFormData({...formData, salons: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 1"
|
||||
placeholder={t('addProperty.salonsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الشرفات</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.balconies')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.balconies}
|
||||
onChange={(e) => setFormData({...formData, balconies: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 1"
|
||||
placeholder={t('addProperty.balconiesPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">الخدمات المتوفرة <span className="text-red-500">*</span></h3>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.servicesLabel')} <span className="text-red-500">*</span></h3>
|
||||
<div className="space-y-3">
|
||||
{serviceList.map((service) => {
|
||||
const Icon = service.icon;
|
||||
@ -1010,7 +1012,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.serviceDetails[service.id] || ''}
|
||||
onChange={(e) => updateServiceDetail(service.id, e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="تفاصيل الخدمة (مثال: في جميع الغرف)"
|
||||
placeholder={t('addProperty.serviceDetailPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -1023,11 +1025,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="bg-white rounded-xl p-6 border border-gray-200 mt-6">
|
||||
<h3 className="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5 text-amber-600" />
|
||||
القرب من الخدمات
|
||||
{t('addProperty.nearbyServicesTitle')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مدرسة (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbySchoolLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1035,11 +1037,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbySchool}
|
||||
onChange={(e) => setFormData({...formData, nearbySchool: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 0.5"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مستشفى (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyHospitalLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1047,11 +1049,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbyHospital}
|
||||
onChange={(e) => setFormData({...formData, nearbyHospital: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 1.0"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مطعم (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyRestaurantLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1059,11 +1061,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbyRestaurant}
|
||||
onChange={(e) => setFormData({...formData, nearbyRestaurant: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 0.3"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب جامعة (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyUniversityLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1071,11 +1073,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbyUniversity}
|
||||
onChange={(e) => setFormData({...formData, nearbyUniversity: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 2.0"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب حديقة (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyParkLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1083,11 +1085,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbyPark}
|
||||
onChange={(e) => setFormData({...formData, nearbyPark: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 0.5"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مول (كم)</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyMallLabel')}</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@ -1095,7 +1097,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={formData.nearbyMall}
|
||||
onChange={(e) => setFormData({...formData, nearbyMall: e.target.value})}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 1.5"
|
||||
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1103,7 +1105,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
{purpose === 'rent' && (
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">شروط استخدام العقار</h3>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.termsTitle')}</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{termsList.map((term) => {
|
||||
const Icon = term.icon;
|
||||
@ -1137,14 +1139,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
{/* Custom Terms */}
|
||||
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">إضافة شرط مخصص</p>
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">{t('addProperty.customTermsTitle')}</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={customTermInput}
|
||||
onChange={(e) => setCustomTermInput(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addCustomTerm(); } }}
|
||||
placeholder="اكتب شرطاً مخصصاً..."
|
||||
placeholder={t('addProperty.customTermPlaceholder')}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
||||
/>
|
||||
<button
|
||||
@ -1153,7 +1155,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
disabled={!customTermInput.trim()}
|
||||
className="px-4 py-2 bg-amber-500 text-white rounded-lg text-sm font-medium hover:bg-amber-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
إضافة
|
||||
{t('addProperty.add')}
|
||||
</button>
|
||||
</div>
|
||||
{customTerms.length > 0 && (
|
||||
@ -1187,13 +1189,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<DollarSign className="w-10 h-10 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">سعر البيع</h2>
|
||||
<p className="text-gray-600">حدد سعر البيع والعملة</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.salePriceTitle')}</h2>
|
||||
<p className="text-gray-600">{t('addProperty.salePriceSubtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
سعر البيع (ل.س) <span className="text-red-500">*</span>
|
||||
{t('addProperty.salePriceLabel')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1211,7 +1213,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||
errors.salePrice ? 'border-red-500' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="مثال: 50000000"
|
||||
placeholder={t('addProperty.salePricePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{errors.salePrice && <p className="text-red-500 text-sm mt-1">{errors.salePrice}</p>}
|
||||
@ -1219,16 +1221,16 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
العملة <span className="text-red-500">*</span>
|
||||
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={selectedCurrencyId}
|
||||
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value={Currency.USD}>دولار امريكي $</option>
|
||||
<option value={Currency.SYP}>عملة سورية SP</option>
|
||||
</select>
|
||||
<select
|
||||
value={selectedCurrencyId}
|
||||
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value={Currency.USD}>{t('addProperty.usdOption')}</option>
|
||||
<option value={Currency.SYP}>{t('addProperty.sypOption')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@ -1239,13 +1241,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<DollarSign className="w-10 h-10 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">نوع العرض والسعر</h2>
|
||||
<p className="text-gray-600">اختر نوع العرض وحدد السعر المناسب</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.offerAndPriceTitle')}</h2>
|
||||
<p className="text-gray-600">{t('addProperty.offerAndPriceSubtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
نوع العرض <span className="text-red-500">*</span>
|
||||
{t('addProperty.offerTypeLabel')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{offerTypes.map((type) => {
|
||||
@ -1272,15 +1274,15 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
{/* Currency dropdown */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
العملة <span className="text-red-500">*</span>
|
||||
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={selectedCurrencyId}
|
||||
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value={Currency.USD}>دولار امريكي $</option>
|
||||
<option value={Currency.SYP}>عملة سورية SP</option>
|
||||
<option value={Currency.USD}>{t('addProperty.usdOption')}</option>
|
||||
<option value={Currency.SYP}>{t('addProperty.sypOption')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -1292,7 +1294,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
{!formData.offerType && (
|
||||
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center">
|
||||
<DollarSign className="w-8 h-8 text-gray-300 mx-auto mb-3" />
|
||||
<p className="text-gray-400 font-medium">اختر نوع العرض اولا</p>
|
||||
<p className="text-gray-400 font-medium">{t('addProperty.selectOfferTypeFirst')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1307,7 +1309,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
{t('addProperty.dailyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1325,7 +1327,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="مثال: 50000"
|
||||
placeholder={t('addProperty.dailyPricePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{errors.dailyPrice && (
|
||||
@ -1334,7 +1336,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
مبلغ التأمين ({currencySymbol})
|
||||
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1350,7 +1352,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
}}
|
||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 500000"
|
||||
placeholder={t('addProperty.depositPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1367,7 +1369,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
{t('addProperty.monthlyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1385,7 +1387,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="مثال: 1000000"
|
||||
placeholder={t('addProperty.monthlyPricePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{errors.monthlyPrice && (
|
||||
@ -1394,7 +1396,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
مبلغ التأمين ({currencySymbol})
|
||||
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1410,7 +1412,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
}}
|
||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 500000"
|
||||
placeholder={t('addProperty.depositPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1427,7 +1429,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
{t('addProperty.dailyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1445,7 +1447,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="مثال: 50000"
|
||||
placeholder={t('addProperty.dailyPricePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{errors.dailyPrice && (
|
||||
@ -1454,7 +1456,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
{t('addProperty.monthlyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1472,7 +1474,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
||||
}`}
|
||||
placeholder="مثال: 1000000"
|
||||
placeholder={t('addProperty.monthlyPricePlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
{errors.monthlyPrice && (
|
||||
@ -1481,7 +1483,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
مبلغ التأمين ({currencySymbol})
|
||||
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||
@ -1497,7 +1499,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
}}
|
||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
placeholder="مثال: 500000"
|
||||
placeholder={t('addProperty.depositPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1513,12 +1515,12 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<MapPin className="w-10 h-10 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">الموقع والصور</h2>
|
||||
<p className="text-gray-600">حدد موقع العقار وأضف الصور</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.locationAndImagesTitle')}</h2>
|
||||
<p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">حدد موقع العقار على الخريطة</h3>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.selectLocationTitle')}</h3>
|
||||
|
||||
<div className="flex gap-2 mb-4">
|
||||
<div className="flex-1 relative">
|
||||
@ -1527,7 +1529,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
||||
placeholder="ابحث عن عنوان..."
|
||||
placeholder={t('addProperty.searchAddress')}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-12"
|
||||
/>
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
@ -1536,16 +1538,16 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
onClick={handleSearch}
|
||||
className="px-6 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors"
|
||||
>
|
||||
بحث
|
||||
{t('addProperty.searchButton')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-4 text-sm text-amber-800 leading-relaxed">
|
||||
<Search className="w-4 h-4 inline ml-1" />
|
||||
ابحث أولاً ثم اضغط على النتيجة لتحريك الخريطة و تثبيت العلامة .
|
||||
{t('addProperty.searchHint')}
|
||||
<br />
|
||||
<MapPin className="w-4 h-4 inline ml-1" />
|
||||
اضغط على الخريطة لتحديد موقع العقار
|
||||
{t('addProperty.clickMapHint')}
|
||||
</div>
|
||||
|
||||
<div className="relative w-full h-96 rounded-xl overflow-hidden border-2 border-gray-200 mb-4">
|
||||
@ -1564,7 +1566,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className="w-full mb-4 bg-green-500 text-white py-3 rounded-xl font-medium hover:bg-green-600 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<CheckCircle className="w-5 h-5" />
|
||||
تأكيد هذا الموقع
|
||||
{t('addProperty.confirmLocation')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@ -1572,7 +1574,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4 mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||
<span className="text-green-800 font-medium">تم تأكيد الموقع:</span>
|
||||
<span className="text-green-800 font-medium">{t('addProperty.locationConfirmed')}:</span>
|
||||
</div>
|
||||
<p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p>
|
||||
</div>
|
||||
@ -1584,7 +1586,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">صور العقار</h3>
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.imagesTitle')}</h3>
|
||||
|
||||
<div
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
@ -1602,16 +1604,16 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
/>
|
||||
|
||||
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<p className="text-gray-600 font-medium">اضغط لرفع الصور</p>
|
||||
<p className="text-gray-600 font-medium">{t('addProperty.uploadImages')}</p>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
JPEG, PNG, JPG • حتى 5MB • 800x600 بكسل • 2-5 صور
|
||||
{t('addProperty.imageFormatHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-500 mt-2 mb-4 text-center">
|
||||
يرجى رفع عدة صور واضحة
|
||||
{t('addProperty.uploadMultipleHintLine1')}
|
||||
<br />
|
||||
(اضغط على الرفع أكثر من مرة)
|
||||
{t('addProperty.uploadMultipleHintLine2')}
|
||||
</p>
|
||||
|
||||
{errors.images && (
|
||||
@ -1649,8 +1651,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
<div className="border-t border-gray-200 pt-6 mt-8">
|
||||
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-200">
|
||||
<p className="text-sm text-gray-700 leading-relaxed mb-4">
|
||||
أنا صاحب العقار أقر أنني مالك العقار أو مخول قانونياً بعرضه للإيجار أو البيع،
|
||||
وأن جميع المعلومات المدخلة صحيحة، وأتحمل كامل المسؤولية عن أي معلومات غير صحيحة.
|
||||
{t('addProperty.ownerAgreement')}
|
||||
</p>
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<input
|
||||
@ -1660,9 +1661,9 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className="w-5 h-5 mt-0.5 text-amber-500 rounded shrink-0"
|
||||
/>
|
||||
<span className="text-sm text-gray-600">
|
||||
أوافق على{' '}
|
||||
{t('addProperty.agreeToText')}{' '}
|
||||
<Link href="/terms" target="_blank" className="text-amber-600 underline hover:text-amber-700">
|
||||
شروط الاستخدام
|
||||
{t('addProperty.termsOfUseText')}
|
||||
</Link>
|
||||
</span>
|
||||
</label>
|
||||
@ -1680,7 +1681,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
className="flex-1 py-3 px-4 bg-gray-100 text-gray-700 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
السابق
|
||||
{t('addProperty.previous')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@ -1691,7 +1692,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
step === 1 ? 'w-full' : ''
|
||||
}`}
|
||||
>
|
||||
التالي
|
||||
{t('addProperty.next')}
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
) : (
|
||||
@ -1703,12 +1704,12 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
جاري الحفظ...
|
||||
{t('addProperty.saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-5 h-5" />
|
||||
حفظ العقار
|
||||
{t('addProperty.saveProperty')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -643,12 +643,12 @@
|
||||
|
||||
|
||||
|
||||
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
@ -660,8 +660,6 @@ import {
|
||||
MapPin,
|
||||
DollarSign,
|
||||
Home,
|
||||
ArrowLeft,
|
||||
RefreshCw,
|
||||
Flag,
|
||||
} from 'lucide-react';
|
||||
import toast, { Toaster } from 'react-hot-toast';
|
||||
@ -680,34 +678,37 @@ const STATUS_MAP = [
|
||||
'cancelled',
|
||||
];
|
||||
|
||||
const STATUS_I18N_KEYS = {
|
||||
pending: 'bookingStatus.pending',
|
||||
ownerConfirmed: 'bookingStatus.ownerConfirmed',
|
||||
depositPaid: 'bookingStatus.depositPaid',
|
||||
depositConfirmed: 'bookingStatus.depositConfirmed',
|
||||
completed: 'bookingStatus.completed',
|
||||
cancelled: 'bookingStatus.cancelled',
|
||||
};
|
||||
|
||||
const STATUS_UI = {
|
||||
pending: {
|
||||
label: 'قيد الانتظار',
|
||||
color: 'bg-yellow-100 text-yellow-800',
|
||||
icon: Clock,
|
||||
},
|
||||
ownerConfirmed: {
|
||||
label: 'مؤكد',
|
||||
color: 'bg-green-100 text-green-800',
|
||||
icon: CheckCircle,
|
||||
},
|
||||
depositPaid: {
|
||||
label: 'تم دفع السلفة',
|
||||
color: 'bg-indigo-100 text-indigo-800',
|
||||
icon: DollarSign,
|
||||
},
|
||||
depositConfirmed: {
|
||||
label: 'مؤكد نهائياً',
|
||||
color: 'bg-green-100 text-green-800',
|
||||
icon: CheckCircle,
|
||||
},
|
||||
completed: {
|
||||
label: 'منتهي',
|
||||
color: 'bg-blue-100 text-blue-800',
|
||||
icon: CheckCircle,
|
||||
},
|
||||
cancelled: {
|
||||
label: 'ملغي',
|
||||
color: 'bg-gray-100 text-gray-800',
|
||||
icon: XCircle,
|
||||
},
|
||||
@ -715,7 +716,7 @@ const STATUS_UI = {
|
||||
|
||||
const getStatusKey = (code) => STATUS_MAP[Number(code)] || 'pending';
|
||||
|
||||
const sLabel = (code) => STATUS_UI[getStatusKey(code)]?.label ?? String(code);
|
||||
const sLabel = (code, t) => t(STATUS_I18N_KEYS[getStatusKey(code)]) || String(code);
|
||||
|
||||
const sColor = (code) =>
|
||||
STATUS_UI[getStatusKey(code)]?.color ?? 'bg-gray-100 text-gray-700';
|
||||
@ -723,6 +724,7 @@ const sColor = (code) =>
|
||||
const sIcon = (code) => STATUS_UI[getStatusKey(code)]?.icon ?? Clock;
|
||||
|
||||
function StatusBadge({ code }) {
|
||||
const { t } = useTranslation();
|
||||
const Icon = sIcon(code);
|
||||
|
||||
return (
|
||||
@ -731,7 +733,7 @@ function StatusBadge({ code }) {
|
||||
code
|
||||
)}`}
|
||||
>
|
||||
<Icon className="w-3 h-3" /> {sLabel(code)}
|
||||
<Icon className="w-3 h-3" /> {sLabel(code, t)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -839,10 +841,10 @@ async function reportReservation(reservationId, message) {
|
||||
const rid = Number(reservationId);
|
||||
|
||||
if (!Number.isInteger(rid)) {
|
||||
throw new Error('رقم الحجز غير صالح');
|
||||
throw new Error(t('invalidReservationId'));
|
||||
}
|
||||
if (!Number.isInteger(reporter)) {
|
||||
throw new Error('تعذر تحديد المستخدم الحالي');
|
||||
throw new Error(t('couldNotIdentifyUser'));
|
||||
}
|
||||
|
||||
const token = getAuthToken();
|
||||
@ -860,7 +862,7 @@ async function reportReservation(reservationId, message) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
let errorMessage = 'فشل إرسال البلاغ';
|
||||
let errorMessage = t('reportFailed');
|
||||
try {
|
||||
const data = await res.json();
|
||||
errorMessage = data?.message || data?.title || errorMessage;
|
||||
@ -889,6 +891,7 @@ function OwnerCard({
|
||||
actionLoadingId,
|
||||
reportingId,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const p = r._prop;
|
||||
const imgs = pImgs(p);
|
||||
const img = imgs.length > 0 ? buildImageUrl(imgs[0]) : null;
|
||||
@ -926,21 +929,21 @@ function OwnerCard({
|
||||
<div className="text-lg font-bold text-amber-600">
|
||||
{r.totalPrice?.toLocaleString() ?? '—'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">السعر الإجمالي</div>
|
||||
<div className="text-xs text-gray-500">{t('totalPrice')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(pBeds(p) || pBaths(p)) && (
|
||||
<div className="flex gap-3 mb-3 text-sm text-gray-600">
|
||||
{pBeds(p) > 0 && <span>{pBeds(p)} غرف</span>}
|
||||
{pBaths(p) > 0 && <span>{pBaths(p)} حمامات</span>}
|
||||
{pBeds(p) > 0 && <span>{pBeds(p)} {t('reservationsBedrooms')}</span>}
|
||||
{pBaths(p) > 0 && <span>{pBaths(p)} {t('reservationsBathrooms')}</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
||||
<div className="bg-gray-50 p-2 rounded-lg">
|
||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-xs text-gray-500">من</div>
|
||||
<div className="text-xs text-gray-500">{t('from')}</div>
|
||||
<div className="text-sm font-medium">
|
||||
{r.startDate
|
||||
? new Date(r.startDate).toLocaleDateString('ar')
|
||||
@ -950,7 +953,7 @@ function OwnerCard({
|
||||
|
||||
<div className="bg-gray-50 p-2 rounded-lg">
|
||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||
<div className="text-xs text-gray-500">إلى</div>
|
||||
<div className="text-xs text-gray-500">{t('to')}</div>
|
||||
<div className="text-sm font-medium">
|
||||
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
||||
</div>
|
||||
@ -968,7 +971,7 @@ function OwnerCard({
|
||||
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
التفاصيل
|
||||
{t('details')}
|
||||
</button>
|
||||
|
||||
{isPending && (
|
||||
@ -984,7 +987,7 @@ function OwnerCard({
|
||||
) : (
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
)}
|
||||
قبول
|
||||
{t('accept')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@ -998,7 +1001,7 @@ function OwnerCard({
|
||||
) : (
|
||||
<XCircle className="w-4 h-4" />
|
||||
)}
|
||||
رفض
|
||||
{t('reject')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@ -1019,7 +1022,7 @@ function OwnerCard({
|
||||
) : (
|
||||
<Flag className="w-4 h-4" />
|
||||
)}
|
||||
{isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
|
||||
{isReporting ? t('reporting') : t('report')}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -1027,6 +1030,7 @@ function OwnerCard({
|
||||
}
|
||||
|
||||
function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
const { t } = useTranslation();
|
||||
if (!isOpen || !r) return null;
|
||||
|
||||
const p = r._prop;
|
||||
@ -1049,7 +1053,7 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
>
|
||||
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold">طلب حجز #{r.id}</h2>
|
||||
<h2 className="text-xl font-bold">{t('reservationRequest', { id: r.id })}</h2>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@ -1066,11 +1070,11 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||
<Home className="w-5 h-5 text-amber-500" />
|
||||
معلومات العقار
|
||||
{t('propertyInfo')}
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<span className="text-gray-500">العنوان:</span>{' '}
|
||||
<span className="text-gray-500">{t('address')}</span>{' '}
|
||||
{pAddr(p) || '—'}
|
||||
</p>
|
||||
|
||||
@ -1078,13 +1082,13 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
<div className="flex gap-3 mt-2">
|
||||
{pBeds(p) > 0 && (
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
||||
{pBeds(p)} غرف
|
||||
{pBeds(p)} {t('reservationsBedrooms')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{pBaths(p) > 0 && (
|
||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
||||
{pBaths(p)} حمامات
|
||||
{pBaths(p)} {t('reservationsBathrooms')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -1095,12 +1099,12 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5 text-amber-500" />
|
||||
تفاصيل الحجز
|
||||
{t('reservationDetails')}
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-gray-500">تاريخ البداية</p>
|
||||
<p className="text-gray-500">{t('startDate')}</p>
|
||||
<p className="font-medium">
|
||||
{r.startDate
|
||||
? new Date(r.startDate).toLocaleDateString('ar')
|
||||
@ -1109,19 +1113,19 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-gray-500">تاريخ النهاية</p>
|
||||
<p className="text-gray-500">{t('endDate')}</p>
|
||||
<p className="font-medium">
|
||||
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-gray-500">الحالة</p>
|
||||
<p className="text-gray-500">{t('statusLabel')}</p>
|
||||
<StatusBadge code={r.status} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-gray-500">تاريخ الإنشاء</p>
|
||||
<p className="text-gray-500">{t('createdAt')}</p>
|
||||
<p className="font-medium">
|
||||
{r.createdAt
|
||||
? new Date(r.createdAt).toLocaleDateString('ar')
|
||||
@ -1134,11 +1138,11 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
<div className="bg-amber-50 p-4 rounded-xl">
|
||||
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2">
|
||||
<DollarSign className="w-5 h-5" />
|
||||
المعلومات المالية
|
||||
{t('financialInfo')}
|
||||
</h3>
|
||||
|
||||
<div className="flex justify-between font-bold">
|
||||
<span className="text-gray-900">الإجمالي</span>
|
||||
<span className="text-gray-900">{t('total')}</span>
|
||||
<span className="text-amber-600 text-lg">
|
||||
{r.totalPrice?.toLocaleString() ?? '—'}
|
||||
</span>
|
||||
@ -1151,6 +1155,7 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||
}
|
||||
|
||||
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||
const { t } = useTranslation();
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@ -1177,8 +1182,8 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 text-white">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">الإبلاغ عن الحجز</h2>
|
||||
<p className="text-red-100 text-sm mt-1">رقم الحجز: #{reservation.id}</p>
|
||||
<h2 className="text-xl font-bold">{t('reportReservation')}</h2>
|
||||
<p className="text-red-100 text-sm mt-1">{t('reservationId', { id: reservation.id })}</p>
|
||||
</div>
|
||||
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
|
||||
<XCircle className="h-6 w-6" />
|
||||
@ -1188,13 +1193,13 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||
|
||||
<div className="p-6">
|
||||
<p className="text-gray-700 mb-4 leading-7">
|
||||
اخبر فريق الدعم بما حدث التفاصيل الواضحة تساعدنا على مراجعة هذا الحجز بشكل اسرع
|
||||
{t('reportDescription')}
|
||||
</p>
|
||||
|
||||
<textarea
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
placeholder="اكتب تفاصيل البلاغ هنا..."
|
||||
placeholder={t('reportPlaceholder')}
|
||||
rows={5}
|
||||
className="w-full resize-none rounded-xl border border-gray-300 p-3 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
/>
|
||||
@ -1208,14 +1213,14 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||
}`}
|
||||
>
|
||||
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Flag className="h-4 w-4" />}
|
||||
{submitting ? 'جاري الإرسال...' : 'إرسال البلاغ'}
|
||||
{submitting ? t('submitting') : t('submitReport')}
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
disabled={submitting}
|
||||
className="rounded-xl bg-gray-200 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-300 transition-colors disabled:cursor-not-allowed"
|
||||
>
|
||||
إلغاء
|
||||
{t('cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1225,6 +1230,7 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||
}
|
||||
|
||||
export default function OwnerReservationRequestsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
const [reservations, setReservations] = useState([]);
|
||||
@ -1270,7 +1276,7 @@ export default function OwnerReservationRequestsPage() {
|
||||
setFiltered(enriched);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('فشل تحميل طلبات الحجز');
|
||||
toast.error(t('loadFailedReservations'));
|
||||
setReservations([]);
|
||||
setFiltered([]);
|
||||
} finally {
|
||||
@ -1321,18 +1327,18 @@ export default function OwnerReservationRequestsPage() {
|
||||
throw new Error(errorText || `HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
toast.success('تم قبول الحجز بنجاح');
|
||||
toast.success(t('acceptSuccess'));
|
||||
await loadReservations();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('فشل قبول الحجز');
|
||||
toast.error(t('acceptFailed'));
|
||||
} finally {
|
||||
setActionLoadingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (r) => {
|
||||
if (!confirm('هل أنت متأكد من رفض هذا الحجز؟')) return;
|
||||
if (!confirm(t('rejectConfirm'))) return;
|
||||
|
||||
try {
|
||||
setActionLoadingId(r.id);
|
||||
@ -1348,11 +1354,11 @@ export default function OwnerReservationRequestsPage() {
|
||||
throw new Error(errorText || `HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
toast.success('تم رفض الحجز');
|
||||
toast.success(t('rejectSuccess'));
|
||||
await loadReservations();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('فشل رفض الحجز');
|
||||
toast.error(t('rejectFailed'));
|
||||
} finally {
|
||||
setActionLoadingId(null);
|
||||
}
|
||||
@ -1372,11 +1378,11 @@ export default function OwnerReservationRequestsPage() {
|
||||
setReportingId(reportDialog.reservation.id);
|
||||
try {
|
||||
await reportReservation(reportDialog.reservation.id, message.trim() || null);
|
||||
toast.success('تم إرسال البلاغ بنجاح');
|
||||
toast.success(t('reportSuccess'));
|
||||
closeReportDialog();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(err?.message || 'فشل إرسال البلاغ');
|
||||
toast.error(err?.message || t('reportFailed'));
|
||||
} finally {
|
||||
setReportingId(null);
|
||||
}
|
||||
@ -1405,7 +1411,7 @@ export default function OwnerReservationRequestsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
|
||||
<DetailsModal
|
||||
@ -1429,30 +1435,11 @@ export default function OwnerReservationRequestsPage() {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 mb-4"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
الرجوع
|
||||
</button>
|
||||
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">
|
||||
طلبات الحجز
|
||||
</h1>
|
||||
<p className="text-gray-600">لديك {reservations.length} طلب</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={loadReservations}
|
||||
className="p-2 bg-white shadow rounded-xl hover:shadow-md transition-all"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<div className="mb-4">
|
||||
<h1 className="text-3xl font-bold text-gray-900">
|
||||
{t('reservationsTitle')}
|
||||
</h1>
|
||||
<p className="text-gray-600">{t('reservationsCount', { count: reservations.length })}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@ -1471,7 +1458,7 @@ export default function OwnerReservationRequestsPage() {
|
||||
>
|
||||
<div className="text-2xl font-bold text-amber-600">{c}</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{s === 'all' ? 'الكل' : STATUS_UI[s]?.label || s}
|
||||
{s === 'all' ? t('filterAll') : t(STATUS_I18N_KEYS[s]) || s}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
@ -1482,7 +1469,7 @@ export default function OwnerReservationRequestsPage() {
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="ابحث بعنوان العقار أو رقم الحجز..."
|
||||
placeholder={t('searchPlaceholderReservations')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
@ -1493,10 +1480,10 @@ export default function OwnerReservationRequestsPage() {
|
||||
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
||||
لا توجد طلبات
|
||||
{t('noReservations')}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
لم يتم استلام أي طلبات حجز حتى الآن
|
||||
{t('noReservationsHint')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@ -1518,4 +1505,4 @@ export default function OwnerReservationRequestsPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user