ticket console

This commit is contained in:
hamid zarghami
2025-10-25 10:45:26 +03:30
parent 9c73185a13
commit 3615c72a93
12 changed files with 1331 additions and 1 deletions
+325
View File
@@ -0,0 +1,325 @@
import { type FC, Fragment, useState } from 'react'
// import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Textarea from '@/components/Textarea'
import UploadBox from '../../components/UploadBox'
import Button from '../../components/Button'
import { CloseCircle, InfoCircle, Paperclip2, Send2 } from 'iconsax-react'
import { useNavigate } from 'react-router-dom'
import { type MessageType, type AttachmentType } from './types/TicketTypes'
// import { useAddMessageTicket, useCloseTicket, useGetMessages, useMultiUpload } from './hooks/useTicketData'
import { staticTicketDetailData } from './data/staticData'
// import PageLoading from '../../components/PageLoading'
import moment from 'moment-jalaali'
// import { useFormik } from 'formik'
// import { type AddMessageTicketType } from './types/TicketTypes'
// import * as Yup from 'yup'
// import { toast } from '../../components/Toast'
// import { type ErrorType } from '../../helpers/types'
import { clx } from '../../helpers/utils'
import { fa } from '@/locale/fa'
// import { Pages } from '../../config/Pages'
// import { Helmet } from 'react-helmet-async'
const TicketDetail: FC = () => {
// const { t } = useTranslation('global')
const t = (key: string) => {
const keys = key.split('.');
let value: unknown = fa;
for (const k of keys) {
value = (value as Record<string, unknown>)?.[k];
}
return (value as string) || key;
}
const navigate = useNavigate()
// const { id: _id } = useParams()
const [, setFiles] = useState<File[]>([])
const [percent] = useState<number>(0)
// const getMessages = useGetMessages(id ? id : '')
// const addMessage = useAddMessageTicket(id ? id : '')
// const multiUpload = useMultiUpload(setPercent)
// const closeTicket = useCloseTicket()
// استفاده از داده‌های استاتیک
const getMessages = {
isPending: false,
data: staticTicketDetailData
}
const addMessage = {
isPending: false,
mutate: () => { },
mutateAsync: () => Promise.resolve()
}
const multiUpload = {
isPending: false,
mutateAsync: () => Promise.resolve()
}
const closeTicket = {
isPending: false,
mutate: () => { }
}
const [isResetFiles] = useState<boolean>(false)
// Mock formik
const formik = {
values: {
content: '',
attachmentUrls: [],
},
handleChange: () => { },
handleSubmit: () => { },
resetForm: () => { },
touched: { content: false },
errors: { content: '' }
}
const hadleCloseTicket = () => {
closeTicket.mutate()
navigate('/tickets')
}
const handleSendAnswer = () => {
const messageEndRef = document.getElementById('content');
if (messageEndRef) {
messageEndRef.scrollIntoView({ behavior: 'smooth' });
messageEndRef.focus();
}
}
return (
<div className='mt-4'>
{/* <Helmet>
<title>
داناک | تیکت
</title>
</Helmet> */}
<div className='flex gap-1'>
<div>{t('ticket.ticket_number')}</div>
</div>
{
getMessages.isPending ?
<div>Loading...</div>
:
<div className='flex xl:flex-row flex-col-reverse gap-6 xl:mt-8 mt-6'>
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<Input
label={t('ticket.subject')}
value={getMessages.data?.data?.ticket?.subject}
readOnly
/>
<div className='gap-6 rowTwoInput mt-5'>
<Input
label={t('ticket.select_your_service')}
value={getMessages.data?.data?.ticket?.danakService?.name}
readOnly
/>
<Input
label={t('ticket.user_name')}
value={getMessages.data?.data?.ticket?.user?.firstName + ' ' + getMessages.data?.data?.ticket?.user?.lastName}
readOnly
/>
</div>
{
getMessages.data?.data?.messages.map((item: MessageType) => {
if (item?.author?.roles[0]?.name === 'user') {
return (
<div key={item.id} className='mt-6 xl:text-sm text-xs bg-[#F6F7FA] p-6 rounded-3xl rounded-tr-none xl:max-w-[70%] max-w-[90%]'>
<div className='leading-7'>
{item.content}
</div>
<div className='flex dltr end mt-6 text-xs text-description'>
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
<div className='flex gap-2 flex-wrap text-sm'>
{
item.attachments && item?.attachments?.map((attachment: AttachmentType, index: number) => {
return (
<div className='text-[#0047FF] mt-2 flex gap-1 items-center' key={attachment.id}>
<Paperclip2 size={20} color='#0047FF' />
<a href={attachment.attachmentUrl} target='_blank' rel='noreferrer'>
{t('attach') + ' ' + Number(index + 1)}
</a>
</div>
)
})
}
</div>
</div>
)
} else {
return (
<div className='w-full flex justify-end' key={item.id}>
<div className='mt-6 xl:text-sm text-xs bg-[#EBEDF5] p-6 rounded-3xl rounded-tl-none xl:max-w-[70%] max-w-[90%]'>
<div className='flex gap-1'>
<div className='font-bold'>
{t('ticket.expert')}
</div>
<div>{item.author?.firstName + ' ' + item?.author?.lastName}</div>
</div>
<div className='leading-7 mt-4'>
{item.content}
</div>
<div className='flex dltr end mt-6 text-xs text-description'>
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
</div>
<div className='flex gap-2 flex-wrap text-sm'>
{
item.attachments && item?.attachments?.map((attachment: AttachmentType, index: number) => {
return (
<div className='text-[#0047FF] mt-2 flex gap-1 items-center' key={attachment.id}>
<Paperclip2 size={20} color='#0047FF' />
<a href={attachment.attachmentUrl} target='_blank' rel='noreferrer'>
{t('attach') + ' ' + Number(index + 1)}
</a>
</div>
)
})
}
</div>
</div>
)
}
})
}
{
getMessages.data?.data?.ticket?.status !== 'CLOSED' &&
<Fragment>
<div className='mt-9'>
<Textarea
id='content'
placeholder={t('ticket.your_description')}
name='content'
value={formik.values.content}
onChange={formik.handleChange}
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
/>
</div>
<div className='mt-7'>
<UploadBox
label={t('ticket.attachment')}
onChange={setFiles}
isMultiple
isReset={isResetFiles}
/>
</div>
<div className='flex justify-end mt-6'>
<Button
className='xl:max-w-[100px]'
onClick={() => formik.handleSubmit()}
percentage={percent}
isLoading={addMessage.isPending || multiUpload.isPending}
>
<div className='flex gap-2 items-center'>
<Send2 size={20} color='black' />
<div>
{t('ticket.send')}
</div>
</div>
</Button>
</div>
</Fragment>
}
<div className='h-8'></div>
</div>
<div className={`h-fit lg:sticky left-0 lg:top-0 xl:w-[300px] w-full`}>
{
getMessages.data?.data?.ticket?.status !== 'CLOSED' &&
<div className='bg-white xl:p-6 p-4 rounded-3xl flex gap-4'>
<Button
label={t('ticket.send_answer')}
className='text-xs xl:h-10 h-8'
onClick={handleSendAnswer}
/>
<Button
className='bg-[#D52903] xl:h-10 h-8'
onClick={hadleCloseTicket}
isLoading={closeTicket.isPending}
>
<div className='flex gap-2 text-xs items-center'>
<CloseCircle
color='white'
size={20}
/>
<div className='text-white'>
{t('ticket.close_ticket')}
</div>
</div>
</Button>
</div>
}
<div className={clx(
'bg-white p-6 rounded-3xl mt-8',
getMessages.data?.data?.ticket?.status === 'CLOSED' && 'mt-0'
)}>
<div className='flex justify-between'>
<div className='text-sm'>
{t('ticket.info_ticket')}
</div>
<div className={clx(
'h-7 px-3 rounded-xl flex items-center text-xs bg-green-100 text-green-400',
getMessages.data?.data?.ticket?.status === 'PENDING' && 'bg-orange-100 text-orange-700',
getMessages.data?.data?.ticket?.status === 'CLOSED' && 'bg-red-100 text-red-400'
)}>
{t(`ticket.${getMessages.data?.data?.ticket?.status}`)}
</div>
</div>
<div className='mt-14 text-xs w-full'>
<div className='flex justify-between text-description'>
<div>{t('ticket.ticket_number')}</div>
<div>
{getMessages.data?.data?.ticket?.numericId}
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.asignto')}</div>
<div>
{getMessages.data?.data?.ticket?.assignedTo ? getMessages.data?.data?.ticket?.assignedTo?.firstName + ' ' + getMessages.data?.data?.ticket?.assignedTo?.lastName : ''}
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('date')}</div>
<div className='dltr'>
{moment(getMessages.data?.data?.ticket?.createdAt).format('jYYYY/jMM/jDD HH:mm')}
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.category')}</div>
<div>
{getMessages.data?.data?.ticket?.category?.title}
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.priority')}</div>
<div>
{getMessages.data?.data?.ticket?.priority === 'low' ? t('ticket.low') : getMessages.data?.data?.ticket?.priority === 'medium' ? t('ticket.medium') : t('ticket.high')}
</div>
</div>
</div>
<div className='mt-10 border-t text-xs pt-5 border-border items-center flex gap-2'>
<InfoCircle size={20} color='black' />
<div>
{t('ticket.update_sms')}
</div>
</div>
</div>
</div>
</div>
}
</div>
)
}
export default TicketDetail