ticket
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,45 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 201.8 46.7" xml:space="preserve" class="logo-shape dark" style="enable-background:new 0 0 201.8 46.7;"><g><g><path d="M51,38.6h-1.9v5.1h-0.8v-5.1h-1.9V38H51L51,38.6L51,38.6z" class="st0"></path> <path d="M57.6,43.8c-0.6,0-1.1-0.2-1.4-0.6c-0.3-0.4-0.5-0.9-0.5-1.5v-0.2c0-0.6,0.2-1.1,0.5-1.5s0.8-0.6,1.3-0.6
|
||||||
|
c0.6,0,1,0.2,1.3,0.5s0.4,0.8,0.4,1.4v0.4h-2.7l0,0c0,0.4,0.1,0.8,0.3,1c0.2,0.3,0.5,0.4,0.9,0.4c0.3,0,0.5,0,0.7-0.1
|
||||||
|
c0.2-0.1,0.4-0.2,0.5-0.3l0.3,0.5c-0.1,0.1-0.4,0.3-0.6,0.4C58.3,43.7,58,43.8,57.6,43.8z M57.5,40c-0.3,0-0.5,0.1-0.7,0.3
|
||||||
|
c-0.2,0.2-0.3,0.5-0.3,0.8l0,0h1.9V41c0-0.3-0.1-0.6-0.2-0.8S57.8,40,57.5,40z" class="st0"></path> <path d="M66.3,43.2c0.2,0,0.5-0.1,0.6-0.2s0.3-0.4,0.3-0.6h0.7l0,0c0,0.4-0.1,0.7-0.5,1c-0.3,0.3-0.7,0.4-1.1,0.4
|
||||||
|
c-0.6,0-1.1-0.2-1.4-0.6c-0.3-0.4-0.5-0.9-0.5-1.5v-0.2c0-0.6,0.2-1.1,0.5-1.5s0.8-0.6,1.4-0.6c0.5,0,0.9,0.1,1.2,0.4
|
||||||
|
c0.3,0.3,0.5,0.7,0.4,1.1l0,0h-0.7c0-0.3-0.1-0.5-0.3-0.7S66.5,40,66.3,40c-0.4,0-0.7,0.1-0.8,0.4c-0.2,0.3-0.3,0.6-0.3,1.1v0.2
|
||||||
|
c0,0.4,0.1,0.8,0.3,1.1C65.6,43,65.9,43.2,66.3,43.2z" class="st0"></path> <path d="M74,40c0.1-0.2,0.3-0.4,0.5-0.5s0.5-0.2,0.7-0.2c0.5,0,0.8,0.1,1.1,0.4s0.4,0.7,0.4,1.3v2.7H76V41
|
||||||
|
c0-0.4-0.1-0.6-0.2-0.8C75.6,40.1,75.3,40,75,40c-0.2,0-0.4,0-0.6,0.1c-0.2,0.1-0.3,0.2-0.4,0.4v3.2h-0.8v-6.1H74L74,40L74,40z" class="st0"></path> <path d="M83,39.4l0.1,0.6c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.5-0.2,0.7-0.2c0.5,0,0.8,0.1,1.1,0.4s0.4,0.7,0.4,1.3
|
||||||
|
v2.7H85V41c0-0.4-0.1-0.6-0.2-0.8c-0.1-0.2-0.4-0.2-0.7-0.2c-0.2,0-0.4,0-0.6,0.1c-0.2,0.1-0.3,0.2-0.4,0.4v3.2h-0.8v-4.3L83,39.4
|
||||||
|
L83,39.4z" class="st0"></path> <path d="M91.2,41.5c0-0.6,0.2-1.1,0.5-1.6s0.8-0.6,1.4-0.6c0.6,0,1.1,0.2,1.4,0.6s0.5,0.9,0.5,1.6v0.1
|
||||||
|
c0,0.6-0.2,1.2-0.5,1.6s-0.8,0.6-1.4,0.6c-0.6,0-1-0.2-1.4-0.6C91.4,42.8,91.2,42.3,91.2,41.5L91.2,41.5z M92,41.6
|
||||||
|
c0,0.5,0.1,0.8,0.3,1.1s0.5,0.4,0.9,0.4c0.4,0,0.6-0.1,0.8-0.4s0.3-0.7,0.3-1.1v-0.1c0-0.5-0.1-0.8-0.3-1.1
|
||||||
|
c-0.2-0.3-0.5-0.5-0.9-0.5s-0.6,0.1-0.8,0.5S92,41.1,92,41.6L92,41.6z" class="st0"></path> <path d="M101.3,43.7h-0.8v-6.1h0.8V43.7z" class="st0"></path> <path d="M106.8,41.5c0-0.6,0.2-1.1,0.5-1.6s0.8-0.6,1.4-0.6c0.6,0,1.1,0.2,1.4,0.6s0.5,0.9,0.5,1.6v0.1
|
||||||
|
c0,0.6-0.2,1.2-0.5,1.6s-0.8,0.6-1.4,0.6s-1-0.2-1.4-0.6C107,42.8,106.8,42.3,106.8,41.5L106.8,41.5z M107.6,41.6
|
||||||
|
c0,0.5,0.1,0.8,0.3,1.1s0.5,0.4,0.9,0.4c0.4,0,0.6-0.1,0.8-0.4s0.3-0.7,0.3-1.1v-0.1c0-0.5-0.1-0.8-0.3-1.1s-0.5-0.5-0.9-0.5
|
||||||
|
s-0.6,0.1-0.8,0.5S107.6,41.1,107.6,41.6L107.6,41.6z" class="st0"></path> <path d="M115.9,41.7c0-0.7,0.1-1.2,0.4-1.7s0.7-0.6,1.2-0.6c0.3,0,0.5,0,0.7,0.2c0.2,0.1,0.4,0.3,0.5,0.5l0.1-0.6h0.6
|
||||||
|
v4.3c0,0.5-0.2,1-0.5,1.3s-0.8,0.4-1.4,0.4c-0.2,0-0.4,0-0.7-0.1c-0.2-0.1-0.5-0.1-0.6-0.2l0.1-0.6c0.1,0.1,0.3,0.1,0.5,0.2
|
||||||
|
c0.2,0,0.4,0.1,0.6,0.1c0.4,0,0.6-0.1,0.8-0.3c0.2-0.2,0.3-0.4,0.3-0.8v-0.5c-0.1,0.2-0.3,0.3-0.5,0.4c-0.2,0.1-0.4,0.1-0.7,0.1
|
||||||
|
c-0.5,0-0.9-0.2-1.2-0.6S115.9,42.4,115.9,41.7L115.9,41.7L115.9,41.7z M116.7,41.7c0,0.4,0.1,0.8,0.3,1s0.5,0.4,0.8,0.4
|
||||||
|
c0.2,0,0.4,0,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.5v-2c-0.1-0.2-0.2-0.3-0.4-0.4c-0.2-0.1-0.4-0.2-0.6-0.2c-0.4,0-0.6,0.1-0.8,0.5
|
||||||
|
S116.7,41.2,116.7,41.7L116.7,41.7L116.7,41.7z" class="st0"></path> <path d="M126.4,42.1l0.1,0.5h0l1-3.2h0.9l-1.8,4.9c-0.1,0.3-0.3,0.5-0.5,0.8c-0.2,0.2-0.5,0.3-0.8,0.3
|
||||||
|
c-0.1,0-0.1,0-0.2,0c-0.1,0-0.2,0-0.2,0l0.1-0.6c0,0,0,0,0.1,0c0.1,0,0.2,0,0.2,0c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.3,0.3-0.5
|
||||||
|
l0.2-0.5l-1.6-4.2h0.9L126.4,42.1z" class="st0"></path> <path d="M144.4,41.9L144.4,41.9c0,0.6-0.2,1-0.6,1.4s-0.9,0.5-1.5,0.5c-0.6,0-1.2-0.2-1.6-0.7s-0.6-1-0.6-1.8v-1
|
||||||
|
c0-0.7,0.2-1.3,0.6-1.8s0.9-0.7,1.6-0.7s1.2,0.2,1.5,0.5c0.4,0.3,0.6,0.8,0.6,1.4l0,0h-0.8c0-0.4-0.1-0.7-0.4-1
|
||||||
|
c-0.2-0.2-0.6-0.4-1-0.4s-0.8,0.2-1,0.5c-0.3,0.4-0.4,0.8-0.4,1.3v1c0,0.5,0.1,1,0.4,1.3s0.6,0.5,1,0.5c0.4,0,0.8-0.1,1-0.4
|
||||||
|
s0.4-0.5,0.4-1L144.4,41.9L144.4,41.9z" class="st0"></path> <path d="M149.6,41.5c0-0.6,0.2-1.1,0.5-1.6s0.8-0.6,1.4-0.6c0.6,0,1.1,0.2,1.4,0.6s0.5,0.9,0.5,1.6v0.1
|
||||||
|
c0,0.6-0.2,1.2-0.5,1.6s-0.8,0.6-1.4,0.6s-1-0.2-1.4-0.6C149.8,42.8,149.6,42.3,149.6,41.5L149.6,41.5z M150.4,41.6
|
||||||
|
c0,0.5,0.1,0.8,0.3,1.1s0.5,0.4,0.9,0.4c0.4,0,0.6-0.1,0.8-0.4s0.3-0.7,0.3-1.1v-0.1c0-0.5-0.1-0.8-0.3-1.1s-0.5-0.5-0.9-0.5
|
||||||
|
s-0.6,0.1-0.8,0.5S150.4,41.1,150.4,41.6L150.4,41.6z" class="st0"></path> <path d="M159.5,39.4l0,0.6c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.5-0.2,0.8-0.2s0.5,0.1,0.7,0.2s0.4,0.3,0.5,0.6
|
||||||
|
c0.1-0.3,0.3-0.4,0.5-0.6c0.2-0.1,0.5-0.2,0.8-0.2c0.4,0,0.8,0.1,1,0.5c0.3,0.3,0.4,0.8,0.4,1.3v2.5H164v-2.6
|
||||||
|
c0-0.4-0.1-0.7-0.2-0.9c-0.1-0.2-0.4-0.3-0.6-0.3c-0.3,0-0.5,0.1-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.7v0v2.7h-0.8v-2.6
|
||||||
|
c0-0.4-0.1-0.7-0.2-0.9s-0.4-0.3-0.6-0.3c-0.2,0-0.4,0-0.6,0.1s-0.3,0.2-0.4,0.4v3.2h-0.8v-4.3L159.5,39.4L159.5,39.4z" class="st0"></path> <path d="M174,41.7c0,0.6-0.1,1.1-0.4,1.5s-0.7,0.6-1.2,0.6c-0.3,0-0.5,0-0.7-0.1s-0.4-0.2-0.5-0.4v2.1h-0.8v-5.9h0.6
|
||||||
|
l0.1,0.5c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.5-0.2,0.7-0.2c0.5,0,0.9,0.2,1.2,0.6S174,41,174,41.7L174,41.7L174,41.7z M173.2,41.7
|
||||||
|
c0-0.5-0.1-0.9-0.3-1.2s-0.5-0.5-0.8-0.5c-0.2,0-0.4,0-0.6,0.1s-0.3,0.2-0.4,0.4v2.1c0.1,0.2,0.2,0.3,0.4,0.4s0.4,0.1,0.6,0.1
|
||||||
|
c0.4,0,0.6-0.1,0.8-0.4C173.1,42.5,173.2,42.2,173.2,41.7L173.2,41.7L173.2,41.7z" class="st0"></path> <path d="M182.1,43.7c0-0.1,0-0.2-0.1-0.3s0-0.2,0-0.3c-0.1,0.2-0.3,0.4-0.6,0.5s-0.5,0.2-0.8,0.2
|
||||||
|
c-0.4,0-0.8-0.1-1-0.3s-0.4-0.5-0.4-0.9c0-0.4,0.2-0.7,0.5-1c0.3-0.2,0.8-0.3,1.3-0.3h0.8v-0.4c0-0.3-0.1-0.5-0.2-0.6
|
||||||
|
s-0.4-0.2-0.6-0.2c-0.3,0-0.5,0.1-0.6,0.2c-0.2,0.1-0.2,0.3-0.2,0.5l-0.7,0l0,0c0-0.3,0.1-0.6,0.4-0.9s0.7-0.4,1.2-0.4
|
||||||
|
c0.5,0,0.9,0.1,1.2,0.4c0.3,0.3,0.4,0.6,0.4,1.1v2c0,0.1,0,0.3,0,0.4c0,0.1,0,0.3,0.1,0.4L182.1,43.7L182.1,43.7z M180.8,43.1
|
||||||
|
c0.3,0,0.5-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.4-0.5v-0.7h-0.9c-0.3,0-0.6,0.1-0.8,0.2s-0.3,0.3-0.3,0.6c0,0.2,0.1,0.3,0.2,0.5
|
||||||
|
S180.5,43.1,180.8,43.1z" class="st0"></path> <path d="M189.1,39.4l0.1,0.6c0.1-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.5-0.2,0.7-0.2c0.5,0,0.8,0.1,1.1,0.4s0.4,0.7,0.4,1.3
|
||||||
|
v2.7H191V41c0-0.4-0.1-0.6-0.2-0.8c-0.1-0.2-0.4-0.2-0.7-0.2c-0.2,0-0.4,0-0.6,0.1c-0.2,0.1-0.3,0.2-0.4,0.4v3.2h-0.8v-4.3
|
||||||
|
L189.1,39.4L189.1,39.4z" class="st0"></path> <path d="M198.7,42.1l0.1,0.5h0l1-3.2h0.9l-1.8,4.9c-0.1,0.3-0.3,0.5-0.5,0.8s-0.5,0.3-0.8,0.3c-0.1,0-0.1,0-0.2,0
|
||||||
|
c-0.1,0-0.2,0-0.2,0l0.1-0.6c0,0,0,0,0.1,0s0.2,0,0.2,0c0.2,0,0.3-0.1,0.4-0.2s0.2-0.3,0.3-0.5l0.2-0.5l-1.6-4.2h0.9L198.7,42.1z" class="st0"></path></g></g> <g><path d="M46.4,31.9V9.9h6.2c1.9,0,3.6,0.4,5.1,1.3c1.5,0.9,2.6,2.1,3.4,3.6c0.8,1.6,1.2,3.4,1.2,5.4v1.4
|
||||||
|
c0,2.1-0.4,3.9-1.2,5.5c-0.8,1.6-2,2.8-3.4,3.6s-3.2,1.3-5.2,1.3H46.4z M49.3,12.2v17.3h3.1c2.2,0,4-0.7,5.2-2.1
|
||||||
|
c1.3-1.4,1.9-3.4,1.9-6v-1.3c0-2.5-0.6-4.5-1.8-5.9s-2.9-2.1-5-2.1L49.3,12.2L49.3,12.2z" class="st0"></path> <path d="M131.9,31.9H129l-11.1-17v17h-2.9V9.9h2.9L129,26.9V9.9h2.9L131.9,31.9L131.9,31.9z" class="st0"></path> <path d="M189.6,21.7l-2.7,2.8v7.5H184V9.8h2.9v10.9l9.8-10.9h3.5l-8.7,9.7l9.4,12.3h-3.5L189.6,21.7z" class="st0"></path> <polygon points="88.9,13.4 95.6,31.9 98.6,31.9 90.1,9.8 87.6,9.8 79.2,31.9 82.2,31.9 " class="st0"></polygon> <polygon points="157.9,13.4 164.7,31.9 167.6,31.9 159.2,9.8 156.7,9.8 148.2,31.9 151.2,31.9 " class="st0"></polygon></g> <path d="M27.5,30.2c0,0-5.5,5.3-10.1,9.8v6.1c-0.5,0.4,16.4-15.9,16.4-15.9L17.4,13v6.5L27.5,30.2z" class="st0"></path> <g><rect x="9.1" y="13.1" transform="matrix(0.7298 -0.6837 0.6837 0.7298 -14.5845 13.0725)" width="0.3" height="23.8" class="st0"></rect> <rect x="9.9" y="13.4" transform="matrix(0.7287 -0.6849 0.6849 0.7287 -13.8472 13.4064)" width="0.3" height="21.6" class="st0"></rect> <polygon points="17.3,30.5 4,16.4 4.2,16.2 17.5,30.3 " class="st0"></polygon> <polygon points="17.3,28.9 5.5,16.5 5.7,16.3 17.5,28.7 " class="st0"></polygon> <polygon points="17.2,27.2 7,16.5 7.2,16.3 17.5,27.1 " class="st0"></polygon></g> <g><polygon points="1.3,16.5 1.1,16.3 17.3,0.5 17.5,0.7 " class="st0"></polygon> <polygon points="2.8,16.5 2.6,16.3 17.3,2 17.4,2.2 " class="st0"></polygon> <polygon points="4.3,16.5 4.1,16.3 17.2,3.5 17.4,3.7 " class="st0"></polygon> <polygon points="5.8,16.5 5.6,16.3 17.2,5 17.4,5.2 " class="st0"></polygon> <polygon points="7.4,16.5 7.2,16.3 17.2,6.5 17.4,6.7 " class="st0"></polygon></g></svg>
|
||||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,68 @@
|
|||||||
|
import { FC, useState } from 'react'
|
||||||
|
import DefaulModal from './DefaulModal'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from './Button'
|
||||||
|
import Textarea from './Textarea'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isLoading?: boolean,
|
||||||
|
close: () => void,
|
||||||
|
isOpen: boolean,
|
||||||
|
onConfrim: (text?: string) => void,
|
||||||
|
label?: string,
|
||||||
|
isHasDescription?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalConfrim: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [description, setDescription] = useState<string>('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaulModal
|
||||||
|
open={props.isOpen}
|
||||||
|
close={props.close}
|
||||||
|
title_header={t('confrim.subject')}
|
||||||
|
isHeader
|
||||||
|
>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='text-sm text-center'>
|
||||||
|
{
|
||||||
|
props.label ?
|
||||||
|
props.label
|
||||||
|
:
|
||||||
|
t('confrim.content')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
props.isHasDescription &&
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Textarea
|
||||||
|
placeholder={t('description')}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
value={description}
|
||||||
|
className='bg-transparent border border-gray-500 rounded-xl w-[300px] p-2'
|
||||||
|
label=''
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-4 justify-center mt-10'>
|
||||||
|
<Button
|
||||||
|
label={t('confrim.yes')}
|
||||||
|
onClick={() => props.onConfrim(props.isHasDescription ? description : undefined)}
|
||||||
|
isLoading={props.isLoading}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t('confrim.cancel')}
|
||||||
|
className='bg-transparent text-black border border-primary'
|
||||||
|
onClick={props.close}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalConfrim
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import Logo from '../assets/images/logo_orig.svg'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const PageLoading: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex bg-transparent w-fit mx-auto flex-col gap-4 items-center'>
|
||||||
|
<img src={Logo} alt='logo' className='w-28' />
|
||||||
|
<div className='text-xs text-gray-700'>
|
||||||
|
{t('loading')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageLoading
|
||||||
@@ -2,6 +2,7 @@ import { FC, InputHTMLAttributes } from 'react'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string,
|
label: string,
|
||||||
|
error_text?: string,
|
||||||
} & InputHTMLAttributes<HTMLTextAreaElement>
|
} & InputHTMLAttributes<HTMLTextAreaElement>
|
||||||
|
|
||||||
const Textarea: FC<Props> = (props: Props) => {
|
const Textarea: FC<Props> = (props: Props) => {
|
||||||
|
|||||||
+70
-7
@@ -99,7 +99,10 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"last_announcement": "آخرین اطلاعیه ها",
|
"last_announcement": "آخرین اطلاعیه ها",
|
||||||
"see_all": "دیدن همه",
|
"see_all": "دیدن همه",
|
||||||
"companies": "شرکت ها"
|
"companies": "شرکت ها",
|
||||||
|
"request_pending": "درخواست شما برای ثبت شرکت، هنوز تایید نشده است",
|
||||||
|
"request_approved": "درخواست شما برای ثبت شرکت، تایید شد.",
|
||||||
|
"request_rejected": "درخواست شما برای ثبت شرکت، رد شده است."
|
||||||
},
|
},
|
||||||
"all": "همه",
|
"all": "همه",
|
||||||
"service": {
|
"service": {
|
||||||
@@ -172,12 +175,27 @@
|
|||||||
"send_answer": "ارسال پاسخ",
|
"send_answer": "ارسال پاسخ",
|
||||||
"close_ticket": "بستن تیکت",
|
"close_ticket": "بستن تیکت",
|
||||||
"info_ticket": "اطلاعات تیکت",
|
"info_ticket": "اطلاعات تیکت",
|
||||||
"update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود"
|
"update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود",
|
||||||
|
"low": "پایین",
|
||||||
|
"medium": "متوسط",
|
||||||
|
"high": "بالا",
|
||||||
|
"category": "دسته بندی",
|
||||||
|
"asignto": "کارشناس پاسخگویی",
|
||||||
|
"MEDIUM": "متوسط",
|
||||||
|
"LOW": "پایین",
|
||||||
|
"HIGH": "بالا",
|
||||||
|
"PENDING": "در انتظار",
|
||||||
|
"ANSWERED": "پاسخ داده شده",
|
||||||
|
"CLOSED": "بسته شده",
|
||||||
|
"service": "سرویس",
|
||||||
|
"all": "همه"
|
||||||
},
|
},
|
||||||
|
"date": "تاریخ",
|
||||||
|
"success": "عملیات با موفقیت انجام شد",
|
||||||
|
"select": "انتخاب",
|
||||||
"receip": {
|
"receip": {
|
||||||
|
"receip": "صورتحساب",
|
||||||
"receip_list": "لیست صورتحساب ها",
|
"receip_list": "لیست صورتحساب ها",
|
||||||
"user_list": "لیست کاربران",
|
|
||||||
"Language_learners_list": "لیست زبان آموزان",
|
|
||||||
"dont_pay": "پرداخت نشده",
|
"dont_pay": "پرداخت نشده",
|
||||||
"dont_confrim": "تایید نشده",
|
"dont_confrim": "تایید نشده",
|
||||||
"paid": "پرداخت شده",
|
"paid": "پرداخت شده",
|
||||||
@@ -185,6 +203,8 @@
|
|||||||
"returned": "پس داده شده",
|
"returned": "پس داده شده",
|
||||||
"its_time": "سررسیده شده",
|
"its_time": "سررسیده شده",
|
||||||
"archive": "بایگانی شده",
|
"archive": "بایگانی شده",
|
||||||
|
"overdue": "سررسید شده",
|
||||||
|
"late_fee": "جریمه دیرکرد",
|
||||||
"detail_account": "جزییات صورتحساب ",
|
"detail_account": "جزییات صورتحساب ",
|
||||||
"personal_information": "اطلاعات شخصی",
|
"personal_information": "اطلاعات شخصی",
|
||||||
"type_person": "نوع شخص : ",
|
"type_person": "نوع شخص : ",
|
||||||
@@ -193,11 +213,11 @@
|
|||||||
"company_name": "نام شرکت/ سازمان :",
|
"company_name": "نام شرکت/ سازمان :",
|
||||||
"registration_number": "شماره ثبت :",
|
"registration_number": "شماره ثبت :",
|
||||||
"tel_company": "تلفن شرکت :",
|
"tel_company": "تلفن شرکت :",
|
||||||
"national_code": "کد ملی نماینده :",
|
"national_code": " شناسه ملی :",
|
||||||
"postal_code": "کد پستی :",
|
"postal_code": "کد پستی :",
|
||||||
"state": "استان :",
|
"state": "استان :",
|
||||||
"city": "شهر :",
|
"city": "شهر :",
|
||||||
"address": "آدرس :",
|
"address": "آدرس ثبتی :",
|
||||||
"detail_receip": "مشخصات صورتحساب",
|
"detail_receip": "مشخصات صورتحساب",
|
||||||
"not_paid": "پرداخت نشده",
|
"not_paid": "پرداخت نشده",
|
||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
@@ -206,8 +226,51 @@
|
|||||||
"date_factor": "تاریخ صدور فاکتور",
|
"date_factor": "تاریخ صدور فاکتور",
|
||||||
"due_date": "تاریخ سررسید فاکتور",
|
"due_date": "تاریخ سررسید فاکتور",
|
||||||
"factor_number": "شماره فاکتور",
|
"factor_number": "شماره فاکتور",
|
||||||
"status_factor": "وضعیت فاکتور"
|
"status_factor": "وضعیت فاکتور",
|
||||||
|
"customer": "مشتری",
|
||||||
|
"receipt_information": "اطلاعات صورتحساب",
|
||||||
|
"number": "شماره",
|
||||||
|
"product_name": "نام کالا",
|
||||||
|
"count": "تعداد",
|
||||||
|
"unit_amount": "مبلغ واحد",
|
||||||
|
"discount": "تخفیف(٪)",
|
||||||
|
"total_amount": "مبلغ کل(تومان)",
|
||||||
|
"tax": "مالیات بر ارزش افزوده (۰.۹ درصد)",
|
||||||
|
"total": "جمع",
|
||||||
|
"error_empty": "همه فیلد ها را پر کنید",
|
||||||
|
"date_receipt": "تاریخ صورتحساب",
|
||||||
|
"last_date_receipt": "آخرین مهلت پرداخت",
|
||||||
|
"service": "سرویس",
|
||||||
|
"status": "وضعیت ",
|
||||||
|
"status_paid": "وضعیت پرداخت",
|
||||||
|
"PENDING": "در انتظار تایید ",
|
||||||
|
"PAID": "پرداخت شده",
|
||||||
|
"CANCELED": "لغو شده",
|
||||||
|
"EXPIRED": "منقضی شده",
|
||||||
|
"WAIT_PAYMENT": "در انتظار پرداخت",
|
||||||
|
"real_person": "شخص حقیقی",
|
||||||
|
"fullName": "نام و نام خانوادگی",
|
||||||
|
"compelete_financal_info_error": "برای دریافت فاکتور رسمی اطلاعات مالی خود را وارد کنید",
|
||||||
|
"compelete_financal_info": "تکمیل اطلاعات مالی",
|
||||||
|
"get_factor": "دریافت پیش فاکتور",
|
||||||
|
"pay": "پرداخت",
|
||||||
|
"confrim_factor": "تایید فاکتور",
|
||||||
|
"sure_confrim_factor": "برای تایید فاکتور مطمئن هستید؟",
|
||||||
|
"accepted": "تایید شده",
|
||||||
|
"sure_confrim_pay": "آیا مطمئن هستید که مبلغ صورتحساب از کیف پول شما کسر شود؟",
|
||||||
|
"thank_pay_1": "ضمن تشکر از خرید شما",
|
||||||
|
"thank_pay_2": "مبلغ این صورت حساب پرداخت شده است.",
|
||||||
|
"discount_code": "کد تخفیف",
|
||||||
|
"apply": "اعمال",
|
||||||
|
"discount_applied": "کد تخفیف با موفقیت اعمال شد",
|
||||||
|
"discount_error": "کد تخفیف معتبر نیست",
|
||||||
|
"pending": "در انتظار تایید",
|
||||||
|
"discount_removed": "کد تخفیف با موفقیت حذف شد",
|
||||||
|
"remove_discount": "حذف تخفیف",
|
||||||
|
"charge_wallet": "شارژ کیف پول",
|
||||||
|
"sure_charge_wallet": "آیا از شارژ کیف پول اطمینان دارید؟"
|
||||||
},
|
},
|
||||||
|
"loading": "در حال بارکذاری...",
|
||||||
"edit": "ویرایش",
|
"edit": "ویرایش",
|
||||||
"transaction": {
|
"transaction": {
|
||||||
"transaction": "تراکنش ها",
|
"transaction": "تراکنش ها",
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import StatusWithText from '../../components/StatusWithText'
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
|
import { ArrowLeft2 } from 'iconsax-react'
|
||||||
|
import { useGetAnnoncementDetail } from './hooks/useAnnoncementData'
|
||||||
|
import PageLoading from '../../components/PageLoading'
|
||||||
|
import { Helmet } from 'react-helmet-async'
|
||||||
|
|
||||||
|
const AnnouncementDetail: FC = () => {
|
||||||
|
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const getAnnoncementDetail = useGetAnnoncementDetail(id ? id : '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
داناک | اطلاعیه
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<div>
|
||||||
|
{t('announcement.text_announcement')}
|
||||||
|
</div>
|
||||||
|
<div onClick={() => navigate(-1)} className='text-xs flex gap-0.5 items-center font-extralight'>
|
||||||
|
{t('back')}
|
||||||
|
<ArrowLeft2 size={12} color='black' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
getAnnoncementDetail.isPending ?
|
||||||
|
<PageLoading />
|
||||||
|
:
|
||||||
|
<div className='flex-1 mt-4 bg-white py-6 px-4 rounded-2xl'>
|
||||||
|
<div className='flex justify-between items-start'>
|
||||||
|
<div className='text-sm max-w-[70%] leading-67'>
|
||||||
|
{getAnnoncementDetail.data?.data?.announcement?.title}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
getAnnoncementDetail.data?.data?.announcement.isImportant &&
|
||||||
|
<StatusWithText
|
||||||
|
variant='error'
|
||||||
|
text={t('announcement.important')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: getAnnoncementDetail.data?.data?.announcement?.content }} className='mt-5 leading-7 text-xs font-light'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnnouncementDetail
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { FC, Fragment, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Input from '../../components/Input'
|
||||||
|
import StatusWithText from '../../components/StatusWithText'
|
||||||
|
import { clx } from '../../helpers/utils'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { useGetAnnoncement } from './hooks/useAnnoncementData'
|
||||||
|
import { AnnoncementItemType } from './types/AnnoncementTypes'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import { Helmet } from 'react-helmet-async'
|
||||||
|
import AnouncementSkeleton from './components/AnouncementSkeleton'
|
||||||
|
|
||||||
|
const AnnouncementtList: FC = () => {
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [search, setSearch] = useState<string>('')
|
||||||
|
const [itemSelected, setItemSelected] = useState<AnnoncementItemType>()
|
||||||
|
const getAnnoncements = useGetAnnoncement(search)
|
||||||
|
|
||||||
|
const handleChange = (item: AnnoncementItemType) => {
|
||||||
|
if (window.innerWidth < 1280) {
|
||||||
|
navigate(Pages.announcement.detail + item.announcement.id)
|
||||||
|
} else {
|
||||||
|
setItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4 text-sm'>
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
داناک | اطلاعیه ها
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className='text-base'>
|
||||||
|
{t('announcement.announcements')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-10 flex gap-8'>
|
||||||
|
<div className='bg-white xl:w-[400px] w-full rounded-2xl p-6'>
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<div className='flex-1'>
|
||||||
|
<Input
|
||||||
|
variant='search'
|
||||||
|
placeholder={t('announcement.search')}
|
||||||
|
className='-mt-1'
|
||||||
|
onChangeSearchFinal={(value) => setSearch(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div>
|
||||||
|
<Select
|
||||||
|
items={[
|
||||||
|
{ value: '1', label: 'همه' },
|
||||||
|
{ value: '2', label: 'فعال' },
|
||||||
|
{ value: '3', label: 'غیر فعال' },
|
||||||
|
]}
|
||||||
|
className='bg-[#EEF0F7] w-[68px]'
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Fragment>
|
||||||
|
<div className='mt-8'>
|
||||||
|
{
|
||||||
|
getAnnoncements.isPending ?
|
||||||
|
<Fragment>
|
||||||
|
<AnouncementSkeleton />
|
||||||
|
<AnouncementSkeleton />
|
||||||
|
<AnouncementSkeleton />
|
||||||
|
</Fragment>
|
||||||
|
:
|
||||||
|
getAnnoncements.data?.data?.announcements?.map((item: AnnoncementItemType) => {
|
||||||
|
return (
|
||||||
|
<div onClick={() => handleChange(item)} className={clx(
|
||||||
|
'p-4 border-b cursor-pointer',
|
||||||
|
item.id === itemSelected?.id && 'bg-[#F8F9FB]'
|
||||||
|
)}>
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
|
||||||
|
{item.announcement.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
{
|
||||||
|
item.announcement.isImportant &&
|
||||||
|
<StatusWithText
|
||||||
|
variant='error'
|
||||||
|
text={t('announcement.important')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!item.isRead &&
|
||||||
|
<StatusWithText
|
||||||
|
variant='success'
|
||||||
|
text={t('announcement.new')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='m-2 flex justify-between items-center text-[11px]'>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: item.announcement.content }} className='max-w-[70%] truncate text-description'>
|
||||||
|
</div>
|
||||||
|
<div className='text-description'>
|
||||||
|
{moment(item.announcement.publishAt).format('jYYYY/jMM/jDD')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
itemSelected &&
|
||||||
|
<div className='flex-1 hidden xl:block bg-white py-7 px-5 rounded-2xl'>
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div className='text-lg'>
|
||||||
|
متن اطلاعیه
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
itemSelected.announcement.isImportant &&
|
||||||
|
<StatusWithText
|
||||||
|
variant='error'
|
||||||
|
text={t('announcement.important')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8'>
|
||||||
|
{itemSelected.announcement.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: itemSelected.announcement.content }} className='mt-5 leading-7 text-xs font-light'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnnouncementtList
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import Skeleton from 'react-loading-skeleton'
|
||||||
|
|
||||||
|
const AnouncementSkeleton: FC = () => {
|
||||||
|
return (
|
||||||
|
<div className={'p-4 border-b'}>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
|
||||||
|
<Skeleton
|
||||||
|
height={7}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<Skeleton
|
||||||
|
className='!w-10 !h-6 !rounded-full'
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
className='!w-10 !h-6 !rounded-full'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='m-2 flex justify-between items-center text-[11px]'>
|
||||||
|
<Skeleton
|
||||||
|
height={7}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
<div className='text-description'>
|
||||||
|
<Skeleton
|
||||||
|
height={7}
|
||||||
|
width={40}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnouncementSkeleton
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import * as api from "../service/AnnoncementService";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
export const useGetAnnoncement = (search: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["announcements", search],
|
||||||
|
queryFn: () => api.getAnnoncements(search),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetAnnoncementDetail = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["announcement", id],
|
||||||
|
queryFn: () => api.getAnnoncementDetail(id),
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import axios from "../../../config/axios";
|
||||||
|
|
||||||
|
export const getAnnoncements = async (search: string) => {
|
||||||
|
const { data } = await axios.get(`/announcements/user?q=${search}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAnnoncementDetail = async (id: string) => {
|
||||||
|
const { data } = await axios.get(`/announcements/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export type AnnoncementItemType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
announcement: {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
publishAt: string;
|
||||||
|
targetService: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
isImportant: boolean;
|
||||||
|
};
|
||||||
|
isRead: boolean;
|
||||||
|
};
|
||||||
+34
-6
@@ -1,31 +1,59 @@
|
|||||||
import { Building, InfoCircle } from 'iconsax-react'
|
import { Building, CloseCircle, InfoCircle, TickCircle } from 'iconsax-react'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { buildPath, clx } from '../../helpers/utils'
|
import { buildPath, clx } from '../../helpers/utils'
|
||||||
import StatusWithText from '../../components/StatusWithText'
|
import StatusWithText from '../../components/StatusWithText'
|
||||||
import TitleSeeAll from '../../components/TitleSeeAll'
|
import TitleSeeAll from '../../components/TitleSeeAll'
|
||||||
import { useGetCompanies, useGetIndustry } from './hooks/useHomeData'
|
import { useGetCompanies, useGetIndustry, useGetCompanyRequest } from './hooks/useHomeData'
|
||||||
import { CompanyItemType, IndustryItemType } from './types/HomeTypes'
|
import { CompanyItemType, IndustryItemType } from './types/HomeTypes'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { CompanyRequestStatus } from './enum/Enum'
|
||||||
const Home: FC = () => {
|
const Home: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [activeIndustry, setActiveIndustry] = useState<string>('')
|
const [activeIndustry, setActiveIndustry] = useState<string>('')
|
||||||
const { data: industry } = useGetIndustry()
|
const { data: industry } = useGetIndustry()
|
||||||
const { data: companies } = useGetCompanies(activeIndustry)
|
const { data: companies } = useGetCompanies(activeIndustry)
|
||||||
|
const { data: companyRequest } = useGetCompanyRequest()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<div className='w-full h-10 flex gap-2 items-center px-4 rounded-xl bg-[#FCE7CE] text-[#FF7B00]'>
|
{
|
||||||
|
companyRequest?.data?.requests &&
|
||||||
|
<div className={clx(
|
||||||
|
'w-full h-10 flex gap-2 items-center px-4 rounded-xl ',
|
||||||
|
companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.PENDING ? 'bg-[#FCE7CE] text-[#FF7B00]'
|
||||||
|
: companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.APPROVED ? 'bg-[#E6FFF4] text-[#039855]'
|
||||||
|
: 'bg-[#FFE6E6] text-[#FF0000]'
|
||||||
|
)}>
|
||||||
|
{
|
||||||
|
companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.PENDING ?
|
||||||
<InfoCircle
|
<InfoCircle
|
||||||
size={24}
|
size={22}
|
||||||
color='#FF7B00'
|
color='#FF7B00'
|
||||||
/>
|
/>
|
||||||
<div className='text-xs'>
|
: companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.APPROVED ?
|
||||||
درخواست شما برای ثبت شرکت، هنوز تایید نشده است
|
<TickCircle
|
||||||
|
size={22}
|
||||||
|
color='#039855'
|
||||||
|
/>
|
||||||
|
: companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.REJECTED ?
|
||||||
|
<CloseCircle
|
||||||
|
size={22}
|
||||||
|
color='#FF0000'
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="text-xs">
|
||||||
|
{companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.PENDING
|
||||||
|
? t('home.request_pending')
|
||||||
|
: companyRequest?.data?.requests?.requestStatus === CompanyRequestStatus.APPROVED
|
||||||
|
? t('home.request_approved')
|
||||||
|
: t('home.request_rejected')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<TitleSeeAll
|
<TitleSeeAll
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export enum CompanyStatus {
|
||||||
|
APPROVED = "APPROVED",
|
||||||
|
PENDING = "PENDING",
|
||||||
|
REJECTED = "REJECTED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CompanyRequestStatus {
|
||||||
|
PENDING = "PENDING",
|
||||||
|
APPROVED = "APPROVED",
|
||||||
|
REJECTED = "REJECTED",
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
getIdWithSlug,
|
getIdWithSlug,
|
||||||
getIndustry,
|
getIndustry,
|
||||||
getCompanyDetail,
|
getCompanyDetail,
|
||||||
|
getCompanyRequest,
|
||||||
} from "../service/HomeService";
|
} from "../service/HomeService";
|
||||||
|
|
||||||
export const useGetSlugId = () => {
|
export const useGetSlugId = () => {
|
||||||
@@ -33,3 +34,10 @@ export const useGetCompanyDetail = (id: string) => {
|
|||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetCompanyRequest = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["company-request"],
|
||||||
|
queryFn: () => getCompanyRequest(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,3 +25,8 @@ export const getCompanyDetail = async (id: string) => {
|
|||||||
const { data } = await axios.get(`/companies/public/${id}`);
|
const { data } = await axios.get(`/companies/public/${id}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCompanyRequest = async () => {
|
||||||
|
const { data } = await axios.get(`/companies/requests/user`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
+238
-163
@@ -1,156 +1,143 @@
|
|||||||
import { FC } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Edit } from 'iconsax-react'
|
import { useParams } from 'react-router-dom'
|
||||||
import StatusCircle from '../../components/StatusCircle'
|
import { useApplyDiscount, useGetInvoiceDetail, useRemoveDiscount } from './hooks/useReceiptData'
|
||||||
import SamanImage from '../../assets/images/saman.png'
|
import { ApplyDiscountType, ReceiptDetailItemType } from './types/ReceiptTypes'
|
||||||
|
import { NumberFormat } from '../../config/func'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import LegalInfo from './components/LegalInfo'
|
||||||
|
import RealInfo from './components/RealInfo'
|
||||||
|
import RegisterInfo from './components/RegisterInfo'
|
||||||
|
import { clx } from '../../helpers/utils'
|
||||||
|
import Button from '../../components/Button'
|
||||||
|
import ApproveInvoice from './components/ApproveInvoice'
|
||||||
|
import PayInvoice from './components/PayInvoice'
|
||||||
|
import Input from '../../components/Input'
|
||||||
|
import { useGetProfile } from '../profile/hooks/useProfileData'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { ErrorType } from '../../helpers/types'
|
||||||
|
// import PDFGenerator from '../../components/PDFGenerator'
|
||||||
|
// import { useGetGetWays } from '../wallet/hooks/useWalletData'
|
||||||
|
// import { GatewayItemType } from '../wallet/types/WalletTypes'
|
||||||
|
|
||||||
const ReceiptsDetail: FC = () => {
|
const ReceiptsDetail: FC = () => {
|
||||||
|
|
||||||
|
const { id } = useParams()
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const [code, setCode] = useState<string>('')
|
||||||
|
const [gateWayId, setGateWayId] = useState<string>('')
|
||||||
|
const getProfile = useGetProfile()
|
||||||
|
const getInvoce = useGetInvoiceDetail(id ? id : '')
|
||||||
|
const applyDiscount = useApplyDiscount()
|
||||||
|
const removeDiscount = useRemoveDiscount()
|
||||||
|
// const getWays = useGetGetWays()
|
||||||
|
|
||||||
|
const handleApplyDiscount = () => {
|
||||||
|
const params: ApplyDiscountType = {
|
||||||
|
id: id ? id : '',
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
applyDiscount.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t('receip.discount_applied'))
|
||||||
|
getInvoce.refetch()
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error?.response?.data?.error?.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveDiscount = () => {
|
||||||
|
removeDiscount.mutate({ id: id ? id : '' }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t('receip.discount_removed'))
|
||||||
|
getInvoce.refetch()
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error?.response?.data?.error?.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
|
||||||
|
// if (getWays.data) {
|
||||||
|
// setGateWayId(getWays.data?.data?.paymentGateways?.[0]?.id)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }, [getWays.data])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4 pb-20'>
|
<div className='mt-4'>
|
||||||
<div>
|
<div className='flex xl:flex-row flex-col gap-4 items-center'>
|
||||||
{t('receip.detail_account')}
|
<div className='flex gap-6 items-center'>
|
||||||
۱۲۳۱۲
|
<div className='whitespace-nowrap'>{t('receip.detail_account')}</div>
|
||||||
|
|
||||||
|
<ApproveInvoice status={getInvoce.data?.data?.invoice?.status} id={id ? id : ''} refetch={getInvoce.refetch} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
!getInvoce.data?.data?.invoice?.discount && ['PENDING', 'APPROVED', 'WAIT_PAYMENT'].includes(getInvoce.data?.data?.invoice?.status) ?
|
||||||
|
<div className='flex -mt-1 gap-2 items-center w-full'>
|
||||||
|
<div className='w-full xl:w-fit'>
|
||||||
|
<Input
|
||||||
|
className='bg-transparent border border-border xl:max-w-40'
|
||||||
|
placeholder={t('receip.discount_code')}
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('receip.apply')}
|
||||||
|
className='bg-transparent text-black border border-black mt-1 px-5 w-fit'
|
||||||
|
onClick={handleApplyDiscount}
|
||||||
|
isLoading={applyDiscount.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: getInvoce.data?.data?.invoice?.discount &&
|
||||||
|
<div className='flex -mt-1 gap-2 items-center w-full'>
|
||||||
|
<div className='w-full xl:w-fit'>
|
||||||
|
<Button
|
||||||
|
label={t('receip.remove_discount')}
|
||||||
|
className='bg-transparent text-black border border-black mt-1 px-5 w-fit'
|
||||||
|
onClick={handleRemoveDiscount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
<div className='flex xl:flex-row flex-col mt-8 gap-6'>
|
<div className='flex xl:flex-row flex-col mt-8 gap-6'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div className='w-full bg-white rounded-3xl p-8'>
|
<div id="receipt-details" className='w-full bg-white rounded-3xl p-8'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center mb-6'>
|
||||||
<div>
|
<div className='text-lg font-medium'>
|
||||||
{t('receip.personal_information')}
|
{t('receip.personal_information')}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-1 items-center text-xs text-[#0047FF]'>
|
|
||||||
<Edit size={16} color='#0047FF' />
|
|
||||||
<div>
|
|
||||||
{t('edit')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
{/* {
|
||||||
<div className='flex gap-2'>
|
getProfile.data?.data?.user?.financialType === 'LEGAL' ?
|
||||||
<div className='text-description'>
|
<LegalInfo />
|
||||||
{t('receip.type_person')}
|
:
|
||||||
</div>
|
getProfile.data?.data?.user?.financialType === 'REAL' ?
|
||||||
<div>
|
<RealInfo />
|
||||||
{t('receip.company')}
|
: <RegisterInfo data={getProfile.data?.data} />
|
||||||
</div>
|
} */}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
<div className='mt-8'>
|
||||||
<div className='text-description'>
|
<div className='flex justify-between items-center mb-6'>
|
||||||
{t('receip.representativeـname')}
|
<div className='text-lg font-medium'>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
مهرداد مظفری
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.company_name')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
داناک
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.registration_number')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
۱۲۳۴
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.tel_company')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
۰۰۱۲۳۴۵۶۷۸
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.national_code')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
۰۰۱۲۳۴۵۶۷۸
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.postal_code')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
۳۸۲۱۳۵۸۸۹۲
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.state')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
مرکزی
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.state')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
مرکزی
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.city')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
اراک
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='text-description'>
|
|
||||||
{t('receip.address')}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
اراک -خیابان شریعتی-خیابان جنت
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='w-full bg-white rounded-3xl mt-8 p-8'>
|
|
||||||
<div className='flex justify-between items-center'>
|
|
||||||
<div>
|
|
||||||
{t('receip.detail_receip')}
|
{t('receip.detail_receip')}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-2 text-xs items-center'>
|
|
||||||
<StatusCircle
|
|
||||||
color='#FF7B00'
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
{t('receip.not_paid')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-7 text-sm'>
|
<div className='mt-7 text-sm'>
|
||||||
<div className='flex justify-between items-center text-description h-14'>
|
<div className='flex justify-between items-center text-description h-14 font-medium'>
|
||||||
<div>
|
<div>
|
||||||
{t('receip.description')}
|
{t('receip.description')}
|
||||||
</div>
|
</div>
|
||||||
@@ -158,45 +145,52 @@ const ReceiptsDetail: FC = () => {
|
|||||||
{t('receip.price')}
|
{t('receip.price')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
{
|
||||||
<div>
|
getInvoce.data?.data?.invoice?.items?.map((item: ReceiptDetailItemType) => {
|
||||||
اکانت یکساله با دسترسی
|
return (
|
||||||
|
<div key={item.id} className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||||
|
<div className='flex gap-0.5'>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
<span>{item.count}</span>
|
||||||
|
<span>عدد</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
۱۰,۰۰۰,۰۰۰ ریال
|
<span>{NumberFormat(item.totalPrice)}</span> تومان
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||||
<div>
|
<div>
|
||||||
۱۰٪ ارزش افزوده
|
۱۰٪ ارزش افزوده
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
۱۰,۰۰۰,۰۰۰ ریال
|
<span>{NumberFormat(getInvoce.data?.data?.invoice?.tax)}</span> تومان
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
<div className='flex justify-between gap-1 items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||||
<div>
|
<div>
|
||||||
جمع کل
|
جمع کل
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className='flex gap-1'>
|
||||||
۱۰,۰۰۰,۰۰۰ ریال
|
{
|
||||||
</div>
|
getInvoce.data?.data?.invoice?.originalPrice !== getInvoce.data?.data?.invoice?.totalPrice && (
|
||||||
|
<><span className='line-through'>{NumberFormat(getInvoce.data?.data?.invoice?.originalPrice)}</span></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<span>{NumberFormat(getInvoce.data?.data?.invoice?.totalPrice)}</span> تومان
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='bg-white xl:w-sidebar text-sm h-fit rounded-3xl p-8'>
|
<div className='mt-8'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center mb-6'>
|
||||||
<div>
|
<div className='text-lg font-medium'>
|
||||||
{t('receip.receip_information')}
|
{t('receip.receip_information')}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-2 text-xs items-center'>
|
|
||||||
<StatusCircle
|
|
||||||
color='#FF7B00'
|
|
||||||
/>
|
|
||||||
<div>پرداخت نشده</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6 text-xs font-extralight'>
|
<div className='mt-6 text-xs font-extralight'>
|
||||||
@@ -205,7 +199,7 @@ const ReceiptsDetail: FC = () => {
|
|||||||
{t('receip.date_factor')}
|
{t('receip.date_factor')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
۱۴۰۲/۱۰/۰۴
|
{moment(getInvoce.data?.data?.invoice?.createdAt).format('jYYYY-jMM-jDD')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between h-12 border-b items-center'>
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
@@ -213,7 +207,7 @@ const ReceiptsDetail: FC = () => {
|
|||||||
{t('receip.due_date')}
|
{t('receip.due_date')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
۱۴۰۲/۱۰/۰۴
|
{moment(getInvoce.data?.data?.invoice?.dueDate).format('jYYYY-jMM-jDD')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between h-12 border-b items-center'>
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
@@ -221,7 +215,7 @@ const ReceiptsDetail: FC = () => {
|
|||||||
{t('receip.factor_number')}
|
{t('receip.factor_number')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
۱۲۳۱
|
{moment(getInvoce.data?.data?.invoice.createdAt).format('jYYYY') + getInvoce.data?.data?.invoice.numericId}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between h-12 border-b items-center'>
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
@@ -229,27 +223,108 @@ const ReceiptsDetail: FC = () => {
|
|||||||
{t('receip.status_factor')}
|
{t('receip.status_factor')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
تایید شده
|
{t(`receip.${getInvoce.data?.data?.invoice?.status}`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='bg-white xl:w-sidebar text-sm h-fit rounded-3xl p-8'>
|
||||||
|
{
|
||||||
|
getInvoce.data?.data?.invoice?.status === 'PAID' &&
|
||||||
|
<div className='p-6 bg-green-50 rounded-2xl text-green-400 text-xs mb-6'>
|
||||||
|
<p>{t('receip.thank_pay_1')}</p>
|
||||||
|
<p className='mt-1'>{t('receip.thank_pay_2')}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div className='flex justify-between items-center'>
|
||||||
|
<div>
|
||||||
|
{t('receip.receip_information')}
|
||||||
|
</div>
|
||||||
|
<div className={clx(
|
||||||
|
'border border-orange-400 text-orange-400 rounded-lg text-[9px] h-6 flex items-center px-1',
|
||||||
|
getInvoce.data?.data?.invoice?.status === 'PAID' ? 'border-green-400 text-green-400' : getInvoce.data?.data?.invoice?.status !== 'PENDING' ? 'border-red-400 text-red-400' : 'border-orange-400 text-orange-400'
|
||||||
|
)}>
|
||||||
|
<div>{t(`receip.${getInvoce.data?.data?.invoice?.status}`)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='mt-5 flex justify-center gap-2'>
|
<div className='mt-6 text-xs font-extralight'>
|
||||||
<div className='border size-[72px] flex-col text-[10px] justify-center items-center rounded-xl border-border flex'>
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
<img src={SamanImage} alt='saman' className='w-8 h-8' />
|
<div className='text-description'>
|
||||||
<div className='mt-2'>
|
{t('receip.date_factor')}
|
||||||
بانک سامان
|
</div>
|
||||||
|
<div>
|
||||||
|
{moment(getInvoce.data?.data?.invoice?.createdAt).format('jYYYY-jMM-jDD')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='border size-[72px] flex-col text-[10px] justify-center items-center rounded-xl border-border flex'>
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
<img src={SamanImage} alt='saman' className='w-8 h-8' />
|
<div className='text-description'>
|
||||||
<div className='mt-2'>
|
{t('receip.due_date')}
|
||||||
بانک سامان
|
</div>
|
||||||
|
<div>
|
||||||
|
{moment(getInvoce.data?.data?.invoice?.dueDate).format('jYYYY-jMM-jDD')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.factor_number')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{moment(getInvoce.data?.data?.invoice.createdAt).format('jYYYY') + getInvoce.data?.data?.invoice.numericId}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex justify-between h-12 border-b items-center'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.status_factor')}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{t(`receip.${getInvoce.data?.data?.invoice?.status}`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
getInvoce.data?.data?.invoice?.status === 'WAIT_PAYMENT' &&
|
||||||
|
<div className='flex gap-4 items-center mt-8'>
|
||||||
|
{/* <PDFGenerator
|
||||||
|
elementId="receipt-details"
|
||||||
|
fileName={`receipt-${getInvoce.data?.data?.invoice.numericId}`}
|
||||||
|
/> */}
|
||||||
|
<PayInvoice
|
||||||
|
id={id ? id : ''}
|
||||||
|
refetch={getInvoce.refetch}
|
||||||
|
gateWayId={gateWayId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{/* {
|
||||||
|
getInvoce.data?.data?.remainingToCharge > 0 &&
|
||||||
|
<div className='flex mt-6 justify-center gap-2'>
|
||||||
|
{
|
||||||
|
getWays.data?.data?.paymentGateways?.map((item: GatewayItemType) => {
|
||||||
|
return (
|
||||||
|
<div key={item.id} className={clx(
|
||||||
|
'size-[72px] rounded-xl border border-border flex flex-col justify-center items-center',
|
||||||
|
gateWayId === item.id && 'border-black'
|
||||||
|
)}>
|
||||||
|
<img src={item.logoUrl} className='w-8' />
|
||||||
|
<div className='mt-1 text-[10px]'>
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+73
-55
@@ -1,21 +1,28 @@
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Tabs from '../../components/Tabs'
|
import Tabs from '../../components/Tabs'
|
||||||
import { Calendar, CloseCircle, Eye, FolderOpen, MoneyChange, WalletCheck, WalletRemove, WalletSearch } from 'iconsax-react'
|
import { Eye, FolderOpen, WalletCheck, WalletMinus, WalletRemove, WalletSearch } from 'iconsax-react'
|
||||||
import Td from '../../components/Td'
|
import Td from '../../components/Td'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { useGetInvoices } from './hooks/useReceiptData'
|
||||||
|
import { NumberFormat } from '../../config/func'
|
||||||
|
import { ReceiptItemType } from './types/ReceiptTypes'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import { InvoiceStatus } from './types/ReceiptTypes'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ReceiptsList: FC = () => {
|
const ReceiptsList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [activeTab, setActiveTab] = useState<string>('unpaid')
|
const [activeTab, setActiveTab] = useState<InvoiceStatus>('PENDING')
|
||||||
|
const getInvoices = useGetInvoices(activeTab)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center'>
|
<div className='mt-4'>
|
||||||
<div className='mt-4 w-full max-w-[1200px]'>
|
|
||||||
<div>
|
<div>
|
||||||
{t('receip.Language_learners_list')}
|
{t('receip.receip_list')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-14'>
|
<div className='mt-14'>
|
||||||
@@ -23,89 +30,100 @@ const ReceiptsList: FC = () => {
|
|||||||
active={activeTab}
|
active={activeTab}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
icon: <WalletRemove color={activeTab === 'unpaid' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <WalletSearch color={activeTab === 'PENDING' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
|
label: t('receip.pending'),
|
||||||
|
value: 'PENDING'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <WalletRemove color={activeTab === 'WAIT_PAYMENT' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('receip.dont_pay'),
|
label: t('receip.dont_pay'),
|
||||||
value: 'unpaid'
|
value: 'WAIT_PAYMENT'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <WalletSearch color={activeTab === 'not_confimed' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <WalletCheck color={activeTab === 'PAID' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('receip.dont_confrim'),
|
|
||||||
value: 'not_confimed'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <WalletCheck color={activeTab === 'paid' ? 'black' : '#8C90A3'} size={22} />,
|
|
||||||
label: t('receip.paid'),
|
label: t('receip.paid'),
|
||||||
value: 'paid'
|
value: 'PAID'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <CloseCircle color={activeTab === 'canceled' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <WalletMinus color={activeTab === 'OVERDUE' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('receip.canceled'),
|
label: t('receip.overdue'),
|
||||||
value: 'canceled'
|
value: 'OVERDUE'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MoneyChange color={activeTab === 'returned' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <FolderOpen color={activeTab === 'ARCHIVED' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('receip.returned'),
|
|
||||||
value: 'returned'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Calendar color={activeTab === 'time' ? 'black' : '#8C90A3'} size={22} />,
|
|
||||||
label: t('receip.its_time'),
|
|
||||||
value: 'time'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <FolderOpen color={activeTab === 'archive' ? 'black' : '#8C90A3'} size={22} />,
|
|
||||||
label: t('receip.archive'),
|
label: t('receip.archive'),
|
||||||
value: 'archive'
|
value: 'ARCHIVED'
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={setActiveTab}
|
onChange={(value) => setActiveTab(value as InvoiceStatus)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||||
<table className='w-full text-sm '>
|
<table className='w-full text-sm '>
|
||||||
<thead className='thead'>
|
<thead className='thead'>
|
||||||
<tr>
|
<tr>
|
||||||
<Td text={t('ticket.number')} />
|
<Td text={t('ticket.number')} />
|
||||||
<Td text={t('ticket.title')} />
|
{/* <Td text={t('receip.customer')} /> */}
|
||||||
<Td text={t('ticket.team')} />
|
<Td text={t('receip.date_receipt')} />
|
||||||
<Td text={t('ticket.date')} />
|
<Td text={t('receip.last_date_receipt')} />
|
||||||
<Td text={t('ticket.status')} />
|
<Td text={t('receip.total')} />
|
||||||
<Td text={t('ticket.priority')} />
|
<Td text={t('receip.service')} />
|
||||||
<Td text={t('ticket.detail')} />
|
<Td text={t('receip.late_fee')} />
|
||||||
|
<Td text={t('receip.status')} />
|
||||||
|
<Td text={t('receip.status_paid')} />
|
||||||
<Td text={''} />
|
<Td text={''} />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{
|
||||||
|
|
||||||
|
getInvoices.data?.data?.invoices?.map((item: ReceiptItemType) => {
|
||||||
|
return (
|
||||||
<tr className='tr'>
|
<tr className='tr'>
|
||||||
<Td text={t('ticket.number')} />
|
<Td text={moment(item.createdAt).format('jYYYY') + item.numericId} />
|
||||||
<Td text={t('ticket.title')} />
|
{/* <Td text={item.user.firstName + ' ' + item.user.lastName} /> */}
|
||||||
<Td text={t('ticket.team')} />
|
|
||||||
<Td text={t('ticket.date')} />
|
|
||||||
<Td text={t('ticket.status')} />
|
|
||||||
<Td text={t('ticket.priority')} />
|
|
||||||
<Td text={t('ticket.detail')} />
|
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<Link to={Pages.receipts.detail + '1'}>
|
<div className='dltr text-right'>
|
||||||
|
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
<Td text={''}>
|
||||||
|
<div className='dltr text-right'>
|
||||||
|
{moment(item.dueDate).format('jYYYY-jMM-jDD HH:mm')}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text={NumberFormat(item.totalPrice)} />
|
||||||
|
<Td text={''}>
|
||||||
|
<div className='flex flex-wrap gap-1.5'>
|
||||||
|
{
|
||||||
|
item.items.map((item => {
|
||||||
|
return (
|
||||||
|
<div className='bg-primary p-2 rounded-full text-white flex gap-1'><span>{item.name}</span> <span>({item.count})</span></div>
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text={NumberFormat(item.lateFee)} />
|
||||||
|
<Td text={t(`receip.${item.status}`)} />
|
||||||
|
<Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} />
|
||||||
|
<Td text={''}>
|
||||||
|
<Link to={Pages.receipts.detail + item.id}>
|
||||||
<Eye size={20} color='black' />
|
<Eye size={20} color='black' />
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='tr'>
|
)
|
||||||
<Td text={t('ticket.number')} />
|
})
|
||||||
<Td text={t('ticket.title')} />
|
}
|
||||||
<Td text={t('ticket.team')} />
|
|
||||||
<Td text={t('ticket.date')} />
|
|
||||||
<Td text={t('ticket.status')} />
|
|
||||||
<Td text={t('ticket.priority')} />
|
|
||||||
<Td text={t('ticket.detail')} />
|
|
||||||
<Td text={''} />
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import { Fragment, FC, useState } from 'react'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { TickCircle } from 'iconsax-react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useApproveInvoice, useRequestApprove } from '../hooks/useReceiptData'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { clx } from '../../../helpers/utils'
|
||||||
|
import { useGetProfile } from '../../profile/hooks/useProfileData'
|
||||||
|
import { RequestApproveInvoiceType } from '../types/ReceiptTypes'
|
||||||
|
import DefaulModal from '../../../components/DefaulModal'
|
||||||
|
import OTPInput from 'react-otp-input'
|
||||||
|
import { useCountDown } from '../../../hooks/useCountDown'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string,
|
||||||
|
refetch: () => void,
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApproveInvoice: FC<Props> = ({ id, refetch, status }) => {
|
||||||
|
|
||||||
|
const approveInvoce = useApproveInvoice()
|
||||||
|
const requestApprove = useRequestApprove()
|
||||||
|
const getProfile = useGetProfile()
|
||||||
|
const { value, reset } = useCountDown(2, true)
|
||||||
|
const [showModal, setShowModal] = useState<boolean>(false)
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [code, setCode] = useState<string>('')
|
||||||
|
|
||||||
|
const handleConfrim = () => {
|
||||||
|
approveInvoce.mutate({ code: code, id: id, phone: getProfile.data?.data?.user?.phone }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t('success'))
|
||||||
|
refetch()
|
||||||
|
setShowModal(false)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShowModal = () => {
|
||||||
|
const params: RequestApproveInvoiceType = {
|
||||||
|
id: id,
|
||||||
|
phone: getProfile.data?.data?.user?.phone
|
||||||
|
}
|
||||||
|
requestApprove.mutate(params, {
|
||||||
|
onSuccess: () => {
|
||||||
|
setShowModal(true)
|
||||||
|
reset()
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'PENDING' || status === 'WAIT_PAYMENT' && getProfile.isSuccess)
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
disabled={approveInvoce.isPending || requestApprove.isPending}
|
||||||
|
onClick={() => status === 'PENDING' ? handleShowModal() : null}
|
||||||
|
className={clx(
|
||||||
|
'px-9 w-fit bg-white bg-opacity-70 text-description',
|
||||||
|
status === 'WAIT_PAYMENT' && 'bg-green-300 text-[#00BA4B] bg-opacity-10'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2.5'>
|
||||||
|
<TickCircle
|
||||||
|
size={20}
|
||||||
|
color={status === 'PENDING' ? '#888888' : '#00BA4B'}
|
||||||
|
/>
|
||||||
|
<div className='whitespace-nowrap'>
|
||||||
|
{
|
||||||
|
status === 'WAIT_PAYMENT' ?
|
||||||
|
t('receip.accepted')
|
||||||
|
:
|
||||||
|
t('receip.confrim_factor')
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DefaulModal
|
||||||
|
open={showModal}
|
||||||
|
close={() => setShowModal(false)}
|
||||||
|
isHeader
|
||||||
|
title_header={t('profile.confrim')}
|
||||||
|
>
|
||||||
|
<div className='mt-7'>
|
||||||
|
<div className='text-xs text-center'>
|
||||||
|
کد تایید به شماره {getProfile.data?.data?.user?.phone} ارسال شده است.برای تایید کد مربوطه را وارد کنید.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-10'>کد تایید</div>
|
||||||
|
<div className='mt-2 w-full flex justify-center dltr otp'>
|
||||||
|
<OTPInput
|
||||||
|
value={code}
|
||||||
|
onChange={setCode}
|
||||||
|
shouldAutoFocus
|
||||||
|
numInputs={5}
|
||||||
|
renderInput={(props) =>
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
type='tel'
|
||||||
|
autoComplete="one-time-code"
|
||||||
|
inputMode="numeric"
|
||||||
|
className='w-full h-[50px] flex-1 mx-2 bg-white bg-opacity-30 border rounded-2.5' />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex mt-7 justify-center'>
|
||||||
|
{
|
||||||
|
value !== '00:00' ?
|
||||||
|
<div className='flex gap-1 text-description text-xs'>
|
||||||
|
<div>{value}</div>
|
||||||
|
<div>{t('auth.counter_otp')}</div>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div onClick={handleShowModal} className='flex cursor-pointer gap-1 pl-2 text-black text-sm'>
|
||||||
|
<div className=''>
|
||||||
|
{t('auth.try_again')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-10 flex justify-end border-t border-border pt-8'>
|
||||||
|
<div className='flex gap-5'>
|
||||||
|
<Button
|
||||||
|
className='bg-white bg-opacity-40 text-description w-[150px] text-xs'
|
||||||
|
label='لغو'
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className='w-[150px] text-xs'
|
||||||
|
onClick={handleConfrim}
|
||||||
|
disabled={code.length !== 5}
|
||||||
|
isLoading={approveInvoce.isPending || requestApprove.isPending}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle
|
||||||
|
size={20}
|
||||||
|
color='white'
|
||||||
|
/>
|
||||||
|
<div>{t('save')}</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</DefaulModal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
else return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApproveInvoice
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { FC, Fragment } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useGetLegalInfo } from '../../financial/hooks/useFinancialData'
|
||||||
|
import { useGetProfile } from '../../profile/hooks/useProfileData'
|
||||||
|
|
||||||
|
|
||||||
|
const LegalInfo: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { data: d, isSuccess } = useGetLegalInfo()
|
||||||
|
const getProfile = useGetProfile()
|
||||||
|
|
||||||
|
const data = d?.data?.legalUser
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.type_person')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
t('receip.company')
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.representativeـname')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{getProfile.data?.data?.user?.firstName + ' ' + getProfile.data?.data?.user?.lastName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.company_name')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data.registrationName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.registration_number')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data.registrationCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.tel_company')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data.phone}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.national_code')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.nationalIdentity}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.postal_code')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.postalCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.state')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.city?.province?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.city')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.city?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.address')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.fullAddress}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LegalInfo
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { FC, Fragment, useState } from 'react'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useGetInvoiceDetail, usePayInvoice } from '../hooks/useReceiptData'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import ModalConfrim from '../../../components/ModalConfrim'
|
||||||
|
// import { usePayment } from '../../wallet/hooks/useWalletData'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string,
|
||||||
|
refetch: () => void,
|
||||||
|
gateWayId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PayInvoice: FC<Props> = ({ id, refetch, gateWayId }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [showModal, setShowModal] = useState<boolean>(false)
|
||||||
|
const getInvoce = useGetInvoiceDetail(id)
|
||||||
|
const payInvoice = usePayInvoice()
|
||||||
|
// const payment = usePayment()
|
||||||
|
|
||||||
|
|
||||||
|
const handleConfrim = () => {
|
||||||
|
payInvoice.mutate(id, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('success'), 'success')
|
||||||
|
refetch()
|
||||||
|
setShowModal(false)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error.message[0], 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChartWallet = () => {
|
||||||
|
// payment.mutate({ amount: getInvoce.data?.data?.remainingToCharge, gatewayId: gateWayId, invoiceId: id }, {
|
||||||
|
// onSuccess: (data) => {
|
||||||
|
// window.location.href = data?.data?.redirectUrl
|
||||||
|
// toast(t('wallet.transfer_to_getway'), 'success')
|
||||||
|
// },
|
||||||
|
// onError: (error: ErrorType) => {
|
||||||
|
// toast(error.response?.data?.error.message[0], 'error')
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
label={getInvoce.data?.data?.remainingToCharge > 0 ? t('receip.charge_wallet') : t('receip.pay')}
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ModalConfrim
|
||||||
|
isOpen={showModal}
|
||||||
|
close={() => setShowModal(false)}
|
||||||
|
onConfrim={getInvoce.data?.data?.remainingToCharge > 0 ? handleChartWallet : handleConfrim}
|
||||||
|
label={getInvoce.data?.data?.remainingToCharge > 0 ? t('receip.sure_charge_wallet') : t('receip.sure_confrim_pay')}
|
||||||
|
isLoading={payInvoice.isPending}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PayInvoice
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { FC, Fragment } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useGetRealInfo } from '../../financial/hooks/useFinancialData'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const RealInfo: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { data: d } = useGetRealInfo()
|
||||||
|
|
||||||
|
const data = d?.data?.realUser
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.type_person')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t('receip.real_person')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.fullName')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.firstName + ' ' + data?.lastName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('financial.father_name')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.fatherName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('financial.sex')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.gender === 'MALE' ? 'مرد' : 'زن'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('financial.phone')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.phone}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.postal_code')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.postalCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.state')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.city?.province?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.city')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.city?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.address')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data?.address?.fullAddress}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RealInfo
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { FC, Fragment } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { FinancialDataType } from '../../financial/types/FinancialTypes'
|
||||||
|
import { InfoCircle } from 'iconsax-react'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../../config/Pages'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: FinancialDataType
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegisterInfo: FC<Props> = ({ data }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
|
||||||
|
<div className='mt-8 xl:flex-row flex-col xl:h-10 py-4 xl:py-0 text-xs bg-[#FFF2F2] text-[#DB0105] rounded-xl px-4 flex gap-2 items-center'>
|
||||||
|
<InfoCircle
|
||||||
|
size={18}
|
||||||
|
color='#DB0105'
|
||||||
|
className='min-w-[18px]'
|
||||||
|
/>
|
||||||
|
<div className='text-center xl:text-right'>{t('receip.compelete_financal_info_error')}</div>
|
||||||
|
|
||||||
|
<div className='flex-1 flex xl:justify-end'>
|
||||||
|
<Link to={Pages.financial}>
|
||||||
|
<Button
|
||||||
|
label={t('receip.compelete_financal_info')}
|
||||||
|
className='w-fit whitespace-nowrap px-4 h-7 text-[10px]'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.type_person')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t('receip.real_person')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('receip.fullName')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data.user?.firstName + ' ' + data.user?.lastName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='text-description'>
|
||||||
|
{t('financial.phone')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{data.user?.phone}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RegisterInfo
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/ReceiptService";
|
||||||
|
import {
|
||||||
|
ApplyDiscountType,
|
||||||
|
ApproveInvoiceType,
|
||||||
|
RemoveDiscountType,
|
||||||
|
RequestApproveInvoiceType,
|
||||||
|
} from "../types/ReceiptTypes";
|
||||||
|
|
||||||
|
export const useGetInvoices = (status: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["invoices", status],
|
||||||
|
queryFn: () => api.getInvoice(status),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetInvoiceDetail = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["invoice", id],
|
||||||
|
queryFn: () => api.getInvoiceDetail(id),
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useApproveInvoice = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ApproveInvoiceType) =>
|
||||||
|
api.approveInvoice(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePayInvoice = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: string) => api.payInvoice(variables),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.refetchQueries({
|
||||||
|
queryKey: ["wallet-balance"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRequestApprove = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: RequestApproveInvoiceType) =>
|
||||||
|
api.requestApprove(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useApplyDiscount = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: ApplyDiscountType) => api.applyDiscount(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRemoveDiscount = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: RemoveDiscountType) =>
|
||||||
|
api.removeDiscount(variables.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import axios from "../../../config/axios";
|
||||||
|
import {
|
||||||
|
ApplyDiscountType,
|
||||||
|
ApproveInvoiceType,
|
||||||
|
RequestApproveInvoiceType,
|
||||||
|
} from "../types/ReceiptTypes";
|
||||||
|
|
||||||
|
export const getInvoice = async (status: string) => {
|
||||||
|
const { data } = await axios.get(`/invoices/user?status=${status}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInvoiceDetail = async (id: string) => {
|
||||||
|
const { data } = await axios.get(`/invoices/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestApprove = async (params: RequestApproveInvoiceType) => {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`/invoices/${params.id}/approve/request`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const approveInvoice = async (params: ApproveInvoiceType) => {
|
||||||
|
const { data } = await axios.patch(`/invoices/${params.id}/approve`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const payInvoice = async (id: string) => {
|
||||||
|
const { data } = await axios.post(`/invoices/${id}/pay`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyDiscount = async (params: ApplyDiscountType) => {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`/invoices/${params.id}/apply-discount`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeDiscount = async (id: string) => {
|
||||||
|
const { data } = await axios.post(`/invoices/${id}/cancel-discount`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
export type InvoiceStatus =
|
||||||
|
| "PENDING"
|
||||||
|
| "WAIT_PAYMENT"
|
||||||
|
| "PAID"
|
||||||
|
| "ARCHIVED"
|
||||||
|
| "OVERDUE";
|
||||||
|
|
||||||
|
export type ReceiptItemType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
count: string | number;
|
||||||
|
unitPrice: string | number;
|
||||||
|
discount: string | number;
|
||||||
|
totalPrice: number;
|
||||||
|
subscriptionPlan?: string | null;
|
||||||
|
items: ReceiptItemType[];
|
||||||
|
user: {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
};
|
||||||
|
dueDate: string;
|
||||||
|
status: InvoiceStatus;
|
||||||
|
paidAt: string;
|
||||||
|
numericId: string;
|
||||||
|
lateFee: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReceiptDetailItemType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
name: string;
|
||||||
|
count: string | number;
|
||||||
|
unitPrice: string | number;
|
||||||
|
discount: string | number;
|
||||||
|
totalPrice: number;
|
||||||
|
subscriptionPlan?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RequestApproveInvoiceType = {
|
||||||
|
phone: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApproveInvoiceType = {
|
||||||
|
phone: string;
|
||||||
|
code: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApplyDiscountType = {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RemoveDiscountType = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -5,27 +5,33 @@ 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, useGetCategoriesTicket, useMultiUpload } from './hooks/useTicketData'
|
import { useCreateTicket, useGetCategoriesTicket, useMultiUpload } from './hooks/useTicketData'
|
||||||
import { useGetProfile } from '../profile/hooks/useProfileData'
|
import { useGetProfile } from '../profile/hooks/useProfileData'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { CreateTicketType } from './types/TicketTypes'
|
import { CreateTicketType } from './types/TicketTypes'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
|
import withAuth from '../../hoc/withAuth'
|
||||||
|
import { buildPath } from '../../helpers/utils'
|
||||||
|
// import { useGetMyServices } from '../service/hooks/useServiceData'
|
||||||
|
// import { MyServicesItem } from '../service/types/ServiecTypes'
|
||||||
|
|
||||||
const CreateTicket: FC = () => {
|
const CreateTicket: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const { slug } = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [files, setFiles] = useState<File[]>([])
|
const [files, setFiles] = useState<File[]>([])
|
||||||
const getProfile = useGetProfile()
|
const getProfile = useGetProfile()
|
||||||
const getCategories = useGetCategoriesTicket()
|
const getCategories = useGetCategoriesTicket()
|
||||||
const multiUpload = useMultiUpload()
|
const multiUpload = useMultiUpload()
|
||||||
const createTicket = useCreateTicket()
|
const createTicket = useCreateTicket()
|
||||||
|
// const getMyservice = useGetMyServices('')
|
||||||
|
|
||||||
const formik = useFormik<CreateTicketType>({
|
const formik = useFormik<CreateTicketType>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -33,7 +39,6 @@ const CreateTicket: FC = () => {
|
|||||||
message: '',
|
message: '',
|
||||||
categoryId: '',
|
categoryId: '',
|
||||||
priority: 'LOW',
|
priority: 'LOW',
|
||||||
danakServiceId: '',
|
|
||||||
title: '',
|
title: '',
|
||||||
attachmentUrls: []
|
attachmentUrls: []
|
||||||
},
|
},
|
||||||
@@ -54,7 +59,7 @@ const CreateTicket: FC = () => {
|
|||||||
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
|
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -62,24 +67,23 @@ const CreateTicket: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
createTicket.mutate(values, {
|
createTicket.mutate(values, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
navigate(Pages.ticket.detail + data.data?.ticketId)
|
navigate(buildPath(Pages.ticket.detail + data.data?.ticketId, slug))
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(formik.errors);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4 pb-12'>
|
||||||
<div>
|
<div>
|
||||||
{t('ticket.new_ticket')}
|
{t('ticket.new_ticket')}
|
||||||
</div>
|
</div>
|
||||||
@@ -143,18 +147,6 @@ const CreateTicket: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-6 rowTwoInput'>
|
<div className='mt-6 rowTwoInput'>
|
||||||
<Select
|
|
||||||
label={t('ticket.select_your_service')}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
value: ''
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
placeholder={t('ticket.not_once')}
|
|
||||||
className='border'
|
|
||||||
name='danakServiceId'
|
|
||||||
/>
|
|
||||||
<Select
|
<Select
|
||||||
label={t('ticket.category')}
|
label={t('ticket.category')}
|
||||||
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string, title: string }) => ({
|
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string, title: string }) => ({
|
||||||
@@ -191,11 +183,17 @@ const CreateTicket: FC = () => {
|
|||||||
|
|
||||||
<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()}
|
onClick={() => formik.handleSubmit()}
|
||||||
isLoading={createTicket.isPending || multiUpload.isPending}
|
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>
|
||||||
|
|
||||||
@@ -226,7 +224,7 @@ 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>
|
||||||
@@ -240,4 +238,4 @@ const CreateTicket: FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateTicket
|
export default withAuth(CreateTicket)
|
||||||
+33
-22
@@ -4,7 +4,7 @@ import Input from '../../components/Input'
|
|||||||
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 { CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react'
|
import { CloseCircle, InfoCircle, Paperclip2, Send2 } from 'iconsax-react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { useAddMessageTicket, useCloseTicket, useGetMessages, useMultiUpload } from './hooks/useTicketData'
|
import { useAddMessageTicket, useCloseTicket, useGetMessages, useMultiUpload } from './hooks/useTicketData'
|
||||||
import PageLoading from '../../components/PageLoading'
|
import PageLoading from '../../components/PageLoading'
|
||||||
@@ -12,7 +12,7 @@ import moment from 'moment-jalaali'
|
|||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { AddMessageTicketType } from './types/TicketTypes'
|
import { AddMessageTicketType } from './types/TicketTypes'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from '../../components/Toast'
|
||||||
import { ErrorType } from '../../helpers/types'
|
import { ErrorType } from '../../helpers/types'
|
||||||
import { clx } from '../../helpers/utils'
|
import { clx } from '../../helpers/utils'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
@@ -22,8 +22,8 @@ const TicketDetail: FC = () => {
|
|||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const getMessages = useGetMessages(id ? id : '')
|
|
||||||
const [files, setFiles] = useState<File[]>([])
|
const [files, setFiles] = useState<File[]>([])
|
||||||
|
const getMessages = useGetMessages(id ? id : '')
|
||||||
const addMessage = useAddMessageTicket(id ? id : '')
|
const addMessage = useAddMessageTicket(id ? id : '')
|
||||||
const multiUpload = useMultiUpload()
|
const multiUpload = useMultiUpload()
|
||||||
const [isResetFiles, setIsResetFiles] = useState<boolean>(false)
|
const [isResetFiles, setIsResetFiles] = useState<boolean>(false)
|
||||||
@@ -48,7 +48,7 @@ const TicketDetail: FC = () => {
|
|||||||
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
|
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +57,7 @@ const TicketDetail: FC = () => {
|
|||||||
|
|
||||||
addMessage.mutate(values, {
|
addMessage.mutate(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
formik.resetForm()
|
formik.resetForm()
|
||||||
setFiles([])
|
setFiles([])
|
||||||
setIsResetFiles(true)
|
setIsResetFiles(true)
|
||||||
@@ -66,7 +66,7 @@ const TicketDetail: FC = () => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -75,20 +75,27 @@ const TicketDetail: FC = () => {
|
|||||||
const hadleCloseTicket = () => {
|
const hadleCloseTicket = () => {
|
||||||
closeTicket.mutate(id ? id : '', {
|
closeTicket.mutate(id ? id : '', {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t('success'))
|
toast(t('success'), 'success')
|
||||||
navigate(Pages.ticket.list)
|
navigate(Pages.ticket.list)
|
||||||
},
|
},
|
||||||
onError: (error: ErrorType) => {
|
onError: (error: ErrorType) => {
|
||||||
toast.error(error.response?.data?.error?.message[0])
|
toast(error.response?.data?.error?.message[0], 'error')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSendAnswer = () => {
|
||||||
|
const messageEndRef = document.getElementById('content');
|
||||||
|
if (messageEndRef) {
|
||||||
|
messageEndRef.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
messageEndRef.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<div className='flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
<div>{t('ticket.ticket_number')}</div>
|
<div>{t('ticket.ticket_number')}</div>
|
||||||
<div>12312</div>
|
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
getMessages.isPending ?
|
getMessages.isPending ?
|
||||||
@@ -96,12 +103,12 @@ const TicketDetail: FC = () => {
|
|||||||
:
|
:
|
||||||
<div className='flex xl:flex-row flex-col-reverse gap-6 xl:mt-8 mt-6'>
|
<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'>
|
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||||
<div className='gap-6 rowTwoInput'>
|
|
||||||
<Input
|
<Input
|
||||||
label={t('ticket.select_your_service')}
|
label={t('ticket.subject')}
|
||||||
value={'دی منو'}
|
value={getMessages.data?.data?.ticket?.subject}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
<div className='gap-6 rowTwoInput mt-5'>
|
||||||
<Input
|
<Input
|
||||||
label={t('ticket.user_name')}
|
label={t('ticket.user_name')}
|
||||||
value={getMessages.data?.data?.ticket?.user?.firstName + ' ' + getMessages.data?.data?.ticket?.user?.lastName}
|
value={getMessages.data?.data?.ticket?.user?.firstName + ' ' + getMessages.data?.data?.ticket?.user?.lastName}
|
||||||
@@ -109,13 +116,9 @@ const TicketDetail: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 xl:text-sm text-xs'>
|
|
||||||
{getMessages.data?.data?.ticket?.subject}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
{
|
||||||
getMessages.data?.data?.messages.map((item: any) => {
|
getMessages.data?.data?.messages.map((item: any) => {
|
||||||
if (item?.author?.role?.name === 'user') {
|
if (item?.author?.role?.name === 'USER') {
|
||||||
return (
|
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 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'>
|
<div className='leading-7'>
|
||||||
@@ -154,8 +157,8 @@ const TicketDetail: FC = () => {
|
|||||||
<div className='leading-7 mt-4'>
|
<div className='leading-7 mt-4'>
|
||||||
{item.content}
|
{item.content}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-end mt-6 text-xs text-description'>
|
<div className='flex dltr end mt-6 text-xs text-description'>
|
||||||
10:07 | 1403/09/30
|
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -186,6 +189,7 @@ const TicketDetail: FC = () => {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div className='mt-9'>
|
<div className='mt-9'>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
id='content'
|
||||||
label={t('ticket.answer')}
|
label={t('ticket.answer')}
|
||||||
placeholder={t('ticket.your_description')}
|
placeholder={t('ticket.your_description')}
|
||||||
value={formik.values.content}
|
value={formik.values.content}
|
||||||
@@ -206,11 +210,17 @@ const TicketDetail: FC = () => {
|
|||||||
|
|
||||||
<div className='flex justify-end mt-6'>
|
<div className='flex justify-end mt-6'>
|
||||||
<Button
|
<Button
|
||||||
label={t('ticket.send')}
|
className='xl:max-w-[100px]'
|
||||||
className='max-w-[100px]'
|
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
isLoading={addMessage.isPending || multiUpload.isPending}
|
isLoading={addMessage.isPending || multiUpload.isPending}
|
||||||
/>
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<Send2 size={20} color='white' />
|
||||||
|
<div>
|
||||||
|
{t('ticket.send')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
@@ -223,6 +233,7 @@ const TicketDetail: FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
label={t('ticket.send_answer')}
|
label={t('ticket.send_answer')}
|
||||||
className='text-xs xl:h-10 h-8'
|
className='text-xs xl:h-10 h-8'
|
||||||
|
onClick={handleSendAnswer}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className='bg-[#D52903] xl:h-10 h-8'
|
className='bg-[#D52903] xl:h-10 h-8'
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '../../components/Button'
|
import Button from '../../components/Button'
|
||||||
import { Add, Eye, MessageRemove, MessageTick, MessageTime } from 'iconsax-react'
|
import { Add, Eye, MessageRemove, Messages1, MessageTick, MessageTime } from 'iconsax-react'
|
||||||
import Tabs from '../../components/Tabs'
|
import Tabs from '../../components/Tabs'
|
||||||
import Td from '../../components/Td'
|
import Td from '../../components/Td'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Pages } from '../../config/Pages'
|
import { Pages } from '../../config/Pages'
|
||||||
import { useGetTickets } from './hooks/useTicketData'
|
import { useGetTickets } from './hooks/useTicketData'
|
||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
|
import { buildPath } from '../../helpers/utils'
|
||||||
|
|
||||||
const TicketList: FC = () => {
|
const TicketList: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
const [activeTab, setActiveTab] = useState<'ANSWERED' | 'PENDING' | 'CLOSED'>('ANSWERED')
|
const [activeTab, setActiveTab] = useState<'ANSWERED' | 'PENDING' | 'CLOSED' | ''>('')
|
||||||
const getTickets = useGetTickets(activeTab)
|
const getTickets = useGetTickets(activeTab)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,7 +23,7 @@ const TicketList: FC = () => {
|
|||||||
{t('ticket.tickets')}
|
{t('ticket.tickets')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link to={Pages.ticket.create}>
|
<Link to={buildPath(Pages.ticket.create)}>
|
||||||
<Button
|
<Button
|
||||||
className='w-[172px]'
|
className='w-[172px]'
|
||||||
>
|
>
|
||||||
@@ -41,20 +42,20 @@ const TicketList: FC = () => {
|
|||||||
active={activeTab}
|
active={activeTab}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
icon: <MessageTick color={activeTab === 'ANSWERED' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <Messages1 color={activeTab === '' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('ticket.answered'),
|
label: t('ticket.all'),
|
||||||
value: 'ANSWERED'
|
value: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MessageTime color={activeTab === 'PENDING' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <MessageTime color={activeTab === 'PENDING' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('ticket.checking'),
|
label: t('ticket.checking'),
|
||||||
value: 'PENDING'
|
value: 'PENDING'
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// icon: <Message2 color={activeTab === 'wating' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <MessageTick color={activeTab === 'ANSWERED' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
// label: t('ticket.wait_answered'),
|
label: t('ticket.answered'),
|
||||||
// value: 'wating'
|
value: 'ANSWERED'
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
icon: <MessageRemove color={activeTab === 'CLOSED' ? 'black' : '#8C90A3'} size={22} />,
|
icon: <MessageRemove color={activeTab === 'CLOSED' ? 'black' : '#8C90A3'} size={22} />,
|
||||||
label: t('ticket.closed'),
|
label: t('ticket.closed'),
|
||||||
@@ -71,7 +72,8 @@ const TicketList: FC = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<Td text={t('ticket.number')} />
|
<Td text={t('ticket.number')} />
|
||||||
<Td text={t('ticket.title')} />
|
<Td text={t('ticket.title')} />
|
||||||
<Td text={t('ticket.team')} />
|
<Td text={t('ticket.category')} />
|
||||||
|
<Td text={t('ticket.service')} />
|
||||||
<Td text={t('ticket.date')} />
|
<Td text={t('ticket.date')} />
|
||||||
<Td text={t('ticket.status')} />
|
<Td text={t('ticket.status')} />
|
||||||
<Td text={t('ticket.priority')} />
|
<Td text={t('ticket.priority')} />
|
||||||
@@ -86,6 +88,7 @@ const TicketList: FC = () => {
|
|||||||
<Td text={item.numericId} />
|
<Td text={item.numericId} />
|
||||||
<Td text={item.subject} />
|
<Td text={item.subject} />
|
||||||
<Td text={item?.category?.title} />
|
<Td text={item?.category?.title} />
|
||||||
|
<Td text={item?.danakService?.name} />
|
||||||
<Td text={''}>
|
<Td text={''}>
|
||||||
<div className='dltr text-right'>
|
<div className='dltr text-right'>
|
||||||
{moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')}
|
{moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')}
|
||||||
|
|||||||
@@ -22,14 +22,15 @@ export const useGetCategoriesTicket = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSingleUpload = () => {
|
export const useSingleUpload = (callback?: (value: number) => void) => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: FormData) => api.uploadSingle(variables),
|
mutationFn: (variables: FormData) => api.uploadSingle(variables, callback),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const useMultiUpload = () => {
|
export const useMultiUpload = (callback?: (value: number) => void) => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: FormData) => api.uploadMultiple(variables),
|
mutationFn: (variables: FormData) =>
|
||||||
|
api.uploadMultiple(variables, callback),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,7 +57,16 @@ export const useAddMessageTicket = (id: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useCloseTicket = () => {
|
export const useCloseTicket = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: string) => api.closeTicket(id),
|
mutationFn: (id: string) => api.closeTicket(id),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.refetchQueries({
|
||||||
|
queryKey: ["tickets"],
|
||||||
|
});
|
||||||
|
queryClient.refetchQueries({
|
||||||
|
queryKey: ["ticket-messages"],
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import axios from "../../../config/axios";
|
|||||||
import { AddMessageTicketType, CreateTicketType } from "../types/TicketTypes";
|
import { AddMessageTicketType, CreateTicketType } from "../types/TicketTypes";
|
||||||
|
|
||||||
export const getTickets = async (status: string) => {
|
export const getTickets = async (status: string) => {
|
||||||
const { data } = await axios.get(`/tickets?status=${status}`);
|
let query = ``;
|
||||||
|
if (status) {
|
||||||
|
query = `?status=${status}`;
|
||||||
|
}
|
||||||
|
const { data } = await axios.get(`/tickets${query}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,13 +20,45 @@ export const getCategoriesTicket = async () => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadSingle = async (params: FormData) => {
|
export const uploadSingle = async (
|
||||||
const { data } = await axios.post(`/uploader/single-file`, params);
|
params: FormData,
|
||||||
|
callback?: (value: number) => void
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(`/uploader/single-file`, params, {
|
||||||
|
onUploadProgress: (progressEvent: any) => {
|
||||||
|
if (progressEvent.bytes) {
|
||||||
|
if (callback) {
|
||||||
|
callback(
|
||||||
|
Math.round((progressEvent.loaded / progressEvent.total) * 100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (callback) {
|
||||||
|
callback(0);
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadMultiple = async (params: FormData) => {
|
export const uploadMultiple = async (
|
||||||
const { data } = await axios.post(`/uploader/multi-file`, params);
|
params: FormData,
|
||||||
|
callback?: (value: number) => void
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(`/uploader/multi-file`, params, {
|
||||||
|
onUploadProgress: (progressEvent: any) => {
|
||||||
|
if (progressEvent.bytes) {
|
||||||
|
if (callback) {
|
||||||
|
callback(
|
||||||
|
Math.round((progressEvent.loaded / progressEvent.total) * 100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (callback) {
|
||||||
|
callback(0);
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import { useGetSlugId } from "../pages/home/hooks/useHomeData"
|
|||||||
import { ErrorType } from "../helpers/types"
|
import { ErrorType } from "../helpers/types"
|
||||||
import { toast } from "react-toastify"
|
import { toast } from "react-toastify"
|
||||||
import Request from "../pages/request/Request"
|
import Request from "../pages/request/Request"
|
||||||
|
import TicketList from "../pages/ticket/TicketList"
|
||||||
|
import TicketCreate from "../pages/ticket/CreateTicket"
|
||||||
|
import TicketDetail from "../pages/ticket/Detail"
|
||||||
const MainRouter = () => {
|
const MainRouter = () => {
|
||||||
const { slug } = useParams()
|
const { slug } = useParams()
|
||||||
const getSlugId = useGetSlugId()
|
const getSlugId = useGetSlugId()
|
||||||
@@ -71,6 +74,9 @@ const MainRouter = () => {
|
|||||||
<Route path="reportCard/list" element={<ReportCardList />} />
|
<Route path="reportCard/list" element={<ReportCardList />} />
|
||||||
<Route path="profile" element={<Profile />} />
|
<Route path="profile" element={<Profile />} />
|
||||||
<Route path="request" element={<Request />} />
|
<Route path="request" element={<Request />} />
|
||||||
|
<Route path="tickets" element={<TicketList />} />
|
||||||
|
<Route path="tickets/create" element={<TicketCreate />} />
|
||||||
|
<Route path="tickets/messages/:id" element={<TicketDetail />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user