Files
shop-server/client/src/widgets/reviews-block/ui/ReviewsBlock.tsx
T
2026-05-21 12:02:29 +05:00

156 lines
6.2 KiB
TypeScript
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 StarRoundedIcon from '@mui/icons-material/StarRounded'
import Alert from '@mui/material/Alert'
import Avatar from '@mui/material/Avatar'
import Box from '@mui/material/Box'
import Paper from '@mui/material/Paper'
import Rating from '@mui/material/Rating'
import Skeleton from '@mui/material/Skeleton'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { useQuery } from '@tanstack/react-query'
import { Link as RouterLink } from 'react-router-dom'
import { fetchLatestApprovedReviews } from '@/entities/review/api/reviews-api'
import { OptimizedImage } from '@/shared/ui/OptimizedImage'
import { RichTextMessageContent } from '@/shared/ui/RichTextMessageContent'
function initials(display: string) {
const s = display.trim()
if (!s) return '?'
return s.slice(0, 1).toUpperCase()
}
function formatReviewDate(iso: string): string {
try {
return new Date(iso).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' })
} catch {
return ''
}
}
export function ReviewsBlock() {
const q = useQuery({
queryKey: ['reviews', 'latest', 5],
queryFn: () => fetchLatestApprovedReviews(5),
})
const items = !q.isLoading && !q.isError && q.data ? q.data.items : []
return (
<Paper variant="outlined" sx={{ p: { xs: 2, sm: 3 }, borderRadius: 2, bgcolor: 'background.paper' }}>
<Stack spacing={0.75} sx={{ mb: 2 }}>
<Typography variant="h5">Отзывы</Typography>
<Typography variant="body2" color="text.secondary">
Последние отзывы о товарах
</Typography>
</Stack>
{q.isLoading && (
<Stack spacing={2}>
{[1, 2, 3].map((i) => (
<Skeleton key={i} variant="rounded" height={92} sx={{ borderRadius: 2 }} />
))}
</Stack>
)}
{q.isError && <Alert severity="error">Не удалось загрузить отзывы.</Alert>}
{!q.isLoading && !q.isError && q.data && items.length === 0 && (
<Box sx={{ py: 4 }}>
<Typography variant="h6" color="text.secondary" sx={{ mb: 2 }}>
Отзывов пока нет
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 400 }}>
Будьте первым, кто оставит отзыв о наших товарах. Ваше мнение поможет другим покупателям сделать правильный
выбор.
</Typography>
</Box>
)}
{items.length > 0 && (
<Stack spacing={2}>
{items.map((r, i) => {
const zebra = i % 2 === 0
const text = typeof r.text === 'string' && r.text.trim() ? r.text.trim() : 'Без комментария'
return (
<Paper
key={r.id}
variant={zebra ? undefined : 'outlined'}
elevation={zebra ? 0 : undefined}
sx={{
p: { xs: 1.75, sm: 2 },
borderRadius: 2,
...(zebra ? { bgcolor: 'action.hover' } : {}),
}}
>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ alignItems: { sm: 'flex-start' } }}>
{r.imageUrl && (
<Box
sx={{
width: { xs: 80, sm: 80 },
height: { xs: 80, sm: 80 },
borderRadius: 1.5,
border: 1,
borderColor: 'divider',
overflow: 'hidden',
flexShrink: 0,
alignSelf: { xs: 'flex-start', sm: 'center' },
}}
>
<OptimizedImage
src={r.imageUrl}
alt="Фото к отзыву"
widths={[320, 640]}
sizes="80px"
sx={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
</Box>
)}
<Stack direction="row" spacing={1.5} sx={{ minWidth: { sm: 200 }, alignItems: 'center' }}>
<Avatar sx={{ bgcolor: 'primary.main', color: 'primary.contrastText', fontWeight: 800 }}>
{initials(r.authorDisplay)}
</Avatar>
<Box>
<Typography sx={{ fontWeight: 800, lineHeight: 1.15 }}>{r.authorDisplay}</Typography>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
<Rating
value={r.rating}
readOnly
size="small"
icon={<StarRoundedIcon fontSize="inherit" />}
emptyIcon={<StarRoundedIcon fontSize="inherit" />}
/>
<Typography variant="caption" color="text.secondary">
{formatReviewDate(r.createdAt)}
</Typography>
</Stack>
<Typography
variant="caption"
component={RouterLink}
to={`/products/${r.productId}`}
sx={{
display: 'block',
mt: 0.25,
color: 'primary.main',
textDecoration: 'none',
'&:hover': { textDecoration: 'underline' },
}}
>
{r.productTitle}
</Typography>
</Box>
</Stack>
<Box sx={{ color: 'text.secondary', flex: 1 }}>
<RichTextMessageContent value={text} tone="review" />
</Box>
</Stack>
</Paper>
)
})}
</Stack>
)}
</Paper>
)
}