report category list

This commit is contained in:
hamid zarghami
2025-10-04 11:43:35 +03:30
parent afc90b3156
commit 443709b406
12 changed files with 219 additions and 8 deletions
+3
View File
@@ -191,4 +191,7 @@ export const Pages = {
list: "/jobs/list",
resumes: "/jobs/resumes",
},
contactUs: {
list: "/contact-us/list",
},
};
+77
View File
@@ -0,0 +1,77 @@
import { type FC, useState } from 'react'
import { useGetContactUs } from './hooks/useContactUsData';
import { type ContactUsItem } from './types';
import PageLoading from '../../components/PageLoading';
import Error from '../../components/Error';
import PaginationUi from '../../components/PaginationUi';
import Td from '../../components/Td';
const ContactUsList: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const { data: contactUsData, isLoading, error } = useGetContactUs(currentPage);
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 contactUs = contactUsData?.results?.contactUs || [];
const pager = contactUsData?.results?.pager;
const truncateText = (text: string, maxLength: number = 100) => {
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
};
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={'پیام'} />
</tr>
</thead>
<tbody>
{contactUs.length === 0 ? (
<tr className='tr'>
<td colSpan={3} className="text-center py-8 text-gray-500">
هیچ پیامی یافت نشد
</td>
</tr>
) : (
contactUs.map((contact: ContactUsItem, index: number) => (
<tr key={index} className='tr'>
<Td text={contact.fullName} />
<Td text={contact.email_phone} />
<Td text={truncateText(contact.message)} />
</tr>
))
)}
</tbody>
</table>
</div>
<PaginationUi
pager={pager}
onPageChange={(page) => {
setCurrentPage(page);
}}
/>
</div>
)
}
export default ContactUsList
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import * as api from "../service/ContactService";
export const useGetContactUs = (page: number = 1, limit: number = 10) => {
return useQuery({
queryKey: ["contactUs", page, limit],
queryFn: () => api.getContactUs(page, limit),
});
};
@@ -0,0 +1,12 @@
import axios from "@/config/axios";
import type { ContactUsResponse } from "../types";
export const getContactUs = async (
page: number = 1,
limit: number = 10
): Promise<ContactUsResponse> => {
const { data } = await axios.get(
`/admin/settings/contact-us?page=${page}&limit=${limit}`
);
return data;
};
+16
View File
@@ -0,0 +1,16 @@
import { type PagerType } from '../../../shared/types/SharedTypes';
export interface ContactUsItem {
fullName: string;
email_phone: string;
message: string;
}
export interface ContactUsResponse {
status: number;
success: boolean;
results: {
pager: PagerType;
contactUs: ContactUsItem[];
};
}
+63 -2
View File
@@ -1,10 +1,44 @@
import { type FC, useState } from 'react'
import Button from '@/components/Button';
import CreateReportCategoryModal from './components/CreateReportCategoryModal.tsx';
import { useGetReportCategories } from './hooks/useReportData.ts';
import { type Reason } from './types/Types';
import PageLoading from '../../components/PageLoading';
import Error from '../../components/Error';
import Td from '../../components/Td';
import { useQueryClient } from '@tanstack/react-query';
const CategoryReport: FC = () => {
const queryClient = useQueryClient();
const { data: reportCategoriesResponse, isLoading, error } = useGetReportCategories();
const [isModalOpen, setIsModalOpen] = useState(false);
const handleCloseModal = () => {
setIsModalOpen(false);
queryClient.invalidateQueries({ queryKey: ['reportCategories'] });
};
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 reportCategories = Array.isArray(reportCategoriesResponse?.results?.questions)
? reportCategoriesResponse.results.questions
: [];
return (
<div>
<div className='flex justify-end mt-5'>
@@ -14,11 +48,38 @@ const CategoryReport: FC = () => {
className='w-fit'
/>
</div>
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'></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={'نوع'} />
</tr>
</thead>
<tbody>
{reportCategories.length === 0 ? (
<tr className='tr'>
<td colSpan={3} className="text-center py-8 text-gray-500">
هیچ دستهبندی یافت نشد
</td>
</tr>
) : (
reportCategories.map((category: Reason) => (
<tr key={category._id} className='tr'>
<Td text={category.title} />
<Td text={new Date(category.createdAt).toLocaleDateString('fa-IR')} />
<Td text={category.type} />
</tr>
))
)}
</tbody>
</table>
</div>
<CreateReportCategoryModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onClose={handleCloseModal}
/>
</div>
)
+7
View File
@@ -13,3 +13,10 @@ export const useCreateReportCategory = () => {
mutationFn: api.createReportCategory,
});
};
export const useGetReportCategories = () => {
return useQuery({
queryKey: ["reportCategories"],
queryFn: api.getReportCategories,
});
};
+8 -2
View File
@@ -1,5 +1,9 @@
import axios from "../../../config/axios";
import type { CreateReportCategoryType, ReportResponse } from "../types/Types";
import type {
CreateReportCategoryType,
ReportResponse,
ReportCategoriesResponse,
} from "../types/Types";
export const getProductReport = async (): Promise<ReportResponse> => {
const { data } = await axios.get("/admin/products/user-reports");
@@ -13,7 +17,9 @@ export const createReportCategory = async (
return data;
};
export const getReportQuestions = async () => {
export const getReportCategories = async (): Promise<
ReportCategoriesResponse
> => {
const { data } = await axios.get("/admin/products/report-questions");
return data;
};
+8
View File
@@ -85,3 +85,11 @@ export interface ReportResponse {
export interface CreateReportCategoryType {
title: string;
}
export interface ReportCategoriesResponse {
status: number;
success: boolean;
results: {
questions: Reason[];
};
}
+3
View File
@@ -60,6 +60,7 @@ import Banners from '@/pages/setting/Banners'
import SiteSetting from '@/pages/setting/SiteSetting'
import JobsList from '@/pages/jobs/List'
import Resumes from '@/pages/jobs/Resume'
import ContactUsList from '@/pages/contactUs/List'
const MainRouter: FC = () => {
@@ -146,6 +147,8 @@ const MainRouter: FC = () => {
<Route path={Pages.jobs.list} element={<JobsList />} />
<Route path={Pages.jobs.resumes} element={<Resumes />} />
<Route path={Pages.contactUs.list} element={<ContactUsList />} />
</Routes>
</div>
</div>
+4 -4
View File
@@ -212,7 +212,7 @@ const SideBar: FC = () => {
icon={<Headphone variant={isActive('contact') ? 'Bold' : 'Outline'} color={isActive('contact') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
title="ارتباط با ما"
isActive={isActive('contact')}
link="/contact"
link={Pages.contactUs.list}
activeName='contact'
/>
@@ -276,7 +276,7 @@ const SideBar: FC = () => {
: subMenuName === 'tickets' ? <TicketsSubMenu />
: subMenuName === 'reports' ? <ReportsSubMenu />
: subMenuName === 'chat' ? <ChatSubMenu />
: subMenuName === 'contact' ? <ContactSubMenu />
: subMenuName === 'contact-us' ? <ContactSubMenu />
: subMenuName === 'settings' ? <SettingsSubMenu />
: subMenuName === 'pages' ? <PagesSubMenu />
: subMenuName === 'jobs' ? <JobsSubMenu />
@@ -732,8 +732,8 @@ const ContactSubMenu: FC = () => {
<div className='flex flex-col mt-10 gap-4'>
<SubMenuItem
title={'پیام های دریافتی'}
isActive={isActive('messages')}
link="/contact/messages"
isActive={isActive('contact')}
link={Pages.contactUs.list}
/>
</div>
</div>
+9
View File
@@ -6,3 +6,12 @@ export type SharedStoreType = {
subMenuName: string;
setSubMenuName: (value: string) => void;
};
export type PagerType = {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean | null;
nextPage: string | null;
};