fine
This commit is contained in:
@@ -35,7 +35,7 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
<div className='border-0 xl:h-auto max-h-[80vh] p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full bg-white outline-none focus:outline-none'>
|
||||
{
|
||||
props.isHeader && props.title_header &&
|
||||
<div className='pb-6 border-b border-white/20'>
|
||||
<div className='pb-3 border-b border-border'>
|
||||
<div className='h-[5px] w-[200px] mx-auto bg-[#D1D3D7] rounded-full mb-4 xl:hidden'></div>
|
||||
<HeaderModal close={props.close} label={props.title_header} />
|
||||
</div>
|
||||
|
||||
+159
-6
@@ -1,10 +1,163 @@
|
||||
import { type FC } from 'react'
|
||||
import { type FC, useState } from 'react'
|
||||
import { useGetFines } from './hooks/usePaymentData';
|
||||
import { type FineRule } from './types/Types';
|
||||
import PageLoading from '../../components/PageLoading';
|
||||
import Error from '../../components/Error';
|
||||
import Td from '../../components/Td';
|
||||
import Button from '@/components/Button';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Pages } from '@/config/Pages';
|
||||
import DefaulModal from '../../components/DefaulModal';
|
||||
import { Eye } from 'iconsax-react';
|
||||
|
||||
const Fines: FC = () => {
|
||||
|
||||
return (
|
||||
<div>Fines</div>
|
||||
)
|
||||
}
|
||||
const { data, isLoading, error } = useGetFines();
|
||||
const [selectedFine, setSelectedFine] = useState<FineRule | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
export default Fines
|
||||
const openModal = (fine: FineRule) => {
|
||||
setSelectedFine(fine);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedFine(null);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<PageLoading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<Error errorText="خطا در بارگذاری قوانین جریمه" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const fines = data?.results?.fineRules || [];
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('fa-IR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-end mt-5'>
|
||||
<Link to={Pages.financial.createFine}>
|
||||
<Button
|
||||
label='افزودن قانون جریمه'
|
||||
className='w-fit'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={'عنوان'} />
|
||||
<Td text={'درصد جریمه'} />
|
||||
<Td text={'تاریخ ایجاد'} />
|
||||
<Td text={'عملیات'} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fines.length === 0 ? (
|
||||
<tr className='tr'>
|
||||
<td colSpan={4} className="text-center py-8 text-gray-500">
|
||||
هیچ قانون جریمهای یافت نشد
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
fines.map((fine: FineRule) => (
|
||||
<tr key={fine._id} className='tr'>
|
||||
<Td text={fine.title} />
|
||||
<Td text={`${fine.fine_percentage}%`} />
|
||||
<Td text={formatDate(fine.createdAt)} />
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => openModal(fine)}
|
||||
className="h-8 text-xs w-fit"
|
||||
variant="outline"
|
||||
>
|
||||
<Eye color='#000' size={16} className="ml-1" />
|
||||
جزئیات
|
||||
</Button>
|
||||
|
||||
{/* عملیات ویرایش - در صورت نیاز */}
|
||||
{/* <Link to={`${Pages.financial.updateFine}${fine._id}`}>
|
||||
<Edit color='#8C90A3' size={16} className="cursor-pointer hover:text-blue-500" />
|
||||
</Link> */}
|
||||
|
||||
{/* عملیات حذف - در صورت نیاز */}
|
||||
{/* <TrashWithConfrim
|
||||
onDelete={() => {
|
||||
console.log('Delete fine:', fine._id);
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<DefaulModal
|
||||
open={isModalOpen}
|
||||
close={closeModal}
|
||||
isHeader={true}
|
||||
title_header="جزئیات قانون جریمه"
|
||||
width={500}
|
||||
>
|
||||
{selectedFine && (
|
||||
<div className="space-y-6 w-[400px]">
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-800">اطلاعات قانون جریمه</h3>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">عنوان</label>
|
||||
<p className="text-gray-900">{selectedFine.title}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">درصد جریمه</label>
|
||||
<p className="text-gray-900">{selectedFine.fine_percentage}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">وضعیت</label>
|
||||
<p className={`text-sm ${selectedFine.deleted ? 'text-red-600' : 'text-green-600'}`}>
|
||||
{selectedFine.deleted ? 'غیرفعال' : 'فعال'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">تاریخ ایجاد</label>
|
||||
<p className="text-gray-900">{formatDate(selectedFine.createdAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">آخرین بروزرسانی</label>
|
||||
<p className="text-gray-900">{formatDate(selectedFine.updatedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DefaulModal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Fines;
|
||||
@@ -1,5 +1,9 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type { CreateFineType, PaymentResponse } from "../types/Types";
|
||||
import type {
|
||||
CreateFineType,
|
||||
PaymentResponse,
|
||||
GetFinesResponse,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getPayments = async (
|
||||
page: number = 1
|
||||
@@ -13,7 +17,7 @@ export const createFine = async (params: CreateFineType) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getFines = async () => {
|
||||
const { data } = await axios.get(`/admin/fine/fines`);
|
||||
export const getFines = async (): Promise<GetFinesResponse> => {
|
||||
const { data } = await axios.get(`/admin/fine/rule`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -54,3 +54,20 @@ export type CreateFineType = {
|
||||
title: string;
|
||||
fine_percentage: number;
|
||||
};
|
||||
|
||||
export interface FineRule {
|
||||
_id: string;
|
||||
title: string;
|
||||
fine_percentage: number;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface GetFinesResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
fineRules: FineRule[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import Button from '@/components/Button';
|
||||
import CreateReportCategoryModal from './components/CreateReportCategoryModal.tsx';
|
||||
|
||||
const CategoryReport: FC = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-end mt-5'>
|
||||
<Button
|
||||
label='افزودن دستهبندی'
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className='w-fit'
|
||||
/>
|
||||
</div>
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'></div>
|
||||
|
||||
<CreateReportCategoryModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategoryReport
|
||||
@@ -0,0 +1,84 @@
|
||||
import { type FC } from 'react'
|
||||
import { useCreateReportCategory } from '../hooks/useReportData';
|
||||
import DefaulModal from '@/components/DefaulModal';
|
||||
import Input from '@/components/Input';
|
||||
import Button from '@/components/Button';
|
||||
import { TickCircle } from 'iconsax-react';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const CreateReportCategoryModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => {
|
||||
const createReportCategoryMutation = useCreateReportCategory();
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
title: '',
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
title: Yup.string().required('عنوان دستهبندی الزامی است'),
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
createReportCategoryMutation.mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('دستهبندی گزارش با موفقیت ایجاد شد');
|
||||
onClose();
|
||||
formik.resetForm();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('خطا در ایجاد دستهبندی گزارش');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
formik.resetForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={isOpen}
|
||||
close={handleClose}
|
||||
isHeader={true}
|
||||
title_header="افزودن دستهبندی گزارش"
|
||||
width={500}
|
||||
>
|
||||
<div className='mt-4'>
|
||||
<form onSubmit={formik.handleSubmit} className='space-y-6'>
|
||||
<Input
|
||||
label='عنوان دستهبندی'
|
||||
placeholder='عنوان دستهبندی را وارد کنید...'
|
||||
{...formik.getFieldProps('title')}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||
/>
|
||||
|
||||
<div className='flex justify-end gap-3'>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
className='px-6'
|
||||
>
|
||||
لغو
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={createReportCategoryMutation.isPending}
|
||||
disabled={!formik.isValid || createReportCategoryMutation.isPending}
|
||||
className='px-6'
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={16} color='#fff' />
|
||||
<div>ذخیره</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateReportCategoryModal
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/ReportService";
|
||||
|
||||
export const useGetProductReport = () => {
|
||||
@@ -7,3 +7,9 @@ export const useGetProductReport = () => {
|
||||
queryFn: api.getProductReport,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateReportCategory = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createReportCategory,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type { ReportResponse } from "../types/Types";
|
||||
import type { CreateReportCategoryType, ReportResponse } from "../types/Types";
|
||||
|
||||
export const getProductReport = async (): Promise<ReportResponse> => {
|
||||
const { data } = await axios.get("/admin/products/user-reports");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createReportCategory = async (
|
||||
params: CreateReportCategoryType
|
||||
) => {
|
||||
const { data } = await axios.post("/admin/products/report-question", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getReportQuestions = async () => {
|
||||
const { data } = await axios.get("/admin/products/report-questions");
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -81,3 +81,7 @@ export interface ReportResponse {
|
||||
success: boolean;
|
||||
results: ReportResults;
|
||||
}
|
||||
|
||||
export interface CreateReportCategoryType {
|
||||
title: string;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import CreateFine from '@/pages/payment/CreateFine'
|
||||
import Fines from '@/pages/payment/Fines'
|
||||
import TicketsList from '@/pages/tickets/List'
|
||||
import ProductReport from '@/pages/report/ProductReport'
|
||||
import CategoryReport from '@/pages/report/Category'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
@@ -109,6 +110,7 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.ticket.list} element={<TicketsList />} />
|
||||
|
||||
<Route path={Pages.report.product} element={<ProductReport />} />
|
||||
<Route path={Pages.report.category} element={<CategoryReport />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user