astro_avtourist/frontend/src/components/reviews/ReviewsList.astro

146 lines
3.8 KiB
Text
Raw Normal View History

---
import ReviewCard from './ReviewCard.astro';
import VotingSummary from './VotingSummary.astro';
interface ReviewRecord {
id: string;
name: string;
surname?: string;
profession?: string;
text: string;
rating: number;
status: string;
votesCount: number;
created: string;
expand?: {
user?: {
id: string;
name: string;
firstName?: string;
email: string;
avatar?: string;
};
};
}
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || 'http://127.0.0.1:8090';
interface Props {
status?: string;
}
const { status = 'published' } = Astro.props;
// Загрузка данных с сервера
let reviews: ReviewRecord[] = [];
let error: string | null = null;
let averageRating = 0;
let totalVotes = 0;
let totalReviews = 0;
let ratingDistribution: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
try {
const filter = `status = "${status}"`;
const expand = 'user';
const sort = '-created';
const response = await fetch(
`${POCKETBASE_URL}/api/collections/reviews/records?expand=${expand}&filter=${encodeURIComponent(filter)}&sort=${sort}`
);
if (response.ok) {
const data = await response.json();
reviews = data.items || [];
// Расчёт статистики
totalReviews = reviews.length;
totalVotes = reviews.reduce((sum, r) => sum + (r.votesCount || 0), 0);
const totalRating = reviews.reduce((sum, r) => sum + r.rating, 0);
averageRating = totalReviews > 0 ? parseFloat((totalRating / totalReviews).toFixed(1)) : 0;
ratingDistribution = reviews.reduce((acc, r) => {
acc[r.rating] = (acc[r.rating] || 0) + 1;
return acc;
}, {} as Record<number, number>);
} else {
error = 'Не удалось загрузить отзывы';
}
} catch (e) {
console.error('[ReviewsList] Error:', e);
error = 'Ошибка загрузки отзывов';
}
// Цвета для аватарок
const colors = [
'bg-blue-100 text-blue-600', 'bg-teal-100 text-teal-600', 'bg-orange-100 text-orange-600',
'bg-pink-100 text-pink-600', 'bg-purple-100 text-purple-600', 'bg-indigo-100 text-indigo-600',
'bg-green-100 text-green-600', 'bg-yellow-100 text-yellow-600', 'bg-red-100 text-red-600'
];
const getAvatarInfo = (name: string) => {
const initial = name.charAt(0).toUpperCase();
const color = colors[initial.charCodeAt(0) % colors.length];
return { initial, color };
};
---
{error && (
<div class="text-center py-12 text-red-500">
<p>{error}</p>
</div>
)}
{!error && (
<>
{reviews.length > 0 && (
<VotingSummary
averageRating={averageRating}
totalVotes={totalVotes}
totalReviews={totalReviews}
ratingDistribution={ratingDistribution}
/>
)}
<div class="reviews-grid">
{reviews.length > 0 ? (
reviews.map((review) => {
const avatarInfo = getAvatarInfo(review.name);
const fullName = `${review.name} ${review.surname || ''}`.trim();
return (
<ReviewCard
name={fullName}
profession={review.profession || 'Клиент'}
text={review.text}
rating={review.rating}
initial={avatarInfo.initial}
color={avatarInfo.color}
date={review.created}
votesCount={review.votesCount || 0}
reviewId={review.id}
/>
);
})
) : (
<div class="text-center py-12 text-gray-500" style="grid-column: 1 / -1;">
<p>Пока нет отзывов. Будьте первым!</p>
</div>
)}
</div>
</>
)}
<style>
.reviews-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 2rem;
margin: 3rem 0;
}
@media (max-width: 640px) {
.reviews-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
</style>