added a swicher on the properties
Some checks failed
Build frontend / build (push) Failing after 40s

This commit is contained in:
mouazkh
2026-05-26 22:53:18 +03:00
parent 01b966c1d2
commit 29de55196a
2 changed files with 46 additions and 25 deletions

View File

@ -20,8 +20,6 @@ import AuthService from '../../services/AuthService';
import { useFavorites } from '@/app/contexts/FavoritesContext'; import { useFavorites } from '@/app/contexts/FavoritesContext';
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums'; import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
import PropertyRatingList from '@/app/components/ratings/PropertyRatingList'; import PropertyRatingList from '@/app/components/ratings/PropertyRatingList';
import PropertyRatingForm from '@/app/components/ratings/PropertyRatingForm';
import StarRating from '@/app/components/ratings/StarRating';
import { getPropertyAverageRating } from '../../utils/ratings'; import { getPropertyAverageRating } from '../../utils/ratings';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
@ -189,7 +187,6 @@ export default function PropertyDetailsPage() {
const [isOwnProperty, setIsOwnProperty] = useState(false); const [isOwnProperty, setIsOwnProperty] = useState(false);
const [favLoading, setFavLoading] = useState(false); const [favLoading, setFavLoading] = useState(false);
const [avgRating, setAvgRating] = useState(null); const [avgRating, setAvgRating] = useState(null);
const [showRatingForm, setShowRatingForm] = useState(false);
const [showContact, setShowContact] = useState(false); const [showContact, setShowContact] = useState(false);
const [contactInfo, setContactInfo] = useState(null); const [contactInfo, setContactInfo] = useState(null);
const [ownerData, setOwnerData] = useState(null); const [ownerData, setOwnerData] = useState(null);
@ -264,7 +261,6 @@ export default function PropertyDetailsPage() {
const avg = await getPropertyAverageRating(propId); const avg = await getPropertyAverageRating(propId);
setAvgRating(avg); setAvgRating(avg);
} catch {} } catch {}
};
const fetchContactInfo = async () => { const fetchContactInfo = async () => {
if (!property) return; if (!property) return;
@ -605,7 +601,7 @@ export default function PropertyDetailsPage() {
<div className="text-[10px] text-gray-500">بلكونات</div> <div className="text-[10px] text-gray-500">بلكونات</div>
</div> </div>
)} )}
{avgRating !== null && avgRating > 0 && ( {avgRating !== null && avgRating > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center"> <div className="bg-gray-50 rounded-lg p-2 text-center">
<Star className="w-4 h-4 text-amber-500 mx-auto mb-0.5 fill-amber-500" /> <Star className="w-4 h-4 text-amber-500 mx-auto mb-0.5 fill-amber-500" />
<div className="font-bold text-gray-900 text-sm">{avgRating.toFixed(1)}</div> <div className="font-bold text-gray-900 text-sm">{avgRating.toFixed(1)}</div>
@ -782,23 +778,6 @@ export default function PropertyDetailsPage() {
<PropertyRatingList propertyId={property._raw?.propertyInformationId || property.id} /> <PropertyRatingList propertyId={property._raw?.propertyInformationId || property.id} />
</motion.div> </motion.div>
{AuthService.isAuthenticated() && !showRatingForm && (
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
className="bg-amber-50 border-2 border-dashed border-amber-200 rounded-2xl p-6 text-center cursor-pointer hover:bg-amber-100 transition-colors"
onClick={() => setShowRatingForm(true)}>
<Star className="w-10 h-10 text-amber-500 mx-auto mb-2" />
<h3 className="font-bold text-amber-800 text-lg mb-1">قيّم هذا العقار</h3>
<p className="text-amber-600 text-sm">شارك تجربتك مع المستأجرين الآخرين</p>
</motion.div>
)}
{showRatingForm && (
<PropertyRatingForm
reservationId={property._raw?.propertyInformationId || property.id}
onSuccess={handleRatingSuccess}
onCancel={() => setShowRatingForm(false)}
/>
)}
</div> </div>
{/* Sidebar */} {/* Sidebar */}

View File

@ -5,11 +5,12 @@ import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import {
Calendar, Clock, CheckCircle, XCircle, Eye, Loader2, Search, Calendar, Clock, CheckCircle, XCircle, Eye, Loader2, Search,
MapPin, DollarSign, Home, ArrowLeft, CreditCard, Timer, MapPin, DollarSign, Home, ArrowLeft, CreditCard, Timer, Star,
} from 'lucide-react'; } from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast'; import toast, { Toaster } from 'react-hot-toast';
import AuthService from '../services/AuthService'; import AuthService from '../services/AuthService';
import { getRentProperties, getUserReservations, payDeposit } from '../utils/api'; import { getRentProperties, getUserReservations, payDeposit } from '../utils/api';
import { addPropertyRating } from '../utils/ratings';
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api'; const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
@ -106,12 +107,17 @@ function ReservationCard({ r, onViewDetails, onPay, payingId }) {
const beds = propBeds(p, r); const beds = propBeds(p, r);
const baths = propBaths(p, r); const baths = propBaths(p, r);
const isOwnerConfirmed = STATUS_MAP[r.status] === 'ownerConfirmed'; const isOwnerConfirmed = STATUS_MAP[r.status] === 'ownerConfirmed';
const canRate = STATUS_MAP[r.status] === 'depositPaid' || STATUS_MAP[r.status] === 'completed';
const hasTimeWindow = r.ownerApprovalDate && p?.allowedPaymentPeriod; const hasTimeWindow = r.ownerApprovalDate && p?.allowedPaymentPeriod;
const deadline = hasTimeWindow const deadline = hasTimeWindow
? new Date(r.ownerApprovalDate).getTime() + parseTimeSpan(p.allowedPaymentPeriod) ? new Date(r.ownerApprovalDate).getTime() + parseTimeSpan(p.allowedPaymentPeriod)
: null; : null;
const isExpired = deadline ? Date.now() > deadline : false; const isExpired = deadline ? Date.now() > deadline : false;
const isPaying = payingId === r.id; const isPaying = payingId === r.id;
const [showRating, setShowRating] = useState(false);
const [ratingValue, setRatingValue] = useState(0);
const [ratingComment, setRatingComment] = useState('');
const [submittingRating, setSubmittingRating] = useState(false);
return ( return (
<motion.div initial={{ opacity:0,y:20 }} animate={{ opacity:1,y:0 }} <motion.div initial={{ opacity:0,y:20 }} animate={{ opacity:1,y:0 }}
@ -139,7 +145,7 @@ function ReservationCard({ r, onViewDetails, onPay, payingId }) {
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div> <div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
</div> </div>
</div> </div>
{hasTimeWindow && <div className="bg-blue-50 p-3 rounded-xl mb-3"> {isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-3 rounded-xl mb-3">
<div className="flex items-center justify-between mb-1"> <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"/> متبقي للدفع:</span>
<CountdownTimer deadline={deadline} /> <CountdownTimer deadline={deadline} />
@ -156,6 +162,42 @@ function ReservationCard({ r, onViewDetails, onPay, payingId }) {
{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 ? 'جاري الدفع...' : 'ادفع الآن'}
</button>} </button>}
</div> </div>
{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"/> قيّم هذا العقار
</button>}
{canRate && showRating && <div className="mt-3 bg-amber-50 p-3 rounded-xl">
<div className="flex gap-1 justify-center mb-2">
{[1,2,3,4,5].map(n => (
<button key={n} onClick={() => setRatingValue(n)}
className={`p-1 rounded-full transition-colors ${n <= ratingValue ? 'text-amber-500' : 'text-gray-300'}`}>
<Star className={`w-6 h-6 ${n <= ratingValue ? 'fill-amber-500' : ''}`} />
</button>
))}
</div>
<textarea value={ratingComment} onChange={e => setRatingComment(e.target.value)}
placeholder="أكتب تعليقك (اختياري)"
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 (!ratingValue) return toast.error('اختر تقييماً');
setSubmittingRating(true);
try {
await addPropertyRating({ reservationId: r.id, cleanRating: ratingValue, servicesRating: ratingValue, ownerBehaviorRating: ratingValue, experienceRating: ratingValue, comment: ratingComment || null });
toast.success('تم إرسال التقييم');
setShowRating(false);
setRatingValue(0);
setRatingComment('');
} catch (e) { toast.error(e?.message || 'فشل إرسال التقييم'); }
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 ? 'جاري الإرسال...' : 'إرسال التقييم'}
</button>
<button onClick={() => { setShowRating(false); setRatingValue(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>
</div>
</div>}
</div> </div>
</motion.div> </motion.div>
); );
@ -206,7 +248,7 @@ function DetailsModal({ r, isOpen, onClose, onPay, payingId }) {
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2"><DollarSign className="w-5 h-5"/> المعلومات المالية</h3> <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> <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>
</div> </div>
{hasTimeWindow && <div className="bg-blue-50 p-4 rounded-xl"> {isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-4 rounded-xl">
<div className="flex items-center justify-between mb-2"> <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"/> متبقي للدفع:</span>
<CountdownTimer deadline={deadline} /> <CountdownTimer deadline={deadline} />