reason return

This commit is contained in:
hamid zarghami
2025-10-08 12:48:44 +03:30
parent 30c4ab91fb
commit a3537e5a66
5 changed files with 195 additions and 51 deletions
+24 -50
View File
@@ -1,29 +1,21 @@
import { type FC, useState } from 'react' import { type FC } from 'react'
import ModalCreateReturnReason from './components/ModalCreateReturnReason'
import { useGetReturnReasons, useDeleteReturnReason } from './hooks/useOrderData'
import { type ReturnReasonType } from './types/Types'
import PageLoading from '../../components/PageLoading' import PageLoading from '../../components/PageLoading'
import Error from '../../components/Error' import Error from '../../components/Error'
import PaginationUi from '../../components/PaginationUi'
import Td from '../../components/Td' import Td from '../../components/Td'
import TrashWithConfrim from '../../components/TrashWithConfrim' import TrashWithConfrim from '../../components/TrashWithConfrim'
import { Edit } from 'iconsax-react'
import { Link } from 'react-router-dom'
import Button from '@/components/Button'
import { useNavigate } from 'react-router-dom'
import { type ReturnCategoryType, type PagerType } from './types/Types'
const ReturnCategories: FC = () => { const ReturnCategories: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
// TODO: Add category data fetching hook
const isLoading = false;
const error = null;
const refetch = () => { };
// TODO: Add delete mutation
const deleteCategoryMutation = { isPending: false, mutateAsync: async (_id: string) => { } };
const navigate = useNavigate(); const { data: returnReasonsData, isLoading, error, refetch } = useGetReturnReasons()
const deleteReturnReasonMutation = useDeleteReturnReason();
// Mock data for now const handleDeleteReturnReason = async (reasonId: string) => {
const categories: ReturnCategoryType[] = []; await deleteReturnReasonMutation.mutateAsync(reasonId);
const pager: PagerType = { page: currentPage, limit: 10, totalPages: 1, totalItems: 0, prevPage: null, nextPage: null }; refetch()
};
if (isLoading) { if (isLoading) {
return ( return (
@@ -36,63 +28,52 @@ const ReturnCategories: FC = () => {
if (error) { if (error) {
return ( return (
<div className="flex justify-center items-center min-h-[400px]"> <div className="flex justify-center items-center min-h-[400px]">
<Error errorText="خطا در بارگذاری دسته‌بندی مرجوعی‌ها" /> <Error errorText="خطا در بارگذاری دلایل بازگشت" />
</div> </div>
); );
} }
const handleDeleteCategory = async (categoryId: string) => { const returnReasons = returnReasonsData?.results?.reasons || [];
await deleteCategoryMutation.mutateAsync(categoryId);
refetch()
};
return ( return (
<div> <div>
<div className='flex justify-end mt-5'> <div className='flex justify-end mt-5'>
<Button <ModalCreateReturnReason />
label='افزودن دسته‌بندی مرجوعی'
onClick={() => navigate('/orders/return-categories/create')}
className='w-fit'
/>
</div> </div>
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'> <div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
<table className='w-full text-sm'> <table className='w-full text-sm'>
<thead className='thead'> <thead className='thead'>
<tr> <tr>
<Td text={'عنوان'} /> <Td text={'عنوان'} />
<Td text={'توضیحات'} /> <Td text={'قانون جریمه'} />
<Td text={'وضعیت'} /> <Td text={'وضعیت'} />
<Td text={'عملیات'} /> <Td text={'عملیات'} />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{categories.length === 0 ? ( {returnReasons.length === 0 ? (
<tr className='tr'> <tr className='tr'>
<td colSpan={4} className="text-center py-8 text-gray-500"> <td colSpan={4} className="text-center py-8 text-gray-500">
هیچ دستهبندی مرجوعی یافت نشد هیچ دلیل بازگشتی یافت نشد
</td> </td>
</tr> </tr>
) : ( ) : (
categories.map((category: any) => ( returnReasons.map((reason: ReturnReasonType) => (
<tr key={category.id} className='tr'> <tr key={reason._id} className='tr'>
<Td text={category.title} /> <Td text={reason.title} />
<Td text={category.description} /> <Td text={reason.fineRule?.title || 'بدون جریمه'} />
<Td text=""> <Td text="">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={`px-2 py-1 rounded-full text-xs`}> <span className="px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
{category.status} فعال
</span> </span>
</div> </div>
</Td> </Td>
<Td text=""> <Td text="">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link to={`/orders/return-categories/${category.id}`}>
<Edit color='#8C90A3' size={16} className="cursor-pointer hover:text-blue-500" />
</Link>
<TrashWithConfrim <TrashWithConfrim
onDelete={() => handleDeleteCategory(category.id)} onDelete={() => handleDeleteReturnReason(reason._id)}
isLoading={deleteCategoryMutation.isPending} isLoading={deleteReturnReasonMutation.isPending}
/> />
</div> </div>
</Td> </Td>
@@ -102,13 +83,6 @@ const ReturnCategories: FC = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
<PaginationUi
pager={pager}
onPageChange={(page) => {
setCurrentPage(page);
}}
/>
</div> </div>
) )
} }
@@ -0,0 +1,106 @@
import Button from '@/components/Button'
import DefaulModal from '@/components/DefaulModal'
import Input from '@/components/Input'
import Select from '@/components/Select'
import Switch from '@/components/Switch'
import { useState, type FC } from 'react'
import type { CreateReturnReasonType } from '../types/Types'
import { useCreateReturnReason } from '../hooks/useOrderData'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { useGetFines } from '@/pages/payment/hooks/usePaymentData'
const ModalCreateReturnReason: FC = () => {
const [open, setOpen] = useState<boolean>(false)
const createReturnReasonMutation = useCreateReturnReason()
const { data: finesData } = useGetFines()
const formik = useFormik<CreateReturnReasonType>({
initialValues: {
fineRule: '',
title: '',
is_mandatory_picture: false,
},
validationSchema: Yup.object({
fineRule: Yup.string().required('انتخاب قانون جریمه الزامی است'),
title: Yup.string().required('عنوان دلیل بازگشت الزامی است'),
is_mandatory_picture: Yup.boolean(),
}),
onSubmit: async (values) => {
try {
await createReturnReasonMutation.mutateAsync(values)
setOpen(false)
formik.resetForm()
} catch (error) {
console.error('خطا در ایجاد دلیل بازگشت:', error)
}
},
})
return (
<div>
<Button
label='افزودن دلیل بازگشت'
onClick={() => setOpen(true)}
className='w-fit px-5 whitespace-nowrap'
/>
<DefaulModal
open={open}
close={() => setOpen(false)}
title_header='افزودن دلیل بازگشت'
isHeader
>
<form onSubmit={formik.handleSubmit} className='space-y-4 w-[400px] mt-6'>
<Input
label='عنوان دلیل بازگشت'
placeholder='عنوان دلیل بازگشت را وارد کنید'
name='title'
value={formik.values.title}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : undefined}
/>
<Select
label='قانون جریمه'
placeholder='قانون جریمه را انتخاب کنید'
name='fineRule'
value={formik.values.fineRule}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error_text={formik.touched.fineRule && formik.errors.fineRule ? formik.errors.fineRule : undefined}
items={finesData?.results?.fineRules?.map(fine => ({
value: fine._id,
label: `${fine.title} (${fine.fine_percentage}%)`
})) || []}
/>
<Switch
label='تصویر اجباری'
active={formik.values.is_mandatory_picture}
onChange={(checked) => formik.setFieldValue('is_mandatory_picture', checked)}
/>
<div className='flex gap-2 pt-4'>
<Button
type='submit'
label={createReturnReasonMutation.isPending ? 'در حال ذخیره...' : 'ذخیره'}
disabled={createReturnReasonMutation.isPending}
className='flex-1'
/>
<Button
type='button'
label='لغو'
variant='outline'
onClick={() => setOpen(false)}
className='flex-1'
/>
</div>
</form>
</DefaulModal>
</div>
)
}
export default ModalCreateReturnReason
+19
View File
@@ -49,3 +49,22 @@ export const useDeleteCancelReason = () => {
mutationFn: api.deleteCancelReason, mutationFn: api.deleteCancelReason,
}); });
}; };
export const useCreateReturnReason = () => {
return useMutation({
mutationFn: api.createReturnReason,
});
};
export const useGetReturnReasons = () => {
return useQuery({
queryKey: ["return-reasons"],
queryFn: api.getReturnReasons,
});
};
export const useDeleteReturnReason = () => {
return useMutation({
mutationFn: api.deleteReturnReason,
});
};
+20 -1
View File
@@ -2,8 +2,10 @@ import axios from "../../../config/axios";
import type { import type {
CancelReasonsResponseType, CancelReasonsResponseType,
CreateCancelReasonType, CreateCancelReasonType,
CreateReturnReasonType,
NativeOrdersResponseType, NativeOrdersResponseType,
OrderDetailResponseType, OrderDetailResponseType,
ReturnReasonsResponseType,
} from "../types/Types"; } from "../types/Types";
export const getNativeOrders = async ( export const getNativeOrders = async (
@@ -46,6 +48,23 @@ export const getCancelReasons = async (): Promise<
}; };
export const deleteCancelReason = async (id: string) => { export const deleteCancelReason = async (id: string) => {
const { data } = await axios.delete(`/cancel/reasons/${id}`); const { data } = await axios.delete(`/admin/orders/reasons/${id}/delete`);
return data;
};
export const createReturnReason = async (params: CreateReturnReasonType) => {
const { data } = await axios.post(`/admin/orders/returns/reasons`, params);
return data;
};
export const getReturnReasons = async (): Promise<
ReturnReasonsResponseType
> => {
const { data } = await axios.get(`/returns/reasons`);
return data;
};
export const deleteReturnReason = async (id: string) => {
const { data } = await axios.delete(`/admin/orders/returns/reasons/${id}`);
return data; return data;
}; };
+26
View File
@@ -197,6 +197,9 @@ export type ReturnCategoryType = {
description: string; description: string;
status: string; status: string;
deleted: boolean; deleted: boolean;
fineRule?: FineRuleType;
createdAt?: string;
updatedAt?: string;
}; };
// تایپ‌های صفحه‌بندی // تایپ‌های صفحه‌بندی
@@ -311,3 +314,26 @@ export type CancelReasonsResponseType = {
reasons: CancelReasonType[]; reasons: CancelReasonType[];
}; };
}; };
export type ReturnReasonType = {
_id: string;
title: string;
is_mandatory_picture: boolean;
fineRule: FineRuleType;
createdAt: string;
updatedAt: string;
};
export type ReturnReasonsResponseType = {
status: number;
success: boolean;
results: {
reasons: ReturnReasonType[];
};
};
export type CreateReturnReasonType = {
title: string;
is_mandatory_picture: boolean;
fineRule: string;
};