317 lines
12 KiB
JavaScript
317 lines
12 KiB
JavaScript
|
|
// import { useState } from 'react';
|
||
|
|
// import { motion } from 'framer-motion';
|
||
|
|
// import { Star, Edit2, X, Check, Clock } from 'lucide-react';
|
||
|
|
// import StarRating from './StarRating.js';
|
||
|
|
// import toast, { Toaster } from 'react-hot-toast';
|
||
|
|
// import { rateProperty, rateCustomer, getUserPropertyRating, canRateProperty } from '../../utils/ratings.js';
|
||
|
|
|
||
|
|
// const RatingForm = ({
|
||
|
|
// propertyId,
|
||
|
|
// userId,
|
||
|
|
// propertyOwner = false,
|
||
|
|
// initialRating = 0,
|
||
|
|
// initialComment = '',
|
||
|
|
// onSubmitSuccess
|
||
|
|
// }) => {
|
||
|
|
// const [rating, setRating] = useState(initialRating);
|
||
|
|
// const [comment, setComment] = useState(initialComment);
|
||
|
|
// const [loading, setLoading] = useState(false);
|
||
|
|
// const [showForm, setShowForm] = useState(false);
|
||
|
|
// const [userRating, setUserRating] = useState(null);
|
||
|
|
|
||
|
|
// // Check if user has already rated
|
||
|
|
// useState(() => {
|
||
|
|
// async function fetchUserRating() {
|
||
|
|
// try {
|
||
|
|
// const rating = await getUserPropertyRating(propertyId, userId);
|
||
|
|
// if (rating) {
|
||
|
|
// setUserRating(rating);
|
||
|
|
// setRating(rating.rating);
|
||
|
|
// setComment(rating.comment || '');
|
||
|
|
// }
|
||
|
|
// } catch (error) {
|
||
|
|
// console.error('[RatingForm] Failed to fetch user rating:', error);
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// if (propertyId && userId) {
|
||
|
|
// fetchUserRating();
|
||
|
|
// }
|
||
|
|
// }, [propertyId, userId]);
|
||
|
|
|
||
|
|
// const handleSubmit = async (e) => {
|
||
|
|
// e.preventDefault();
|
||
|
|
|
||
|
|
// if (!rating) {
|
||
|
|
// toast.error('يرجى إعطاء تقييم من 1 إلى 5 نجوم');
|
||
|
|
// return;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// setLoading(true);
|
||
|
|
|
||
|
|
// try {
|
||
|
|
// const ratingData = {
|
||
|
|
// propertyId,
|
||
|
|
// customerId: userId,
|
||
|
|
// rating,
|
||
|
|
// comment: comment.trim() || null
|
||
|
|
// };
|
||
|
|
|
||
|
|
// await rateProperty(ratingData);
|
||
|
|
|
||
|
|
// toast.success('تم إرسال التقييم بنجاح!');
|
||
|
|
|
||
|
|
// // Reset form
|
||
|
|
// setRating(0);
|
||
|
|
// setComment('');
|
||
|
|
// setShowForm(false);
|
||
|
|
|
||
|
|
// if (onSubmitSuccess) {
|
||
|
|
// onSubmitSuccess();
|
||
|
|
// }
|
||
|
|
// } catch (error) {
|
||
|
|
// console.error('[RatingForm] Failed to submit rating:', error);
|
||
|
|
// toast.error('حدث خطأ أثناء إرسال التقييم. يرجى المحاولة مرة أخرى.');
|
||
|
|
// } finally {
|
||
|
|
// setLoading(false);
|
||
|
|
// }
|
||
|
|
// };
|
||
|
|
|
||
|
|
// const handleEdit = () => {
|
||
|
|
// setShowForm(true);
|
||
|
|
// setRating(userRating?.rating || 0);
|
||
|
|
// setComment(userRating?.comment || '');
|
||
|
|
// };
|
||
|
|
|
||
|
|
// const handleCancel = () => {
|
||
|
|
// setShowForm(false);
|
||
|
|
// setRating(userRating?.rating || 0);
|
||
|
|
// setComment(userRating?.comment || '');
|
||
|
|
// };
|
||
|
|
|
||
|
|
// if (!propertyId || !userId) {
|
||
|
|
// return null;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// return (
|
||
|
|
// <div className="space-y-4">
|
||
|
|
// <Toaster position="top-center" reverseOrder={false} />
|
||
|
|
|
||
|
|
// {/* Display existing rating */}
|
||
|
|
// {userRating && !showForm && (
|
||
|
|
// <motion.div
|
||
|
|
// initial={{ opacity: 0, y: 20 }}
|
||
|
|
// animate={{ opacity: 1, y: 0 }}
|
||
|
|
// className="bg-gray-50 rounded-xl p-4 border border-gray-200"
|
||
|
|
// >
|
||
|
|
// <div className="flex items-center justify-between mb-3">
|
||
|
|
// <div className="flex items-center gap-2">
|
||
|
|
// <Star className="w-5 h-5 text-amber-500" />
|
||
|
|
// <span className="font-medium text-gray-900">{userRating.rating}</span>
|
||
|
|
// <span className="text-sm text-gray-500">من 5</span>
|
||
|
|
// </div>
|
||
|
|
// <button
|
||
|
|
// onClick={handleEdit}
|
||
|
|
// className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm hover:bg-gray-200 transition-colors flex items-center gap-1"
|
||
|
|
// >
|
||
|
|
// <Edit2 className="w-4 h-4" />
|
||
|
|
// تعديل
|
||
|
|
// </button>
|
||
|
|
// </div>
|
||
|
|
|
||
|
|
// {userRating.comment && (
|
||
|
|
// <div className="text-gray-600 text-sm mb-3 line-clamp-3">
|
||
|
|
// "{userRating.comment}"
|
||
|
|
// </div>
|
||
|
|
// )}
|
||
|
|
|
||
|
|
// <div className="flex items-center gap-2 text-xs text-gray-400">
|
||
|
|
// <Clock className="w-3 h-3" />
|
||
|
|
// <span>{userRating.createdAt ? new Date(userRating.createdAt).toLocaleDateString('ar-SA') : ''}</span>
|
||
|
|
// </div>
|
||
|
|
// </motion.div>
|
||
|
|
// )}
|
||
|
|
|
||
|
|
// {/* Rating form */}
|
||
|
|
// {showForm && (
|
||
|
|
// <motion.div
|
||
|
|
// initial={{ opacity: 0, y: 20 }}
|
||
|
|
// animate={{ opacity: 1, y: 0 }}
|
||
|
|
// className="bg-white rounded-xl p-6 border border-gray-200 shadow-sm"
|
||
|
|
// >
|
||
|
|
// <form onSubmit={handleSubmit}>
|
||
|
|
// <div className="mb-4">
|
||
|
|
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||
|
|
// تقييمك للعقار
|
||
|
|
// </label>
|
||
|
|
// <div className="flex items-center gap-2">
|
||
|
|
// <StarRating
|
||
|
|
// rating={rating}
|
||
|
|
// onRatingChange={setRating}
|
||
|
|
// readOnly={false}
|
||
|
|
// size={28}
|
||
|
|
// color="#ffc107"
|
||
|
|
// />
|
||
|
|
// <span className="text-lg font-bold text-gray-900">{rating || '1'}</span>
|
||
|
|
// <span className="text-sm text-gray-400">/5</span>
|
||
|
|
// </div>
|
||
|
|
// </div>
|
||
|
|
|
||
|
|
// <div className="mb-4">
|
||
|
|
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||
|
|
// تعليق (اختياري)
|
||
|
|
// </label>
|
||
|
|
// <textarea
|
||
|
|
// rows="3"
|
||
|
|
// value={comment}
|
||
|
|
// onChange={(e) => setComment(e.target.value)}
|
||
|
|
// placeholder="شارك تجربتك مع العقار..."
|
||
|
|
// className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 transition-all resize-none"
|
||
|
|
// />
|
||
|
|
// </div>
|
||
|
|
|
||
|
|
// <div className="flex gap-3">
|
||
|
|
// <button
|
||
|
|
// type="button"
|
||
|
|
// onClick={handleCancel}
|
||
|
|
// disabled={loading}
|
||
|
|
// className="flex-1 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||
|
|
// >
|
||
|
|
// إلغاء
|
||
|
|
// </button>
|
||
|
|
// <button
|
||
|
|
// type="submit"
|
||
|
|
// disabled={loading || !rating}
|
||
|
|
// className="flex-1 px-4 py-2 bg-amber-500 text-white rounded-lg font-medium hover:bg-amber-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-1"
|
||
|
|
// >
|
||
|
|
// {loading ? (
|
||
|
|
// <div className="w-4 h-4 border-2 border-white/50 border-t-white rounded-full animate-spin" />
|
||
|
|
// ) : (
|
||
|
|
// <Check className="w-5 h-5" />
|
||
|
|
// )}
|
||
|
|
// {loading ? 'إرسال' : 'إرسال التقييم'}
|
||
|
|
// </button>
|
||
|
|
// </div>
|
||
|
|
// </form>
|
||
|
|
// </motion.div>
|
||
|
|
// )}
|
||
|
|
|
||
|
|
// {/* Add rating button */}
|
||
|
|
// {!userRating && !showForm && (
|
||
|
|
// <motion.div
|
||
|
|
// initial={{ opacity: 0, y: 20 }}
|
||
|
|
// animate={{ opacity: 1, y: 0 }}
|
||
|
|
// className="bg-amber-50 border-2 border-amber-200 rounded-xl p-4 text-center cursor-pointer hover:border-amber-300 transition-all"
|
||
|
|
// onClick={() => setShowForm(true)}
|
||
|
|
// >
|
||
|
|
// <Star className="w-8 h-8 text-amber-500 mx-auto mb-2" />
|
||
|
|
// <h3 className="font-bold text-amber-700 mb-2">قيّم هذا العقار</h3>
|
||
|
|
// <p className="text-sm text-amber-600">شارك تجربتك مع المستأجرين الآخرين</p>
|
||
|
|
// </motion.div>
|
||
|
|
// )}
|
||
|
|
// </div>
|
||
|
|
// );
|
||
|
|
// };
|
||
|
|
|
||
|
|
// export default RatingForm;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState } from 'react';
|
||
|
|
import { X, Loader2 } from 'lucide-react';
|
||
|
|
import toast from 'react-hot-toast';
|
||
|
|
import StarRating from './StarRating';
|
||
|
|
import { addPropertyRating } from '../../utils/ratings';
|
||
|
|
|
||
|
|
const RatingField = ({ label, value, onChange }) => (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<label className="block text-sm font-medium text-gray-700">{label} <span className="text-red-500">*</span></label>
|
||
|
|
<StarRating rating={value} onRatingChange={onChange} size={28} />
|
||
|
|
{value === 0 && <p className="text-xs text-red-500">مطلوب</p>}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
|
||
|
|
export default function PropertyRatingForm({ reservationId, onSuccess, onCancel }) {
|
||
|
|
const [cleanRating, setCleanRating] = useState(0);
|
||
|
|
const [servicesRating, setServicesRating] = useState(0);
|
||
|
|
const [ownerBehaviorRating, setOwnerBehaviorRating] = useState(0);
|
||
|
|
const [experienceRating, setExperienceRating] = useState(0);
|
||
|
|
const [comment, setComment] = useState('');
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
|
||
|
|
const validate = () => {
|
||
|
|
if (cleanRating === 0) return 'نظافة العقار';
|
||
|
|
if (servicesRating === 0) return 'جودة الخدمات';
|
||
|
|
if (ownerBehaviorRating === 0) return 'سلوك المالك';
|
||
|
|
if (experienceRating === 0) return 'التجربة العامة';
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSubmit = async (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
const missing = validate();
|
||
|
|
if (missing) {
|
||
|
|
toast.error(`يرجى تقييم: ${missing}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
await addPropertyRating({
|
||
|
|
reservationId,
|
||
|
|
cleanRating,
|
||
|
|
servicesRating,
|
||
|
|
ownerBehaviorRating,
|
||
|
|
experienceRating,
|
||
|
|
comment: comment.trim() || null,
|
||
|
|
});
|
||
|
|
toast.success('تم إرسال التقييم بنجاح!');
|
||
|
|
onSuccess?.();
|
||
|
|
} catch (err) {
|
||
|
|
console.error(err);
|
||
|
|
toast.error('حدث خطأ، حاول مرة أخرى');
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-6 max-w-lg mx-auto">
|
||
|
|
<div className="flex justify-between items-center mb-4">
|
||
|
|
<h3 className="text-xl font-bold text-gray-900">تقييم العقار</h3>
|
||
|
|
{onCancel && (
|
||
|
|
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
||
|
|
<X size={20} />
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||
|
|
<RatingField label="نظافة العقار" value={cleanRating} onChange={setCleanRating} />
|
||
|
|
<RatingField label="جودة الخدمات" value={servicesRating} onChange={setServicesRating} />
|
||
|
|
<RatingField label="سلوك المالك" value={ownerBehaviorRating} onChange={setOwnerBehaviorRating} />
|
||
|
|
<RatingField label="التجربة العامة" value={experienceRating} onChange={setExperienceRating} />
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-gray-700">تعليق (اختياري)</label>
|
||
|
|
<textarea
|
||
|
|
rows={3}
|
||
|
|
value={comment}
|
||
|
|
onChange={(e) => setComment(e.target.value)}
|
||
|
|
className="w-full mt-1 px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
|
||
|
|
placeholder="شارك تجربتك..."
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
type="submit"
|
||
|
|
disabled={loading}
|
||
|
|
className="w-full bg-amber-500 hover:bg-amber-600 text-white font-bold py-3 rounded-xl transition flex items-center justify-center gap-2 disabled:opacity-50"
|
||
|
|
>
|
||
|
|
{loading && <Loader2 className="w-5 h-5 animate-spin" />}
|
||
|
|
{loading ? 'جاري الإرسال...' : 'إرسال التقييم'}
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|