forked from amer2004/SweetHome
Added translation to all site with edit style
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Home, Star, MapPin, Calendar, Clock, Check, X, Loader2,
|
||||
User, MessageCircle, ChevronDown, Image as ImageIcon,
|
||||
@ -14,27 +15,29 @@ import PropertyRatingForm from '../components/ratings/PropertyRatingForm';
|
||||
const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled'];
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
|
||||
ownerConfirmed: { label: 'مؤكد من المالك', color: 'bg-blue-100 text-blue-800 border-blue-300' },
|
||||
depositPaid: { label: 'تم دفع السلفة', color: 'bg-orange-100 text-orange-800 border-orange-300' },
|
||||
depositConfirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800 border-green-300' },
|
||||
completed: { label: 'منتهي', color: 'bg-teal-100 text-teal-800 border-teal-300' },
|
||||
cancelled: { label: 'ملغي', color: 'bg-red-100 text-red-800 border-red-300' },
|
||||
pending: { color: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
|
||||
ownerConfirmed: { color: 'bg-blue-100 text-blue-800 border-blue-300' },
|
||||
depositPaid: { color: 'bg-orange-100 text-orange-800 border-orange-300' },
|
||||
depositConfirmed: { color: 'bg-green-100 text-green-800 border-green-300' },
|
||||
completed: { color: 'bg-teal-100 text-teal-800 border-teal-300' },
|
||||
cancelled: { color: 'bg-red-100 text-red-800 border-red-300' },
|
||||
};
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||||
|
||||
function StatusBadge({ code }) {
|
||||
const { t } = useTranslation();
|
||||
const key = STATUS_MAP[code] || 'pending';
|
||||
const cfg = STATUS_CONFIG[key];
|
||||
return (
|
||||
<span className={`inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium border ${cfg.color}`}>
|
||||
<Clock className="w-3 h-3" /> {cfg.label}
|
||||
<Clock className="w-3 h-3" /> {t(`bookingStatus.${key}`)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function ReservationCard({ reservation: r, onRate }) {
|
||||
const { t } = useTranslation();
|
||||
const isCompleted = STATUS_MAP[r.status] === 'completed';
|
||||
const imgSrc = r.propertyImage || r._prop?.images?.[0] || (r.propertyInfo?.images?.[0]);
|
||||
const imageUrl = imgSrc ? `${API_BASE}${imgSrc}` : null;
|
||||
@ -49,7 +52,7 @@ function ReservationCard({ reservation: r, onRate }) {
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex items-center gap-2">
|
||||
<Home className="w-5 h-5 text-amber-500" />
|
||||
<span className="font-semibold text-gray-900">عقار #{r.propertyId || r.id}</span>
|
||||
<span className="font-semibold text-gray-900">{t('propertyId', { id: r.propertyId || r.id })}</span>
|
||||
</div>
|
||||
<StatusBadge code={r.status} />
|
||||
</div>
|
||||
@ -73,38 +76,38 @@ function ReservationCard({ reservation: r, onRate }) {
|
||||
|
||||
{(beds > 0 || baths > 0) && (
|
||||
<div className="flex gap-3 text-sm text-gray-600">
|
||||
{beds > 0 && <span>{beds} غرف</span>}
|
||||
{baths > 0 && <span>{baths} حمامات</span>}
|
||||
{beds > 0 && <span>{beds} {t('rooms')}</span>}
|
||||
{baths > 0 && <span>{baths} {t('bathrooms')}</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<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">{new Date(r.startDate).toLocaleDateString('ar')}</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||
<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">{new Date(r.endDate).toLocaleDateString('ar')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 p-3 rounded-xl text-center">
|
||||
<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 className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<User className="w-3 h-3" />
|
||||
<span>رقم الحجز: #{r.id}</span>
|
||||
<span>{t('reservationNumber', { id: r.id })}</span>
|
||||
</div>
|
||||
|
||||
{isCompleted && (
|
||||
<button onClick={onRate}
|
||||
className="w-full bg-amber-500 hover:bg-amber-600 text-white py-2.5 rounded-xl text-sm font-medium transition flex items-center justify-center gap-2">
|
||||
<Star className="w-4 h-4" /> تقييم العقار
|
||||
<Star className="w-4 h-4" /> {t('rateProperty')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -113,6 +116,7 @@ function ReservationCard({ reservation: r, onRate }) {
|
||||
}
|
||||
|
||||
export default function BookedPropertiesPage() {
|
||||
const { t } = useTranslation();
|
||||
const [reservations, setReservations] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [ratingReservation, setRatingReservation] = useState(null);
|
||||
@ -120,7 +124,7 @@ export default function BookedPropertiesPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.getToken()) {
|
||||
toast.error('يرجى تسجيل الدخول أولاً');
|
||||
toast.error(t('loginRequired'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@ -133,7 +137,7 @@ export default function BookedPropertiesPage() {
|
||||
setReservations(Array.isArray(data) ? data : []);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error('فشل تحميل الحجوزات');
|
||||
toast.error(t('failedToLoadBookings'));
|
||||
setReservations([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -155,14 +159,14 @@ export default function BookedPropertiesPage() {
|
||||
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">العقارات المحجوزة</h1>
|
||||
<p className="text-gray-600">لديك {reservations.length} حجز</p>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('bookedProperties')}</h1>
|
||||
<p className="text-gray-600">{t('youHaveBookings', { count: reservations.length })}</p>
|
||||
</div>
|
||||
{reservations.length > 0 && (
|
||||
<button onClick={() => setExpandedId(expandedId ? null : 'all')}
|
||||
className="flex items-center gap-1 text-sm text-amber-600 hover:text-amber-700 transition">
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${expandedId ? 'rotate-180' : ''}`} />
|
||||
{expandedId ? 'طي الكل' : 'عرض الكل'}
|
||||
{expandedId ? t('collapseAll') : t('showAll')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@ -172,8 +176,8 @@ export default function BookedPropertiesPage() {
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||
<Home className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد حجوزات</h3>
|
||||
<p className="text-gray-500">لم تقم بحجز أي عقار حتى الآن</p>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
|
||||
<p className="text-gray-500">{t('noBookingsDesc')}</p>
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
@ -188,8 +192,8 @@ export default function BookedPropertiesPage() {
|
||||
className="mt-8 bg-amber-50 border border-amber-200 rounded-xl p-4 flex items-start gap-3">
|
||||
<MessageCircle className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-amber-800 font-medium">تقييم العقارات</p>
|
||||
<p className="text-amber-600 text-sm">يمكنك تقييم العقارات المنتهية حجزها لمساعدة المستأجرين الآخرين</p>
|
||||
<p className="text-amber-800 font-medium">{t('rateProperties')}</p>
|
||||
<p className="text-amber-600 text-sm">{t('ratePropertiesDesc')}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@ -209,7 +213,7 @@ export default function BookedPropertiesPage() {
|
||||
</button>
|
||||
<PropertyRatingForm
|
||||
reservationId={ratingReservation.id}
|
||||
onSuccess={() => { setRatingReservation(null); toast.success('تم إرسال التقييم بنجاح!'); }}
|
||||
onSuccess={() => { setRatingReservation(null); toast.success(t('ratingSubmitted')); }}
|
||||
onCancel={() => setRatingReservation(null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user