From 6f00af1d60149faf312bffefd36897647c92561b Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 14 Dec 2025 10:36:56 +0330 Subject: [PATCH] report list --- src/langs/fa.json | 2 +- src/pages/report/List.tsx | 86 +++++++++++++++ .../report/components/ReportFiltersFields.tsx | 12 ++ .../report/components/ReportTableColumns.tsx | 104 ++++++++++++++++++ src/pages/report/hooks/useReportData.ts | 9 ++ src/pages/report/hooks/useReportFilters.ts | 46 ++++++++ src/pages/report/service/ReportService.ts | 11 ++ src/pages/report/types/Types.ts | 14 +++ src/router/Main.tsx | 3 + src/shared/SideBar.tsx | 14 ++- 10 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 src/pages/report/List.tsx create mode 100644 src/pages/report/components/ReportFiltersFields.tsx create mode 100644 src/pages/report/components/ReportTableColumns.tsx create mode 100644 src/pages/report/hooks/useReportData.ts create mode 100644 src/pages/report/hooks/useReportFilters.ts create mode 100644 src/pages/report/service/ReportService.ts create mode 100644 src/pages/report/types/Types.ts diff --git a/src/langs/fa.json b/src/langs/fa.json index b523067..996656d 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -57,7 +57,7 @@ "discount": "کد تخفیف", "other": "سایر", "announcements": "اطلاعیه ها", - "reports": "گزارشات", + "reports": "گزارش اشکال", "comments": "نظرات", "setting": "تنظیمات", "logout": "خروج", diff --git a/src/pages/report/List.tsx b/src/pages/report/List.tsx new file mode 100644 index 0000000..af310d6 --- /dev/null +++ b/src/pages/report/List.tsx @@ -0,0 +1,86 @@ +import { type FC, useState } from 'react' +import Table from '@/components/Table' +import Filters from '@/components/Filters' +import { useGetReports } from './hooks/useReportData' +import { useReportFilters } from './hooks/useReportFilters' +import { getReportTableColumns } from './components/ReportTableColumns' +import { useReportFiltersFields } from './components/ReportFiltersFields' +import type { Report } from './types/Types' +import type { RowDataType } from '@/components/types/TableTypes' +import DefaulModal from '@/components/DefaulModal' + +const ReportsList: FC = () => { + const [selectedContent, setSelectedContent] = useState(null) + const [isModalOpen, setIsModalOpen] = useState(false) + + const { + filters, + currentPage, + apiParams, + handleFiltersChange, + handlePageChange, + limit, + } = useReportFilters() + + const { data: reportsData, isLoading } = useGetReports(apiParams) + + const reports = reportsData?.data || [] + const filterFields = useReportFiltersFields() + + const handleViewContent = (content: string) => { + setSelectedContent(content) + setIsModalOpen(true) + } + + const handleCloseModal = () => { + setIsModalOpen(false) + setSelectedContent(null) + } + + const columns = getReportTableColumns({ onViewContent: handleViewContent }) + + const totalPages = Math.ceil(reports.length / limit) || 1 + + return ( +
+
+

لیست گزارش اشکال

+
+ +
+ +
+ + + columns={columns} + data={reports as (Report & RowDataType)[]} + isloading={isLoading} + pagination={{ + currentPage, + totalPages, + onPageChange: handlePageChange, + }} + /> + + +
+

+ {selectedContent || '-'} +

+
+
+
+ ) +} + +export default ReportsList \ No newline at end of file diff --git a/src/pages/report/components/ReportFiltersFields.tsx b/src/pages/report/components/ReportFiltersFields.tsx new file mode 100644 index 0000000..f02d6db --- /dev/null +++ b/src/pages/report/components/ReportFiltersFields.tsx @@ -0,0 +1,12 @@ +import { useMemo } from 'react' +import type { FieldType } from '@/components/Filters' + +export const useReportFiltersFields = (): FieldType[] => { + return useMemo(() => [ + { + type: 'input', + name: 'search', + placeholder: 'جستجو (موضوع یا محتوا)', + }, + ], []) +} \ No newline at end of file diff --git a/src/pages/report/components/ReportTableColumns.tsx b/src/pages/report/components/ReportTableColumns.tsx new file mode 100644 index 0000000..ac5dfb2 --- /dev/null +++ b/src/pages/report/components/ReportTableColumns.tsx @@ -0,0 +1,104 @@ +import { Eye } from 'iconsax-react' +import type { ColumnType } from '@/components/types/TableTypes' +import type { Report } from '../types/Types' +import { formatOptionalDate } from '@/helpers/func' +import Status from '@/components/Status' + +interface GetReportTableColumnsProps { + onViewContent?: (content: string) => void +} + +const getStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'pending' => { + const variantMap: Record = { + 'pending': 'pending', + 'read': 'info', + 'resolved': 'success', + 'rejected': 'error', + } + return variantMap[status] || 'pending' +} + +export const getReportTableColumns = (props?: GetReportTableColumnsProps): ColumnType[] => { + return [ + { + key: 'subject', + title: 'موضوع', + render: (item: Report) => { + return ( +
+ {item.subject || '-'} +
+ ) + } + }, + { + key: 'content', + title: 'محتوا', + render: (item: Report) => { + return ( +
+ {item.content || '-'} +
+ ) + } + }, + { + key: 'phone', + title: 'شماره تماس', + render: (item: Report) => { + return item.phone || '-' + } + }, + { + key: 'status', + title: 'وضعیت', + render: (item: Report) => { + const statusLabels: Record = { + 'pending': 'در انتظار', + 'read': 'خوانده شده', + 'resolved': 'حل شده', + 'rejected': 'رد شده', + } + return ( + + ) + } + }, + { + key: 'createdAt', + title: 'تاریخ ثبت', + render: (item: Report) => { + return formatOptionalDate(item.createdAt, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }) + } + }, + { + key: 'details', + title: '', + render: (item: Report) => { + if (!item.content || !props?.onViewContent) { + return null + } + return ( + { + e.stopPropagation() + props.onViewContent?.(item.content) + }} + /> + ) + } + }, + ] +} \ No newline at end of file diff --git a/src/pages/report/hooks/useReportData.ts b/src/pages/report/hooks/useReportData.ts new file mode 100644 index 0000000..f8efd7d --- /dev/null +++ b/src/pages/report/hooks/useReportData.ts @@ -0,0 +1,9 @@ +import * as api from "../service/ReportService"; +import { useQuery } from "@tanstack/react-query"; + +export const useGetReports = (params?: Record) => { + return useQuery({ + queryKey: ["reports", params], + queryFn: () => api.getReports(params), + }); +}; diff --git a/src/pages/report/hooks/useReportFilters.ts b/src/pages/report/hooks/useReportFilters.ts new file mode 100644 index 0000000..09a15ee --- /dev/null +++ b/src/pages/report/hooks/useReportFilters.ts @@ -0,0 +1,46 @@ +import { useState, useMemo } from "react"; +import type { FilterValues } from "@/components/Filters"; + +const DEFAULT_PAGE = 1; +const DEFAULT_LIMIT = 10; + +export const useReportFilters = () => { + const [filters, setFilters] = useState({}); + const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE); + const [limit] = useState(DEFAULT_LIMIT); + + const apiParams = useMemo(() => { + const params: Record = { + page: currentPage, + limit: limit, + }; + + if (filters.search) { + params.search = filters.search as string; + } + + if (filters.status) { + params.status = filters.status as string; + } + + return params; + }, [filters, currentPage, limit]); + + const handleFiltersChange = (newFilters: FilterValues) => { + setFilters(newFilters); + setCurrentPage(DEFAULT_PAGE); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + return { + filters, + currentPage, + apiParams, + handleFiltersChange, + handlePageChange, + limit, + }; +}; diff --git a/src/pages/report/service/ReportService.ts b/src/pages/report/service/ReportService.ts new file mode 100644 index 0000000..5cf1aa1 --- /dev/null +++ b/src/pages/report/service/ReportService.ts @@ -0,0 +1,11 @@ +import axios from "@/config/axios"; +import type { GetReportsResponse } from "../types/Types"; + +export const getReports = async ( + params?: Record +): Promise => { + const { data } = await axios.get("/admin/contact", { + params, + }); + return data; +}; diff --git a/src/pages/report/types/Types.ts b/src/pages/report/types/Types.ts new file mode 100644 index 0000000..4bf48be --- /dev/null +++ b/src/pages/report/types/Types.ts @@ -0,0 +1,14 @@ +import type { IResponse } from "@/types/response.types"; + +export interface Report { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + subject: string; + content: string; + phone: string | null; + status: string; +} + +export type GetReportsResponse = IResponse; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 369705c..f1e1a6a 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -41,6 +41,7 @@ import DetailReview from '@/pages/review/Detail' import PagersList from '@/pages/pager/List' import NotificationsList from '@/pages/notifications/List' import Notification from '@/components/Notification' +import ReportsList from '@/pages/report/List' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -99,6 +100,8 @@ const MainRouter: FC = () => { } /> } /> + + } /> diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index c09175a..5b71029 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -2,7 +2,7 @@ import { type FC, useEffect } from 'react' import LogoImage from '../assets/images/logo.svg' import LogoSmall from '../assets/images/logo-small.svg' import { useTranslation } from 'react-i18next' -import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Chart, Calendar, Security, Card, SecurityUser, TruckFast, Element3, Star1, NotificationBing } from 'iconsax-react' +import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Calendar, Security, Card, SecurityUser, TruckFast, Element3, Star1, NotificationBing, SmsEdit } from 'iconsax-react' import SideBarItem from './SideBarItem' import { useLocation } from 'react-router-dom' import { Pages } from '../config/Pages' @@ -199,13 +199,13 @@ const SideBar: FC = () => { link={Pages.announcements.list} activeName='announcements' /> - } title={t('sidebar.reports')} isActive={isActive('reports')} link={Pages.reports.list} activeName='reports' - /> + /> */} } title={t('sidebar.comments')} @@ -213,6 +213,14 @@ const SideBar: FC = () => { link={Pages.comments.list} activeName='comments' /> + + } + title={t('sidebar.reports')} + isActive={isActive('reports')} + link={Pages.reports.list} + activeName='reports' + />