318 lines
11 KiB
Vue
318 lines
11 KiB
Vue
<script setup lang="ts">
|
|
// imports
|
|
|
|
import useCreateMessage, {
|
|
type CreateMessageRequest,
|
|
} from "~/composables/api/tickets/useCreateMessage";
|
|
import useGetTicket from "~/composables/api/tickets/useGetTicket";
|
|
import useUploadAttachment from "~/composables/api/tickets/useUploadAttachment";
|
|
import { useToast } from "~/composables/global/useToast";
|
|
import useVuelidate from "@vuelidate/core";
|
|
import { helpers, required, minLength } from "@vuelidate/validators";
|
|
import { QUERY_KEYS } from "~/constants";
|
|
|
|
// meta
|
|
|
|
useSeoMeta({
|
|
title : "پنل کاربری تیکت"
|
|
});
|
|
|
|
definePageMeta({
|
|
middleware: "check-is-logged-in",
|
|
layout: "profile",
|
|
});
|
|
|
|
// computed
|
|
|
|
const ticketId = computed(() => route.params.id as string);
|
|
|
|
// state
|
|
|
|
const route = useRoute();
|
|
|
|
const { $queryClient: queryClient } = useNuxtApp();
|
|
|
|
const { addToast } = useToast();
|
|
|
|
const messagesContainerRefEl = ref<HTMLElement | null>(null);
|
|
|
|
const { y: messagesContainerRefElY } = useScroll(messagesContainerRefEl, {
|
|
behavior: "smooth",
|
|
});
|
|
|
|
const messageData = ref<CreateMessageRequest>({
|
|
content: "",
|
|
attachments: [],
|
|
ticket_id: ticketId.value,
|
|
});
|
|
|
|
// computed
|
|
|
|
const formRules = computed(() => {
|
|
return {
|
|
content: {
|
|
required: helpers.withMessage(
|
|
"فیلد متن تیکت الزامی می باشد",
|
|
required
|
|
),
|
|
minLength: helpers.withMessage(
|
|
"فیلد متن تیکت حداقل ۵ کرکتر می باشد",
|
|
minLength(5)
|
|
),
|
|
},
|
|
};
|
|
});
|
|
|
|
const formValidator$ = useVuelidate(formRules, messageData);
|
|
|
|
// queries
|
|
|
|
const { data: ticket, isLoading: ticketIsLoading } = useGetTicket(ticketId);
|
|
|
|
const { mutateAsync: uploadAttachment, isPending: uploadAttachmentIsPending } =
|
|
useUploadAttachment();
|
|
|
|
const { mutateAsync: createMessage, isPending: createMessageIsPending } =
|
|
useCreateMessage();
|
|
|
|
// methods
|
|
|
|
const handleAddNewMessageToMessages = async () => {
|
|
await ticket.value?.messages.push({
|
|
id: 0,
|
|
content: messageData.value.content,
|
|
is_user: true,
|
|
attachments: messageData.value.attachments,
|
|
created_at: Date.now().toString(),
|
|
});
|
|
};
|
|
|
|
const handleUploadAttachment = (file: File) => {
|
|
uploadAttachment(
|
|
{ file },
|
|
{
|
|
onSuccess: (data) => {
|
|
messageData.value.attachments.push({ ...data });
|
|
},
|
|
onError: (error) => {
|
|
addToast({
|
|
message: error.message
|
|
? error.message
|
|
: "خطایی در آپلود پیوست رخ داد",
|
|
options: {
|
|
status: "error",
|
|
description: "لطفا مجدد تلاش کنید",
|
|
},
|
|
});
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
await formValidator$.value.$validate();
|
|
if (!formValidator$.value.$errors.length) {
|
|
handleAddNewMessageToMessages().then(() => {
|
|
setTimeout(() => {
|
|
messagesContainerRefElY.value =
|
|
messagesContainerRefEl.value?.scrollHeight!;
|
|
}, 500);
|
|
});
|
|
createMessage(
|
|
{ ...messageData.value },
|
|
{
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: [QUERY_KEYS.ticket],
|
|
});
|
|
addToast({
|
|
message: "پیام شما با موفقیت ثبت شد",
|
|
options: {
|
|
status: "success",
|
|
description:
|
|
"پس از بررسی پشتیبانی به شما اطلاع رسانی می شود",
|
|
},
|
|
});
|
|
messageData.value = {
|
|
content: "",
|
|
attachments: [],
|
|
ticket_id: ticketId.value,
|
|
};
|
|
formValidator$.value.$reset();
|
|
},
|
|
onError: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: [QUERY_KEYS.ticket],
|
|
});
|
|
addToast({
|
|
message: "خطایی در ثبت پیام رخ داد",
|
|
options: {
|
|
status: "success",
|
|
description: "لطفا مجدد تلاش کنید",
|
|
},
|
|
});
|
|
},
|
|
}
|
|
);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="w-full flex flex-col gap-5">
|
|
<ProfilePageTitle :title="`تیکت شماره ${ticketId}`" icon="ci:bi-ticket" />
|
|
|
|
<div
|
|
class="flex flex-wrap items-center justify-between w-full py-4 border-b lg:px-5 border-slate-200"
|
|
>
|
|
<div
|
|
class="flex flex-col items-start w-1/2 gap-4 lg:gap-5 lg:w-full lg:items-center lg:justify-between lg:flex-row"
|
|
>
|
|
<div class="flex items-center w-full gap-4">
|
|
<div
|
|
class="flex flex-col w-full gap-2 lg:py-2 lg:pe-5 lg:w-1/3 lg:border-e border-slate-200"
|
|
>
|
|
<p class="text-xs lg:text-sm text-dynamic-secondary">
|
|
دسته‌بندی :
|
|
</p>
|
|
<Skeleton
|
|
v-if="ticketIsLoading"
|
|
class="!w-1/2 !h-5 !rounded-sm"
|
|
/>
|
|
<p
|
|
v-else
|
|
class="text-xs font-semibold lg:text-sm text-black"
|
|
>
|
|
{{ ticket?.ticket_category }}
|
|
</p>
|
|
</div>
|
|
<div
|
|
class="flex flex-col w-full gap-2 lg:py-2 lg:pe-5 lg:w-1/3"
|
|
>
|
|
<p class="text-xs lg:text-sm text-dynamic-secondary">
|
|
وضعیت :
|
|
</p>
|
|
<Skeleton
|
|
v-if="ticketIsLoading"
|
|
class="!w-1/2 !h-5 !rounded-sm"
|
|
/>
|
|
<p
|
|
v-else
|
|
class="text-xs font-semibold lg:text-sm text-black text-dynamic-secondary"
|
|
>
|
|
{{ ticket?.status }}
|
|
</p>
|
|
</div>
|
|
<div
|
|
class="flex flex-col w-full gap-2 lg:hidden border-black"
|
|
>
|
|
<p class="text-xs lg:text-sm text-dynamic-secondary">
|
|
موضوع :
|
|
</p>
|
|
<p class="text-xs font-semibold lg:text-sm text-black">
|
|
{{ ticket?.subject }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="items-center justify-end hidden w-1/4 lg:flex">
|
|
<NuxtLink :to="{ name: 'profile-tickets' }">
|
|
<Button
|
|
class="rounded-full"
|
|
size="md"
|
|
end-icon="ci:bi-arrow-left"
|
|
>
|
|
بازگشت به تیکت ها
|
|
</Button>
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="hidden w-full text-sm px-5 pb-6 mt-1 border-b lg:block text-md border-slate-200"
|
|
>
|
|
<span class="text-black/50"> موضوع : </span>
|
|
<Skeleton v-if="ticketIsLoading" class="!w-1/2 !h-5 !rounded-sm" />
|
|
<span v-else>
|
|
{{ ticket?.subject }}
|
|
</span>
|
|
</div>
|
|
|
|
<div
|
|
ref="messagesContainerRefEl"
|
|
class="w-full flex flex-col gap-5 h-[32rem] overflow-y-auto"
|
|
>
|
|
<template v-if="ticketIsLoading">
|
|
<TicketBubbleLoading
|
|
v-for="i in 4"
|
|
:key="i"
|
|
:is_user="i % 2 != 0 ? true : false"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<TicketBubble
|
|
v-for="(message, index) in ticket?.messages"
|
|
:key="index"
|
|
:is_user="message.is_user"
|
|
:date="message.created_at"
|
|
:message="message.content"
|
|
:files="message.attachments"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<div
|
|
v-if="ticket?.status !== 'بسته شده' && !ticketIsLoading"
|
|
class="w-full flex flex-col gap-5"
|
|
>
|
|
<div
|
|
class="grid grid-cols-1 lg:grid-cols-2 gap-5 pt-5 border-t border-slate-200"
|
|
>
|
|
<DataField
|
|
id="message"
|
|
:required="true"
|
|
label="متن پیام"
|
|
:error="formValidator$.content"
|
|
>
|
|
<Textarea
|
|
v-model="messageData.content"
|
|
:error="formValidator$.content.$error"
|
|
class="h-[10rem] lg:h-[20rem]"
|
|
variant="outlined"
|
|
placeholder="متن پیام را اینجا بنویسید ..."
|
|
/>
|
|
</DataField>
|
|
<FileInput
|
|
v-model="messageData.attachments"
|
|
@change="handleUploadAttachment"
|
|
:loading="uploadAttachmentIsPending"
|
|
/>
|
|
</div>
|
|
|
|
<div class="w-full flex-center py-5">
|
|
<Button
|
|
@click="handleSubmit"
|
|
:loading="
|
|
createMessageIsPending || uploadAttachmentIsPending
|
|
"
|
|
class="rounded-full w-[14rem] h-11"
|
|
size="md"
|
|
>
|
|
<Icon
|
|
v-if="createMessageIsPending"
|
|
:name="
|
|
createMessageIsPending
|
|
? 'svg-spinners:3-dots-bounce'
|
|
: 'ci:bi-send'
|
|
"
|
|
/>
|
|
<span v-else>ارسال پیام</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|