diff --git a/src/pages/discount-codes/core/_models.ts b/src/pages/discount-codes/core/_models.ts index 0584f25..7eef745 100644 --- a/src/pages/discount-codes/core/_models.ts +++ b/src/pages/discount-codes/core/_models.ts @@ -7,6 +7,10 @@ export type DiscountApplicationLevel = | "shipping" | "product_fee"; +export type DiscountApplicationLevels = + | DiscountApplicationLevel + | DiscountApplicationLevel[]; + export type DiscountStatus = "active" | "inactive"; export type UserGroup = "new" | "loyal" | "all"; @@ -42,7 +46,7 @@ export interface DiscountCode { type: DiscountCodeType; value: number; status: DiscountStatus; - application_level: DiscountApplicationLevel; + application_level: DiscountApplicationLevels; min_purchase_amount?: number; max_discount_amount?: number; usage_limit?: number; @@ -53,8 +57,8 @@ export interface DiscountCode { user_restrictions?: DiscountUserRestrictions; stepped_discount?: SteppedDiscount; meta?: DiscountMeta; - specific_product_ids?: number[]; - specific_category_ids?: number[]; + product_ids?: number[]; + category_ids?: number[]; created_at?: string; updated_at?: string; } @@ -64,7 +68,7 @@ export interface DiscountCodeFilters { limit?: number; status?: DiscountStatus; type?: DiscountCodeType; - application_level?: DiscountApplicationLevel; + application_level?: DiscountApplicationLevels; code?: string; active_only?: boolean; } @@ -76,7 +80,7 @@ export interface CreateDiscountCodeRequest { type: DiscountCodeType; value: number; status: DiscountStatus; - application_level: DiscountApplicationLevel; + application_level: DiscountApplicationLevels; min_purchase_amount?: number; max_discount_amount?: number; usage_limit?: number; @@ -87,8 +91,8 @@ export interface CreateDiscountCodeRequest { user_restrictions?: DiscountUserRestrictions; stepped_discount?: SteppedDiscount; meta?: DiscountMeta; - specific_product_ids?: number[]; - specific_category_ids?: number[]; + product_ids?: number[]; + category_ids?: number[]; } export interface UpdateDiscountCodeRequest diff --git a/src/pages/discount-codes/core/_requests.ts b/src/pages/discount-codes/core/_requests.ts index e0eef13..fdcc9a0 100644 --- a/src/pages/discount-codes/core/_requests.ts +++ b/src/pages/discount-codes/core/_requests.ts @@ -21,8 +21,15 @@ export const getDiscountCodes = async (filters?: DiscountCodeFilters) => { if (filters?.limit) queryParams.limit = filters.limit; if (filters?.status) queryParams.status = filters.status; if (filters?.type) queryParams.type = filters.type; - if (filters?.application_level) - queryParams.application_level = filters.application_level; + if (filters?.application_level) { + if (Array.isArray(filters.application_level)) { + if (filters.application_level.length > 0) { + queryParams.application_level = filters.application_level.join(","); + } + } else { + queryParams.application_level = filters.application_level; + } + } if (filters?.code) queryParams.code = filters.code; if (typeof filters?.active_only === "boolean") queryParams.active_only = filters.active_only ? "true" : "false"; diff --git a/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx b/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx index d694d82..6a7477e 100644 --- a/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx +++ b/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx @@ -26,21 +26,24 @@ const schema = yup.object({ .transform((val, original) => parseFormattedNumber(original) as any) .typeError('مقدار نامعتبر است') .required('مقدار الزامی است') - .min(0.01, 'مقدار باید بیشتر از صفر باشد'), + .min(0.01, 'مقدار باید بیشتر از صفر باشد') + .max(999999, 'مقدار نباید بیشتر از ۹۹۹,۹۹۹ باشد'), status: yup.mixed<'active' | 'inactive'>().oneOf(['active', 'inactive']).required('وضعیت الزامی است'), - application_level: yup.mixed<'invoice' | 'category' | 'product' | 'shipping' | 'product_fee'>().oneOf(['invoice', 'category', 'product', 'shipping', 'product_fee']).required('سطح اعمال الزامی است'), + application_level: yup.array().of(yup.mixed<'invoice' | 'category' | 'product' | 'shipping' | 'product_fee'>().oneOf(['invoice', 'category', 'product', 'shipping', 'product_fee'])).min(1, 'حداقل یک سطح اعمال انتخاب کنید').required('سطح اعمال الزامی است'), min_purchase_amount: yup .number() .transform((val, original) => parseFormattedNumber(original) as any) .min(0.01, 'مبلغ باید بیشتر از صفر باشد') + .max(99999999, 'مبلغ نباید بیشتر از ۹۹,۹۹۹,۹۹۹ باشد') .nullable(), max_discount_amount: yup .number() .transform((val, original) => parseFormattedNumber(original) as any) .min(0.01, 'مبلغ باید بیشتر از صفر باشد') + .max(99999999, 'مبلغ نباید بیشتر از ۹۹,۹۹۹,۹۹۹ باشد') .nullable(), - usage_limit: yup.number().transform((v, o) => o === '' ? undefined : v).min(1, 'حداقل ۱ بار استفاده').nullable(), - user_usage_limit: yup.number().transform((v, o) => o === '' ? undefined : v).min(1, 'حداقل ۱ بار استفاده').nullable(), + usage_limit: yup.number().transform((v, o) => o === '' ? undefined : v).min(1, 'حداقل ۱ بار استفاده').max(999999, 'حداکثر ۹۹۹,۹۹۹ بار استفاده').nullable(), + user_usage_limit: yup.number().transform((v, o) => o === '' ? undefined : v).min(1, 'حداقل ۱ بار استفاده').max(99999, 'حداکثر ۹۹,۹۹۹ بار استفاده').nullable(), single_use: yup.boolean().required('این فیلد الزامی است'), valid_from: yup.string().nullable(), valid_to: yup.string().nullable(), @@ -77,6 +80,7 @@ const DiscountCodeFormPage = () => { const [selectedUserIds, setSelectedUserIds] = useState([]); const [selectedProductIds, setSelectedProductIds] = useState([]); const [selectedCategoryIds, setSelectedCategoryIds] = useState([]); + const [selectedApplicationLevels, setSelectedApplicationLevels] = useState(['invoice']); const { data: dc, isLoading: dcLoading } = useDiscountCode(id || ''); const { mutate: create, isPending: creating } = useCreateDiscountCode(); @@ -176,7 +180,7 @@ const DiscountCodeFormPage = () => { const { register, handleSubmit, formState: { errors, isValid }, reset, watch } = useForm({ resolver: yupResolver(schema as any), mode: 'onChange', - defaultValues: { status: 'active', type: 'percentage', application_level: 'invoice', single_use: false } + defaultValues: { status: 'active', type: 'percentage', application_level: ['invoice'], single_use: false } }); const applicationLevel = watch('application_level'); @@ -207,14 +211,23 @@ const DiscountCodeFormPage = () => { setSelectedUserIds(dc.user_restrictions.user_ids); } + // Set selected application levels + if (dc.application_level) { + setSelectedApplicationLevels( + Array.isArray(dc.application_level) + ? dc.application_level + : [dc.application_level] + ); + } + // Set selected product IDs - if (dc.specific_product_ids) { - setSelectedProductIds(dc.specific_product_ids); + if (dc.product_ids) { + setSelectedProductIds(dc.product_ids); } // Set selected category IDs - if (dc.specific_category_ids) { - setSelectedCategoryIds(dc.specific_category_ids); + if (dc.category_ids) { + setSelectedCategoryIds(dc.category_ids); } } }, [isEdit, dc, reset]); @@ -225,14 +238,17 @@ const DiscountCodeFormPage = () => { const formData: CreateDiscountCodeRequest = { ...data, + application_level: selectedApplicationLevels.length === 1 + ? selectedApplicationLevels[0] as any + : selectedApplicationLevels as any, valid_from: toApiDateTime(data.valid_from), valid_to: toApiDateTime(data.valid_to), - user_restrictions: { + user_restrictions: selectedUserIds.length > 0 ? { ...cleanRestrictions, - user_ids: selectedUserIds.length > 0 ? selectedUserIds : undefined, - }, - specific_product_ids: selectedProductIds.length > 0 ? selectedProductIds : undefined, - specific_category_ids: selectedCategoryIds.length > 0 ? selectedCategoryIds : undefined, + user_ids: selectedUserIds, + } : cleanRestrictions.user_group ? cleanRestrictions : undefined, + product_ids: selectedProductIds.length > 0 ? selectedProductIds : undefined, + category_ids: selectedCategoryIds.length > 0 ? selectedCategoryIds : undefined, }; if (isEdit && id) { @@ -337,7 +353,7 @@ const DiscountCodeFormPage = () => { label="مقدار تخفیف" type="number" step="0.01" - placeholder="300000" + placeholder="حداکثر ۹۹۹,۹۹۹" error={errors.value?.message as string} thousandSeparator numeric @@ -358,73 +374,223 @@ const DiscountCodeFormPage = () => { {errors.status &&

{errors.status.message as string}

} -
- - - {errors.application_level &&

{errors.application_level.message as string}

} +
+ +
+ {[ + { + value: 'invoice', + label: 'کل سبد خرید', + description: 'تخفیف روی کل مبلغ سبد خرید', + icon: '🛒', + color: 'blue' + }, + { + value: 'category', + label: 'دسته‌بندی خاص', + description: 'تخفیف روی محصولات دسته‌بندی‌های خاص', + icon: '📁', + color: 'green' + }, + { + value: 'product', + label: 'محصول خاص', + description: 'تخفیف روی محصولات خاص', + icon: '📦', + color: 'purple' + }, + { + value: 'shipping', + label: 'هزینه ارسال', + description: 'تخفیف روی هزینه ارسال', + icon: '🚚', + color: 'orange' + }, + { + value: 'product_fee', + label: 'کارمزد محصول', + description: 'تخفیف روی کارمزد محصول', + icon: '💰', + color: 'red' + } + ].map(option => { + const isSelected = selectedApplicationLevels.includes(option.value); + + const getColorClasses = (color: string, selected: boolean) => { + const baseClasses = 'relative flex flex-col p-4 border-2 rounded-xl cursor-pointer transition-all duration-200 hover:shadow-lg transform hover:-translate-y-1'; + + if (selected) { + switch (color) { + case 'blue': + return `${baseClasses} border-blue-500 bg-blue-50 dark:bg-blue-900/30 shadow-lg`; + case 'green': + return `${baseClasses} border-green-500 bg-green-50 dark:bg-green-900/30 shadow-lg`; + case 'purple': + return `${baseClasses} border-purple-500 bg-purple-50 dark:bg-purple-900/30 shadow-lg`; + case 'orange': + return `${baseClasses} border-orange-500 bg-orange-50 dark:bg-orange-900/30 shadow-lg`; + case 'red': + return `${baseClasses} border-red-500 bg-red-50 dark:bg-red-900/30 shadow-lg`; + default: + return `${baseClasses} border-gray-500 bg-gray-50 dark:bg-gray-900/30 shadow-lg`; + } + } else { + return `${baseClasses} border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-500`; + } + }; + + const getIndicatorClasses = (color: string, selected: boolean) => { + const baseClasses = 'absolute top-3 left-3 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all'; + + if (selected) { + switch (color) { + case 'blue': + return `${baseClasses} border-blue-500 bg-blue-500`; + case 'green': + return `${baseClasses} border-green-500 bg-green-500`; + case 'purple': + return `${baseClasses} border-purple-500 bg-purple-500`; + case 'orange': + return `${baseClasses} border-orange-500 bg-orange-500`; + case 'red': + return `${baseClasses} border-red-500 bg-red-500`; + default: + return `${baseClasses} border-gray-500 bg-gray-500`; + } + } else { + return `${baseClasses} border-gray-300 dark:border-gray-600`; + } + }; + + return ( + + ); + })} +
+ {errors.application_level &&

{errors.application_level.message as string}

}
{/* Conditional Product Selection */} - {applicationLevel === 'product' && ( + {selectedApplicationLevels.includes('product') && (
- { setProductSearch(q); setProductPage(1); }} - onLoadMore={() => { - if (!productsLoading && productSearchResult && (productSearchResult.total > accumulatedProducts.length)) { - setProductPage(prev => prev + 1); - } - }} - hasMore={!!productSearchResult && accumulatedProducts.length < (productSearchResult.total || 0)} - loadingMore={productsLoading && productPage > 1} - /> -

- در صورت انتخاب محصولات، تخفیف فقط روی این محصولات اعمال خواهد شد. -

+
+
+
+ 📦 +
+
+

انتخاب محصولات خاص

+

محصولات مورد نظر برای اعمال تخفیف را انتخاب کنید

+
+
+ { setProductSearch(q); setProductPage(1); }} + onLoadMore={() => { + if (!productsLoading && productSearchResult && ((productSearchResult.total || 0) > accumulatedProducts.length)) { + setProductPage(prev => prev + 1); + } + }} + hasMore={!!productSearchResult && accumulatedProducts.length < (productSearchResult.total || 0)} + loadingMore={productsLoading && productPage > 1} + /> +
+

+ + + + در صورت انتخاب محصولات، تخفیف فقط روی این محصولات اعمال خواهد شد. +

+
+
)} {/* Conditional Category Selection */} - {applicationLevel === 'category' && ( + {selectedApplicationLevels.includes('category') && (
- { setCategorySearch(q); setCategoryPage(1); }} - onLoadMore={() => { - if (!categoriesLoading && categorySearchResult && (categorySearchResult.length > 0)) { - setCategoryPage(prev => prev + 1); - } - }} - hasMore={!!categorySearchResult && categorySearchResult.length >= CATEGORIES_PAGE_SIZE} - loadingMore={categoriesLoading && categoryPage > 1} - /> -

- در صورت انتخاب دسته‌بندی‌ها، تخفیف فقط روی محصولات این دسته‌ها اعمال خواهد شد. -

+
+
+
+ 📁 +
+
+

انتخاب دسته‌بندی‌های خاص

+

دسته‌بندی‌های مورد نظر برای اعمال تخفیف را انتخاب کنید

+
+
+ { setCategorySearch(q); setCategoryPage(1); }} + onLoadMore={() => { + if (!categoriesLoading && categorySearchResult && (categorySearchResult.length > 0)) { + setCategoryPage(prev => prev + 1); + } + }} + hasMore={!!categorySearchResult && categorySearchResult.length >= CATEGORIES_PAGE_SIZE} + loadingMore={categoriesLoading && categoryPage > 1} + /> +
+

+ + + + در صورت انتخاب دسته‌بندی‌ها، تخفیف فقط روی محصولات این دسته‌ها اعمال خواهد شد. +

+
+
)} @@ -432,7 +598,7 @@ const DiscountCodeFormPage = () => { label="حداقل مبلغ خرید" type="number" step="0.01" - placeholder="100000" + placeholder="حداکثر ۹۹,۹۹۹,۹۹۹" error={errors.min_purchase_amount?.message as string} thousandSeparator numeric @@ -442,7 +608,7 @@ const DiscountCodeFormPage = () => { label="حداکثر مبلغ تخفیف" type="number" step="0.01" - placeholder="50000" + placeholder="حداکثر ۹۹,۹۹۹,۹۹۹" error={errors.max_discount_amount?.message as string} thousandSeparator numeric @@ -454,14 +620,14 @@ const DiscountCodeFormPage = () => {