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

145 lines
No EOL
3.8 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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}
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>