feedback
This commit is contained in:
@@ -43,6 +43,9 @@ export const Pages = {
|
|||||||
list: "/criticisms/list",
|
list: "/criticisms/list",
|
||||||
detail: "/ccriticisms/detail/",
|
detail: "/ccriticisms/detail/",
|
||||||
},
|
},
|
||||||
|
feedback: {
|
||||||
|
list: "/feedback/list",
|
||||||
|
},
|
||||||
learning: {
|
learning: {
|
||||||
list: "/learning/list",
|
list: "/learning/list",
|
||||||
detail: "/learning/detail/",
|
detail: "/learning/detail/",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
+2
-1
@@ -71,7 +71,8 @@
|
|||||||
"card": "حساب های بانکی",
|
"card": "حساب های بانکی",
|
||||||
"payments": "پرداخت ها",
|
"payments": "پرداخت ها",
|
||||||
"sliders": "اسلایدر",
|
"sliders": "اسلایدر",
|
||||||
"support": "پلن پشتیبانی"
|
"support": "پلن پشتیبانی",
|
||||||
|
"feedback": "گزارش ها"
|
||||||
},
|
},
|
||||||
"slider": {
|
"slider": {
|
||||||
"new_slider": "اسلایدر جدید",
|
"new_slider": "اسلایدر جدید",
|
||||||
|
|||||||
@@ -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<number>(1)
|
||||||
|
const [selectedFeedback, setSelectedFeedback] = useState<Feedback | null>(null)
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false)
|
||||||
|
const { data, isLoading } = useGetFeedbacks(page)
|
||||||
|
|
||||||
|
const handleViewFeedback = (feedback: Feedback) => {
|
||||||
|
setSelectedFeedback(feedback)
|
||||||
|
setIsModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setIsModalOpen(false)
|
||||||
|
setSelectedFeedback(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <PageLoading />
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedbacks = data?.data?.feedbacks || []
|
||||||
|
const pager = data?.data?.pager
|
||||||
|
|
||||||
|
const renderUserInfo = (feedback: Feedback) => {
|
||||||
|
const contact = feedback.email || feedback.phone || 'نامشخص'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-xs text-gray-400">{contact}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
|
<div className='flex w-full justify-between items-center'>
|
||||||
|
<div>
|
||||||
|
لیست فیدبکها
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
isLoading ?
|
||||||
|
<PageLoading />
|
||||||
|
:
|
||||||
|
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||||
|
<table className='w-full text-sm'>
|
||||||
|
<thead className='thead'>
|
||||||
|
<tr>
|
||||||
|
<Td text="کاربر" />
|
||||||
|
<Td text="IP" />
|
||||||
|
<Td text="پلتفرم" />
|
||||||
|
<Td text="سرویس" />
|
||||||
|
<Td text="محتوا" />
|
||||||
|
<Td text="امتیاز" />
|
||||||
|
<Td text="تاریخ" />
|
||||||
|
<Td text="" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
feedbacks.map((feedback: Feedback) => {
|
||||||
|
return (
|
||||||
|
<tr key={feedback.id} className='tr'>
|
||||||
|
<Td text="">
|
||||||
|
{renderUserInfo(feedback)}
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div className="text-xs text-gray-500 font-mono">
|
||||||
|
{feedback.metadata.userIp}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text={feedback.metadata?.platform ?? ''} />
|
||||||
|
<Td text="">
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="font-medium text-gray-900">
|
||||||
|
{feedback.service.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
{feedback.service.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
<Td text="">
|
||||||
|
<div className="max-w-xs">
|
||||||
|
<p className="text-sm text-gray-900 line-clamp-3">
|
||||||
|
{feedback.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
{feedback.rating}
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{formatDate(feedback.createdAt)}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
<Td text="">
|
||||||
|
<span
|
||||||
|
className='text-[blue] cursor-pointer flex items-center gap-2'
|
||||||
|
onClick={() => handleViewFeedback(feedback)}
|
||||||
|
>
|
||||||
|
<Eye size={20} color='blue' />
|
||||||
|
<span className='text-xs'>مشاهده</span>
|
||||||
|
</span>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{feedbacks.length === 0 && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-gray-500 text-lg">هیچ فیدبکی یافت نشد</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pager && pager.totalPages > 1 && (
|
||||||
|
<Pagination
|
||||||
|
totalPages={pager.totalPages}
|
||||||
|
currentPage={page}
|
||||||
|
onPageChange={setPage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* مودال نمایش جزئیات فیدبک */}
|
||||||
|
<DefaulModal
|
||||||
|
open={isModalOpen}
|
||||||
|
close={closeModal}
|
||||||
|
isHeader={true}
|
||||||
|
title_header="جزئیات فیدبک"
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
{selectedFeedback && (
|
||||||
|
<div className="space-y-6 mt-6">
|
||||||
|
{selectedFeedback.content}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DefaulModal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default List
|
||||||
@@ -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<FeedbackResponse>({
|
||||||
|
queryKey: ["feedbacks", page],
|
||||||
|
queryFn: () => api.getFeedbacks(page),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@ import PlanUsers from '../pages/support/PlanUsers'
|
|||||||
import List from '../pages/guide/List'
|
import List from '../pages/guide/List'
|
||||||
import CreateGuide from '../pages/guide/Create'
|
import CreateGuide from '../pages/guide/Create'
|
||||||
import UpdateGuide from '../pages/guide/Update.tsx'
|
import UpdateGuide from '../pages/guide/Update.tsx'
|
||||||
|
import ListFeedback from '../pages/feedback/List'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -155,6 +156,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.services.guide + ':id'} element={<List />} />
|
<Route path={Pages.services.guide + ':id'} element={<List />} />
|
||||||
<Route path={Pages.services.guide + ':id/create'} element={<CreateGuide />} />
|
<Route path={Pages.services.guide + ':id/create'} element={<CreateGuide />} />
|
||||||
<Route path={Pages.services.guide + ':id/:guideId'} element={<UpdateGuide />} />
|
<Route path={Pages.services.guide + ':id/:guideId'} element={<UpdateGuide />} />
|
||||||
|
<Route path={Pages.feedback.list} element={<ListFeedback />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { FC, useEffect } from 'react'
|
|||||||
import LogoImage from '../assets/images/logo.svg'
|
import LogoImage from '../assets/images/logo.svg'
|
||||||
import LogoSmall from '../assets/images/logo-small.svg'
|
import LogoSmall from '../assets/images/logo-small.svg'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 SideBarItem from './SideBarItem'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
@@ -176,6 +176,14 @@ const SideBar: FC = () => {
|
|||||||
activeName='blogs'
|
activeName='blogs'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SideBarItem
|
||||||
|
icon={<Message variant={isActive('feedback') ? 'Bold' : 'Outline'} color={isActive('feedback') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
|
title={t('sidebar.feedback')}
|
||||||
|
isActive={isActive('feedback')}
|
||||||
|
link={Pages.feedback.list}
|
||||||
|
activeName='services'
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user