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'>
|
<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 &&
|
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>
|
<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} />
|
<HeaderModal close={props.close} label={props.title_header} />
|
||||||
</div>
|
</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 = () => {
|
const Fines: FC = () => {
|
||||||
|
|
||||||
return (
|
const { data, isLoading, error } = useGetFines();
|
||||||
<div>Fines</div>
|
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 axios from "../../../config/axios";
|
||||||
import type { CreateFineType, PaymentResponse } from "../types/Types";
|
import type {
|
||||||
|
CreateFineType,
|
||||||
|
PaymentResponse,
|
||||||
|
GetFinesResponse,
|
||||||
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getPayments = async (
|
export const getPayments = async (
|
||||||
page: number = 1
|
page: number = 1
|
||||||
@@ -13,7 +17,7 @@ export const createFine = async (params: CreateFineType) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFines = async () => {
|
export const getFines = async (): Promise<GetFinesResponse> => {
|
||||||
const { data } = await axios.get(`/admin/fine/fines`);
|
const { data } = await axios.get(`/admin/fine/rule`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,3 +54,20 @@ export type CreateFineType = {
|
|||||||
title: string;
|
title: string;
|
||||||
fine_percentage: number;
|
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";
|
import * as api from "../service/ReportService";
|
||||||
|
|
||||||
export const useGetProductReport = () => {
|
export const useGetProductReport = () => {
|
||||||
@@ -7,3 +7,9 @@ export const useGetProductReport = () => {
|
|||||||
queryFn: api.getProductReport,
|
queryFn: api.getProductReport,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateReportCategory = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.createReportCategory,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
import axios from "../../../config/axios";
|
import axios from "../../../config/axios";
|
||||||
import type { ReportResponse } from "../types/Types";
|
import type { CreateReportCategoryType, ReportResponse } from "../types/Types";
|
||||||
|
|
||||||
export const getProductReport = async (): Promise<ReportResponse> => {
|
export const getProductReport = async (): Promise<ReportResponse> => {
|
||||||
const { data } = await axios.get("/admin/products/user-reports");
|
const { data } = await axios.get("/admin/products/user-reports");
|
||||||
return data;
|
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;
|
success: boolean;
|
||||||
results: ReportResults;
|
results: ReportResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateReportCategoryType {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import CreateFine from '@/pages/payment/CreateFine'
|
|||||||
import Fines from '@/pages/payment/Fines'
|
import Fines from '@/pages/payment/Fines'
|
||||||
import TicketsList from '@/pages/tickets/List'
|
import TicketsList from '@/pages/tickets/List'
|
||||||
import ProductReport from '@/pages/report/ProductReport'
|
import ProductReport from '@/pages/report/ProductReport'
|
||||||
|
import CategoryReport from '@/pages/report/Category'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -109,6 +110,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.ticket.list} element={<TicketsList />} />
|
<Route path={Pages.ticket.list} element={<TicketsList />} />
|
||||||
|
|
||||||
<Route path={Pages.report.product} element={<ProductReport />} />
|
<Route path={Pages.report.product} element={<ProductReport />} />
|
||||||
|
<Route path={Pages.report.category} element={<CategoryReport />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user