diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx index 7e5d06a..1725c9b 100644 --- a/src/components/DatePicker.tsx +++ b/src/components/DatePicker.tsx @@ -35,15 +35,21 @@ const DatePickerComponent: FC = (props: Props) => { }, [value]); useEffect(() => { - if (props.defaulValue && !value) { + if (props.defaulValue) { const defaultDate = new DateObject({ date: props.defaulValue, calendar: persian, locale: persian_fa, }); - setValue(defaultDate); + const currentValueStr = value ? `${value.year}-${value.month.number}-${value.day}` : null; + const defaultValueStr = `${defaultDate.year}-${defaultDate.month.number}-${defaultDate.day}`; + if (currentValueStr !== defaultValueStr) { + setValue(defaultDate); + } + } else if (!props.defaulValue && value) { + setValue(null); } - }, [props.defaulValue, value]); + }, [props.defaulValue]); return (
diff --git a/src/pages/coupon/Update.tsx b/src/pages/coupon/Update.tsx new file mode 100644 index 0000000..cf5558e --- /dev/null +++ b/src/pages/coupon/Update.tsx @@ -0,0 +1,174 @@ +import { type FC, useState, useMemo, useEffect } from 'react' +import { useParams, useNavigate } from 'react-router-dom' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import { TickCircle } from 'iconsax-react' +import { toast } from 'react-toastify' +import Button from '@/components/Button' +import type { CreateCouponType } from './types/Types' +import { useGetCouponDetails, useUpdateCoupon } from './hooks/useCouponData' +import { useGetCategories } from '@/pages/food/hooks/useFoodData' +import { useGetFoods } from '@/pages/food/hooks/useFoodData' +import { Pages } from '@/config/Pages' +import type { ErrorType } from '@/helpers/types' +import { extractErrorMessage } from '@/config/func' +import CouponBasicInfo from './components/CouponBasicInfo' +import CouponDiscountSettings from './components/CouponDiscountSettings' +import CouponUsageSettings from './components/CouponUsageSettings' +import CouponSidebar from './components/CouponSidebar' + +const UpdateCoupon: FC = () => { + const navigate = useNavigate() + const { id } = useParams<{ id: string }>() + const { data: couponData, isLoading } = useGetCouponDetails(id || '') + const { mutate: updateCoupon, isPending: isUpdating } = useUpdateCoupon() + const { data: categoriesData } = useGetCategories() + const { data: foodsData } = useGetFoods() + + const [couponType, setCouponType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE') + + const categories = useMemo(() => categoriesData?.data || [], [categoriesData?.data]) + const foods = useMemo(() => foodsData?.data || [], [foodsData?.data]) + + const formik = useFormik({ + initialValues: { + code: '', + name: '', + description: '', + value: 0, + maxDiscount: null, + minOrderAmount: 0, + maxUses: 0, + maxUsesPerUser: null, + startDate: null, + endDate: null, + isActive: true, + foodCategories: [], + foods: [], + userPhone: '', + type: 'PERCENTAGE', + }, + validationSchema: Yup.object().shape({ + type: Yup.string().required('نوع کوپن الزامی است'), + code: Yup.string().required('کد کوپن الزامی است'), + name: Yup.string().required('نام کوپن الزامی است'), + description: Yup.string(), + value: Yup.number().min(0, 'مقدار باید بیشتر از صفر باشد').required('مقدار کوپن الزامی است'), + maxDiscount: Yup.number().nullable().min(0, 'حداکثر تخفیف باید بیشتر از صفر باشد'), + minOrderAmount: Yup.number().min(0, 'حداقل مبلغ سفارش باید بیشتر از صفر باشد').required('حداقل مبلغ سفارش الزامی است'), + maxUses: Yup.number().min(0, 'حداکثر استفاده باید بیشتر از صفر باشد').required('حداکثر استفاده الزامی است'), + maxUsesPerUser: Yup.number().nullable().min(0, 'حداکثر استفاده برای هر کاربر باید بیشتر از صفر باشد'), + startDate: Yup.string().nullable(), + endDate: Yup.string().nullable(), + userPhone: Yup.string().nullable(), + }), + enableReinitialize: true, + onSubmit: (values) => { + if (!id) { + toast.error('شناسه کوپن یافت نشد') + return + } + + const submitValues = { + ...values, + userPhone: values.userPhone?.trim() || undefined, + } + updateCoupon( + { id, params: submitValues }, + { + onSuccess: () => { + toast.success('کوپن با موفقیت به‌روزرسانی شد') + navigate(Pages.coupons.list) + }, + onError: (error: ErrorType) => { + toast.error(extractErrorMessage(error)) + }, + } + ) + }, + }) + + useEffect(() => { + if (couponData?.data) { + const coupon = couponData.data + setCouponType(coupon.type) + formik.setValues({ + code: coupon.code, + name: coupon.name, + description: coupon.description || '', + value: coupon.value, + maxDiscount: coupon.maxDiscount, + minOrderAmount: coupon.minOrderAmount, + maxUses: coupon.maxUses, + maxUsesPerUser: coupon.maxUsesPerUser, + startDate: coupon.startDate, + endDate: coupon.endDate, + isActive: coupon.isActive, + foodCategories: coupon.foodCategories || [], + foods: coupon.foods || [], + userPhone: coupon.userPhone || '', + type: coupon.type, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [couponData?.data]) + + if (isLoading) { + return ( +
+
در حال بارگذاری...
+
+ ) + } + + return ( +
+
+

ویرایش کد تخفیف

+
+ +
+ +
+ +
+
+
+
ویرایش کد تخفیف
+ + + + + + +
+
+ +
+ +
+
+
+ ) +} + +export default UpdateCoupon \ No newline at end of file diff --git a/src/pages/coupon/components/CouponUsageSettings.tsx b/src/pages/coupon/components/CouponUsageSettings.tsx index a544d35..91dff76 100644 --- a/src/pages/coupon/components/CouponUsageSettings.tsx +++ b/src/pages/coupon/components/CouponUsageSettings.tsx @@ -16,6 +16,12 @@ const CouponUsageSettings: FC = ({ formik }) => { return gregorianDate } + const convertToPersian = (gregorianDate: string | null): string | undefined => { + if (!gregorianDate) return undefined + const persianDate = moment(gregorianDate, 'YYYY-MM-DD').format('jYYYY-jMM-jDD') + return persianDate + } + return ( <>
@@ -44,16 +50,20 @@ const CouponUsageSettings: FC = ({ formik }) => {
{ const gregorianDate = convertToGregorian(date) formik.setFieldValue('startDate', gregorianDate) }} /> { const gregorianDate = convertToGregorian(date) formik.setFieldValue('endDate', gregorianDate) diff --git a/src/pages/coupon/hooks/useCouponData.ts b/src/pages/coupon/hooks/useCouponData.ts index facb80c..8d3e2ab 100644 --- a/src/pages/coupon/hooks/useCouponData.ts +++ b/src/pages/coupon/hooks/useCouponData.ts @@ -1,5 +1,6 @@ import * as api from "../service/CouponService"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import type { CreateCouponType } from "../types/Types"; export const useGetCoupons = () => { return useQuery({ @@ -37,3 +38,27 @@ export const useGenerateCouponCode = () => { mutationFn: api.generateCouponCode, }); }; + +export const useGetCouponDetails = (id: string) => { + return useQuery({ + queryKey: ["coupon", id], + queryFn: () => api.getCouponDetails(id), + enabled: !!id, + }); +}; + +export const useUpdateCoupon = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, params }: { id: string; params: CreateCouponType }) => + api.updateCoupon(id, params), + onSuccess: (_, { id }) => { + queryClient.invalidateQueries({ + queryKey: ["coupons"], + }); + queryClient.invalidateQueries({ + queryKey: ["coupon", id], + }); + }, + }); +}; diff --git a/src/pages/coupon/service/CouponService.ts b/src/pages/coupon/service/CouponService.ts index 6473af6..e72bd19 100644 --- a/src/pages/coupon/service/CouponService.ts +++ b/src/pages/coupon/service/CouponService.ts @@ -1,5 +1,9 @@ import axios from "@/config/axios"; -import type { CreateCouponType, IGetCouponsResponse } from "../types/Types"; +import type { + CreateCouponType, + IGetCouponsResponse, + IGetCouponDetailsResponse, +} from "../types/Types"; export const getCoupons = async (): Promise => { const { data } = await axios.get("/admin/coupons"); @@ -20,3 +24,17 @@ export const generateCouponCode = async () => { const { data } = await axios.get(`/admin/coupons/generate-code`); return data; }; + +export const getCouponDetails = async ( + id: string +): Promise => { + const { data } = await axios.get( + `/admin/coupons/${id}` + ); + return data; +}; + +export const updateCoupon = async (id: string, params: CreateCouponType) => { + const { data } = await axios.patch(`/admin/coupons/${id}`, params); + return data; +}; diff --git a/src/pages/coupon/types/Types.ts b/src/pages/coupon/types/Types.ts index 7809596..7b30d83 100644 --- a/src/pages/coupon/types/Types.ts +++ b/src/pages/coupon/types/Types.ts @@ -1,4 +1,5 @@ import type { IResponse } from "@/types/response.types"; +import type { Restaurant } from "@/pages/paymentMethods/types/Types"; export type CouponType = "PERCENTAGE" | "FIXED"; @@ -24,8 +25,34 @@ export interface ICoupon { [key: string]: unknown; } +export interface ICouponDetails { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + restaurant: Restaurant; + code: string; + name: string; + description: string; + type: CouponType; + value: number; + maxDiscount: number | null; + minOrderAmount: number; + maxUses: number; + usedCount: number; + maxUsesPerUser: number | null; + startDate: string | null; + endDate: string | null; + isActive: boolean; + foodCategories: string[]; + foods: string[]; + userPhone: string | null; +} + export type IGetCouponsResponse = IResponse; +export type IGetCouponDetailsResponse = IResponse; + export type CreateCouponType = { code: string; name: string; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 5b99dd5..88c2207 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -35,6 +35,7 @@ import CreateShipmentMethod from '@/pages/shipmentMethod/Create' import UpdateShipmentMethod from '@/pages/shipmentMethod/Update' import CouponsList from '@/pages/coupon/List' import CreateCoupon from '@/pages/coupon/Create' +import UpdateCoupon from '@/pages/coupon/Update' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -85,6 +86,7 @@ const MainRouter: FC = () => { } /> } /> + } />