Improve variant management and fix callback handling
- Fix refetch after category deletion in List component - Fix variant mutations callbacks to ensure proper data refresh in useVariantLogic - Update ProductVariant to correctly display color name and meterage value - Add support for no-variant theme (noColor_noSize) in CreateVariant - Add ColorType and MeterageType definitions to Types - Prevent creating additional variants for products without variant theme
This commit is contained in:
@@ -14,12 +14,15 @@ import { extractErrorMessage } from '@/helpers/utils'
|
|||||||
const CategoryList: FC = () => {
|
const CategoryList: FC = () => {
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isLoading } = useGetCategories();
|
const { data, isLoading, refetch } = useGetCategories();
|
||||||
const deleteCategoryMutation = useDeleteCategory();
|
const deleteCategoryMutation = useDeleteCategory();
|
||||||
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
|
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const handleDeleteCategory = async (categoryId: string) => {
|
const handleDeleteCategory = async (categoryId: string) => {
|
||||||
await deleteCategoryMutation.mutateAsync(categoryId, {
|
await deleteCategoryMutation.mutateAsync(categoryId, {
|
||||||
|
onSuccess: () => {
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(extractErrorMessage(error));
|
toast.error(extractErrorMessage(error));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,12 +106,14 @@ export const useVariantLogic = ({ id }: UseVariantLogicProps) => {
|
|||||||
const mutation = hasExistingVariants ? updateVariantMutation : createVariantMutation
|
const mutation = hasExistingVariants ? updateVariantMutation : createVariantMutation
|
||||||
|
|
||||||
mutation.mutate(variantData, {
|
mutation.mutate(variantData, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
||||||
|
resetModal()
|
||||||
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(extractErrorMessage(error))
|
toast.error(extractErrorMessage(error))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
resetModal()
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteVariant = async (variantType: 'sizes' | 'colors' | 'meterages', index: number) => {
|
const deleteVariant = async (variantType: 'sizes' | 'colors' | 'meterages', index: number) => {
|
||||||
@@ -140,8 +142,14 @@ export const useVariantLogic = ({ id }: UseVariantLogicProps) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateVariantMutation.mutateAsync({ ...variantData, theme: theme! })
|
await updateVariantMutation.mutateAsync({ ...variantData, theme: theme! }, {
|
||||||
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(extractErrorMessage(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const ProductVariant: FC = () => {
|
|||||||
const getVariantValue = (variant: ProductVariantDetailType) => {
|
const getVariantValue = (variant: ProductVariantDetailType) => {
|
||||||
switch (categoryTheme) {
|
switch (categoryTheme) {
|
||||||
case 'Size': return variant.size?.value || '-'
|
case 'Size': return variant.size?.value || '-'
|
||||||
case 'Color': return 'رنگ' // TODO: باید نام رنگ را نمایش دهیم
|
case 'Color': return variant.color?.name || '-'
|
||||||
case 'Meterage': return '-' // TODO: باید فیلد meterage اضافه شود
|
case 'Meterage': return variant.meterage?.value || '-'
|
||||||
default: return '-'
|
default: return '-'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Input from '@/components/Input'
|
|||||||
import Select from '@/components/Select'
|
import Select from '@/components/Select'
|
||||||
import { useState, type FC } from 'react'
|
import { useState, type FC } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useCreateProductVariant, useGetProductById } from '../hooks/useProductData'
|
import { useCreateProductVariant, useGetProductById, useGetProductVariants } from '../hooks/useProductData'
|
||||||
import { useGetCategoryVariants } from '@/pages/category'
|
import { useGetCategoryVariants } from '@/pages/category'
|
||||||
import type { CreateProductVariantRequestType } from '../types/Types'
|
import type { CreateProductVariantRequestType } from '../types/Types'
|
||||||
import { useGetWarranties } from '@/pages/warranty/hooks/useWarrantyData'
|
import { useGetWarranties } from '@/pages/warranty/hooks/useWarrantyData'
|
||||||
@@ -13,6 +13,7 @@ import { extractErrorMessage } from '@/helpers/utils'
|
|||||||
import { Formik, Form, Field, ErrorMessage } from 'formik'
|
import { Formik, Form, Field, ErrorMessage } from 'formik'
|
||||||
import type { FieldInputProps } from 'formik'
|
import type { FieldInputProps } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
interface FormikFieldProps {
|
interface FormikFieldProps {
|
||||||
field: FieldInputProps<string | number>
|
field: FieldInputProps<string | number>
|
||||||
@@ -21,9 +22,11 @@ interface FormikFieldProps {
|
|||||||
const CreateVariant: FC = () => {
|
const CreateVariant: FC = () => {
|
||||||
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const { data: productData } = useGetProductById(id!)
|
const { data: productData } = useGetProductById(id!)
|
||||||
const { data: categoryData } = useGetCategoryVariants(productData?.results?.product?.category?._id ?? '')
|
const { data: categoryData } = useGetCategoryVariants(productData?.results?.product?.category?._id ?? '')
|
||||||
|
const { data: variantsData, refetch: refetchVariants } = useGetProductVariants(id!)
|
||||||
const createProductVariant = useCreateProductVariant()
|
const createProductVariant = useCreateProductVariant()
|
||||||
const { data: warrantiesData } = useGetWarranties()
|
const { data: warrantiesData } = useGetWarranties()
|
||||||
|
|
||||||
@@ -31,9 +34,14 @@ const CreateVariant: FC = () => {
|
|||||||
|
|
||||||
const categoryTheme = productData?.results?.product?.category?.theme
|
const categoryTheme = productData?.results?.product?.category?.theme
|
||||||
const categoryVariants = categoryData?.results?.data?.category?.categoryVariants
|
const categoryVariants = categoryData?.results?.data?.category?.categoryVariants
|
||||||
|
const isNoVariantTheme = categoryTheme === 'noColor_noSize'
|
||||||
|
const existingVariantsCount = variantsData?.results?.count || 0
|
||||||
|
const canCreateVariant = !isNoVariantTheme || existingVariantsCount === 0
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
variantId: Yup.string().required('انتخاب تنوع الزامی است'),
|
variantId: isNoVariantTheme
|
||||||
|
? Yup.string().optional()
|
||||||
|
: Yup.string().required('انتخاب تنوع الزامی است'),
|
||||||
warrantyId: Yup.string().required('انتخاب گارانتی الزامی است'),
|
warrantyId: Yup.string().required('انتخاب گارانتی الزامی است'),
|
||||||
order_limit: Yup.number().required('محدودیت سفارش الزامی است').min(1, 'حداقل ۱ عدد'),
|
order_limit: Yup.number().required('محدودیت سفارش الزامی است').min(1, 'حداقل ۱ عدد'),
|
||||||
sellerSpecialCode: Yup.string().required('کد ویژه فروشنده الزامی است'),
|
sellerSpecialCode: Yup.string().required('کد ویژه فروشنده الزامی است'),
|
||||||
@@ -116,7 +124,10 @@ const CreateVariant: FC = () => {
|
|||||||
// اضافه کردن variant بر اساس theme
|
// اضافه کردن variant بر اساس theme
|
||||||
let variantData: CreateProductVariantRequestType
|
let variantData: CreateProductVariantRequestType
|
||||||
|
|
||||||
if (categoryTheme === 'Size') {
|
if (isNoVariantTheme) {
|
||||||
|
// در حالت noColor_noSize هیچ variantId ارسال نمیشود
|
||||||
|
variantData = baseData
|
||||||
|
} else if (categoryTheme === 'Size') {
|
||||||
variantData = { ...baseData, sizeId: parseInt(values.variantId) }
|
variantData = { ...baseData, sizeId: parseInt(values.variantId) }
|
||||||
} else if (categoryTheme === 'Color') {
|
} else if (categoryTheme === 'Color') {
|
||||||
// برای رنگها از index به عنوان ID استفاده میکنیم
|
// برای رنگها از index به عنوان ID استفاده میکنیم
|
||||||
@@ -131,6 +142,10 @@ const CreateVariant: FC = () => {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setShowModal(false)
|
setShowModal(false)
|
||||||
toast.success('تنوع جدید با موفقیت ایجاد شد')
|
toast.success('تنوع جدید با موفقیت ایجاد شد')
|
||||||
|
// Refetch لیست تنوعها
|
||||||
|
refetchVariants()
|
||||||
|
// یا میتوانید از invalidateQueries استفاده کنید
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['product-variants', id] })
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(extractErrorMessage(error))
|
toast.error(extractErrorMessage(error))
|
||||||
@@ -140,7 +155,19 @@ const CreateVariant: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-end mt-5'>
|
<div className='flex justify-end mt-5'>
|
||||||
<Button className='w-fit' label='ساخت تنوع جدید' onClick={() => setShowModal(true)} />
|
{!canCreateVariant && (
|
||||||
|
<div className="flex-1 p-3 bg-yellow-50 border border-yellow-200 rounded-lg ml-4">
|
||||||
|
<p className="text-sm text-yellow-800">
|
||||||
|
این محصول تنوع ندارد و قبلاً یک نسخه ایجاد شده است. امکان ایجاد تنوع جدید وجود ندارد.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className='w-fit'
|
||||||
|
label='ساخت تنوع جدید'
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
disabled={!canCreateVariant}
|
||||||
|
/>
|
||||||
|
|
||||||
<DefaulModal
|
<DefaulModal
|
||||||
open={showModal}
|
open={showModal}
|
||||||
@@ -156,24 +183,35 @@ const CreateVariant: FC = () => {
|
|||||||
>
|
>
|
||||||
{({ setFieldValue }) => (
|
{({ setFieldValue }) => (
|
||||||
<Form className="space-y-4 w-[500px] mt-4">
|
<Form className="space-y-4 w-[500px] mt-4">
|
||||||
{/* انتخاب variant */}
|
{/* انتخاب variant - فقط در صورتی که theme نوع noColor_noSize نباشد */}
|
||||||
<div>
|
{!isNoVariantTheme && (
|
||||||
<label className="text-sm font-medium text-gray-700 mb-1 block">
|
<div>
|
||||||
{getVariantLabel()}
|
<label className="text-sm font-medium text-gray-700 mb-1 block">
|
||||||
</label>
|
{getVariantLabel()}
|
||||||
<Field name="variantId">
|
</label>
|
||||||
{({ field }: FormikFieldProps) => (
|
<Field name="variantId">
|
||||||
<Select
|
{({ field }: FormikFieldProps) => (
|
||||||
{...field}
|
<Select
|
||||||
placeholder={`انتخاب ${getVariantLabel().toLowerCase()}`}
|
{...field}
|
||||||
value={field.value}
|
placeholder={`انتخاب ${getVariantLabel().toLowerCase()}`}
|
||||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setFieldValue('variantId', e.target.value)}
|
value={field.value}
|
||||||
items={getVariantOptions()}
|
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setFieldValue('variantId', e.target.value)}
|
||||||
/>
|
items={getVariantOptions()}
|
||||||
)}
|
/>
|
||||||
</Field>
|
)}
|
||||||
<ErrorMessage name="variantId" component="div" className="text-red-500 text-sm mt-1" />
|
</Field>
|
||||||
</div>
|
<ErrorMessage name="variantId" component="div" className="text-red-500 text-sm mt-1" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* پیام اطلاعرسانی برای محصولات بدون تنوع */}
|
||||||
|
{isNoVariantTheme && (
|
||||||
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
|
<p className="text-sm text-blue-800">
|
||||||
|
این محصول تنوع ندارد. فقط میتوانید یک نسخه از محصول ایجاد کنید.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* اطلاعات پایه */}
|
{/* اطلاعات پایه */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
|||||||
@@ -212,6 +212,17 @@ export type SizeType = {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ColorType = {
|
||||||
|
_id: number;
|
||||||
|
name: string;
|
||||||
|
hexColor: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MeterageType = {
|
||||||
|
_id: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProductVariantDetailType = {
|
export type ProductVariantDetailType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
@@ -230,7 +241,9 @@ export type ProductVariantDetailType = {
|
|||||||
product_status: string;
|
product_status: string;
|
||||||
isFreeShip: boolean;
|
isFreeShip: boolean;
|
||||||
isWholeSale: boolean;
|
isWholeSale: boolean;
|
||||||
size: SizeType;
|
size?: SizeType;
|
||||||
|
color?: ColorType;
|
||||||
|
meterage?: MeterageType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetProductVariantsResponseType = {
|
export type GetProductVariantsResponseType = {
|
||||||
|
|||||||
Reference in New Issue
Block a user