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