135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Rental } from "../types";
|
|
import { itemAPI } from "../services/api";
|
|
|
|
interface ItemReviewsProps {
|
|
itemId: string;
|
|
}
|
|
|
|
const ItemReviews: React.FC<ItemReviewsProps> = ({ itemId }) => {
|
|
const navigate = useNavigate();
|
|
const [reviews, setReviews] = useState<Rental[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [averageRating, setAverageRating] = useState(0);
|
|
|
|
useEffect(() => {
|
|
fetchReviews();
|
|
}, [itemId]);
|
|
|
|
const fetchReviews = async () => {
|
|
try {
|
|
const response = await itemAPI.getItemReviews(itemId);
|
|
const { reviews, averageRating } = response.data;
|
|
|
|
setReviews(reviews);
|
|
setAverageRating(averageRating);
|
|
} 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">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"
|
|
onClick={() => rental.renter && navigate(`/users/${rental.renterId}`)}
|
|
style={{ cursor: "pointer" }}
|
|
>
|
|
{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 style={{ color: "#0d6efd" }}>
|
|
{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;
|