Files
hossein-por-shop/frontend/pages/profile/tickets/[id].vue
T

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">
دسته&zwnj;بندی :
</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>