From fffe4112c6fbb36c202edd89e667cb26159cd557 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Thu, 21 Aug 2025 12:37:02 +0330 Subject: [PATCH] feedback --- src/config/Pages.ts | 3 + src/helpers/func.ts | 27 ++++ src/langs/fa.json | 3 +- src/pages/feedback/List.tsx | 163 ++++++++++++++++++++ src/pages/feedback/hooks/useFeedBackData.ts | 10 ++ src/pages/feedback/service/Service.ts | 6 + src/pages/feedback/types/FeedbackTypes.ts | 89 +++++++++++ src/router/Main.tsx | 2 + src/shared/SideBar.tsx | 10 +- 9 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 src/helpers/func.ts create mode 100644 src/pages/feedback/List.tsx create mode 100644 src/pages/feedback/hooks/useFeedBackData.ts create mode 100644 src/pages/feedback/service/Service.ts create mode 100644 src/pages/feedback/types/FeedbackTypes.ts diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 1d10353..f801936 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -43,6 +43,9 @@ export const Pages = { list: "/criticisms/list", detail: "/ccriticisms/detail/", }, + feedback: { + list: "/feedback/list", + }, learning: { list: "/learning/list", detail: "/learning/detail/", diff --git a/src/helpers/func.ts b/src/helpers/func.ts new file mode 100644 index 0000000..c3f4700 --- /dev/null +++ b/src/helpers/func.ts @@ -0,0 +1,27 @@ +export const formatDate = (dateString: string): string => { + try { + const date = new Date(dateString); + return new Intl.DateTimeFormat("fa-IR", { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(date); + } catch (error) { + return dateString; + } +}; + +export const formatDateShort = (dateString: string): string => { + try { + const date = new Date(dateString); + return new Intl.DateTimeFormat("fa-IR", { + year: "numeric", + month: "short", + day: "numeric", + }).format(date); + } catch (error) { + return dateString; + } +}; diff --git a/src/langs/fa.json b/src/langs/fa.json index c42ddf7..3acc481 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -71,7 +71,8 @@ "card": "حساب های بانکی", "payments": "پرداخت ها", "sliders": "اسلایدر", - "support": "پلن پشتیبانی" + "support": "پلن پشتیبانی", + "feedback": "گزارش ها" }, "slider": { "new_slider": "اسلایدر جدید", diff --git a/src/pages/feedback/List.tsx b/src/pages/feedback/List.tsx new file mode 100644 index 0000000..4510f9a --- /dev/null +++ b/src/pages/feedback/List.tsx @@ -0,0 +1,163 @@ +import { FC, useState } from 'react' +import { useGetFeedbacks } from './hooks/useFeedBackData' +import { Feedback } from './types/FeedbackTypes' +import PageLoading from '../../components/PageLoading' +import Pagination from '../../components/Pagination' +import Td from '../../components/Td' +import { formatDate } from '../../helpers/func' +import { Eye } from 'iconsax-react' +import DefaulModal from '../../components/DefaulModal' + +const List: FC = () => { + const [page, setPage] = useState(1) + const [selectedFeedback, setSelectedFeedback] = useState(null) + const [isModalOpen, setIsModalOpen] = useState(false) + const { data, isLoading } = useGetFeedbacks(page) + + const handleViewFeedback = (feedback: Feedback) => { + setSelectedFeedback(feedback) + setIsModalOpen(true) + } + + const closeModal = () => { + setIsModalOpen(false) + setSelectedFeedback(null) + } + + if (isLoading) { + return + } + + const feedbacks = data?.data?.feedbacks || [] + const pager = data?.data?.pager + + const renderUserInfo = (feedback: Feedback) => { + const contact = feedback.email || feedback.phone || 'نامشخص' + + return ( +
+
{contact}
+
+ ) + } + + return ( +
+
+
+ لیست فیدبک‌ها +
+
+ + { + isLoading ? + + : +
+ + + + + + + { + feedbacks.map((feedback: Feedback) => { + return ( + + + + + + + + + + + + + ) + }) + } + +
+ + + + + + + +
+ {renderUserInfo(feedback)} + +
+ {feedback.metadata.userIp} +
+
+ +
+
+ {feedback.service.name} +
+
+ {feedback.service.title} +
+
+
+
+

+ {feedback.content} +

+
+
+ {feedback.rating} + +
+ {formatDate(feedback.createdAt)} +
+
+ handleViewFeedback(feedback)} + > + + مشاهده + +
+ + {feedbacks.length === 0 && ( +
+
هیچ فیدبکی یافت نشد
+
+ )} + + {pager && pager.totalPages > 1 && ( + + )} +
+ } + + {/* مودال نمایش جزئیات فیدبک */} + + {selectedFeedback && ( +
+ {selectedFeedback.content} + +
+ )} +
+
+ ) +} + +export default List \ No newline at end of file diff --git a/src/pages/feedback/hooks/useFeedBackData.ts b/src/pages/feedback/hooks/useFeedBackData.ts new file mode 100644 index 0000000..8b7bdbd --- /dev/null +++ b/src/pages/feedback/hooks/useFeedBackData.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/Service"; +import { FeedbackResponse } from "../types/FeedbackTypes"; + +export const useGetFeedbacks = (page: number) => { + return useQuery({ + queryKey: ["feedbacks", page], + queryFn: () => api.getFeedbacks(page), + }); +}; diff --git a/src/pages/feedback/service/Service.ts b/src/pages/feedback/service/Service.ts new file mode 100644 index 0000000..ade651b --- /dev/null +++ b/src/pages/feedback/service/Service.ts @@ -0,0 +1,6 @@ +import axios from "../../../config/axios"; + +export const getFeedbacks = async (page: number) => { + const { data } = await axios.get(`/danak-services/feedback?page=${page}`); + return data; +}; diff --git a/src/pages/feedback/types/FeedbackTypes.ts b/src/pages/feedback/types/FeedbackTypes.ts new file mode 100644 index 0000000..407752b --- /dev/null +++ b/src/pages/feedback/types/FeedbackTypes.ts @@ -0,0 +1,89 @@ +export interface FeedbackUser { + id: string; + createdAt: string; + updatedAt: string; + email: string | null; + phone: string; + userName: string; + firstName: string; + lastName: string; + birthDate: string; + nationalCode: string; + profilePic: string | null; + emailVerified: boolean; + financialType: string | null; + deletedAt: string | null; +} + +export interface FeedbackService { + id: string; + createdAt: string; + updatedAt: string; + name: string; + title: string; + pageTitle: string; + pageDescription: string; + slug: string; + isDanakSuggest: boolean; + description: string; + author: string; + createDate: string; + userCount: number; + serviceLanguage: string; + softwareLanguage: string; + metaDescription: string; + isActive: boolean; + showInSlider: boolean; + link: string; + icon: string; + coverUrl: string; + deletedAt: string | null; +} + +export interface FeedbackMetadata { + userIp: string; + referer: string; + userAgent: string; + acceptLanguage: string; + acceptEncoding: string; + contentType: string; + origin: string; + host: string; + xForwardedFor: string; + xRealIp: string; + timestamp: string; + platform?: string; + mobile?: string; +} + +export interface Feedback { + id: string; + createdAt: string; + updatedAt: string; + content: string; + rating: number; + metadata: FeedbackMetadata; + user: FeedbackUser; + phone: string | null; + email: string | null; + service: FeedbackService; + deletedAt: string | null; +} + +export interface FeedbackPager { + page: number; + limit: number; + totalItems: number; + totalPages: number; + prevPage: boolean; + nextPage: boolean; +} + +export interface FeedbackResponse { + statusCode: number; + success: boolean; + data: { + pager: FeedbackPager; + feedbacks: Feedback[]; + }; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 6c515d2..d55e604 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -72,6 +72,7 @@ import PlanUsers from '../pages/support/PlanUsers' import List from '../pages/guide/List' import CreateGuide from '../pages/guide/Create' import UpdateGuide from '../pages/guide/Update.tsx' +import ListFeedback from '../pages/feedback/List' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -155,6 +156,7 @@ const MainRouter: FC = () => { } /> } /> } /> + } /> diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index b99d592..36bddab 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -2,7 +2,7 @@ import { 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 { Card, Code, CodeCircle, DocumentLike, DocumentText, Element3, Gallery, Headphone, Home2, Logout, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare } from 'iconsax-react' +import { Card, Code, CodeCircle, DocumentLike, DocumentText, Element3, Gallery, Headphone, Home2, Logout, Message, Messages3, Money3, NotificationStatus, People, Profile, Receipt21, Setting2, SmsTracking, Teacher, TicketDiscount, UserSquare } from 'iconsax-react' import SideBarItem from './SideBarItem' import { useLocation } from 'react-router-dom' import { Pages } from '../config/Pages' @@ -176,6 +176,14 @@ const SideBar: FC = () => { activeName='blogs' /> + } + title={t('sidebar.feedback')} + isActive={isActive('feedback')} + link={Pages.feedback.list} + activeName='services' + /> + {