From cb2a6daa2950e8e25fe30b3af48e33b60c1ee1b8 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 24 Feb 2026 15:40:50 +0330 Subject: [PATCH] ticket detail --- src/locale/fa.ts | 2 + src/pages/ticket/Detail.tsx | 396 +++++++--------------- src/pages/ticket/MessageBubble.tsx | 50 +++ src/pages/ticket/ReplyForm.tsx | 54 +++ src/pages/ticket/TicketDetailActions.tsx | 33 ++ src/pages/ticket/TicketInfoSidebar.tsx | 83 +++++ src/pages/ticket/detailUtils.ts | 39 +++ src/pages/ticket/service/TicketService.ts | 6 +- src/pages/ticket/types/TicketTypes.ts | 27 ++ 9 files changed, 421 insertions(+), 269 deletions(-) create mode 100644 src/pages/ticket/MessageBubble.tsx create mode 100644 src/pages/ticket/ReplyForm.tsx create mode 100644 src/pages/ticket/TicketDetailActions.tsx create mode 100644 src/pages/ticket/TicketInfoSidebar.tsx create mode 100644 src/pages/ticket/detailUtils.ts diff --git a/src/locale/fa.ts b/src/locale/fa.ts index 8d07c4c..783bad9 100644 --- a/src/locale/fa.ts +++ b/src/locale/fa.ts @@ -66,6 +66,7 @@ export const fa = { status: "وضعیت", priority: "اولویت", detail: "جزئیات", + open: "باز", PENDING: "در حال بررسی", ANSWERED: "پاسخ داده شده", CLOSED: "بسته شده", @@ -75,6 +76,7 @@ export const fa = { subject: "موضوع", select_your_service: "انتخاب سرویس شما", user_name: "نام کاربر", + customer: "کاربر", expert: "کارشناس", answer: "پاسخ", your_description: "توضیحات شما", diff --git a/src/pages/ticket/Detail.tsx b/src/pages/ticket/Detail.tsx index ed21799..4d7b124 100644 --- a/src/pages/ticket/Detail.tsx +++ b/src/pages/ticket/Detail.tsx @@ -1,294 +1,158 @@ -import { type FC, Fragment, useState } from 'react' -import Input from '../../components/Input' -import Textarea from '@/components/Textarea' -import UploadBox from '../../components/UploadBox' -import Button from '../../components/Button' -import { CloseCircle, InfoCircle, Paperclip2, Send2 } from 'iconsax-react' -import { useNavigate, useParams } from 'react-router-dom' -import { type MessageType, type AttachmentType } from './types/TicketTypes' -import { useGetTicketById, useCreateOrReplyTicket } from './hooks/useTicketData' -import moment from 'moment-jalaali' -import { clx } from '../../helpers/utils' -import { fa } from '@/locale/fa' -import { Paths } from '@/config/Paths' +import { type FC, useMemo, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import Input from "@/components/Input"; +import { Paths } from "@/config/Paths"; +import { clx } from "@/helpers/utils"; +import { t } from "@/locale"; +import { getTicketDisplayName } from "./types/TicketTypes"; +import type { TicketDetailType } from "./types/TicketTypes"; +import { useCreateOrReplyTicket, useGetTicketById } from "./hooks/useTicketData"; +import { buildThreadItems, getStatusClass } from "./detailUtils"; +import MessageBubble from "./MessageBubble"; +import ReplyForm from "./ReplyForm"; +import TicketInfoSidebar from "./TicketInfoSidebar"; +import TicketDetailActions from "./TicketDetailActions"; + +const STATUS_CLOSED = "closed"; const TicketDetail: FC = () => { - const t = (key: string) => { - const keys = key.split('.'); - let value: unknown = fa; - for (const k of keys) { - value = (value as Record)?.[k]; - } - return (value as string) || key; - } - const navigate = useNavigate() - const { id } = useParams<{ id: string }>() - const [content, setContent] = useState('') - const [files, setFiles] = useState([]) + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [content, setContent] = useState(""); + const [files, setFiles] = useState([]); - const getTicket = useGetTicketById(id ?? '') - const addMessage = useCreateOrReplyTicket() + const getTicket = useGetTicketById(id ?? ""); + const addMessage = useCreateOrReplyTicket(); - const raw = getTicket.data?.data - const ticket = raw?.ticket ?? raw - const messages: MessageType[] = raw?.messages ?? raw?.ticket?.messages ?? [] + const ticket = getTicket.data?.data; + const threadItems = useMemo( + () => (ticket ? buildThreadItems(ticket) : []), + [ticket] + ); + const isClosed = + ticket?.status?.toLowerCase() === STATUS_CLOSED || + ticket?.status?.toUpperCase() === "CLOSED"; + + const handleCloseTicket = () => navigate(Paths.tickets.list); const handleSendReply = () => { - if (!id || !content.trim()) return + if (!id || !ticket || !content.trim()) return; addMessage.mutate( { parentId: id, - subject: (ticket as { subject?: string })?.subject ?? '', + subject: ticket.subject, content: content.trim(), attachments: files.length ? files.map(() => ({})) : [], }, { onSuccess: () => { - setContent('') - setFiles([]) - getTicket.refetch() + setContent(""); + setFiles([]); + getTicket.refetch(); }, } - ) + ); + }; + + const handleScrollToReply = () => { + document.getElementById("content")?.scrollIntoView({ behavior: "smooth" }); + document.getElementById("content")?.focus(); + }; + + if (getTicket.isPending) { + return ( +
+
{t("ticket.ticket_number")}
+
Loading...
+
+ ); } - const hadleCloseTicket = () => { - navigate(Paths.tickets.list) + if (!ticket) { + return ( +
+
{t("ticket.ticket_number")}
+
{t("ticket.info_ticket")}
+
+ ); } - const handleSendAnswer = () => { - const messageEndRef = document.getElementById('content') - if (messageEndRef) { - messageEndRef.scrollIntoView({ behavior: 'smooth' }) - messageEndRef.focus() - } - } + const extendedTicket = ticket as TicketDetailType & { + danakService?: { name?: string }; + numericId?: string; + }; return ( -
-
-
{t('ticket.ticket_number')}
+
+
+ {t("ticket.ticket_number")}: + {extendedTicket.numericId ?? ticket.id} + + {t(`ticket.${ticket.status}`) || t(`ticket.${ticket.status?.toUpperCase()}`)} +
- { - getTicket.isPending ? -
Loading...
- : -
-
+
+
+ +
+ {extendedTicket.danakService && ( -
- - -
- - { - messages.map((item: MessageType) => { - if (item?.author?.roles[0]?.name === 'user') { - return ( -
-
- {item.content} -
-
- {moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')} -
- -
- { - item.attachments && item?.attachments?.map((attachment: AttachmentType, index: number) => { - return ( - - ) - }) - } -
-
- ) - } else { - return ( -
-
-
-
- {t('ticket.expert')} -
-
{item.author?.firstName + ' ' + item?.author?.lastName}
-
-
- {item.content} -
-
- {moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')} -
-
- -
- { - item.attachments && item?.attachments?.map((attachment: AttachmentType, index: number) => { - return ( - - ) - }) - } -
-
- ) - } - }) - } - - - - { - (ticket as { status?: string })?.status !== 'CLOSED' && - -
-