146 lines
4.6 KiB
TypeScript
146 lines
4.6 KiB
TypeScript
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>
|
||
)
|
||
}
|