create ticket admin
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
VITE_TOKEN_NAME = 'admin_token'
|
VITE_TOKEN_NAME = 'admin_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||||
# VITE_BASE_URL = 'http://192.168.1.108:4000'
|
# VITE_BASE_URL = 'http://192.168.1.113:4000'
|
||||||
@@ -100,16 +100,16 @@ const ReceiptsList: FC = () => {
|
|||||||
label: t('receip.paid'),
|
label: t('receip.paid'),
|
||||||
value: 'PAID'
|
value: 'PAID'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: <WalletMinus color={activeTab === 'OVERDUE' ? 'black' : '#8C90A3'} size={22} />,
|
|
||||||
label: t('receip.overdue'),
|
|
||||||
value: 'OVERDUE'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: <FolderOpen color={activeTab === 'ARCHIVED' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <FolderOpen color={activeTab === 'ARCHIVED' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('receip.archive'),
|
label: t('receip.archive'),
|
||||||
value: 'ARCHIVED'
|
value: 'ARCHIVED'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: <WalletMinus color={activeTab === 'OVERDUE' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
|
label: t('receip.overdue'),
|
||||||
|
value: 'OVERDUE'
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onChange={(value) => setActiveTab(value as InvoiceStatus)}
|
onChange={(value) => setActiveTab(value as InvoiceStatus)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,38 +1,115 @@
|
|||||||
import { FC } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '../../components/Input'
|
import Input from '../../components/Input'
|
||||||
import Select from '../../components/Select'
|
import Select from '../../components/Select'
|
||||||
import Textarea from '../../components/Textarea'
|
import Textarea from '../../components/Textarea'
|
||||||
import UploadBox from '../../components/UploadBox'
|
import UploadBox from '../../components/UploadBox'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { TickSquare } from 'iconsax-react'
|
import { TickCircle, TickSquare } from 'iconsax-react'
|
||||||
|
import { useCreateTicket, useGetCategories } from './hooks/useTicketData'
|
||||||
|
import { useGetProfile } from '../profile/hooks/useProfileData'
|
||||||
|
import PageLoading from '../../components/PageLoading'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { ErrorType } from '../../helpers/types'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { CreateTicketAdminType } from './types/TicketTypes'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { useMultiUpload } from '../service/hooks/useServiceData'
|
||||||
|
import { useGetCustomers } from '../customer/hooks/useCustomerData'
|
||||||
|
import { CustomerItemType } from '../customer/types/CustomerTypes'
|
||||||
|
|
||||||
const CreateTicket: FC = () => {
|
const CreateTicket: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [files, setFiles] = useState<File[]>([])
|
||||||
|
const getProfile = useGetProfile()
|
||||||
|
const getCategories = useGetCategories()
|
||||||
|
const multiUpload = useMultiUpload()
|
||||||
|
const createTicket = useCreateTicket()
|
||||||
|
const getCustomers = useGetCustomers(1, true)
|
||||||
|
|
||||||
|
|
||||||
|
const formik = useFormik<CreateTicketAdminType>({
|
||||||
|
initialValues: {
|
||||||
|
subject: '',
|
||||||
|
message: '',
|
||||||
|
categoryId: '',
|
||||||
|
priority: 'LOW',
|
||||||
|
attachmentUrls: [],
|
||||||
|
userId: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
subject: Yup.string().required(t('errors.required')),
|
||||||
|
message: Yup.string().required(t('errors.required')),
|
||||||
|
categoryId: Yup.string().required(t('errors.required')),
|
||||||
|
priority: Yup.string().required(t('errors.required')),
|
||||||
|
}),
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
const images = new FormData()
|
||||||
|
files.forEach(file => {
|
||||||
|
images.append('files', file)
|
||||||
|
})
|
||||||
|
await multiUpload.mutateAsync(images, {
|
||||||
|
onSuccess: async (data) => {
|
||||||
|
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error?.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
delete values.attachmentUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.danakServiceId === '') {
|
||||||
|
delete values.danakServiceId
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createTicket.mutate(values, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(t('success'))
|
||||||
|
formik.resetForm()
|
||||||
|
navigate(Pages.ticket.detail + data.data?.ticketId)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error?.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4 pb-12'>
|
||||||
<div>
|
<div>
|
||||||
{t('ticket.new_ticket')}
|
{t('ticket.new_ticket')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
getProfile.isPending || getCategories.isPending ?
|
||||||
|
<PageLoading />
|
||||||
|
:
|
||||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
<div className='flex gap-6 xl:mt-8 mt-4'>
|
||||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||||
<div>
|
<div>
|
||||||
{t('ticket.submit_your_message')}
|
{t('ticket.submit_your_message')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6 rowTwoInput'>
|
<div className='mt-5'>
|
||||||
<Input
|
<Select
|
||||||
label={t('ticket.name')}
|
label={t('receip.customer')}
|
||||||
readOnly
|
placeholder={t('select')}
|
||||||
placeholder='مهرداد مظفری'
|
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
||||||
/>
|
return {
|
||||||
<Input
|
label: item.firstName + ' ' + item.lastName,
|
||||||
label={t('ticket.email')}
|
value: item.id
|
||||||
readOnly
|
}
|
||||||
placeholder='Info@example.com'
|
})}
|
||||||
|
{...formik.getFieldProps('userId')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,51 +118,85 @@ const CreateTicket: FC = () => {
|
|||||||
<Input
|
<Input
|
||||||
label={t('ticket.subject')}
|
label={t('ticket.subject')}
|
||||||
placeholder={t('ticket.enter_your_subject')}
|
placeholder={t('ticket.enter_your_subject')}
|
||||||
|
name='subject'
|
||||||
|
value={formik.values.subject}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.subject && formik.errors.subject ? formik.errors.subject : ''}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label={t('ticket.subject')}
|
label={t('ticket.priority')}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: '',
|
label: t('ticket.low'),
|
||||||
value: ''
|
value: 'LOW'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ticket.medium'),
|
||||||
|
value: 'MEDIUM'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ticket.high'),
|
||||||
|
value: 'HIGH'
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
placeholder={t('ticket.priority')}
|
placeholder={t('select')}
|
||||||
className='border'
|
className='border'
|
||||||
|
name='priority'
|
||||||
|
value={formik.values.priority}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.priority && formik.errors.priority ? formik.errors.priority : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-6 rowTwoInput'>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label={t('ticket.select_your_service')}
|
label={t('ticket.category')}
|
||||||
items={[
|
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string, title: string }) => ({
|
||||||
{
|
label: item.title,
|
||||||
label: '',
|
value: item.id
|
||||||
value: ''
|
})
|
||||||
}
|
)}
|
||||||
]}
|
placeholder={t('select')}
|
||||||
placeholder={t('ticket.not_once')}
|
|
||||||
className='border'
|
className='border'
|
||||||
|
name='categoryId'
|
||||||
|
value={formik.values.categoryId}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.categoryId && formik.errors.categoryId ? formik.errors.categoryId : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<Textarea
|
<Textarea
|
||||||
label={t('description')}
|
label={t('description')}
|
||||||
placeholder={t('ticket.your_description')}
|
placeholder={t('ticket.your_description')}
|
||||||
|
name='message'
|
||||||
|
value={formik.values.message}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.message && formik.errors.message ? formik.errors.message : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<UploadBox
|
<UploadBox
|
||||||
label={t('ticket.attachment')}
|
label={t('ticket.attachment')}
|
||||||
|
isMultiple
|
||||||
|
onChange={setFiles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 flex justify-end'>
|
<div className='mt-8 flex justify-end'>
|
||||||
<Button
|
<Button
|
||||||
label={t('ticket.send')}
|
className='xl:max-w-[100px]'
|
||||||
className='max-w-[100px]'
|
onClick={() => formik.handleSubmit()}
|
||||||
/>
|
isLoading={createTicket.isPending || multiUpload.isPending}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle size={20} color='white' />
|
||||||
|
<div>
|
||||||
|
{t('ticket.send')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,12 +227,15 @@ const CreateTicket: FC = () => {
|
|||||||
<TickSquare size={20} color='black' variant='Bold' />
|
<TickSquare size={20} color='black' variant='Bold' />
|
||||||
</div>
|
</div>
|
||||||
<div className='text-description text-xs leading-5'>
|
<div className='text-description text-xs leading-5'>
|
||||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
پاسخگویی 24 ساعته تلفنی را تنها از داناک می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import * as api from "../service/TicketServiec";
|
import * as api from "../service/TicketServiec";
|
||||||
import { AddMessageTicketType, CreateCategoryType } from "../types/TicketTypes";
|
import {
|
||||||
|
AddMessageTicketType,
|
||||||
|
CreateCategoryType,
|
||||||
|
CreateTicketAdminType,
|
||||||
|
} from "../types/TicketTypes";
|
||||||
|
|
||||||
export const useGetTickets = (
|
export const useGetTickets = (
|
||||||
status: string,
|
status: string,
|
||||||
@@ -67,3 +71,10 @@ export const useOpenTicket = () => {
|
|||||||
mutationFn: (id: string) => api.openTicket(id),
|
mutationFn: (id: string) => api.openTicket(id),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateTicket = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: CreateTicketAdminType) =>
|
||||||
|
api.createTicket(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import axios from "../../../config/axios";
|
import axios from "../../../config/axios";
|
||||||
import { AddMessageTicketType, CreateCategoryType } from "../types/TicketTypes";
|
import {
|
||||||
|
AddMessageTicketType,
|
||||||
|
CreateCategoryType,
|
||||||
|
CreateTicketAdminType,
|
||||||
|
} from "../types/TicketTypes";
|
||||||
|
|
||||||
export const getTickets = async (
|
export const getTickets = async (
|
||||||
status: string,
|
status: string,
|
||||||
@@ -54,3 +58,8 @@ export const openTicket = async (id: string) => {
|
|||||||
const { data } = await axios.post(`/tickets/${id}/open`);
|
const { data } = await axios.post(`/tickets/${id}/open`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createTicket = async (params: CreateTicketAdminType) => {
|
||||||
|
const { data } = await axios.post(`/tickets/admin`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -79,3 +79,13 @@ export type CategoriesItemType = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateTicketAdminType = {
|
||||||
|
subject: string;
|
||||||
|
priority: "LOW" | "MEDIUM" | "HIGH";
|
||||||
|
danakServiceId?: string;
|
||||||
|
categoryId: string;
|
||||||
|
message: string;
|
||||||
|
attachmentUrls?: string[];
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user