Files
shop-server/client/src/pages/home/ui/HomePage.tsx
T
2026-05-15 12:50:39 +05:00

146 lines
4.6 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 { useMemo } from 'react'
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Grid from '@mui/material/Grid'
import Pagination from '@mui/material/Pagination'
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 { useUnit } from 'effector-react'
import { fetchCategories, fetchPublicProducts } from '@/entities/product/api/product-api'
import { ProductCard } from '@/entities/product/ui/ProductCard'
import { ToggleCartIcon } from '@/features/cart/toggle-cart-icon'
import { $user } from '@/shared/model/auth'
import { CatalogSlider } from '@/widgets/catalog-slider'
import { ReviewsBlock } from '@/widgets/reviews-block'
import { useProductFilters } from '../lib/use-product-filters'
import { ProductFilters } from './ProductFilters'
export function HomePage() {
const user = useUnit($user)
const isAdmin = Boolean(user?.isAdmin)
const filters = useProductFilters()
const categoriesQuery = useQuery({
queryKey: ['categories'],
queryFn: () => fetchCategories(),
})
const productsQuery = useQuery({
queryKey: [
'products',
'public',
{
categorySlug: filters.categorySlug || 'all',
q: filters.q,
sort: filters.sort,
page: filters.page,
pageSize: filters.pageSize,
priceMinRub: filters.priceMinRub,
priceMaxRub: filters.priceMaxRub,
},
],
queryFn: () =>
fetchPublicProducts({
categorySlug: filters.categorySlug || undefined,
q: filters.q || undefined,
sort: filters.sort || '',
page: filters.page,
pageSize: filters.pageSize,
priceMinCents: filters.toCents(filters.priceMinRub),
priceMaxCents: filters.toCents(filters.priceMaxRub),
}),
})
const title = useMemo(
() =>
filters.categorySlug
? `Категория: ${categoriesQuery.data?.find((c) => c.slug === filters.categorySlug)?.name ?? ''}`
: 'Каталог',
[filters.categorySlug, categoriesQuery.data],
)
const products = productsQuery.data?.items ?? []
const total = productsQuery.data?.total ?? 0
const totalPages = Math.max(1, Math.ceil(total / filters.pageSize))
const mediaHeight = Math.round(200 * (filters.cardScale / 100))
return (
<Box>
<CatalogSlider />
<Typography variant="h4" component="h1" gutterBottom>
{title}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
Игрушки, сувениры и другие изделия ручной работы.
</Typography>
<Box sx={{ mb: 3 }}>
<ProductFilters
{...filters}
categories={categoriesQuery.data ?? []}
categoriesLoading={categoriesQuery.isLoading}
/>
</Box>
{productsQuery.isLoading && (
<Grid container spacing={2} sx={{ mt: 2 }}>
{[1, 2, 3].map((i) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={i}>
<Skeleton variant="rectangular" height={360} />
</Grid>
))}
</Grid>
)}
{productsQuery.isError && (
<Alert severity="error" sx={{ mt: 2 }}>
Не удалось загрузить товары. Проверьте, что API запущен.
</Alert>
)}
{productsQuery.isSuccess && products.length === 0 && (
<Typography color="text.secondary" sx={{ mt: 2 }}>
Пока нет опубликованных товаров.
</Typography>
)}
{productsQuery.isSuccess && products.length > 0 && (
<>
<Grid container spacing={2} sx={{ mt: 1 }}>
{products.map((p) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={p.id}>
<ProductCard
product={p}
mediaHeight={mediaHeight}
actions={!isAdmin && p.quantity > 0 ? <ToggleCartIcon productId={p.id} /> : undefined}
/>
</Grid>
))}
</Grid>
{totalPages > 1 && (
<Stack direction="row" sx={{ mt: 3, justifyContent: 'center' }}>
<Pagination
page={filters.page}
count={totalPages}
onChange={(_, v) => filters.setPage(v)}
color="primary"
shape="rounded"
showFirstButton
showLastButton
/>
</Stack>
)}
<Box sx={{ mt: 4 }}>
<ReviewsBlock />
</Box>
</>
)}
</Box>
)
}