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:
hamid zarghami
2025-10-26 12:14:02 +03:30
parent 1e48d12340
commit 2343bc8d6f
5 changed files with 92 additions and 30 deletions
+4 -1
View File
@@ -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));
} }
+12 -4
View File
@@ -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 {
+2 -2
View File
@@ -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 '-'
} }
} }
+60 -22
View File
@@ -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">
+14 -1
View File
@@ -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 = {