This commit is contained in:
hamid zarghami
2025-09-30 09:38:47 +03:30
parent 9ec483383e
commit 676eedad00
10 changed files with 321 additions and 12 deletions
+1 -1
View File
@@ -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
View File
@@ -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;
+7 -3
View File
@@ -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;
}; };
+17
View File
@@ -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[];
};
}
+27
View File
@@ -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
+7 -1
View File
@@ -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,
});
};
+13 -1
View File
@@ -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;
};
+4
View File
@@ -81,3 +81,7 @@ export interface ReportResponse {
success: boolean; success: boolean;
results: ReportResults; results: ReportResults;
} }
export interface CreateReportCategoryType {
title: string;
}
+2
View File
@@ -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>