Added translation to all site with edit style

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

@ -396,6 +396,7 @@ import { useState, useEffect, useCallback } from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import {
Calendar,
Clock,
@ -432,50 +433,49 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io
const STATUS_MAP = ['pending','ownerConfirmed','depositPaid','depositConfirmed','completed','cancelled'];
const STATUS_UI = {
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800', icon: Clock },
ownerConfirmed: { label: 'مؤكد من المالك', color: 'bg-blue-100 text-blue-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-green-100 text-green-800', icon: CheckCircle },
cancelled: { label: 'ملغي', color: 'bg-gray-100 text-gray-800', icon: XCircle },
pending: { color: 'bg-yellow-100 text-yellow-800', icon: Clock },
ownerConfirmed: { color: 'bg-blue-100 text-blue-800', icon: CheckCircle },
depositPaid: { color: 'bg-indigo-100 text-indigo-800', icon: DollarSign },
depositConfirmed: { color: 'bg-green-100 text-green-800', icon: CheckCircle },
completed: { color: 'bg-green-100 text-green-800', icon: CheckCircle },
cancelled: { color: 'bg-gray-100 text-gray-800', icon: XCircle },
};
const PAYMENT_METHODS = [
{
id: 'cash',
label: 'الدفع النقدي',
desc: 'ادفع الآن عبر شام كاش مع تأكيد تلقائي للحجز',
labelKey: 'payment.cash',
descKey: 'payment.cashDesc',
icon: Banknote,
active: true,
},
{
id: 'office',
label: 'نقداً',
desc: 'ادفع في مكتب المنصة. يتم التأكيد لاحقاً',
labelKey: 'payment.office',
descKey: 'payment.officeDesc',
icon: Building,
active: true,
},
{
id: 'transfer',
label: 'تحويل داخلي',
desc: 'حول من حساب داخلي ثم أرسل المرجع',
labelKey: 'payment.transfer',
descKey: 'payment.transferDesc',
icon: ArrowLeftRight,
active: false,
},
{
id: 'electronic',
label: 'دفع إلكتروني',
desc: 'سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً',
labelKey: 'payment.electronic',
descKey: 'payment.electronicDesc',
icon: CreditCard,
active: false,
},
];
function statusLabel(code) { return STATUS_UI[STATUS_MAP[code]]?.label ?? String(code); }
function statusColor(code) { return STATUS_UI[STATUS_MAP[code]]?.color ?? 'bg-gray-100 text-gray-700'; }
function statusIcon(code) { return STATUS_UI[STATUS_MAP[code]]?.icon ?? Clock; }
function formatCurrency(v, sign = 'ل.س') {
function formatCurrency(v, sign = '') {
return `${sign} ${Number(v ?? 0).toLocaleString()}`;
}
@ -487,10 +487,12 @@ function formatDate(date) {
}
function StatusBadge({ code }) {
const { t } = useTranslation();
const Icon = statusIcon(code);
const key = STATUS_MAP[code] || 'pending';
return (
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${statusColor(code)}`}>
<Icon className="w-3 h-3" /> {statusLabel(code)}
<Icon className="w-3 h-3" /> {t(`bookingStatus.${key}`)}
</span>
);
}
@ -563,10 +565,10 @@ async function reportReservation(reservationId, message) {
const rid = Number(reservationId);
if (!Number.isInteger(rid)) {
throw new Error('رقم الحجز غير صالح');
throw new Error('Invalid reservation ID');
}
if (!Number.isInteger(reporter)) {
throw new Error('تعذر تحديد المستخدم الحالي');
throw new Error('Could not identify current user');
}
const token = getAuthToken();
@ -584,7 +586,7 @@ async function reportReservation(reservationId, message) {
});
if (!res.ok) {
let errorMessage = 'فشل إرسال البلاغ';
let errorMessage = 'Failed to submit report';
try {
const data = await res.json();
errorMessage = data?.message || data?.title || errorMessage;
@ -640,6 +642,7 @@ function formatWindowDuration(str) {
}
function CountdownTimer({ deadline }) {
const { t } = useTranslation();
const [remaining, setRemaining] = useState(deadline ? Math.max(0, deadline - Date.now()) : 0);
useEffect(() => {
if (!deadline) return;
@ -648,7 +651,7 @@ function CountdownTimer({ deadline }) {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [deadline]);
if (remaining <= 0) return <span className="text-red-500 text-sm font-medium">انتهت المهلة</span>;
if (remaining <= 0) return <span className="text-red-500 text-sm font-medium">{t('timeExpired')}</span>;
const h = Math.floor(remaining / 3600000);
const m = Math.floor((remaining % 3600000) / 60000);
const s = Math.floor((remaining % 60000) / 1000);
@ -665,6 +668,7 @@ function PaymentDialog({
onConfirmPay,
payingId,
}) {
const { t } = useTranslation();
const [showCashDialog, setShowCashDialog] = useState(false);
useEffect(() => {
@ -674,7 +678,7 @@ function PaymentDialog({
if (!isOpen || !reservation) return null;
const amount = reservation.depositAmount || reservation.totalPrice || 0;
const currencySign = reservation.currencySign || 'ل.س';
const currencySign = reservation.currencySign || t('sypSymbol');
return (
<>
@ -720,12 +724,12 @@ function PaymentDialog({
<div className="px-6 py-5 border-b border-gray-100">
<div className="text-center">
<p className="text-sm text-gray-500 mb-1">مبلغ التأمين (الرعبون)</p>
<p className="text-sm text-gray-500 mb-1">{t('depositAmount')}</p>
<p className="text-4xl font-bold text-amber-600">
{formatCurrency(amount, currencySign)}
</p>
<p className="text-xs text-gray-400 mt-2">
يتم دفع التأمين إلى المنصة وليس إلى المالك
{t('depositPaidToPlatform')}
</p>
</div>
</div>
@ -734,13 +738,13 @@ function PaymentDialog({
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
<p className="text-sm text-amber-800 leading-relaxed">
الدفع النقدي فقط هو المتاح. سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً.
{t('cashOnlyNotice')}
</p>
</div>
</div>
<div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">طريقة الدفع</p>
<p className="text-sm font-bold text-gray-700 mb-3">{t('paymentMethod')}</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{PAYMENT_METHODS.map((method) => {
const isSelected = selectedPayment === method.id;
@ -767,14 +771,14 @@ function PaymentDialog({
</div>
<div className="min-w-0 flex-1">
<p className={`text-sm font-bold ${isSelected ? 'text-amber-900' : 'text-gray-800'}`}>
{method.label}
{t(method.labelKey)}
</p>
<p className="text-xs text-gray-500 mt-0.5 leading-relaxed">
{method.desc}
{t(method.descKey)}
</p>
{!method.active && (
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full">
غير متاح
{t('notAvailable')}
</span>
)}
</div>
@ -801,12 +805,12 @@ function PaymentDialog({
{payingId === reservation.id ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
جاري الدفع...
{t('processingPayment')}
</>
) : (
<>
<Banknote className="w-5 h-5" />
دفع التأمين ({formatCurrency(amount, currencySign)})
{t('payDeposit', { amount: formatCurrency(amount, currencySign) })}
</>
)}
</motion.button>
@ -819,7 +823,7 @@ function PaymentDialog({
className="w-full bg-white border-2 border-amber-200 hover:border-amber-400 text-amber-700 font-bold py-3 px-6 rounded-2xl text-base transition-all flex items-center justify-center gap-2"
>
<Building className="w-5 h-5" />
سأدفع نقداً
{t('payCash')}
</button>
</div>
@ -827,9 +831,9 @@ function PaymentDialog({
<div className="flex items-start gap-3">
<Landmark className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
<div>
<p className="text-sm font-bold text-gray-800 mb-1">موقع الدفع النقدي</p>
<p className="text-sm font-bold text-gray-800 mb-1">{t('cashPaymentLocation')}</p>
<p className="text-sm text-gray-600 leading-relaxed">
مكتب المنصة: أبو رمانة، شارع المالكي، دمشق
{t('platformOfficeAddress')}
</p>
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1">
<Phone className="w-3.5 h-3.5" />
@ -845,7 +849,7 @@ function PaymentDialog({
onClick={onClose}
className="px-5 py-2.5 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
>
إغلاق
{t('close')}
</button>
</div>
</motion.div>
@ -861,15 +865,13 @@ function PaymentDialog({
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-5">
<Clock className="w-8 h-8 text-amber-600" />
</div>
<h3 className="text-xl font-bold text-gray-900 mb-3">الدفع النقدي قيد الانتظار</h3>
<h3 className="text-xl font-bold text-gray-900 mb-3">{t('cashPaymentPending')}</h3>
<p className="text-gray-600 leading-relaxed mb-6">
اذهب إلى موقع المنصة وادفع العربون.
<br />
ستقوم المنصة بتأكيد الدفع بعد استلام المبلغ.
{t('cashPaymentInstructions')}
</p>
<div className="bg-gray-50 rounded-2xl p-4 mb-6 text-right">
<p className="text-sm font-bold text-gray-800 mb-1">موقع المنصة</p>
<p className="text-sm text-gray-600">مكتب المنصة: أبو رمانة، شارع المالكي، دمشق</p>
<p className="text-sm font-bold text-gray-800 mb-1">{t('platformLocation')}</p>
<p className="text-sm text-gray-600">{t('platformOfficeAddress')}</p>
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1" dir="ltr">
<Phone className="w-3.5 h-3.5" />
+963567823411
@ -881,13 +883,13 @@ function PaymentDialog({
onClick={() => setShowCashDialog(false)}
className="w-full px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
>
إغلاق
{t('close')}
</button>
<Link
href="/reservations"
className="w-full px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
>
حجوزاتي
{t('myBookings')}
</Link>
</div>
</motion.div>
@ -898,6 +900,7 @@ function PaymentDialog({
}
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
const { t } = useTranslation();
const [message, setMessage] = useState('');
useEffect(() => {
@ -924,8 +927,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('reservationNumber', { id: reservation.id })}</p>
</div>
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
<XCircle className="h-6 w-6" />
@ -935,13 +938,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"
/>
@ -955,14 +958,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('sending') : t('sendReport')}
</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>
@ -972,6 +975,7 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
}
function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportingId }) {
const { t } = useTranslation();
const p = r._prop;
const imgs = propImages(p, r);
const img = imgs.length > 0 ? `${API_BASE}${imgs[0]}` : null;
@ -1004,54 +1008,54 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
</div>
<div className="text-left">
<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>
{(beds||baths) && <div className="flex gap-3 mb-3 text-sm text-gray-600">{beds>0&&<span>{beds} غرف</span>}{baths>0&&<span>{baths} حمامات</span>}</div>}
{(beds||baths) && <div className="flex gap-3 mb-3 text-sm text-gray-600">{beds>0&&<span>{beds} {t('rooms')}</span>}{baths>0&&<span>{baths} {t('bathrooms')}</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>
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">{t('from')}</div>
<div className="text-sm font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</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>
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">{t('to')}</div>
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
</div>
</div>
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-3 rounded-xl mb-3">
<div className="flex items-center justify-between mb-1">
<span className="text-sm text-blue-800 font-medium flex items-center gap-1"><Timer className="w-4 h-4"/> متبقي للدفع:</span>
<span className="text-sm text-blue-800 font-medium flex items-center gap-1"><Timer className="w-4 h-4"/> {t('remainingForPayment')}</span>
<CountdownTimer deadline={deadline} />
</div>
<div className="text-xs text-blue-600">مدة الدفع: {formatWindowDuration(p.allowedPaymentPeriod)}</div>
<div className="text-xs text-blue-600">{t('paymentDuration', { duration: formatWindowDuration(p.allowedPaymentPeriod) })}</div>
</div>}
<div className="flex gap-3 pt-3 border-t border-gray-100">
<button onClick={() => onViewDetails(r)}
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"/> التفاصيل
<Eye className="w-4 h-4"/> {t('details')}
</button>
{isOwnerConfirmed && !isExpired && <button onClick={() => onPay(r)} disabled={isPaying}
className={`flex-1 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
{isPaying ? <Loader2 className="w-4 h-4 animate-spin"/> : <CreditCard className="w-4 h-4"/>} {isPaying ? 'جاري الدفع...' : 'ادفع الآن'}
{isPaying ? <Loader2 className="w-4 h-4 animate-spin"/> : <CreditCard className="w-4 h-4"/>} {isPaying ? t('processingPayment') : t('payNow')}
</button>}
</div>
<button onClick={() => onReport(r)} disabled={isReporting}
className={`w-full mt-3 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
{isReporting ? <Loader2 className="w-4 h-4 animate-spin"/> : <Flag className="w-4 h-4"/>} {isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
{isReporting ? <Loader2 className="w-4 h-4 animate-spin"/> : <Flag className="w-4 h-4"/>} {isReporting ? t('reportingAction') : t('report')}
</button>
{canRate && !showRating && <button onClick={() => setShowRating(true)}
className="w-full mt-3 bg-amber-50 text-amber-700 py-2 rounded-xl text-sm font-medium hover:bg-amber-100 transition-colors flex items-center justify-center gap-2">
<Star className="w-4 h-4"/> قيّم هذا العقار
<Star className="w-4 h-4"/> {t('rateThisProperty')}
</button>}
{canRate && showRating && <div className="mt-3 bg-amber-50 p-3 rounded-xl">
<div className="space-y-2 mb-3">
{[
{ key: 'clean', label: 'النظافة' },
{ key: 'services', label: 'الخدمات' },
{ key: 'ownerBehavior', label: 'تعامل المالك' },
{ key: 'experience', label: 'التجربة العامة' },
{ key: 'clean', label: 'ratingCategory.clean' },
{ key: 'services', label: 'ratingCategory.services' },
{ key: 'ownerBehavior', label: 'ratingCategory.ownerBehavior' },
{ key: 'experience', label: 'ratingCategory.experience' },
].map(cat => <div key={cat.key} className="flex items-center justify-between">
<span className="text-sm text-gray-700">{cat.label}</span>
<span className="text-sm text-gray-700">{t(cat.label)}</span>
<div className="flex gap-0.5">
{[1,2,3,4,5].map(n => (
<button key={n} onClick={() => setRatings(p => ({...p, [cat.key]: n}))}
@ -1063,26 +1067,26 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
</div>)}
</div>
<textarea value={ratingComment} onChange={e => setRatingComment(e.target.value)}
placeholder="أكتب تعليقك (اختياري)"
placeholder={t('writeCommentOptional')}
className="w-full p-2 text-sm border border-amber-200 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-amber-500 mb-2" rows={2} />
<div className="flex gap-2">
<button onClick={async () => {
if (!ratings.clean || !ratings.services || !ratings.ownerBehavior || !ratings.experience) return toast.error('قيّم جميع الفئات');
if (!ratings.clean || !ratings.services || !ratings.ownerBehavior || !ratings.experience) return toast.error(t('rateAllCategories'));
setSubmittingRating(true);
try {
await addPropertyRating({ reservationId: r.id, cleanRating: ratings.clean, servicesRating: ratings.services, ownerBehaviorRating: ratings.ownerBehavior, experienceRating: ratings.experience, comment: ratingComment || null });
toast.success('تم إرسال التقييم');
toast.success(t('ratingSubmittedToast'));
setShowRating(false);
setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 });
setRatingComment('');
} catch (e) { toast.error(e?.message || 'فشل إرسال التقييم'); }
} catch (e) { toast.error(e?.message || t('ratingFailed')); }
finally { setSubmittingRating(false); }
}} disabled={submittingRating}
className="flex-1 bg-amber-500 text-white py-1.5 rounded-lg text-sm font-medium hover:bg-amber-600 transition-colors disabled:bg-gray-300">
{submittingRating ? 'جاري الإرسال...' : 'إرسال التقييم'}
{submittingRating ? t('sending') : t('submitRating')}
</button>
<button onClick={() => { setShowRating(false); setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 }); setRatingComment(''); }}
className="px-4 py-1.5 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">إلغاء</button>
className="px-4 py-1.5 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">{t('cancel')}</button>
</div>
</div>}
</div>
@ -1091,6 +1095,7 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
}
function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reportingId }) {
const { t } = useTranslation();
if (!isOpen || !r) return null;
const p = r._prop;
const isOwnerConfirmed = STATUS_MAP[r.status] === 'ownerConfirmed';
@ -1109,47 +1114,47 @@ function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reporting
className="bg-white rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto shadow-2xl" onClick={e=>e.stopPropagation()}>
<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">رقم الحجز: #{r.id}</p>
<p className="text-amber-100 text-sm mt-1">{t('reservationNumber', { id: r.id })}</p>
</div>
<div className="p-6 space-y-6">
{p && <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"/> معلومات العقار</h3>
<p><span className="text-gray-500">العنوان:</span> {propAddr(p, r)||''}</p>
<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">{t('addressLabel')}</span> {propAddr(p, r)||''}</p>
{(propBeds(p, r)||propBaths(p, r)) && <div className="flex gap-3 mt-2">
{propBeds(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBeds(p, r)} غرف</span>}
{propBaths(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBaths(p, r)} حمامات</span>}
{propBeds(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBeds(p, r)} {t('rooms')}</span>}
{propBaths(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBaths(p, r)} {t('bathrooms')}</span>}
</div>}
</div>}
<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"/> تفاصيل الحجز</h3>
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2"><Calendar className="w-5 h-5 text-amber-500"/> {t('bookingDetails')}</h3>
<div className="grid grid-cols-2 gap-4">
<div><p className="text-gray-500">تاريخ البداية</p><p className="font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</p></div>
<div><p className="text-gray-500">تاريخ النهاية</p><p className="font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</p></div>
<div><p className="text-gray-500">الحالة</p><StatusBadge code={r.status}/></div>
<div><p className="text-gray-500">تاريخ الإنشاء</p><p className="font-medium">{new Date(r.createdAt).toLocaleDateString('ar')}</p></div>
<div><p className="text-gray-500">{t('startDateLabel')}</p><p className="font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</p></div>
<div><p className="text-gray-500">{t('endDateLabel')}</p><p className="font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</p></div>
<div><p className="text-gray-500">{t('statusLabel')}</p><StatusBadge code={r.status}/></div>
<div><p className="text-gray-500">{t('createdAtLabel')}</p><p className="font-medium">{new Date(r.createdAt).toLocaleDateString('ar')}</p></div>
</div>
</div>
<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"/> المعلومات المالية</h3>
<div className="flex justify-between font-bold"><span className="text-gray-900">الإجمالي</span><span className="text-amber-600 text-lg">{r.totalPrice?.toLocaleString()??''}</span></div>
<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">{t('total')}</span><span className="text-amber-600 text-lg">{r.totalPrice?.toLocaleString()??''}</span></div>
</div>
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-4 rounded-xl">
<div className="flex items-center justify-between mb-2">
<span className="text-blue-800 font-medium flex items-center gap-2"><Timer className="w-5 h-5"/> متبقي للدفع:</span>
<span className="text-blue-800 font-medium flex items-center gap-2"><Timer className="w-5 h-5"/> {t('remainingForPayment')}</span>
<CountdownTimer deadline={deadline} />
</div>
<div className="text-xs text-blue-600 mb-3">مدة الدفع: {formatWindowDuration(p.allowedPaymentPeriod)}</div>
<div className="text-xs text-blue-600 mb-3">{t('paymentDuration', { duration: formatWindowDuration(p.allowedPaymentPeriod) })}</div>
{!isExpired && <button onClick={() => { onPay(r); onClose(); }} disabled={isPaying}
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
{isPaying ? <Loader2 className="w-5 h-5 animate-spin"/> : <CreditCard className="w-5 h-5"/>} {isPaying ? 'جاري الدفع...' : 'ادفع الآن'}
{isPaying ? <Loader2 className="w-5 h-5 animate-spin"/> : <CreditCard className="w-5 h-5"/>} {isPaying ? t('processingPayment') : t('payNow')}
</button>}
</div>}
<button onClick={() => { onReport(r); onClose(); }} disabled={isReporting}
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
{isReporting ? <Loader2 className="w-5 h-5 animate-spin"/> : <Flag className="w-5 h-5"/>} {isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
{isReporting ? <Loader2 className="w-5 h-5 animate-spin"/> : <Flag className="w-5 h-5"/>} {isReporting ? t('reportingAction') : t('report')}
</button>
</div>
</motion.div>
@ -1158,6 +1163,7 @@ function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reporting
}
export default function UserReservationsPage() {
const { t } = useTranslation();
const router = useRouter();
const [reservations, setReservations] = useState([]);
const [filtered, setFiltered] = useState([]);
@ -1196,7 +1202,7 @@ export default function UserReservationsPage() {
setFiltered(enriched);
} catch (err) {
console.error(err);
toast.error('فشل تحميل الحجوزات');
toast.error(t('failedToLoadBookings'));
setReservations([]);
setFiltered([]);
}
@ -1233,11 +1239,11 @@ export default function UserReservationsPage() {
transactionType: 1,
comment: null,
});
toast.success('تم دفع السلفة بنجاح!');
toast.success(t('depositPaidSuccess'));
closePaymentDialog();
loadReservations();
} catch (err) {
toast.error(err?.message || 'فشل عملية الدفع');
toast.error(err?.message || t('paymentFailed'));
} finally {
setPayingId(null);
}
@ -1257,10 +1263,10 @@ export default function UserReservationsPage() {
setReportingId(reportDialog.reservation.id);
try {
await reportReservation(reportDialog.reservation.id, message.trim() || null);
toast.success('تم إرسال البلاغ بنجاح');
toast.success(t('reportSubmittedSuccess'));
closeReportDialog();
} catch (err) {
toast.error(err?.message || 'فشل إرسال البلاغ');
toast.error(err?.message || t('reportFailed'));
} finally {
setReportingId(null);
}
@ -1298,9 +1304,9 @@ export default function UserReservationsPage() {
/>
<div className="container mx-auto px-4">
<motion.div initial={{opacity:0,y:-20}} animate={{opacity:1,y:0}} className="mb-8">
<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>
<h1 className="text-3xl font-bold text-gray-900 mb-2">حجوزاتي</h1>
<p className="text-gray-600">لديك {reservations.length} حجز</p>
<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"/> {t('back')}</button>
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('myBookings')}</h1>
<p className="text-gray-600">{t('youHaveBookings', { count: reservations.length })}</p>
</motion.div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
{Object.entries(counts).map(([s, c]) => (
@ -1308,20 +1314,20 @@ export default function UserReservationsPage() {
className={`bg-white rounded-xl shadow-sm p-4 text-center border cursor-pointer hover:shadow-md transition-all ${filterStatus===s?'border-amber-500 bg-amber-50':'border-gray-200'}`}
onClick={() => setFilterStatus(s)}>
<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)}</div>
<div className="text-sm text-gray-600">{s==='all'?t('all'):t(`bookingStatus.${s}`)}</div>
</motion.div>
))}
</div>
<div className="mb-6 relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"/>
<input type="text" placeholder="ابحث بعنوان العقار أو رقم الحجز..." value={searchTerm} onChange={e=>setSearchTerm(e.target.value)}
<input type="text" placeholder={t('searchPlaceholder')} 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"/>
</div>
{filtered.length === 0 ? (
<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">لا توجد حجوزات</h3>
<p className="text-gray-600">لم تقم بأي حجز حتى الآن</p>
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
<p className="text-gray-600">{t('noReservationsDesc')}</p>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">