Files
hossein-por-shop/frontend/pages/profile/tickets/new.vue
T
2025-03-15 01:48:32 +03:30

360 lines
14 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";
// meta
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: [],
});
// queries
const { data: orders, isLoading: ordersIsLoading } = useGetAllOrders();
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="bi:ticket" />
<ProfileSection title="ارتباط با پشتیبانی">
<template #button>
<NuxtLink :to="{ name: 'profile-tickets' }">
<Button
class="rounded-full"
size="md"
end-icon="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="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"
: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
:items="[
'https://c262408.parspack.net/media/profile_photos/Jackie_Robinson_NPG_97_135.jpg?AWSAccessKeyId=mtiSN2JWjWgyfr2u&Signature=mlUzygzyg2gQD7B5STTlgM2N%2FUM%3D&Expires=1740517316',
'https://c262408.parspack.net/media/profile_photos/Jackie_Robinson_NPG_97_135.jpg?AWSAccessKeyId=mtiSN2JWjWgyfr2u&Signature=mlUzygzyg2gQD7B5STTlgM2N%2FUM%3D&Expires=1740517316',
'https://c262408.parspack.net/media/profile_photos/Jackie_Robinson_NPG_97_135.jpg?AWSAccessKeyId=mtiSN2JWjWgyfr2u&Signature=mlUzygzyg2gQD7B5STTlgM2N%2FUM%3D&Expires=1740517316',
]"
:max="2"
size="32px"
/>
<div
class="flex flex-col items-start gap-1 text-[10px]"
>
<span
>{{
order.count
}}
محصول</span
>
<span>
شماره سفارش : {{ order.id }}
</span>
</div>
</div>
<span>
{{ order.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'
: 'bi:send'
"
/>
<span v-else>ارسال تیکت</span>
</Button>
</div>
</div>
</template>
<style scoped></style>