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

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