messages and reviews
This commit is contained in:
131
frontend/src/components/ItemReviews.tsx
Normal file
131
frontend/src/components/ItemReviews.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Rental } from '../types';
|
||||
import { rentalAPI } from '../services/api';
|
||||
|
||||
interface ItemReviewsProps {
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
const ItemReviews: React.FC<ItemReviewsProps> = ({ itemId }) => {
|
||||
const [reviews, setReviews] = useState<Rental[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [averageRating, setAverageRating] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
fetchReviews();
|
||||
}, [itemId]);
|
||||
|
||||
const fetchReviews = async () => {
|
||||
try {
|
||||
// Fetch all rentals for this item
|
||||
const response = await rentalAPI.getMyListings();
|
||||
const allRentals: Rental[] = response.data;
|
||||
|
||||
// Filter for completed rentals with reviews for this specific item
|
||||
const itemReviews = allRentals.filter(
|
||||
rental => rental.itemId === itemId &&
|
||||
rental.status === 'completed' &&
|
||||
rental.rating &&
|
||||
rental.review
|
||||
);
|
||||
|
||||
setReviews(itemReviews);
|
||||
|
||||
// Calculate average rating
|
||||
if (itemReviews.length > 0) {
|
||||
const sum = itemReviews.reduce((acc, r) => acc + (r.rating || 0), 0);
|
||||
setAverageRating(sum / itemReviews.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch reviews:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderStars = (rating: number) => {
|
||||
return (
|
||||
<span className="text-warning">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<i
|
||||
key={star}
|
||||
className={`bi ${star <= rating ? 'bi-star-fill' : 'bi-star'}`}
|
||||
></i>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<h5>Reviews</h5>
|
||||
<div className="text-center py-3">
|
||||
<div className="spinner-border spinner-border-sm" role="status">
|
||||
<span className="visually-hidden">Loading reviews...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<h5>Reviews</h5>
|
||||
|
||||
{reviews.length === 0 ? (
|
||||
<p className="text-muted">No reviews yet. Be the first to rent and review this item!</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
{renderStars(Math.round(averageRating))}
|
||||
<span className="fw-bold">{averageRating.toFixed(1)}</span>
|
||||
<span className="text-muted">({reviews.length} {reviews.length === 1 ? 'review' : 'reviews'})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-top pt-3">
|
||||
{reviews.map((rental) => (
|
||||
<div key={rental.id} className="mb-3 pb-3 border-bottom">
|
||||
<div className="d-flex justify-content-between align-items-start mb-2">
|
||||
<div>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
{rental.renter?.profileImage ? (
|
||||
<img
|
||||
src={rental.renter.profileImage}
|
||||
alt={`${rental.renter.firstName} ${rental.renter.lastName}`}
|
||||
className="rounded-circle"
|
||||
style={{ width: '32px', height: '32px', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="rounded-circle bg-secondary d-flex align-items-center justify-content-center"
|
||||
style={{ width: '32px', height: '32px' }}
|
||||
>
|
||||
<i className="bi bi-person-fill text-white" style={{ fontSize: '0.8rem' }}></i>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<strong>{rental.renter?.firstName} {rental.renter?.lastName}</strong>
|
||||
<div className="small">
|
||||
{renderStars(rental.rating || 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small className="text-muted">
|
||||
{new Date(rental.updatedAt).toLocaleDateString()}
|
||||
</small>
|
||||
</div>
|
||||
<p className="mb-0">{rental.review}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemReviews;
|
||||
Reference in New Issue
Block a user