146 lines
No EOL
3.8 KiB
Text
146 lines
No EOL
3.8 KiB
Text
---
|
||
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> |