product report list + detail report
This commit is contained in:
@@ -26,11 +26,13 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
{
|
||||
props.open && (
|
||||
<Fragment>
|
||||
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-x-hidden overflow-hidden fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
|
||||
<div className='relative xl:h-full h-[80%] bottom-0 left-0 max-h-[80%] top-0 m-auto overflow-hidden flex xl:items-center sm:h-auto w-full xl:my-6 xl:p-2'>
|
||||
<div className='border-0 h-auto 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'>
|
||||
|
||||
{/* Backdrop */}
|
||||
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 bg-black/50 backdrop-blur-md inset-0 z-50'></div>
|
||||
|
||||
{/* Modal Container */}
|
||||
<div className='fixed inset-0 z-[60] flex xl:justify-center xl:items-start items-end overflow-x-hidden overflow-y-auto'>
|
||||
<div style={{ maxWidth: props.width }} className='relative xl:max-h-[90vh] xl:my-6 xl:max-w-xl mx-auto w-full xl:w-auto'>
|
||||
<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'>
|
||||
@@ -40,13 +42,9 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
{props.children}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 bg-black/50 backdrop-blur-md inset-0 z-50 '></div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -113,6 +113,10 @@ export const Pages = {
|
||||
payment: {
|
||||
list: "/payments/list",
|
||||
},
|
||||
report: {
|
||||
product: "/reports/products",
|
||||
category: "/reports/category",
|
||||
},
|
||||
support: {
|
||||
create: "/support/create",
|
||||
list: "/support/list",
|
||||
@@ -164,5 +168,6 @@ export const Pages = {
|
||||
financial: {
|
||||
list: "/financial/transactions",
|
||||
fines: "/financial/fines",
|
||||
createFine: "/financial/create-fine",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useFormik } from 'formik'
|
||||
import { type FC } from 'react'
|
||||
import type { CreateFineType } from './types/Types'
|
||||
import * as Yup from 'yup'
|
||||
import { useCreateFine } from './hooks/usePaymentData'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Input from '../../components/Input'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/helpers/utils'
|
||||
import { Pages } from '@/config/Pages'
|
||||
|
||||
const CreateFine: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const createFineMutation = useCreateFine()
|
||||
|
||||
const formik = useFormik<CreateFineType>({
|
||||
initialValues: {
|
||||
fine_percentage: 0,
|
||||
title: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
fine_percentage: Yup.number().required('درصد جریمه الزامی است').min(0, 'درصد جریمه نمیتواند منفی باشد'),
|
||||
title: Yup.string().required('عنوان جریمه الزامی است'),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
createFineMutation.mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('قانون جریمه با موفقیت ایجاد شد')
|
||||
navigate(Pages.financial.fines)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h1>ساخت قانون جریمه جدید</h1>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className='px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createFineMutation.isPending}
|
||||
disabled={!formik.isValid || createFineMutation.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={16} color='#fff' />
|
||||
<div>ثبت قانون جریمه</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||
<div className='flex-1 text-sm bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||
<h2 className='text-lg font-medium mb-6'>اطلاعات قانون جریمه</h2>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<Input
|
||||
label='عنوان جریمه'
|
||||
placeholder='مثال: جریمه تأخیر پرداخت'
|
||||
{...formik.getFieldProps('title')}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label='درصد جریمه'
|
||||
type='number'
|
||||
placeholder='مثال: 5'
|
||||
{...formik.getFieldProps('fine_percentage')}
|
||||
error_text={formik.touched.fine_percentage && formik.errors.fine_percentage ? formik.errors.fine_percentage : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateFine
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type FC } from 'react'
|
||||
|
||||
const Fines: FC = () => {
|
||||
|
||||
return (
|
||||
<div>Fines</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Fines
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as api from "../service/PaymentService";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
export const useGetPayments = (page: number = 1) => {
|
||||
return useQuery({
|
||||
@@ -7,3 +7,16 @@ export const useGetPayments = (page: number = 1) => {
|
||||
queryFn: () => api.getPayments(page),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateFine = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createFine,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetFines = () => {
|
||||
return useQuery({
|
||||
queryKey: ["fines"],
|
||||
queryFn: api.getFines,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type { PaymentResponse } from "../types/Types";
|
||||
import type { CreateFineType, PaymentResponse } from "../types/Types";
|
||||
|
||||
export const getPayments = async (
|
||||
page: number = 1
|
||||
@@ -7,3 +7,13 @@ export const getPayments = async (
|
||||
const { data } = await axios.get(`/admin/financial/payments?page=${page}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createFine = async (params: CreateFineType) => {
|
||||
const { data } = await axios.post(`/admin/fine/rule`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getFines = async () => {
|
||||
const { data } = await axios.get(`/admin/fine/fines`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -49,3 +49,8 @@ export interface Pager {
|
||||
prevPage: boolean | null;
|
||||
nextPage: string | null;
|
||||
}
|
||||
|
||||
export type CreateFineType = {
|
||||
title: string;
|
||||
fine_percentage: number;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { useGetProductReport } from './hooks/useReportData'
|
||||
import { type Report } from './types/Types'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import Error from '../../components/Error'
|
||||
import Td from '../../components/Td'
|
||||
import StatusWithText from '../../components/StatusWithText'
|
||||
import DefaulModal from '../../components/DefaulModal'
|
||||
import { Eye } from 'iconsax-react'
|
||||
import Button from '@/components/Button'
|
||||
|
||||
const ProductReport: FC = () => {
|
||||
|
||||
const { data, isLoading, error } = useGetProductReport();
|
||||
const [selectedReport, setSelectedReport] = useState<Report | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const openModal = (report: Report) => {
|
||||
setSelectedReport(report);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedReport(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 reports = data?.results?.reports || [];
|
||||
|
||||
const getStatusVariant = (status: string) => {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'pending':
|
||||
return 'warning';
|
||||
case 'resolved':
|
||||
return 'success';
|
||||
case 'rejected':
|
||||
return 'error';
|
||||
default:
|
||||
return 'warning';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'pending':
|
||||
return 'در انتظار بررسی';
|
||||
case 'resolved':
|
||||
return 'بررسی شده';
|
||||
case 'rejected':
|
||||
return 'رد شده';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('fa-IR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<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={'توضیحات'} />
|
||||
<Td text={'وضعیت'} />
|
||||
<Td text={'تاریخ گزارش'} />
|
||||
<Td text={'عملیات'} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{reports.length === 0 ? (
|
||||
<tr className='tr'>
|
||||
<td colSpan={7} className="text-center py-8 text-gray-500">
|
||||
هیچ گزارشی یافت نشد
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
reports.map((report: Report) => (
|
||||
<tr key={report._id} className='tr'>
|
||||
<Td text="">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">
|
||||
{report.user.fullName}
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs">
|
||||
{report.user.phoneNumber}
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">
|
||||
{report.product.title_fa}
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs">
|
||||
{report.product.title_en}
|
||||
</div>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{report.reason.slice(0, 2).map((reason) => (
|
||||
<span
|
||||
key={reason._id}
|
||||
className="inline-block bg-red-100 text-red-800 text-xs px-2 py-1 rounded-full"
|
||||
>
|
||||
{reason.title}
|
||||
</span>
|
||||
))}
|
||||
{report.reason.length > 2 && (
|
||||
<span className="inline-block bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
|
||||
+{report.reason.length - 2} دلیل دیگر
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="max-w-xs truncate" title={report.description}>
|
||||
{report.description}
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<StatusWithText
|
||||
variant={getStatusVariant(report.status)}
|
||||
text={getStatusText(report.status)}
|
||||
/>
|
||||
</Td>
|
||||
<Td text={formatDate(report.createdAt)} />
|
||||
<Td text="">
|
||||
<Button
|
||||
onClick={() => openModal(report)}
|
||||
className="h-8 text-xs w-fit"
|
||||
variant="outline"
|
||||
>
|
||||
<Eye color='#000' size={16} className="ml-1" />
|
||||
جزئیات
|
||||
</Button>
|
||||
</Td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<DefaulModal
|
||||
open={isModalOpen}
|
||||
close={closeModal}
|
||||
isHeader={true}
|
||||
title_header="جزئیات گزارش محصول"
|
||||
width={600}
|
||||
>
|
||||
{selectedReport && (
|
||||
<div className="space-y-6">
|
||||
{/* اطلاعات گزارشدهنده */}
|
||||
<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 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">نام کامل</label>
|
||||
<p className="text-gray-900">{selectedReport.user.fullName}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">شماره تلفن</label>
|
||||
<p className="text-gray-900">{selectedReport.user.phoneNumber}</p>
|
||||
</div>
|
||||
{selectedReport.user.nationalCode && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">کد ملی</label>
|
||||
<p className="text-gray-900">{selectedReport.user.nationalCode}</p>
|
||||
</div>
|
||||
)}
|
||||
{selectedReport.user.address && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-600">آدرس</label>
|
||||
<p className="text-gray-900">{selectedReport.user.address}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* اطلاعات محصول */}
|
||||
<div className="bg-blue-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-800">محصول گزارششده</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">عنوان فارسی</label>
|
||||
<p className="text-gray-900">{selectedReport.product.title_fa}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">عنوان انگلیسی</label>
|
||||
<p className="text-gray-900">{selectedReport.product.title_en}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">مدل</label>
|
||||
<p className="text-gray-900">{selectedReport.product.model}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">برند</label>
|
||||
<p className="text-gray-900">{selectedReport.product.brand}</p>
|
||||
</div>
|
||||
{selectedReport.product.imagesUrl?.cover && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-600 mb-2">تصویر محصول</label>
|
||||
<img
|
||||
src={selectedReport.product.imagesUrl.cover}
|
||||
alt={selectedReport.product.title_fa}
|
||||
className="w-24 h-24 object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* دلایل گزارش */}
|
||||
<div className="bg-red-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-800">دلایل گزارش</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedReport.reason.map((reason) => (
|
||||
<span
|
||||
key={reason._id}
|
||||
className="inline-block bg-red-100 text-red-800 text-sm px-3 py-1 rounded-full"
|
||||
>
|
||||
{reason.title}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{selectedReport.reason.some(r => r.placeholder) && (
|
||||
<div className="mt-3">
|
||||
<label className="block text-sm font-medium text-gray-600 mb-2">توضیحات دلایل</label>
|
||||
<div className="space-y-2">
|
||||
{selectedReport.reason.filter(r => r.placeholder).map((reason) => (
|
||||
<p key={reason._id} className="text-sm text-gray-700 bg-white p-2 rounded">
|
||||
<strong>{reason.title}:</strong> {reason.placeholder}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* توضیحات گزارش */}
|
||||
<div className="bg-yellow-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-800">توضیحات گزارش</h3>
|
||||
<p className="text-gray-900 whitespace-pre-wrap">{selectedReport.description}</p>
|
||||
</div>
|
||||
|
||||
{/* وضعیت و تاریخ */}
|
||||
<div className="bg-green-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-800">اطلاعات گزارش</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">وضعیت</label>
|
||||
<StatusWithText
|
||||
variant={getStatusVariant(selectedReport.status)}
|
||||
text={getStatusText(selectedReport.status)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">تاریخ گزارش</label>
|
||||
<p className="text-gray-900">{formatDate(selectedReport.createdAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-600">تاریخ آخرین بروزرسانی</label>
|
||||
<p className="text-gray-900">{formatDate(selectedReport.updatedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DefaulModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductReport
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/ReportService";
|
||||
|
||||
export const useGetProductReport = () => {
|
||||
return useQuery({
|
||||
queryKey: ["productReport"],
|
||||
queryFn: api.getProductReport,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type { ReportResponse } from "../types/Types";
|
||||
|
||||
export const getProductReport = async (): Promise<ReportResponse> => {
|
||||
const { data } = await axios.get("/admin/products/user-reports");
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
export interface Reason {
|
||||
_id: number;
|
||||
title: string;
|
||||
type: string;
|
||||
placeholder: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
phoneNumber: string;
|
||||
dateOfBirth: string | null;
|
||||
address: string | null;
|
||||
nationalCode: string | null;
|
||||
homeNumber: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProductImagesUrl {
|
||||
cover: string;
|
||||
list: string[];
|
||||
}
|
||||
|
||||
export interface ProductSpecification {
|
||||
title: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
_id: number;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
model: string;
|
||||
source: string;
|
||||
description: string;
|
||||
metaDescription: string;
|
||||
tags: string[];
|
||||
advantages: string[];
|
||||
disAdvantages: string[];
|
||||
totalRate: number;
|
||||
category: string;
|
||||
shop: string;
|
||||
brand: string;
|
||||
status: string;
|
||||
adminComments: string | null;
|
||||
step: number;
|
||||
isFake: boolean;
|
||||
variants: string[];
|
||||
voice: string | null;
|
||||
deleted: boolean;
|
||||
specifications: ProductSpecification[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
imagesUrl: ProductImagesUrl;
|
||||
url: string;
|
||||
rate?: number;
|
||||
}
|
||||
|
||||
export interface Report {
|
||||
_id: string;
|
||||
user: User;
|
||||
product: Product;
|
||||
reason: Reason[];
|
||||
description: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ReportResults {
|
||||
reports: Report[];
|
||||
}
|
||||
|
||||
export interface ReportResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: ReportResults;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type FC } from 'react'
|
||||
|
||||
const TicketsList: FC = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
Tickets List
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TicketsList
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/TicketService";
|
||||
|
||||
export const useGetTickets = () => {
|
||||
return useQuery({
|
||||
queryKey: ["tickets"],
|
||||
queryFn: api.getTickets,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import axios from "../../../config/axios";
|
||||
|
||||
export const getTickets = async () => {
|
||||
const { data } = await axios.get(`/admin/tickets`);
|
||||
return data;
|
||||
};
|
||||
@@ -39,6 +39,10 @@ import UpdateBrand from '@/pages/brand/Update'
|
||||
import UpdateWarranty from '@/pages/warranty/Update'
|
||||
import PaymentList from '@/pages/payment/List'
|
||||
import UpdateShipment from '@/pages/shipment/Update'
|
||||
import CreateFine from '@/pages/payment/CreateFine'
|
||||
import Fines from '@/pages/payment/Fines'
|
||||
import TicketsList from '@/pages/tickets/List'
|
||||
import ProductReport from '@/pages/report/ProductReport'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
@@ -99,6 +103,12 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.admin.create} element={<AdminCreate />} />
|
||||
|
||||
<Route path={Pages.financial.list} element={<PaymentList />} />
|
||||
<Route path={Pages.financial.createFine} element={<CreateFine />} />
|
||||
<Route path={Pages.financial.fines} element={<Fines />} />
|
||||
|
||||
<Route path={Pages.ticket.list} element={<TicketsList />} />
|
||||
|
||||
<Route path={Pages.report.product} element={<ProductReport />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -667,6 +667,11 @@ const ReportsSubMenu: FC = () => {
|
||||
isActive={isActive('products')}
|
||||
link="/reports/products"
|
||||
/>
|
||||
<SubMenuItem
|
||||
title={'دسته بندی گزارشات'}
|
||||
isActive={isActive('category')}
|
||||
link={Pages.report.category}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user