337 lines
13 KiB
Vue
337 lines
13 KiB
Vue
<script setup lang="ts">
|
|
// imports
|
|
|
|
import useGetAllOrders from "~/composables/api/orders/useGetAllOrders";
|
|
import useCreateTicket, { type CreateTicketRequest } from "~/composables/api/tickets/useCreateTicket";
|
|
import useUploadAttachment from "~/composables/api/tickets/useUploadAttachment";
|
|
import { useToast } from "~/composables/global/useToast";
|
|
import { QUERY_KEYS } from "~/constants";
|
|
import useVuelidate from "@vuelidate/core";
|
|
import { helpers, required, minLength } from "@vuelidate/validators";
|
|
import type { GetAllOrdersRequest } from "~/composables/api/orders/useGetAllOrders";
|
|
|
|
// meta
|
|
|
|
useSeoMeta({
|
|
title: "پنل کاربری تیکت جدید",
|
|
});
|
|
|
|
definePageMeta({
|
|
middleware: "check-is-logged-in",
|
|
layout: "profile",
|
|
});
|
|
|
|
// types
|
|
|
|
type TicketCategory = {
|
|
title: string;
|
|
value: string;
|
|
};
|
|
|
|
// state
|
|
|
|
const router = useRouter();
|
|
|
|
const { $queryClient: queryClient } = useNuxtApp();
|
|
|
|
const { addToast } = useToast();
|
|
|
|
const ticketCategories: TicketCategory[] = [
|
|
{
|
|
title: "مالی و حسابداری",
|
|
value: "finance_and_accounting",
|
|
},
|
|
{
|
|
title: "پروفایل کاربری",
|
|
value: "user_profile",
|
|
},
|
|
{
|
|
title: "پیگیری سفارش",
|
|
value: "order_tracking",
|
|
},
|
|
{
|
|
title: "احراز هویت",
|
|
value: "authentication",
|
|
},
|
|
{
|
|
title: "محصول",
|
|
value: "product",
|
|
},
|
|
{
|
|
title: "اعلام باگ و خطا در وبسایت",
|
|
value: "bug_and_error_reporting",
|
|
},
|
|
{
|
|
title: "سایر",
|
|
value: "other",
|
|
},
|
|
];
|
|
|
|
const ticketData = ref<CreateTicketRequest>({
|
|
ticket_category: undefined,
|
|
order_id: undefined,
|
|
subject: "",
|
|
content: "",
|
|
attachments: [],
|
|
});
|
|
|
|
const ordersFilter = computed<GetAllOrdersRequest>(() => {
|
|
return {
|
|
sort: "created_at",
|
|
status: "RECEIVED",
|
|
page: 1,
|
|
} as any;
|
|
});
|
|
|
|
// queries
|
|
|
|
const { data: orders, isLoading: ordersIsLoading } = useGetAllOrders(ordersFilter);
|
|
|
|
const { mutateAsync: createTicket, isPending: createTicketIsPending } = useCreateTicket();
|
|
|
|
const { mutateAsync: uploadAttachment, isPending: uploadAttachmentIsPending } = useUploadAttachment();
|
|
|
|
// computed
|
|
|
|
const formRules = computed(() => {
|
|
return {
|
|
ticket_category: {
|
|
required: helpers.withMessage("فیلد دسته بندی الزامی می باشد", required),
|
|
},
|
|
subject: {
|
|
required: helpers.withMessage("فیلد عنوان تیکت الزامی می باشد", required),
|
|
minLength: helpers.withMessage("فیلد عنوان تیکت حداقل ۵ کرکتر می باشد", minLength(5)),
|
|
},
|
|
content: {
|
|
required: helpers.withMessage("فیلد متن تیکت الزامی می باشد", required),
|
|
minLength: helpers.withMessage("فیلد متن تیکت حداقل ۵ کرکتر می باشد", minLength(5)),
|
|
},
|
|
};
|
|
});
|
|
|
|
const formValidator$ = useVuelidate(formRules, ticketData);
|
|
|
|
// methods
|
|
|
|
const handleUploadAttachment = (file: File) => {
|
|
uploadAttachment(
|
|
{ file },
|
|
{
|
|
onSuccess: (data) => {
|
|
ticketData.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) {
|
|
createTicket(
|
|
{ ...ticketData.value },
|
|
{
|
|
onSuccess: () => {
|
|
router.push({ name: "profile-tickets" });
|
|
queryClient.invalidateQueries({
|
|
queryKey: [QUERY_KEYS.tickets],
|
|
});
|
|
addToast({
|
|
message: "تیکت شما با موفقیت ثبت شد",
|
|
options: {
|
|
status: "success",
|
|
description: "پس از بررسی پشتیبانی به شما اطلاع رسانی می شود",
|
|
},
|
|
});
|
|
},
|
|
onError: () => {
|
|
addToast({
|
|
message: "خطایی در ثبت تیکت رخ داد",
|
|
options: {
|
|
status: "success",
|
|
description: "لطفا مجدد تلاش کنید",
|
|
},
|
|
});
|
|
},
|
|
}
|
|
);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="w-full flex flex-col gap-5">
|
|
<ProfilePageTitle
|
|
title="تیکت جدید"
|
|
icon="ci:bi-ticket"
|
|
/>
|
|
|
|
<ProfileSection title="ارتباط با پشتیبانی">
|
|
<template #button>
|
|
<NuxtLink :to="{ name: 'profile-tickets' }">
|
|
<Button
|
|
class="rounded-full"
|
|
size="md"
|
|
end-icon="ci:bi-arrow-left"
|
|
>
|
|
بازگشت به تیکت ها
|
|
</Button>
|
|
</NuxtLink>
|
|
</template>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
|
<DataField
|
|
id="category"
|
|
:required="true"
|
|
label="دسته بندی"
|
|
:error="formValidator$.ticket_category"
|
|
>
|
|
<Select
|
|
placeholder="انتخاب کنید"
|
|
variant="outlined"
|
|
:error="formValidator$.ticket_category.$error"
|
|
v-model="ticketData.ticket_category"
|
|
>
|
|
<template #content>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="(category, index) in ticketCategories"
|
|
:key="index"
|
|
class="text-xs leading-none w-full rounded-sm py-5 flex items-center justify-between h-[25px] pr-[12px] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-slate-300 data-[highlighted]:text-black"
|
|
:value="category.value"
|
|
>
|
|
<SelectItemIndicator
|
|
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
|
|
>
|
|
<Icon
|
|
name="ci:bi-check"
|
|
size="20"
|
|
/>
|
|
</SelectItemIndicator>
|
|
<SelectItemText class="text-end font-iran-yekan-x text-sm">
|
|
{{ category.title }}
|
|
</SelectItemText>
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</template>
|
|
</Select>
|
|
</DataField>
|
|
<DataField
|
|
id="orders"
|
|
:required="true"
|
|
label="خرید یا سفارش"
|
|
>
|
|
<Select
|
|
placeholder="انتخاب کنید"
|
|
variant="outlined"
|
|
v-model="ticketData.order_id"
|
|
:loading="ordersIsLoading"
|
|
>
|
|
<template #trigger>
|
|
<SelectValue
|
|
:class="ticketData.order_id ? 'text-black' : 'text-slate-400'"
|
|
class="font-iran-yekan-x text-sm text-start placeholder-slate-400"
|
|
>
|
|
{{ ticketData.order_id ? `شماره سفارش : ${ticketData.order_id}` : "وارد نشده" }}
|
|
</SelectValue>
|
|
</template>
|
|
|
|
<template #content>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="(order, index) in orders?.results"
|
|
:key="index"
|
|
class="text-xs leading-none w-full rounded-sm py-5 flex items-center justify-between h-[25px] px-[12px] shrink-0 relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-slate-300 data-[highlighted]:text-black"
|
|
:value="order.id"
|
|
>
|
|
<SelectItemText
|
|
class="w-full text-end font-iran-yekan-x text-sm flex items-center justify-between"
|
|
>
|
|
<div class="flex items-center gap-4">
|
|
<AvatarGroup
|
|
v-if="order.images.length > 0"
|
|
:items="order.images"
|
|
:max="2"
|
|
size="32px"
|
|
/>
|
|
|
|
<div class="flex items-start gap-1 text-[10px]">
|
|
<span>{{ order.count }} محصول</span>
|
|
|
|
|
<span>
|
|
شماره سفارش :
|
|
{{ order.id }}#
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span class="text-[10px]">
|
|
{{ order.verbose_status }}
|
|
</span>
|
|
</SelectItemText>
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</template>
|
|
</Select>
|
|
</DataField>
|
|
<DataField
|
|
id="subject"
|
|
:required="true"
|
|
label="عنوان تیکت"
|
|
class="col-span-full"
|
|
:error="formValidator$.subject"
|
|
>
|
|
<Input
|
|
v-model="ticketData.subject"
|
|
placeholder="عنوان تیکت را اینجا بنویسید ..."
|
|
variant="outlined"
|
|
:error="formValidator$.subject.$error"
|
|
/>
|
|
</DataField>
|
|
<DataField
|
|
id="message"
|
|
:required="true"
|
|
label="متن تیکت"
|
|
:error="formValidator$.content"
|
|
>
|
|
<Textarea
|
|
v-model="ticketData.content"
|
|
:error="formValidator$.content.$error"
|
|
class="h-[10rem] lg:h-[20rem]"
|
|
variant="outlined"
|
|
placeholder="متن تیکت را اینجا بنویسید ..."
|
|
/>
|
|
</DataField>
|
|
<FileInput
|
|
v-model="ticketData.attachments"
|
|
@change="handleUploadAttachment"
|
|
:loading="uploadAttachmentIsPending"
|
|
/>
|
|
</div>
|
|
</ProfileSection>
|
|
<div class="w-full flex-center py-5">
|
|
<Button
|
|
@click="handleSubmit"
|
|
:loading="createTicketIsPending || uploadAttachmentIsPending"
|
|
class="rounded-full w-[14rem] h-11"
|
|
size="md"
|
|
>
|
|
<Icon
|
|
v-if="createTicketIsPending"
|
|
:name="createTicketIsPending ? 'svg-spinners:3-dots-bounce' : 'ci:bi-send'"
|
|
/>
|
|
<span v-else>ارسال تیکت</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|