This commit is contained in:
Kirill
2026-05-27 18:29:41 +05:00
parent 5071d70746
commit b392884503
5 changed files with 121 additions and 161 deletions
@@ -18,7 +18,7 @@ import type { Swiper as SwiperType } from 'swiper/types'
type Props = { product: Product; mediaHeight?: number; actions?: ReactNode }
const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
const ProductCardInner = ({ product, mediaHeight = 390, actions }: Props) => {
const navigate = useNavigate()
const isMobile = useMediaQuery('(max-width:600px)')
const swiperRef = useRef<SwiperType | null>(null)
@@ -78,7 +78,7 @@ const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
>
<Box sx={{ position: 'relative' }}>
{imageUrls.length ? (
<Box onMouseMove={!isMobile ? onMouseMove : undefined} sx={{ height: mediaHeight, overflow: 'hidden' }}>
<Box onMouseMove={!isMobile ? onMouseMove : undefined} sx={{ width: '100%', aspectRatio: '3/4', maxHeight: mediaHeight, overflow: 'hidden' }}>
<Swiper
slidesPerView={1}
spaceBetween={16}
@@ -86,7 +86,7 @@ const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
onSwiper={(s) => {
swiperRef.current = s
}}
style={{ width: '100%', height: mediaHeight, overflow: 'hidden' }}
style={{ width: '100%', height: '100%', overflow: 'hidden' }}
>
{imageUrls.map((url) => (
<SwiperSlide key={url}>
@@ -94,7 +94,7 @@ const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
className="product-card__media"
sx={{
width: '100%',
height: mediaHeight,
height: '100%',
transition: 'transform 320ms ease',
'@media (prefers-reduced-motion: reduce)': { transition: 'none' },
userSelect: 'none',
@@ -104,7 +104,7 @@ const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
<OptimizedImage
src={url}
alt={product.title}
sizes={`(max-width: 600px) ${mediaHeight}px, (max-width: 1024px) ${Math.round(mediaHeight * 1.5)}px, ${mediaHeight}px`}
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
sx={{
width: '101%',
height: '100%',
@@ -120,7 +120,9 @@ const ProductCardInner = ({ product, mediaHeight = 300, actions }: Props) => {
<CardMedia
component="div"
sx={{
height: mediaHeight,
width: '100%',
aspectRatio: '3/4',
maxHeight: mediaHeight,
bgcolor: 'grey.50',
display: 'flex',
alignItems: 'center',
@@ -13,7 +13,6 @@ export function useProductFilters() {
const [pageSize, setPageSize] = useState(12)
const [priceMinRub, setPriceMinRub] = useState('')
const [priceMaxRub, setPriceMaxRub] = useState('')
const [cardScale, setCardScale] = useState<70 | 90 | 110 | 130>(90)
useEffect(() => {
const t = window.setTimeout(() => {
@@ -54,10 +53,6 @@ export function useProductFilters() {
setPage(1)
}
const handleCardScaleChange = (v: number) => {
if (v === 70 || v === 90 || v === 110 || v === 130) setCardScale(v as 70 | 90 | 110 | 130)
}
const resetFilters = () => {
setCategorySlug('')
setQInput('')
@@ -65,7 +60,6 @@ export function useProductFilters() {
setPriceMinRub('')
setPriceMaxRub('')
setPageSize(12)
setCardScale(90)
setMoreOpen(false)
}
@@ -86,7 +80,6 @@ export function useProductFilters() {
pageSize,
priceMinRub,
priceMaxRub,
cardScale,
setPage,
setQInput,
setMoreOpen,
@@ -95,7 +88,6 @@ export function useProductFilters() {
handlePageSizeChange,
handlePriceMinChange,
handlePriceMaxChange,
handleCardScaleChange,
resetFilters,
toCents,
}
+3 -5
View File
@@ -64,7 +64,6 @@ export function HomePage() {
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(300 * (filters.cardScale / 100))
return (
<Box>
@@ -100,8 +99,8 @@ export function HomePage() {
{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 size={{ xs: 12, sm: 6, md: 3 }} key={i}>
<Skeleton variant="rectangular" sx={{ width: '100%', aspectRatio: '3/4' }} />
</Grid>
))}
</Grid>
@@ -128,10 +127,9 @@ export function HomePage() {
<>
<Grid container spacing={2} sx={{ mt: 1 }}>
{products.map((p) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={p.id}>
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={p.id}>
<ProductCard
product={p}
mediaHeight={mediaHeight}
actions={!isAdmin && p.quantity > 0 ? <ToggleCartIcon productId={p.id} /> : undefined}
/>
</Grid>
@@ -3,7 +3,6 @@ import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Chip from '@mui/material/Chip'
import Collapse from '@mui/material/Collapse'
import Divider from '@mui/material/Divider'
import FormControl from '@mui/material/FormControl'
import InputAdornment from '@mui/material/InputAdornment'
import InputLabel from '@mui/material/InputLabel'
@@ -12,9 +11,6 @@ import Paper from '@mui/material/Paper'
import Select from '@mui/material/Select'
import Stack from '@mui/material/Stack'
import TextField from '@mui/material/TextField'
import ToggleButton from '@mui/material/ToggleButton'
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
import Typography from '@mui/material/Typography'
import { Search, SlidersHorizontal } from 'lucide-react'
import type { Category } from '@/entities/product/model/types'
import type { UseProductFiltersResult } from '../lib/use-product-filters'
@@ -32,7 +28,6 @@ export function ProductFilters({
pageSize,
priceMinRub,
priceMaxRub,
cardScale,
categories,
categoriesLoading,
setQInput,
@@ -42,7 +37,6 @@ export function ProductFilters({
handlePageSizeChange,
handlePriceMinChange,
handlePriceMaxChange,
handleCardScaleChange,
resetFilters,
}: Props) {
const categoriesForFilter = useMemo(() => {
@@ -188,39 +182,6 @@ export function ProductFilters({
</FormControl>
</Stack>
<Divider />
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1.5,
alignItems: { sm: 'center' },
justifyContent: 'space-between',
}}
>
<Typography variant="subtitle2">Масштаб карточек</Typography>
<ToggleButtonGroup
exclusive
size="small"
value={cardScale}
onChange={(_, v) => handleCardScaleChange(v)}
sx={{
alignSelf: { xs: 'flex-start', sm: 'auto' },
'& .MuiToggleButton-root': { px: 1.5, fontWeight: 600, textTransform: 'none' },
'& .MuiToggleButton-root.Mui-selected': {
bgcolor: 'primary.main',
color: 'primary.contrastText',
'&:hover': { bgcolor: 'primary.dark' },
},
}}
>
<ToggleButton value={70}>S</ToggleButton>
<ToggleButton value={90}>M</ToggleButton>
<ToggleButton value={110}>L</ToggleButton>
<ToggleButton value={130}>XL</ToggleButton>
</ToggleButtonGroup>
</Box>
</Paper>
</Collapse>
</Stack>
+15 -8
View File
@@ -57,7 +57,7 @@ export function ProductPage() {
if (productQuery.isLoading) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Skeleton variant="rectangular" height={525} />
<Skeleton variant="rectangular" sx={{ width: '100%', aspectRatio: '3/4' }} />
<Skeleton variant="text" width="60%" />
<Skeleton variant="text" width="40%" />
<Skeleton variant="text" />
@@ -72,7 +72,8 @@ export function ProductPage() {
return (
<Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Stack direction={{ xs: 'column', md: 'row' }} spacing={{ xs: 3, md: 4 }}>
<Box sx={{ flex: { md: '1 1 50%' }, minWidth: 0 }}>
{imageUrls.length > 0 ? (
<Box
sx={{
@@ -81,9 +82,11 @@ export function ProductPage() {
border: 'none',
boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
bgcolor: 'background.paper',
width: '100%',
aspectRatio: '3/4',
}}
>
<Swiper modules={[Navigation]} navigation style={{ width: '100%', height: 525 }}>
<Swiper modules={[Navigation]} navigation style={{ width: '100%', height: '100%' }}>
{imageUrls.map((url, idx) => (
<SwiperSlide key={url}>
<Box
@@ -93,7 +96,7 @@ export function ProductPage() {
}}
sx={{
width: '100%',
height: 525,
height: '100%',
cursor: 'zoom-in',
userSelect: 'none',
}}
@@ -101,7 +104,7 @@ export function ProductPage() {
<OptimizedImage
src={url}
alt={p.title}
sizes="(max-width: 600px) 320px, (max-width: 1024px) 640px, 1024px"
sizes="(max-width: 900px) 100vw, 50vw"
sx={{
width: '100%',
height: '100%',
@@ -116,7 +119,8 @@ export function ProductPage() {
) : (
<Box
sx={{
height: 525,
width: '100%',
aspectRatio: '3/4',
borderRadius: 2,
overflow: 'hidden',
border: 1,
@@ -130,7 +134,9 @@ export function ProductPage() {
<Typography color="text.secondary">Нет фото</Typography>
</Box>
)}
</Box>
<Box sx={{ flex: { md: '1 1 50%' }, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 3 }}>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{p.category?.name && <Chip label={p.category.name} />}
{p.quantity > 0 && <Chip label="В наличии" color="success" />}
@@ -164,8 +170,10 @@ export function ProductPage() {
) : (
<Typography color="text.secondary">Описание появится позже.</Typography>
)}
</Box>
</Stack>
<Divider sx={{ my: 2 }} />
<Divider sx={{ my: { xs: 3, md: 4 } }} />
<Typography variant="h6" sx={{ mb: 1 }}>
Отзывы
@@ -186,7 +194,6 @@ export function ProductPage() {
)}
<ProductReviewsList productId={id} />
</Box>
<Dialog fullScreen open={viewerOpen} onClose={() => setViewerOpen(false)}>
<Box sx={{ position: 'relative', height: '100%', bgcolor: 'black' }}>