Files
SweetHome/app/components/ratings/PropertyRatingList.js

286 lines
10 KiB
JavaScript
Raw Normal View History

2026-04-26 13:46:30 +03:00
// 'use client';
// import { useState, useEffect } from 'react';
// import { motion } from 'framer-motion';
// import { Star } from 'lucide-react';
// import { getPropertyRatings } from '../../utils/ratings.js';
// import toast, { Toaster } from 'react-hot-toast';
// const RatingList = ({ propertyId, userId }) => {
// const [reviews, setReviews] = useState([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState(null);
// useEffect(() => {
// const fetchReviews = async () => {
// if (!propertyId) {
// setLoading(false);
// return;
// }
// try {
// setLoading(true);
// const data = await getPropertyReviews(propertyId);
// setReviews(data || []);
// setError(null);
// } catch (err) {
// console.error('[RatingList] Failed to fetch reviews:', err);
// setError('فشل تحميل التقييمات');
// setReviews([]);
// } finally {
// setLoading(false);
// }
// };
// fetchReviews();
// }, [propertyId]);
// if (loading) {
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="text-center py-8"
// >
// <div className="w-10 h-10 border-2 border-gray-200 border-t-gray-500 rounded-full animate-spin mx-auto mb-4" />
// <p className="text-gray-500">جاري تحميل التقييمات...</p>
// </motion.div>
// );
// }
// if (error) {
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="text-center py-8"
// >
// <p className="text-red-500">{error}</p>
// </motion.div>
// );
// }
// if (reviews.length === 0) {
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="text-center py-8"
// >
// <p className="text-gray-500">لا توجد تقييمات حتى الآن. كن أول من يقيم هذا العقار!</p>
// </motion.div>
// );
// }
// // Calculate average rating
// const averageRating = reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length;
// return (
// <motion.div
// initial={{ opacity: 0, y: 20 }}
// animate={{ opacity: 1, y: 0 }}
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
// >
// <Toaster position="top-center" reverseOrder={false} />
// <div className="space-y-4">
// {/* Header with average rating */}
// <div className="flex items-center justify-between pb-3 border-b border-gray-100">
// <div>
// <h2 className="text-xl font-bold text-gray-900">تقييمات المستأجرين</h2>
// <p className="text-sm text-gray-500">
// {reviews.length} تقييمات
// </p>
// </div>
// <div className="flex items-center gap-2">
// <div className="flex items-center gap-1">
// {Array.from({ length: 5 }).map((_, index) => (
// <Star
// key={index}
// className={`w-5 h-5 ${index < Math.floor(averageRating) ? 'text-amber-500' : 'text-gray-300'}`}
// />
// ))}
// {averageRating % 1 !== 0 && (
// <Star className="w-5 h-5 text-amber-400" />
// )}
// <span className="font-bold text-gray-900 ml-2">{averageRating.toFixed(1)}</span>
// </div>
// </div>
// </div>
// {/* Reviews list */}
// <div className="space-y-4">
// {reviews.map((review, index) => (
// <div key={index} className="border-t border-gray-100 pt-4 first:border-t-0 first:pt-0">
// <div className="flex justify-between items-start mb-2">
// <div className="flex items-start gap-3">
// <div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0">
// <Star className="w-6 h-6 text-gray-600" />
// </div>
// <div>
// <div className="font-medium text-gray-900">{review.userName || 'مستأجر'}</div>
// <div className="flex items-center gap-1 mt-1 text-sm">
// {Array.from({ length: 5 }).map((_, starIndex) => (
// <Star
// key={starIndex}
// className={`w-4 h-4 ${starIndex < review.rating ? 'text-amber-500' : 'text-gray-300'}`}
// />
// ))}
// <span className="ml-1 text-xs text-gray-500">({review.rating}/5)</span>
// </div>
// </div>
// </div>
// <div className="text-xs text-gray-400">
// {review.createdAt ? new Date(review.createdAt).toLocaleDateString('ar-SA') : ''}
// </div>
// </div>
// {review.comment && (
// <p className="text-gray-700 text-sm leading-relaxed">{review.comment}</p>
// )}
// </div>
// ))}
// </div>
// </div>
// </motion.div>
// );
// };
// export default RatingList;
'use client';
import { useState, useEffect } from 'react';
import { Star, User, Calendar, ChevronDown, Loader2 } from 'lucide-react';
import { getPropertyRatings, getPropertyAverageRating } from '../../utils/ratings';
const RatingItem = ({ rating }) => {
const overall = (
rating.cleanRating + rating.servicesRating +
rating.ownerBehaviorRating + rating.experienceRating
) / 4;
return (
<div className="border-b border-gray-100 py-4 last:border-0">
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-amber-100 rounded-full flex items-center justify-center">
<User className="w-4 h-4 text-amber-600" />
</div>
<span className="font-medium text-gray-800">{rating.customerName || 'مستأجر'}</span>
</div>
<div className="flex items-center gap-1 bg-amber-50 px-2 py-1 rounded-full">
<Star className="w-3 h-3 text-amber-500 fill-amber-500" />
<span className="text-sm font-bold">{overall.toFixed(1)}</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm text-gray-600 mb-2">
<div>النظافة: {rating.cleanRating}/5</div>
<div>الخدمات: {rating.servicesRating}/5</div>
<div>سلوك المالك: {rating.ownerBehaviorRating}/5</div>
<div>التجربة: {rating.experienceRating}/5</div>
</div>
{rating.comment && (
<p className="text-gray-700 text-sm mt-2 pr-4 border-r-2 border-amber-200">"{rating.comment}"</p>
)}
<div className="flex items-center gap-1 text-xs text-gray-400 mt-2">
<Calendar className="w-3 h-3" />
<span>{new Date(rating.createdAt).toLocaleDateString('ar-SA')}</span>
</div>
</div>
);
};
export default function PropertyRatingList({ propertyId }) {
const [ratings, setRatings] = useState([]);
const [average, setAverage] = useState(null);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(false);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const fetchRatings = async (reset = false) => {
const currentPage = reset ? 1 : page;
try {
if (reset) setLoading(true);
else setLoadingMore(true);
const result = await getPropertyRatings(propertyId, currentPage, 10);
const items = result?.items || result?.data?.items || result || [];
const totalPages = result?.totalPages || result?.data?.totalPages || 1;
setRatings(prev => reset ? items : [...prev, ...items]);
setHasMore(currentPage < totalPages);
if (reset) setPage(1);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
setLoadingMore(false);
}
};
const fetchAverage = async () => {
try {
const avg = await getPropertyAverageRating(propertyId);
setAverage(avg);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
if (propertyId) {
fetchRatings(true);
fetchAverage();
}
}, [propertyId]);
const loadMore = () => {
const nextPage = page + 1;
setPage(nextPage);
fetchRatings(false);
};
if (loading && ratings.length === 0) {
return (
<div className="text-center py-8">
<Loader2 className="w-8 h-8 animate-spin text-amber-500 mx-auto" />
<p className="text-gray-500 mt-2">جاري تحميل التقييمات...</p>
</div>
);
}
return (
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
<div className="flex justify-between items-center mb-4 pb-3 border-b border-gray-100">
<h3 className="text-xl font-bold text-gray-900">تقييمات المستأجرين</h3>
{average !== null && (
<div className="flex items-center gap-1 bg-amber-50 px-3 py-1 rounded-full">
<Star className="w-5 h-5 text-amber-500 fill-amber-500" />
<span className="font-bold text-lg">{average.toFixed(1)}</span>
<span className="text-gray-500 text-sm">/5</span>
</div>
)}
</div>
{ratings.length === 0 ? (
<p className="text-center text-gray-500 py-6">لا توجد تقييمات حتى الآن. كن أول من يقيم هذا العقار.</p>
) : (
<>
{ratings.map((r, idx) => <RatingItem key={idx} rating={r} />)}
{hasMore && (
<button
onClick={loadMore}
disabled={loadingMore}
className="w-full mt-4 py-2 text-amber-600 hover:text-amber-700 font-medium text-sm flex items-center justify-center gap-1"
>
{loadingMore ? <Loader2 className="w-4 h-4 animate-spin" /> : <ChevronDown className="w-4 h-4" />}
{loadingMore ? 'جاري التحميل...' : 'عرض المزيد'}
</button>
)}
</>
)}
</div>
);
}