Added translation to all site with edit style
All checks were successful
Build frontend / build (push) Successful in 1m40s

This commit is contained in:
Rahaf
2026-07-01 15:43:58 +03:00
parent 3052209df8
commit dab5b62fb7
56 changed files with 5136 additions and 3190 deletions

View File

@ -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>
);
}
}