close ticket

This commit is contained in:
hamid zarghami
2026-02-24 16:43:32 +03:30
parent cb2a6daa29
commit 17b577b0d6
5 changed files with 44 additions and 12 deletions
+13 -5
View File
@@ -1,12 +1,11 @@
import { type FC, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { 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 { useCloseTicket, useCreateOrReplyTicket, useGetTicketById } from "./hooks/useTicketData";
import { buildThreadItems, getStatusClass } from "./detailUtils";
import MessageBubble from "./MessageBubble";
import ReplyForm from "./ReplyForm";
@@ -16,13 +15,13 @@ import TicketDetailActions from "./TicketDetailActions";
const STATUS_CLOSED = "closed";
const TicketDetail: FC = () => {
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
const [content, setContent] = useState("");
const [files, setFiles] = useState<File[]>([]);
const getTicket = useGetTicketById(id ?? "");
const addMessage = useCreateOrReplyTicket();
const closeTicket = useCloseTicket();
const ticket = getTicket.data?.data;
const threadItems = useMemo(
@@ -33,7 +32,15 @@ const TicketDetail: FC = () => {
ticket?.status?.toLowerCase() === STATUS_CLOSED ||
ticket?.status?.toUpperCase() === "CLOSED";
const handleCloseTicket = () => navigate(Paths.tickets.list);
const handleCloseTicket = () => {
if (!id) return;
closeTicket.mutate(
{ id, params: { status: "closed" } },
{
onSuccess: () => getTicket.refetch(),
}
);
};
const handleSendReply = () => {
if (!id || !ticket || !content.trim()) return;
@@ -146,6 +153,7 @@ const TicketDetail: FC = () => {
<TicketDetailActions
onScrollToReply={handleScrollToReply}
onCloseTicket={handleCloseTicket}
isClosing={closeTicket.isPending}
/>
)}
<TicketInfoSidebar ticket={ticket} />
+4 -1
View File
@@ -6,11 +6,13 @@ import { t } from "@/locale";
export type TicketDetailActionsProps = {
onScrollToReply: () => void;
onCloseTicket: () => void;
isClosing?: boolean;
};
const TicketDetailActions: FC<TicketDetailActionsProps> = ({
onScrollToReply,
onCloseTicket,
isClosing = false,
}) => (
<div className="bg-white xl:p-6 p-4 rounded-3xl flex gap-4">
<Button
@@ -21,10 +23,11 @@ const TicketDetailActions: FC<TicketDetailActionsProps> = ({
<Button
className="bg-[#D52903] xl:h-10 h-8"
onClick={onCloseTicket}
disabled={isClosing}
>
<div className="flex gap-2 text-xs items-center text-white">
<CloseCircle color="white" size={20} />
<span>{t("ticket.close_ticket")}</span>
<span>{isClosing ? "..." : t("ticket.close_ticket")}</span>
</div>
</Button>
</div>
+13 -2
View File
@@ -1,6 +1,11 @@
import * as api from "../service/TicketService";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { type AddMessageTicketType, type CreateTicketPayload, type CreateTicketType } from "../types/TicketTypes";
import {
type AddMessageTicketType,
type CloseTicketParamsType,
type CreateTicketPayload,
type CreateTicketType,
} from "../types/TicketTypes";
export const useGetTickets = (status?: string) => {
return useQuery({
@@ -82,7 +87,13 @@ export const useAddMessageTicket = (id: string) => {
export const useCloseTicket = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => api.closeTicket(id),
mutationFn: ({
id,
params,
}: {
id: string;
params: CloseTicketParamsType;
}) => api.closeTicket(id, params),
onSuccess: () => {
queryClient.refetchQueries({
queryKey: ["tickets"],
+3 -2
View File
@@ -2,6 +2,7 @@ import type { AxiosProgressEvent } from "axios";
import axios from "@/config/axios";
import {
type AddMessageTicketType,
type CloseTicketParamsType,
type CreateTicketPayload,
type CreateTicketType,
type GetTicketByIdResponseType,
@@ -92,7 +93,7 @@ export const addMessageTicket = async (
return data;
};
export const closeTicket = async (id: string) => {
const { data } = await axios.post(`/tickets/${id}/close`);
export const closeTicket = async (id: string, params: CloseTicketParamsType) => {
const { data } = await axios.patch(`/public/tickets/${id}/status`, params);
return data;
};
+11 -2
View File
@@ -32,7 +32,10 @@ export type TicketListItemType = {
export type GetTicketsResponseType = BaseResponse<TicketListItemType[]>;
/** آیتم تیکت در children (user به‌صورت id یا null، admin برای تشخیص پیام ادمین/کاربر) */
export type TicketDetailChildType = Omit<TicketListItemType, "user" | "children"> & {
export type TicketDetailChildType = Omit<
TicketListItemType,
"user" | "children"
> & {
user: string | null;
/** null = پیام از کاربر، مقدار دارد = پیام از ادمین/کارشناس */
admin: string | null;
@@ -96,4 +99,10 @@ export type TicketThreadItemType = {
};
export const getTicketDisplayName = (user: TicketUserType): string =>
[user.firstName, user.lastName].filter(Boolean).join(" ").trim() || user.phone || "-";
[user.firstName, user.lastName].filter(Boolean).join(" ").trim() ||
user.phone ||
"-";
export type CloseTicketParamsType = {
status: string;
};