create ticket admin

This commit is contained in:
hamid zarghami
2025-05-25 10:33:22 +03:30
parent 08d66eec06
commit 4bb95b4d08
6 changed files with 253 additions and 109 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
VITE_TOKEN_NAME = 'admin_token'
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
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'
+5 -5
View File
@@ -100,16 +100,16 @@ const ReceiptsList: FC = () => {
label: t('receip.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} />,
label: t('receip.archive'),
value: 'ARCHIVED'
},
{
icon: <WalletMinus color={activeTab === 'OVERDUE' ? 'black' : '#8C90A3'} size={22} />,
label: t('receip.overdue'),
value: 'OVERDUE'
},
]}
onChange={(value) => setActiveTab(value as InvoiceStatus)}
/>
+215 -101
View File
@@ -1,127 +1,241 @@
import { FC } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Select from '../../components/Select'
import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox'
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 { 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 (
<div className='mt-4'>
<div className='mt-4 pb-12'>
<div>
{t('ticket.new_ticket')}
</div>
<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>
{t('ticket.submit_your_message')}
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('ticket.name')}
readOnly
placeholder='مهرداد مظفری'
/>
<Input
label={t('ticket.email')}
readOnly
placeholder='Info@example.com'
/>
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('ticket.subject')}
placeholder={t('ticket.enter_your_subject')}
/>
<Select
label={t('ticket.subject')}
items={[
{
label: '',
value: ''
}
]}
placeholder={t('ticket.priority')}
className='border'
/>
</div>
<div className='mt-6'>
<Select
label={t('ticket.select_your_service')}
items={[
{
label: '',
value: ''
}
]}
placeholder={t('ticket.not_once')}
className='border'
/>
</div>
<div className='mt-6'>
<Textarea
label={t('description')}
placeholder={t('ticket.your_description')}
/>
</div>
<div className='mt-6'>
<UploadBox
label={t('ticket.attachment')}
/>
</div>
<div className='mt-8 flex justify-end'>
<Button
label={t('ticket.send')}
className='max-w-[100px]'
/>
</div>
</div>
<div className='bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm'>
{t('ticket.title_hint')}
</div>
<div className='mt-6'>
<div className='flex items-start gap-2 border-b pb-5'>
{
getProfile.isPending || getCategories.isPending ?
<PageLoading />
:
<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>
<TickSquare size={20} color='black' variant='Bold' />
{t('ticket.submit_your_message')}
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
<div className='mt-5'>
<Select
label={t('receip.customer')}
placeholder={t('select')}
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
return {
label: item.firstName + ' ' + item.lastName,
value: item.id
}
})}
{...formik.getFieldProps('userId')}
/>
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('ticket.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
label={t('ticket.priority')}
items={[
{
label: t('ticket.low'),
value: 'LOW'
},
{
label: t('ticket.medium'),
value: 'MEDIUM'
},
{
label: t('ticket.high'),
value: 'HIGH'
}
]}
placeholder={t('select')}
className='border'
name='priority'
value={formik.values.priority}
onChange={formik.handleChange}
error_text={formik.touched.priority && formik.errors.priority ? formik.errors.priority : ''}
/>
</div>
<div className='mt-6 rowTwoInput'>
<Select
label={t('ticket.category')}
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string, title: string }) => ({
label: item.title,
value: item.id
})
)}
placeholder={t('select')}
className='border'
name='categoryId'
value={formik.values.categoryId}
onChange={formik.handleChange}
error_text={formik.touched.categoryId && formik.errors.categoryId ? formik.errors.categoryId : ''}
/>
</div>
<div className='mt-6'>
<Textarea
label={t('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 className='mt-6'>
<UploadBox
label={t('ticket.attachment')}
isMultiple
onChange={setFiles}
/>
</div>
<div className='mt-8 flex justify-end'>
<Button
className='xl: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 className='flex items-start gap-2 mt-6 border-b pb-5'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
<div className='bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm'>
{t('ticket.title_hint')}
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
</div>
</div>
<div className='flex items-start gap-2 mt-6'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
<div className='mt-6'>
<div className='flex items-start gap-2 border-b pb-5'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
</div>
</div>
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
</div>
</div>
<div className='flex items-start gap-2 mt-6'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
پاسخگویی 24 ساعته تلفنی را تنها از داناک می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
)
+12 -1
View File
@@ -1,6 +1,10 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/TicketServiec";
import { AddMessageTicketType, CreateCategoryType } from "../types/TicketTypes";
import {
AddMessageTicketType,
CreateCategoryType,
CreateTicketAdminType,
} from "../types/TicketTypes";
export const useGetTickets = (
status: string,
@@ -67,3 +71,10 @@ export const useOpenTicket = () => {
mutationFn: (id: string) => api.openTicket(id),
});
};
export const useCreateTicket = () => {
return useMutation({
mutationFn: (variables: CreateTicketAdminType) =>
api.createTicket(variables),
});
};
+10 -1
View File
@@ -1,5 +1,9 @@
import axios from "../../../config/axios";
import { AddMessageTicketType, CreateCategoryType } from "../types/TicketTypes";
import {
AddMessageTicketType,
CreateCategoryType,
CreateTicketAdminType,
} from "../types/TicketTypes";
export const getTickets = async (
status: string,
@@ -54,3 +58,8 @@ export const openTicket = async (id: string) => {
const { data } = await axios.post(`/tickets/${id}/open`);
return data;
};
export const createTicket = async (params: CreateTicketAdminType) => {
const { data } = await axios.post(`/tickets/admin`, params);
return data;
};
+10
View File
@@ -79,3 +79,13 @@ export type CategoriesItemType = {
createdAt: string;
updatedAt: string;
};
export type CreateTicketAdminType = {
subject: string;
priority: "LOW" | "MEDIUM" | "HIGH";
danakServiceId?: string;
categoryId: string;
message: string;
attachmentUrls?: string[];
userId: string;
};