update goods
This commit is contained in:
@@ -14,7 +14,6 @@ export async function fetchPublicProducts(params?: {
|
||||
categorySlug?: string
|
||||
q?: string
|
||||
sort?: 'price_asc' | 'price_desc' | ''
|
||||
availability?: 'all' | 'in_stock' | 'made_to_order'
|
||||
page?: number
|
||||
pageSize?: number
|
||||
priceMinCents?: number
|
||||
@@ -25,7 +24,6 @@ export async function fetchPublicProducts(params?: {
|
||||
categorySlug: params?.categorySlug || undefined,
|
||||
q: params?.q || undefined,
|
||||
sort: params?.sort || undefined,
|
||||
availability: params?.availability || undefined,
|
||||
page: params?.page || undefined,
|
||||
pageSize: params?.pageSize || undefined,
|
||||
priceMin: params?.priceMinCents ?? undefined,
|
||||
@@ -55,16 +53,13 @@ export async function createProduct(body: {
|
||||
slug?: string
|
||||
shortDescription?: string | null
|
||||
description?: string | null
|
||||
quantity?: number | null
|
||||
quantity: number
|
||||
materials?: string[]
|
||||
priceCents: number
|
||||
imageUrl?: string | null
|
||||
imageUrls?: string[]
|
||||
published: boolean
|
||||
inStock?: boolean
|
||||
leadTimeDays?: number | null
|
||||
/** Пустая строка / отсутствует — категория «Не указано» на сервере */
|
||||
categoryId?: string
|
||||
categoryId: string
|
||||
}): Promise<Product> {
|
||||
const { data } = await apiClient.post<Product>('admin/products', body)
|
||||
return data
|
||||
@@ -77,15 +72,13 @@ export async function updateProduct(
|
||||
slug: string
|
||||
shortDescription: string | null
|
||||
description: string | null
|
||||
quantity: number | null
|
||||
quantity: number
|
||||
materials: string[]
|
||||
priceCents: number
|
||||
imageUrl: string | null
|
||||
imageUrls: string[]
|
||||
published: boolean
|
||||
inStock: boolean
|
||||
leadTimeDays: number | null
|
||||
categoryId?: string
|
||||
categoryId: string
|
||||
}>,
|
||||
): Promise<Product> {
|
||||
const { data } = await apiClient.patch<Product>(`admin/products/${id}`, body)
|
||||
|
||||
@@ -23,8 +23,6 @@ export type Product = {
|
||||
imageUrl: string | null
|
||||
imageUrls?: string[] // legacy-friendly (used only in admin payloads)
|
||||
published: boolean
|
||||
inStock: boolean
|
||||
leadTimeDays: number | null
|
||||
categoryId: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
|
||||
@@ -44,12 +44,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
navigate(`/products/${product.id}`)
|
||||
}, [navigate, product.id])
|
||||
|
||||
const stockLabel =
|
||||
product.inStock && product.quantity === 0
|
||||
? { label: 'Нет в наличии', color: 'default' as const }
|
||||
: !product.inStock
|
||||
? { label: `Под заказ · ${product.leadTimeDays ?? '—'} дн.`, color: 'warning' as const }
|
||||
: null
|
||||
const stockLabel = product.quantity > 0 ? null : { label: 'Нет в наличии', color: 'default' as const }
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -132,7 +127,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
label={stockLabel.label}
|
||||
size="small"
|
||||
color={stockLabel.color}
|
||||
variant={stockLabel.color === 'warning' ? 'outlined' : 'filled'}
|
||||
variant="filled"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
@@ -140,8 +135,8 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
fontWeight: 600,
|
||||
fontSize: '0.7rem',
|
||||
backdropFilter: 'blur(4px)',
|
||||
bgcolor: stockLabel.color === 'default' ? 'rgba(0,0,0,0.55)' : undefined,
|
||||
color: stockLabel.color === 'default' ? 'common.white' : undefined,
|
||||
bgcolor: 'rgba(0,0,0,0.55)',
|
||||
color: 'common.white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -63,7 +63,7 @@ export function ToggleCartIcon(props: {
|
||||
<Tooltip title={tooltip}>
|
||||
<span>
|
||||
<IconButton size={size} onClick={onClick} disabled={disabled || busy} aria-label={tooltip} type="button">
|
||||
{user ? (inCart ? <ShoppingCart fill="currentColor" /> : <ShoppingCart />) : <ShoppingCart />}
|
||||
{user ? inCart ? <ShoppingCart fill="currentColor" /> : <ShoppingCart /> : <ShoppingCart />}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -148,7 +148,7 @@ export function AdminCategoriesPage() {
|
||||
<TableCell>{c.sort}</TableCell>
|
||||
<TableCell align="right">
|
||||
<EntityRowActions
|
||||
onEdit={() => openCategoryEdit(c)}
|
||||
onEdit={c.slug === UNSPECIFIED_CATEGORY_SLUG ? undefined : () => openCategoryEdit(c)}
|
||||
onDelete={c.slug === UNSPECIFIED_CATEGORY_SLUG ? undefined : () => setCategoryDeleteTarget(c)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
@@ -51,8 +51,6 @@ type FormState = {
|
||||
priceRub: string
|
||||
imageUrls: string[]
|
||||
published: boolean
|
||||
inStock: boolean
|
||||
leadTimeDays: string
|
||||
categoryId: string
|
||||
}
|
||||
|
||||
@@ -61,13 +59,11 @@ const emptyForm = (): FormState => ({
|
||||
slug: '',
|
||||
shortDescription: '',
|
||||
description: '',
|
||||
quantity: '',
|
||||
quantity: '0',
|
||||
materials: '',
|
||||
priceRub: '',
|
||||
imageUrls: [],
|
||||
published: true,
|
||||
inStock: true,
|
||||
leadTimeDays: '',
|
||||
categoryId: '',
|
||||
})
|
||||
|
||||
@@ -83,7 +79,6 @@ export function AdminProductsPage() {
|
||||
})
|
||||
|
||||
const titleValue = productForm.watch('title')
|
||||
const inStockValue = productForm.watch('inStock')
|
||||
|
||||
const categoriesQuery = useQuery({
|
||||
queryKey: ['categories'],
|
||||
@@ -118,13 +113,11 @@ export function AdminProductsPage() {
|
||||
slug: p.slug,
|
||||
shortDescription: p.shortDescription ?? '',
|
||||
description: p.description ?? '',
|
||||
quantity: p.quantity === null || p.quantity === undefined ? '' : String(p.quantity),
|
||||
quantity: String(p.quantity),
|
||||
materials: (p.materials ?? []).join(', '),
|
||||
priceRub: String(p.priceCents / 100),
|
||||
imageUrls: urls,
|
||||
published: p.published,
|
||||
inStock: p.inStock,
|
||||
leadTimeDays: p.leadTimeDays ? String(p.leadTimeDays) : '',
|
||||
categoryId: p.categoryId,
|
||||
})
|
||||
}
|
||||
@@ -134,14 +127,11 @@ export function AdminProductsPage() {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
const leadTimeDays = form.leadTimeDays.trim() ? Number(form.leadTimeDays) : null
|
||||
if (!form.inStock) {
|
||||
if (!Number.isFinite(leadTimeDays) || !leadTimeDays || leadTimeDays <= 0) {
|
||||
throw new Error('Если "под заказ", укажите срок исполнения (дней) > 0')
|
||||
}
|
||||
}
|
||||
const qty = form.quantity.trim() ? Number(form.quantity) : null
|
||||
if (qty !== null && (!Number.isFinite(qty) || qty < 0)) throw new Error('Некорректное количество')
|
||||
if (!form.categoryId) throw new Error('Выберите категорию')
|
||||
const qty = form.quantity.trim()
|
||||
if (!qty) throw new Error('Укажите количество')
|
||||
const qtyNum = Number(qty)
|
||||
if (!Number.isFinite(qtyNum) || qtyNum < 0) throw new Error('Некорректное количество')
|
||||
const materials = form.materials
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@@ -151,13 +141,11 @@ export function AdminProductsPage() {
|
||||
slug: form.slug.trim() || undefined,
|
||||
shortDescription: form.shortDescription.trim() || null,
|
||||
description: form.description.trim() || null,
|
||||
quantity: qty === null ? null : Math.floor(qty),
|
||||
quantity: Math.floor(qtyNum),
|
||||
materials,
|
||||
priceCents,
|
||||
imageUrls: form.imageUrls,
|
||||
published: form.published,
|
||||
inStock: form.inStock,
|
||||
leadTimeDays: form.inStock ? null : Math.round(leadTimeDays!),
|
||||
categoryId: form.categoryId,
|
||||
})
|
||||
},
|
||||
@@ -172,14 +160,11 @@ export function AdminProductsPage() {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
const leadTimeDays = form.leadTimeDays.trim() ? Number(form.leadTimeDays) : null
|
||||
if (!form.inStock) {
|
||||
if (!Number.isFinite(leadTimeDays) || !leadTimeDays || leadTimeDays <= 0) {
|
||||
throw new Error('Если "под заказ", укажите срок исполнения (дней) > 0')
|
||||
}
|
||||
}
|
||||
const qty = form.quantity.trim() ? Number(form.quantity) : null
|
||||
if (qty !== null && (!Number.isFinite(qty) || qty < 0)) throw new Error('Некорректное количество')
|
||||
if (!form.categoryId) throw new Error('Выберите категорию')
|
||||
const qty = form.quantity.trim()
|
||||
if (!qty) throw new Error('Укажите количество')
|
||||
const qtyNum = Number(qty)
|
||||
if (!Number.isFinite(qtyNum) || qtyNum < 0) throw new Error('Некорректное количество')
|
||||
const materials = form.materials
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@@ -189,13 +174,11 @@ export function AdminProductsPage() {
|
||||
slug: form.slug.trim(),
|
||||
shortDescription: form.shortDescription.trim() || null,
|
||||
description: form.description.trim() || null,
|
||||
quantity: qty === null ? null : Math.floor(qty),
|
||||
quantity: Math.floor(qtyNum),
|
||||
materials,
|
||||
priceCents,
|
||||
imageUrls: form.imageUrls,
|
||||
published: form.published,
|
||||
inStock: form.inStock,
|
||||
leadTimeDays: form.inStock ? null : Math.round(leadTimeDays!),
|
||||
categoryId: form.categoryId,
|
||||
})
|
||||
},
|
||||
@@ -364,13 +347,7 @@ export function AdminProductsPage() {
|
||||
control={productForm.control}
|
||||
name="quantity"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label="Количество"
|
||||
fullWidth
|
||||
{...field}
|
||||
inputMode="numeric"
|
||||
helperText="Оставьте пустым, если не хотите вести учёт"
|
||||
/>
|
||||
<TextField label="Количество" fullWidth {...field} inputMode="numeric" helperText="0 = нет в наличии" />
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
@@ -473,18 +450,16 @@ export function AdminProductsPage() {
|
||||
control={productForm.control}
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth error={!field.value}>
|
||||
<InputLabel id="cat-label">Категория</InputLabel>
|
||||
<Select labelId="cat-label" label="Категория" {...field}>
|
||||
<MenuItem value="">
|
||||
<em>Не указано</em>
|
||||
</MenuItem>
|
||||
{(categoriesQuery.data ?? []).map((c: Category) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{!field.value && <FormHelperText>Выберите категорию</FormHelperText>}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@@ -498,23 +473,6 @@ export function AdminProductsPage() {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="inStock"
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={field.value} onChange={(_, v) => field.onChange(v)} />}
|
||||
label={field.value ? 'В наличии' : 'Под заказ'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{!inStockValue && (
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="leadTimeDays"
|
||||
render={({ field }) => <TextField label="Срок исполнения, дней" fullWidth {...field} />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -522,7 +480,13 @@ export function AdminProductsPage() {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
disabled={!titleValue.trim() || createMut.isPending || updateMut.isPending}
|
||||
disabled={
|
||||
!titleValue.trim() ||
|
||||
!productForm.watch('categoryId') ||
|
||||
!productForm.watch('quantity').trim() ||
|
||||
createMut.isPending ||
|
||||
updateMut.isPending
|
||||
}
|
||||
>
|
||||
{editing ? 'Сохранить' : 'Создать'}
|
||||
</Button>
|
||||
|
||||
@@ -59,8 +59,6 @@ type FormState = {
|
||||
priceRub: string
|
||||
imageUrls: string[]
|
||||
published: boolean
|
||||
inStock: boolean
|
||||
leadTimeDays: string
|
||||
categoryId: string
|
||||
}
|
||||
|
||||
@@ -69,13 +67,11 @@ const emptyForm = (): FormState => ({
|
||||
slug: '',
|
||||
shortDescription: '',
|
||||
description: '',
|
||||
quantity: '',
|
||||
quantity: '0',
|
||||
materials: '',
|
||||
priceRub: '',
|
||||
imageUrls: [],
|
||||
published: true,
|
||||
inStock: true,
|
||||
leadTimeDays: '',
|
||||
categoryId: '',
|
||||
})
|
||||
|
||||
@@ -101,7 +97,6 @@ export function AdminPage() {
|
||||
})
|
||||
|
||||
const titleValue = productForm.watch('title')
|
||||
const inStockValue = productForm.watch('inStock')
|
||||
|
||||
const categoriesQuery = useQuery({
|
||||
queryKey: ['categories'],
|
||||
@@ -147,13 +142,11 @@ export function AdminPage() {
|
||||
slug: p.slug,
|
||||
shortDescription: p.shortDescription ?? '',
|
||||
description: p.description ?? '',
|
||||
quantity: p.quantity === null || p.quantity === undefined ? '' : String(p.quantity),
|
||||
quantity: String(p.quantity),
|
||||
materials: (p.materials ?? []).join(', '),
|
||||
priceRub: String(p.priceCents / 100),
|
||||
imageUrls: urls,
|
||||
published: p.published,
|
||||
inStock: p.inStock,
|
||||
leadTimeDays: p.leadTimeDays ? String(p.leadTimeDays) : '',
|
||||
categoryId: p.categoryId,
|
||||
})
|
||||
}
|
||||
@@ -163,14 +156,11 @@ export function AdminPage() {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
const leadTimeDays = form.leadTimeDays.trim() ? Number(form.leadTimeDays) : null
|
||||
if (!form.inStock) {
|
||||
if (!Number.isFinite(leadTimeDays) || !leadTimeDays || leadTimeDays <= 0) {
|
||||
throw new Error('Если "под заказ", укажите срок исполнения (дней) > 0')
|
||||
}
|
||||
}
|
||||
const qty = form.quantity.trim() ? Number(form.quantity) : null
|
||||
if (qty !== null && (!Number.isFinite(qty) || qty < 0)) throw new Error('Некорректное количество')
|
||||
if (!form.categoryId) throw new Error('Выберите категорию')
|
||||
const qty = form.quantity.trim()
|
||||
if (!qty) throw new Error('Укажите количество')
|
||||
const qtyNum = Number(qty)
|
||||
if (!Number.isFinite(qtyNum) || qtyNum < 0) throw new Error('Некорректное количество')
|
||||
const materials = form.materials
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@@ -180,13 +170,11 @@ export function AdminPage() {
|
||||
slug: form.slug.trim() || undefined,
|
||||
shortDescription: form.shortDescription.trim() || null,
|
||||
description: form.description.trim() || null,
|
||||
quantity: qty === null ? null : Math.floor(qty),
|
||||
quantity: Math.floor(qtyNum),
|
||||
materials,
|
||||
priceCents,
|
||||
imageUrls: form.imageUrls,
|
||||
published: form.published,
|
||||
inStock: form.inStock,
|
||||
leadTimeDays: form.inStock ? null : Math.round(leadTimeDays!),
|
||||
categoryId: form.categoryId,
|
||||
})
|
||||
},
|
||||
@@ -201,14 +189,11 @@ export function AdminPage() {
|
||||
const form = productForm.getValues()
|
||||
const priceCents = Math.round(Number(form.priceRub.replace(',', '.')) * 100)
|
||||
if (!Number.isFinite(priceCents) || priceCents < 0) throw new Error('Некорректная цена')
|
||||
const leadTimeDays = form.leadTimeDays.trim() ? Number(form.leadTimeDays) : null
|
||||
if (!form.inStock) {
|
||||
if (!Number.isFinite(leadTimeDays) || !leadTimeDays || leadTimeDays <= 0) {
|
||||
throw new Error('Если "под заказ", укажите срок исполнения (дней) > 0')
|
||||
}
|
||||
}
|
||||
const qty = form.quantity.trim() ? Number(form.quantity) : null
|
||||
if (qty !== null && (!Number.isFinite(qty) || qty < 0)) throw new Error('Некорректное количество')
|
||||
if (!form.categoryId) throw new Error('Выберите категорию')
|
||||
const qty = form.quantity.trim()
|
||||
if (!qty) throw new Error('Укажите количество')
|
||||
const qtyNum = Number(qty)
|
||||
if (!Number.isFinite(qtyNum) || qtyNum < 0) throw new Error('Некорректное количество')
|
||||
const materials = form.materials
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@@ -218,13 +203,11 @@ export function AdminPage() {
|
||||
slug: form.slug.trim(),
|
||||
shortDescription: form.shortDescription.trim() || null,
|
||||
description: form.description.trim() || null,
|
||||
quantity: qty === null ? null : Math.floor(qty),
|
||||
quantity: Math.floor(qtyNum),
|
||||
materials,
|
||||
priceCents,
|
||||
imageUrls: form.imageUrls,
|
||||
published: form.published,
|
||||
inStock: form.inStock,
|
||||
leadTimeDays: form.inStock ? null : Math.round(leadTimeDays!),
|
||||
categoryId: form.categoryId,
|
||||
})
|
||||
},
|
||||
@@ -517,13 +500,7 @@ export function AdminPage() {
|
||||
control={productForm.control}
|
||||
name="quantity"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label="Количество"
|
||||
fullWidth
|
||||
{...field}
|
||||
inputMode="numeric"
|
||||
helperText="Оставьте пустым, если не хотите вести учёт"
|
||||
/>
|
||||
<TextField label="Количество" fullWidth {...field} inputMode="numeric" helperText="0 = нет в наличии" />
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
@@ -626,18 +603,16 @@ export function AdminPage() {
|
||||
control={productForm.control}
|
||||
name="categoryId"
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth>
|
||||
<FormControl fullWidth error={!field.value}>
|
||||
<InputLabel id="cat-label">Категория</InputLabel>
|
||||
<Select labelId="cat-label" label="Категория" {...field}>
|
||||
<MenuItem value="">
|
||||
<em>Не указано</em>
|
||||
</MenuItem>
|
||||
{(categoriesQuery.data ?? []).map((c) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{!field.value && <FormHelperText>Выберите категорию</FormHelperText>}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@@ -651,23 +626,6 @@ export function AdminPage() {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="inStock"
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={field.value} onChange={(_, v) => field.onChange(v)} />}
|
||||
label={field.value ? 'В наличии' : 'Под заказ'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{!inStockValue && (
|
||||
<Controller
|
||||
control={productForm.control}
|
||||
name="leadTimeDays"
|
||||
render={({ field }) => <TextField label="Срок исполнения, дней" fullWidth {...field} />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -675,7 +633,13 @@ export function AdminPage() {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
disabled={!titleValue.trim() || createMut.isPending || updateMut.isPending}
|
||||
disabled={
|
||||
!titleValue.trim() ||
|
||||
!productForm.watch('categoryId') ||
|
||||
!productForm.watch('quantity').trim() ||
|
||||
createMut.isPending ||
|
||||
updateMut.isPending
|
||||
}
|
||||
>
|
||||
{editing ? 'Сохранить' : 'Создать'}
|
||||
</Button>
|
||||
|
||||
@@ -63,7 +63,7 @@ export function CartPage() {
|
||||
<Stack spacing={2}>
|
||||
{items.map((x) =>
|
||||
(() => {
|
||||
const available = x.product.inStock ? x.product.quantity : 1
|
||||
const available = x.product.quantity
|
||||
const canInc = x.qty < available
|
||||
const over = x.qty > available
|
||||
return (
|
||||
@@ -83,9 +83,9 @@ export function CartPage() {
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
{formatPriceRub(x.product.priceCents)} · {x.qty} шт. · Доступно: {available}
|
||||
</Typography>
|
||||
{!x.product.inStock && (
|
||||
{x.product.quantity === 0 && (
|
||||
<Typography color="text.secondary" variant="caption">
|
||||
Под заказ — доставка после изготовления
|
||||
Нет в наличии
|
||||
</Typography>
|
||||
)}
|
||||
{over && (
|
||||
|
||||
@@ -80,8 +80,7 @@ export function CheckoutPage() {
|
||||
const deliveryFeeCents = deliveryType === 'delivery' && items.length > 0 ? 50000 : 0
|
||||
const total = itemsSubtotalCents + deliveryFeeCents
|
||||
const addresses = addressesQuery.data?.items ?? []
|
||||
const hasOverLimit = items.some((x) => x.qty > (x.product.inStock ? x.product.quantity : 1))
|
||||
const hasMadeToOrder = items.some((x) => !x.product.inStock)
|
||||
const hasOverLimit = items.some((x) => x.qty > x.product.quantity)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -105,7 +104,7 @@ export function CheckoutPage() {
|
||||
<Typography sx={{ fontWeight: 700, mb: 1 }}>Позиции</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
{items.map((x) => {
|
||||
const available = x.product.inStock ? x.product.quantity : 1
|
||||
const available = x.product.quantity
|
||||
const over = x.qty > available
|
||||
return (
|
||||
<Typography key={x.id} color={over ? 'error' : 'text.primary'}>
|
||||
@@ -127,12 +126,6 @@ export function CheckoutPage() {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasMadeToOrder && (
|
||||
<Alert severity="info">
|
||||
В заказе есть товары «под заказ». Доставка будет после изготовления (срок указан в карточке товара).
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<FormControl size="small" fullWidth>
|
||||
<InputLabel id="delivery-type-label">Способ получения</InputLabel>
|
||||
<Select
|
||||
|
||||
@@ -5,7 +5,6 @@ export type UseProductFiltersResult = ReturnType<typeof useProductFilters>
|
||||
|
||||
export function useProductFilters() {
|
||||
const [categorySlug, setCategorySlug] = useState<string>('')
|
||||
const [availability, setAvailability] = useState<'all' | 'in_stock' | 'made_to_order'>('all')
|
||||
const [qInput, setQInput] = useState('')
|
||||
const [q, setQ] = useState('')
|
||||
const [moreOpen, setMoreOpen] = useState(false)
|
||||
@@ -45,13 +44,6 @@ export function useProductFilters() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAvailabilityChange = (v: string) => {
|
||||
if (v === 'all' || v === 'in_stock' || v === 'made_to_order') {
|
||||
setAvailability(v)
|
||||
setPage(1)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePriceMinChange = (v: string) => {
|
||||
setPriceMinRub(v)
|
||||
setPage(1)
|
||||
@@ -68,7 +60,6 @@ export function useProductFilters() {
|
||||
|
||||
const resetFilters = () => {
|
||||
setCategorySlug('')
|
||||
setAvailability('all')
|
||||
setQInput('')
|
||||
setSort('')
|
||||
setPriceMinRub('')
|
||||
@@ -85,7 +76,6 @@ export function useProductFilters() {
|
||||
|
||||
return {
|
||||
categorySlug,
|
||||
availability,
|
||||
qInput,
|
||||
q,
|
||||
moreOpen,
|
||||
@@ -101,7 +91,6 @@ export function useProductFilters() {
|
||||
handleCategoryChange,
|
||||
handleSortChange,
|
||||
handlePageSizeChange,
|
||||
handleAvailabilityChange,
|
||||
handlePriceMinChange,
|
||||
handlePriceMaxChange,
|
||||
handleCardScaleChange,
|
||||
|
||||
@@ -33,7 +33,6 @@ export function HomePage() {
|
||||
'public',
|
||||
{
|
||||
categorySlug: filters.categorySlug || 'all',
|
||||
availability: filters.availability,
|
||||
q: filters.q,
|
||||
sort: filters.sort,
|
||||
page: filters.page,
|
||||
@@ -45,7 +44,6 @@ export function HomePage() {
|
||||
queryFn: () =>
|
||||
fetchPublicProducts({
|
||||
categorySlug: filters.categorySlug || undefined,
|
||||
availability: filters.availability === 'all' ? undefined : filters.availability,
|
||||
q: filters.q || undefined,
|
||||
sort: filters.sort || '',
|
||||
page: filters.page,
|
||||
@@ -117,9 +115,7 @@ export function HomePage() {
|
||||
<ProductCard
|
||||
product={p}
|
||||
mediaHeight={mediaHeight}
|
||||
actions={
|
||||
!isAdmin && !(p.inStock && p.quantity === 0) ? <ToggleCartIcon productId={p.id} /> : undefined
|
||||
}
|
||||
actions={!isAdmin && p.quantity > 0 ? <ToggleCartIcon productId={p.id} /> : undefined}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
@@ -26,7 +26,6 @@ type Props = UseProductFiltersResult & {
|
||||
|
||||
export function ProductFilters({
|
||||
categorySlug,
|
||||
availability,
|
||||
qInput,
|
||||
moreOpen,
|
||||
sort,
|
||||
@@ -41,19 +40,14 @@ export function ProductFilters({
|
||||
handleCategoryChange,
|
||||
handleSortChange,
|
||||
handlePageSizeChange,
|
||||
handleAvailabilityChange,
|
||||
handlePriceMinChange,
|
||||
handlePriceMaxChange,
|
||||
handleCardScaleChange,
|
||||
resetFilters,
|
||||
}: Props) {
|
||||
const categoriesForFilter = useMemo(() => {
|
||||
const list = categories ?? []
|
||||
return [...list].sort((a, b) => {
|
||||
if (a.slug === 'ne-ukazano') return 1
|
||||
if (b.slug === 'ne-ukazano') return -1
|
||||
return a.sort - b.sort || a.name.localeCompare(b.name, 'ru')
|
||||
})
|
||||
const list = (categories ?? []).filter((c) => c.slug !== 'ne-ukazano')
|
||||
return [...list].sort((a, b) => a.sort - b.sort || a.name.localeCompare(b.name, 'ru'))
|
||||
}, [categories])
|
||||
|
||||
return (
|
||||
@@ -124,26 +118,6 @@ export function ProductFilters({
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ToggleButtonGroup
|
||||
exclusive
|
||||
size="small"
|
||||
value={availability}
|
||||
onChange={(_, v) => handleAvailabilityChange(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="all">Все</ToggleButton>
|
||||
<ToggleButton value="in_stock">В наличии</ToggleButton>
|
||||
<ToggleButton value="made_to_order">Под заказ</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Paper>
|
||||
|
||||
<Stack
|
||||
|
||||
@@ -131,7 +131,8 @@ export function ProductPage() {
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{p.category?.name && <Chip label={p.category.name} />}
|
||||
<Chip label={p.inStock ? 'В наличии' : `Под заказ · ${p.leadTimeDays ?? '—'} дн.`} color="default" />
|
||||
{p.quantity > 0 && <Chip label="В наличии" color="success" />}
|
||||
{p.quantity === 0 && <Chip label="Нет в наличии" color="default" />}
|
||||
</Box>
|
||||
|
||||
{(p.materials?.length ?? 0) > 0 && (
|
||||
@@ -154,13 +155,7 @@ export function ProductPage() {
|
||||
{formatPriceRub(p.priceCents)}
|
||||
</Typography>
|
||||
|
||||
{!isAdmin && !(p.inStock && p.quantity === 0) ? <ToggleCartIcon productId={p.id} size="medium" /> : null}
|
||||
|
||||
{!p.inStock && (
|
||||
<Alert severity="info">
|
||||
Этот товар изготавливается под заказ. Доставка будет после изготовления (~{p.leadTimeDays ?? '—'} дн.).
|
||||
</Alert>
|
||||
)}
|
||||
{!isAdmin && p.quantity > 0 ? <ToggleCartIcon productId={p.id} size="medium" /> : null}
|
||||
|
||||
{p.description ? (
|
||||
<Typography sx={{ whiteSpace: 'pre-wrap' }}>{p.description}</Typography>
|
||||
|
||||
Reference in New Issue
Block a user