feat: UI style refresh — Lucide icons, theme, slider, filters, buttons, VK

This commit is contained in:
Kirill
2026-05-14 21:25:11 +05:00
parent 3b85f2cb57
commit 8632601490
18 changed files with 256 additions and 220 deletions
@@ -1,12 +1,6 @@
import type { ReactNode } from 'react'
import { useMemo, useState } from 'react'
import AdminPanelSettingsOutlinedIcon from '@mui/icons-material/AdminPanelSettingsOutlined'
import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined'
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'
import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined'
import PhotoLibraryOutlinedIcon from '@mui/icons-material/PhotoLibraryOutlined'
import RateReviewOutlinedIcon from '@mui/icons-material/RateReviewOutlined'
import StorefrontOutlinedIcon from '@mui/icons-material/StorefrontOutlined'
import { FileText, Image, LayoutGrid, ListOrdered, MessageSquare, People, Store } from 'lucide-react'
import Badge from '@mui/material/Badge'
import Box from '@mui/material/Box'
import Divider from '@mui/material/Divider'
@@ -60,13 +54,13 @@ export function AdminLayoutPage() {
const navItems: NavItem[] = useMemo(
() => [
{ to: '/admin', label: 'Товары', icon: <StorefrontOutlinedIcon /> },
{ to: '/admin/categories', label: 'Категории', icon: <AdminPanelSettingsOutlinedIcon /> },
{ to: '/admin/gallery', label: 'Галерея', icon: <PhotoLibraryOutlinedIcon /> },
{ to: '/admin/orders', label: 'Заказы', icon: <AssignmentOutlinedIcon /> },
{ to: '/admin/reviews', label: 'Отзывы', icon: <RateReviewOutlinedIcon /> },
{ to: '/admin/users', label: 'Пользователи', icon: <PeopleOutlinedIcon /> },
{ to: '/admin/info', label: 'Инфо-страница', icon: <DescriptionOutlinedIcon /> },
{ to: '/admin', label: 'Товары', icon: <Store /> },
{ to: '/admin/categories', label: 'Категории', icon: <LayoutGrid /> },
{ to: '/admin/gallery', label: 'Галерея', icon: <Image /> },
{ to: '/admin/orders', label: 'Заказы', icon: <ListOrdered /> },
{ to: '/admin/reviews', label: 'Отзывы', icon: <MessageSquare /> },
{ to: '/admin/users', label: 'Пользователи', icon: <People /> },
{ to: '/admin/info', label: 'Инфо-страница', icon: <FileText /> },
],
[],
)
@@ -143,7 +137,7 @@ export function AdminLayoutPage() {
bgcolor: 'warning.50',
}}
>
<AdminPanelSettingsOutlinedIcon />
<LayoutGrid />
</IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}>
Админка
+4 -6
View File
@@ -1,6 +1,4 @@
import AddIcon from '@mui/icons-material/Add'
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'
import RemoveIcon from '@mui/icons-material/Remove'
import { Minus, Plus, Trash2 } from 'lucide-react'
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
@@ -115,7 +113,7 @@ export function CartPage() {
size="small"
sx={{ width: 32, height: 32 }}
>
<RemoveIcon />
<Minus />
</IconButton>
<Typography
sx={{
@@ -137,7 +135,7 @@ export function CartPage() {
size="small"
sx={{ width: 32, height: 32 }}
>
<AddIcon />
<Plus />
</IconButton>
</span>
</Tooltip>
@@ -149,7 +147,7 @@ export function CartPage() {
size="small"
sx={{ width: 32, height: 32 }}
>
<DeleteOutlineOutlinedIcon />
<Trash2 />
</IconButton>
</Stack>
</Stack>
+97 -131
View File
@@ -1,9 +1,11 @@
import { useMemo } from 'react'
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'
import MenuItem from '@mui/material/MenuItem'
import Paper from '@mui/material/Paper'
@@ -13,6 +15,7 @@ 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'
@@ -60,53 +63,49 @@ export function ProductFilters({
spacing={2}
sx={{ alignItems: { md: 'center' }, flexWrap: { md: 'wrap' } }}
>
<FormControl sx={{ minWidth: 220 }} size="small">
<InputLabel id="category-filter-label">Категория</InputLabel>
<Select<string>
labelId="category-filter-label"
label="Категория"
value={categorySlug}
onChange={handleCategoryChange}
disabled={categoriesLoading}
>
<MenuItem value="">
<em>Все</em>
</MenuItem>
{categoriesForFilter.map((c) => (
<MenuItem key={c.id} value={c.slug}>
{c.name}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
size="small"
label="Поиск"
placeholder="Поиск"
value={qInput}
onChange={(e) => setQInput(e.target.value)}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">
<Search size={18} />
</InputAdornment>
),
},
}}
sx={{ flexGrow: 1, minWidth: { xs: '100%', md: 360 } }}
/>
</Stack>
<Paper
variant="outlined"
sx={{
p: 1.5,
borderRadius: 2,
bgcolor: 'background.paper',
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1.5,
alignItems: { sm: 'center' },
justifyContent: 'space-between',
}}
sx={{ p: 1.5, borderRadius: 3, bgcolor: 'background.paper', display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1.5, alignItems: { sm: 'center' }, justifyContent: 'space-between' }}
>
<Box>
<Typography variant="subtitle2">Наличие</Typography>
<Typography variant="caption" color="text.secondary">
Быстрый фильтр по наличию
</Typography>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, alignItems: { sm: 'center' }, gap: 1.5, flexGrow: 1 }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75, alignItems: 'center' }}>
<Chip
label="Все"
size="small"
variant={categorySlug === '' ? 'filled' : 'outlined'}
color={categorySlug === '' ? 'primary' : 'default'}
onClick={() => handleCategoryChange('')}
/>
{categoriesForFilter.map((c) => (
<Chip
key={c.id}
label={c.name}
size="small"
variant={categorySlug === c.slug ? 'filled' : 'outlined'}
color={categorySlug === c.slug ? 'primary' : 'default'}
onClick={() => handleCategoryChange(c.slug)}
disabled={categoriesLoading}
/>
))}
</Box>
</Box>
<ToggleButtonGroup
@@ -116,7 +115,7 @@ export function ProductFilters({
onChange={(_, v) => handleAvailabilityChange(v)}
sx={{
alignSelf: { xs: 'flex-start', sm: 'auto' },
'& .MuiToggleButton-root': { px: 2, fontWeight: 700, letterSpacing: 0.2, textTransform: 'none' },
'& .MuiToggleButton-root': { px: 1.5, fontWeight: 600, textTransform: 'none' },
'& .MuiToggleButton-root.Mui-selected': {
bgcolor: 'primary.main',
color: 'primary.contrastText',
@@ -135,114 +134,81 @@ export function ProductFilters({
spacing={1.5}
sx={{ alignItems: { sm: 'center' }, justifyContent: 'space-between', flexWrap: 'wrap' }}
>
<Button variant="text" onClick={() => setMoreOpen((v) => !v)} sx={{ alignSelf: { xs: 'flex-start' } }}>
{moreOpen ? 'Скрыть фильтры' : 'Фильтры и сортировка'}
</Button>
<Button
variant="outlined"
onClick={resetFilters}
variant="text"
onClick={() => setMoreOpen((v) => !v)}
startIcon={<SlidersHorizontal size={18} />}
sx={{ alignSelf: { xs: 'flex-start' } }}
>
{moreOpen ? 'Скрыть' : 'Фильтры'}
</Button>
<Button variant="outlined" onClick={resetFilters} sx={{ alignSelf: { xs: 'flex-start' } }}>
Сбросить
</Button>
</Stack>
<Collapse in={moreOpen} unmountOnExit>
<Stack
direction={{ xs: 'column', md: 'row' }}
spacing={2}
sx={{ mt: 2, alignItems: { md: 'center' }, flexWrap: { md: 'wrap' } }}
>
<FormControl sx={{ minWidth: 220 }} size="small">
<InputLabel id="sort-label">Сортировка</InputLabel>
<Select<string> labelId="sort-label" label="Сортировка" value={sort} onChange={handleSortChange}>
<MenuItem value="">
<em>Сначала новые</em>
</MenuItem>
<MenuItem value="price_asc">Цена: по возрастанию</MenuItem>
<MenuItem value="price_desc">Цена: по убыванию</MenuItem>
</Select>
</FormControl>
<Paper variant="outlined" sx={{ p: 2, borderRadius: 3, display: 'flex', flexDirection: 'column', gap: 2 }}>
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2} sx={{ alignItems: { md: 'center' }, flexWrap: { md: 'wrap' } }}>
<FormControl sx={{ minWidth: 200 }} size="small">
<InputLabel id="sort-label">Сортировка</InputLabel>
<Select<string> labelId="sort-label" label="Сортировка" value={sort} onChange={handleSortChange}>
<MenuItem value=""><em>Сначала новые</em></MenuItem>
<MenuItem value="price_asc">Цена: по возрастанию</MenuItem>
<MenuItem value="price_desc">Цена: по убыванию</MenuItem>
</Select>
</FormControl>
<TextField
size="small"
label="Цена от, ₽"
value={priceMinRub}
onChange={(e) => handlePriceMinChange(e.target.value)}
sx={{ width: { xs: '100%', md: 180 } }}
/>
<TextField
size="small"
label="Цена до, ₽"
value={priceMaxRub}
onChange={(e) => handlePriceMaxChange(e.target.value)}
sx={{ width: { xs: '100%', md: 180 } }}
/>
<TextField
size="small"
label="Цена от, ₽"
value={priceMinRub}
onChange={(e) => handlePriceMinChange(e.target.value)}
sx={{ width: { xs: '100%', md: 160 } }}
/>
<TextField
size="small"
label="Цена до, ₽"
value={priceMaxRub}
onChange={(e) => handlePriceMaxChange(e.target.value)}
sx={{ width: { xs: '100%', md: 160 } }}
/>
<FormControl sx={{ minWidth: 220 }} size="small">
<InputLabel id="page-size-label">На странице</InputLabel>
<Select<string>
labelId="page-size-label"
label="На странице"
value={String(pageSize)}
onChange={handlePageSizeChange}
>
{[6, 12, 18, 24].map((n) => (
<MenuItem key={n} value={String(n)}>
{n}
</MenuItem>
))}
</Select>
</FormControl>
</Stack>
<FormControl sx={{ minWidth: 160 }} size="small">
<InputLabel id="page-size-label">На странице</InputLabel>
<Select<string> labelId="page-size-label" label="На странице" value={String(pageSize)} onChange={handlePageSizeChange}>
{[6, 12, 18, 24].map((n) => (
<MenuItem key={n} value={String(n)}>{n}</MenuItem>
))}
</Select>
</FormControl>
</Stack>
<Divider sx={{ my: 2 }} />
<Divider />
<Paper
variant="outlined"
sx={{
p: 1.5,
borderRadius: 2,
bgcolor: 'background.paper',
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 1.5,
alignItems: { sm: 'center' },
justifyContent: 'space-between',
}}
>
<Box>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 1.5, alignItems: { sm: 'center' }, justifyContent: 'space-between' }}>
<Typography variant="subtitle2">Масштаб карточек</Typography>
<Typography variant="caption" color="text.secondary">
Выберите размер карточек в каталоге
</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>
<ToggleButtonGroup
exclusive
size="small"
value={cardScale}
onChange={(_, v) => handleCardScaleChange(v)}
sx={{
alignSelf: { xs: 'flex-start', sm: 'auto' },
'& .MuiToggleButton-root': {
px: 2,
fontWeight: 700,
letterSpacing: 0.2,
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>
</Paper>
</Collapse>
</Stack>
+6 -10
View File
@@ -1,10 +1,6 @@
import type { ReactNode } from 'react'
import { useMemo, useState } from 'react'
import ChatOutlinedIcon from '@mui/icons-material/ChatOutlined'
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'
import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined'
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'
import TuneOutlinedIcon from '@mui/icons-material/TuneOutlined'
import { MapPin, MessageCircle, Settings, SlidersHorizontal, Truck } from 'lucide-react'
import Alert from '@mui/material/Alert'
import Badge from '@mui/material/Badge'
import Box from '@mui/material/Box'
@@ -56,10 +52,10 @@ export function MeLayoutPage() {
const navItems: NavItem[] = useMemo(
() => [
{ to: '/me/orders', label: 'Заказы', icon: <LocalShippingOutlinedIcon /> },
{ to: '/me/messages', label: 'Сообщения', icon: <ChatOutlinedIcon /> },
{ to: '/me/settings', label: 'Настройки', icon: <SettingsOutlinedIcon /> },
{ to: '/me/addresses', label: 'Адреса доставки', icon: <PlaceOutlinedIcon /> },
{ to: '/me/orders', label: 'Заказы', icon: <Truck /> },
{ to: '/me/messages', label: 'Сообщения', icon: <MessageCircle /> },
{ to: '/me/settings', label: 'Настройки', icon: <Settings /> },
{ to: '/me/addresses', label: 'Адреса доставки', icon: <MapPin /> },
],
[],
)
@@ -143,7 +139,7 @@ export function MeLayoutPage() {
bgcolor: 'background.paper',
}}
>
<TuneOutlinedIcon />
<SlidersHorizontal />
</IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}>
Профиль
+6 -7
View File
@@ -1,6 +1,5 @@
import { useMemo, useState } from 'react'
import CloseIcon from '@mui/icons-material/Close'
import StarRoundedIcon from '@mui/icons-material/StarRounded'
import { Star, X } from 'lucide-react'
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
@@ -180,8 +179,8 @@ export function ProductPage() {
value={p.reviewsSummary.avgRating ?? 0}
readOnly
precision={0.25}
icon={<StarRoundedIcon fontSize="inherit" />}
emptyIcon={<StarRoundedIcon fontSize="inherit" />}
icon={<Star fontSize="inherit" />}
emptyIcon={<Star fontSize="inherit" />}
/>
<Typography variant="body2" color="text.secondary">
{reviewsCountRu(p.reviewsSummary.approvedReviewCount)}
@@ -211,8 +210,8 @@ export function ProductPage() {
value={rv.rating}
readOnly
size="small"
icon={<StarRoundedIcon fontSize="inherit" />}
emptyIcon={<StarRoundedIcon fontSize="inherit" />}
icon={<Star fontSize="inherit" />}
emptyIcon={<Star fontSize="inherit" />}
/>
{body ? (
<Box sx={{ color: 'text.secondary' }}>
@@ -259,7 +258,7 @@ export function ProductPage() {
sx={{ position: 'absolute', top: 12, right: 12, zIndex: 2, color: 'white' }}
aria-label="Закрыть"
>
<CloseIcon />
<X />
</IconButton>
<Swiper