feedback
This commit is contained in:
@@ -43,6 +43,9 @@ export const Pages = {
|
||||
list: "/criticisms/list",
|
||||
detail: "/ccriticisms/detail/",
|
||||
},
|
||||
feedback: {
|
||||
list: "/feedback/list",
|
||||
},
|
||||
learning: {
|
||||
list: "/learning/list",
|
||||
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": "حساب های بانکی",
|
||||
"payments": "پرداخت ها",
|
||||
"sliders": "اسلایدر",
|
||||
"support": "پلن پشتیبانی"
|
||||
"support": "پلن پشتیبانی",
|
||||
"feedback": "گزارش ها"
|
||||
},
|
||||
"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 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 = () => {
|
||||
<Route path={Pages.services.guide + ':id'} element={<List />} />
|
||||
<Route path={Pages.services.guide + ':id/create'} element={<CreateGuide />} />
|
||||
<Route path={Pages.services.guide + ':id/:guideId'} element={<UpdateGuide />} />
|
||||
<Route path={Pages.feedback.list} element={<ListFeedback />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user