receip and ticket and ...

This commit is contained in:
hamid zarghami
2024-12-29 16:27:07 +03:30
parent 7756e20a9f
commit 7a5d9d6447
15 changed files with 589 additions and 15 deletions
+39
View File
@@ -14,6 +14,7 @@
"iconsax-react": "^0.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
"react-i18next": "^15.2.0",
"react-loading": "^2.0.3",
"react-multi-date-picker": "^4.5.2",
@@ -1832,6 +1833,15 @@
"dev": true,
"license": "Python-2.0"
},
"node_modules/attr-accept": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -2522,6 +2532,18 @@
"node": ">=16.0.0"
}
},
"node_modules/file-selector": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
"license": "MIT",
"dependencies": {
"tslib": "^2.7.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3671,6 +3693,23 @@
"react": "^19.0.0"
}
},
"node_modules/react-dropzone": {
"version": "14.3.5",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.5.tgz",
"integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==",
"license": "MIT",
"dependencies": {
"attr-accept": "^2.2.4",
"file-selector": "^2.1.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">= 10.13"
},
"peerDependencies": {
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-element-popper": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.7.tgz",
+1
View File
@@ -16,6 +16,7 @@
"iconsax-react": "^0.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
"react-i18next": "^15.2.0",
"react-loading": "^2.0.3",
"react-multi-date-picker": "^4.5.2",
+1 -1
View File
@@ -12,7 +12,7 @@ type Props = {
const Button: FC<Props> = memo((props: Props) => {
const buttonClass = clx(
'flex rounded-xl items-center justify-center text-center h-12 text-sm bg-primary text-white w-full',
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full',
props.disabled && 'cursor-not-allowed opacity-60',
props.className
);
+2 -2
View File
@@ -22,7 +22,7 @@ const Input: FC<Props> = (props: Props) => {
const inputClass = clx(
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
props.readOnly && 'bg-gray-100',
props.readOnly && 'bg-gray-100 border-0 text-description',
props.variant === 'search' && 'bg-[#EEF0F7] border-0 ps-10',
props.className
);
@@ -34,7 +34,7 @@ const Input: FC<Props> = (props: Props) => {
{props.label}
</label>
<div className='w-full relative'>
<div className='w-full relative mt-1'>
<input {...props} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
{
+10 -1
View File
@@ -1,4 +1,5 @@
import { FC, SelectHTMLAttributes } from 'react'
import { clx } from '../helpers/utils'
export type ItemsSelectType = {
value: string,
@@ -9,12 +10,20 @@ type Props = {
items: ItemsSelectType[],
error_text?: string,
placeholder?: string,
label?: string,
} & SelectHTMLAttributes<HTMLSelectElement>
const Select: FC<Props> = (props: Props) => {
return (
<div className='w-full relative'>
<select {...props} className={`w-full text-black block px-2.5 h-10 text-sm rounded-2.5 bg-gray ${props.className}`}>
<label className='text-sm'>
{props.label}
</label>
<select {...props} className={clx(
'w-full text-black block border-border px-2.5 h-10 text-sm rounded-2.5 bg-gray',
props.className,
props.label && 'mt-1'
)}>
{
props.placeholder &&
<option value="" disabled selected>{props.placeholder}</option>
+25
View File
@@ -0,0 +1,25 @@
import { FC, InputHTMLAttributes } from 'react'
type Props = {
label: string,
} & InputHTMLAttributes<HTMLTextAreaElement>
const Textarea: FC<Props> = (props: Props) => {
return (
<div className='w-full relative'>
<label className='text-sm'>
{props.label}
</label>
<textarea
className='border border-border w-full rounded-xl px-4 py-3 min-h-[100px] mt-1 text-xs'
{...props}
>
</textarea>
</div>
)
}
export default Textarea
+44
View File
@@ -0,0 +1,44 @@
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDropzone } from 'react-dropzone'
type Props = {
label: string,
}
const UploadBox: FC<Props> = (props: Props) => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const onDrop = useCallback((acceptedFiles: File[]) => {
setFile(acceptedFiles[0])
}, [])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
return (
<div>
<div className='text-sm'>{props.label}</div>
<div className='w-full h-10 mt-1 border border-border rounded-xl items-center px-2 flex gap-2.5'>
<button {...getRootProps()} className=' w-fit h-7 rounded-lg text-xs px-6 bg-secondary'>
<input {...getInputProps()} />
{t('select_file')}
</button>
<div className='text-description text-xs'>
{
file ?
file.name
:
t('no_select_file')
}
</div>
</div>
</div>
)
}
export default UploadBox
+2
View File
@@ -13,6 +13,8 @@ export const Pages = {
receipts: "/receipts",
ticket: {
list: "/tickets",
create: "/tickets/create",
detail: "/tickets/messages/",
},
announcement: "/announcement",
criticisms: "/criticisms",
+34 -2
View File
@@ -40,6 +40,7 @@
"errors": {
"required": "این فیلد اجباری می باشد"
},
"description": "توضیحات",
"sidebar": {
"menu": "منو",
"mainpage": "صفحه اصلی",
@@ -94,6 +95,37 @@
"date": "تاریخ",
"status": "وضعیت",
"priority": "الویت",
"detail": "جزییات"
}
"detail": "جزییات",
"submit_your_message": "ثبت پیام شما",
"name": "نام",
"email": "ایمیل",
"subject": "موضوع",
"enter_your_subject": "موضوع درخواست خودرا وارد کنید",
"select_your_service": "سرویس مربوطه",
"not_once": "هیچکدام",
"your_description": "توضیحات شما",
"attachment": "فایل های ضمیمه",
"send": "ارسال",
"title_hint": "جهت پاسخگویی بهتر به مسائل زیر توجه بفرمایید :",
"ticket_number": "تیکت شماره",
"user_name": "نام کاربر",
"expert": "کارشناس: ",
"answer": "پاسخ",
"send_answer": "ارسال پاسخ",
"close_ticket": "بستن تیکت",
"info_ticket": "اطلاعات تیکت",
"update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود"
},
"receip": {
"receip_list": "لیست صورتحساب ها",
"dont_pay": "پرداخت نشده",
"dont_confrim": "تایید نشده",
"paid": "پرداخت شده",
"canceled": "لغو شده",
"returned": "پس داده شده",
"its_time": "سررسیده شده",
"archive": "بایگانی شده"
},
"select_file": "انتخاب فایل",
"no_select_file": "هیچ فایلی انتخاب نشده است"
}
+104
View File
@@ -0,0 +1,104 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Tabs from '../../components/Tabs'
import { Calendar, CloseCircle, FolderOpen, MoneyChange, WalletCheck, WalletRemove, WalletSearch } from 'iconsax-react'
import Td from '../../components/Td'
const ReceiptsList: FC = () => {
const { t } = useTranslation('global')
const [activeTab, setActiveTab] = useState<string>('unpaid')
return (
<div className='mt-4'>
<div>
{t('receip.receip_list')}
</div>
<div className='mt-14'>
<Tabs
active={activeTab}
items={[
{
icon: <WalletRemove color={activeTab === 'unpaid' ? 'black' : '#8C90A3'} size={22} />,
label: t('receip.dont_pay'),
value: 'unpaid'
},
{
icon: <WalletSearch color={activeTab === 'not_confimed' ? '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'),
value: 'paid'
},
{
icon: <CloseCircle color={activeTab === 'canceled' ? 'black' : '#8C90A3'} size={22} />,
label: t('receip.canceled'),
value: 'canceled'
},
{
icon: <MoneyChange color={activeTab === 'returned' ? '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'),
value: 'archive'
},
]}
onChange={setActiveTab}
/>
</div>
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<table className='w-full text-sm '>
<thead className='thead'>
<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>
</thead>
<tbody>
<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>
<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>
</table>
</div>
</div>
)
}
export default ReceiptsList
+131
View File
@@ -0,0 +1,131 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { SIDEBAR_SIZE_LEFT } from '../../config/Const'
import Input from '../../components/Input'
import Select from '../../components/Select'
import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox'
import Button from '../../components/Button'
import { TickSquare } from 'iconsax-react'
const CreateTicket: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4'>
<div>
{t('ticket.new_ticket')}
</div>
<div className='flex gap-6 mt-8'>
<div className='flex-1 bg-white py-8 px-10 rounded-3xl'>
<div>
{t('ticket.submit_your_message')}
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('ticket.name')}
readOnly
placeholder='مهرداد مظفری'
/>
<Input
label={t('ticket.email')}
readOnly
placeholder='Info@example.com'
/>
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('ticket.subject')}
placeholder={t('ticket.enter_your_subject')}
/>
<Select
label={t('ticket.subject')}
items={[
{
label: '',
value: ''
}
]}
placeholder={t('ticket.priority')}
className='border'
/>
</div>
<div className='mt-6'>
<Select
label={t('ticket.select_your_service')}
items={[
{
label: '',
value: ''
}
]}
placeholder={t('ticket.not_once')}
className='border'
/>
</div>
<div className='mt-6'>
<Textarea
label={t('description')}
placeholder={t('ticket.your_description')}
/>
</div>
<div className='mt-6'>
<UploadBox
label={t('ticket.attachment')}
/>
</div>
<div className='mt-8 flex justify-end'>
<Button
label={t('ticket.send')}
className='max-w-[100px]'
/>
</div>
</div>
<div style={{ width: SIDEBAR_SIZE_LEFT }} className='bg-white py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm'>
{t('ticket.title_hint')}
</div>
<div className='mt-6'>
<div className='flex items-start gap-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
</div>
</div>
<div className='flex items-start gap-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
</div>
</div>
<div className='flex items-start gap-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default CreateTicket
+157
View File
@@ -0,0 +1,157 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { SIDEBAR_SIZE_LEFT } from '../../config/Const'
import Input from '../../components/Input'
import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox'
import Button from '../../components/Button'
import { CloseCircle, InfoCircle } from 'iconsax-react'
const TicketDetail: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4'>
<div className='flex gap-1'>
<div>{t('ticket.ticket_number')}</div>
<div>12312</div>
</div>
<div className='flex gap-6 mt-8'>
<div className='flex-1 bg-white py-8 px-10 rounded-3xl'>
<div className='gap-6 rowTwoInput'>
<Input
label={t('ticket.select_your_service')}
value={'دی منو'}
readOnly
/>
<Input
label={t('ticket.user_name')}
value={'مهرداد مظفری'}
readOnly
/>
</div>
<div className='mt-8 text-sm'>
موضوع درخواست پشتیبانی
</div>
<div className='mt-6 text-sm bg-[#F6F7FA] p-6 rounded-3xl rounded-tr-none max-w-[70%]'>
<div className='leading-7'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد،
</div>
<div className='flex justify-end mt-6 text-xs text-description'>
10:07 | 1403/09/30
</div>
</div>
<div className='w-full flex justify-end'>
<div className='mt-6 text-sm bg-[#EBEDF5] p-6 rounded-3xl rounded-tl-none max-w-[70%]'>
<div className='flex gap-1'>
<div className='font-bold'>
{t('ticket.expert')}
</div>
<div>سیما فرهادی</div>
</div>
<div className='leading-7 mt-4'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد،
</div>
<div className='flex justify-end mt-6 text-xs text-description'>
10:07 | 1403/09/30
</div>
</div>
</div>
<div className='mt-9'>
<Textarea
label={t('ticket.answer')}
placeholder={t('ticket.your_description')}
/>
</div>
<div className='mt-7'>
<UploadBox
label={t('ticket.attachment')}
/>
</div>
<div className='flex justify-end mt-6'>
<Button
label={t('ticket.send')}
className='max-w-[100px]'
/>
</div>
</div>
<div style={{ width: SIDEBAR_SIZE_LEFT }} className='h-fit'>
<div className='bg-white p-6 rounded-3xl flex gap-4'>
<Button
label={t('ticket.send_answer')}
className='text-xs'
/>
<Button
className='bg-[#D52903]'
>
<div className='flex gap-2 text-xs items-center'>
<CloseCircle
color='white'
size={20}
/>
<div>
{t('ticket.close_ticket')}
</div>
</div>
</Button>
</div>
<div className='bg-white p-6 rounded-3xl mt-8'>
<div className='flex justify-between'>
<div className='text-sm'>
{t('ticket.info_ticket')}
</div>
<div className='h-7 px-3 rounded-xl flex items-center text-xs bg-green-100 text-green-400'>
{t('ticket.answered')}
</div>
</div>
<div className='mt-14 text-xs w-full'>
<div className='flex justify-between text-description'>
<div>{t('ticket.ticket_number')}</div>
<div>
۱۲۳۴۵۵
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.ticket_number')}</div>
<div>
۱۲۳۴۵۵
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.ticket_number')}</div>
<div>
۱۲۳۴۵۵
</div>
</div>
<div className='flex mt-6 justify-between text-description'>
<div>{t('ticket.ticket_number')}</div>
<div>
۱۲۳۴۵۵
</div>
</div>
</div>
<div className='mt-10 border-t text-xs pt-5 border-border items-center flex gap-2'>
<InfoCircle size={20} color='black' />
<div>
{t('ticket.update_sms')}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default TicketDetail
+13 -9
View File
@@ -4,6 +4,8 @@ import Button from '../../components/Button'
import { Add, Message2, MessageRemove, MessageTick, MessageTime } from 'iconsax-react'
import Tabs from '../../components/Tabs'
import Td from '../../components/Td'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
const TicketList: FC = () => {
@@ -17,16 +19,18 @@ const TicketList: FC = () => {
{t('ticket.tickets')}
</div>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<Add size={20} color='white' />
<div>
{t('ticket.new_ticket')}
<Link to={Pages.ticket.create}>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<Add size={20} color='white' />
<div>
{t('ticket.new_ticket')}
</div>
</div>
</div>
</Button>
</Button>
</Link>
</div>
<div className='mt-14'>
+18
View File
@@ -0,0 +1,18 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
const TransactionList: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4'>
<div>
{t('receip.receip_list')}
</div>
</div>
)
}
export default TransactionList
+8
View File
@@ -6,6 +6,10 @@ import { Pages } from '../config/Pages'
import Home from '../pages/home/Home'
import MyServices from '../pages/service/MyServices'
import TicketList from '../pages/ticket/TicketList'
import CreateTicket from '../pages/ticket/CreateTicket'
import TicketDetail from '../pages/ticket/Detail'
import TransactionList from '../pages/transaction/List'
import ReceiptsList from '../pages/receipts/List'
const MainRouter: FC = () => {
return (
@@ -18,6 +22,10 @@ const MainRouter: FC = () => {
<Route path={Pages.dashboard} element={<Home />} />
<Route path={Pages.services.mine} element={<MyServices />} />
<Route path={Pages.ticket.list} element={<TicketList />} />
<Route path={Pages.ticket.create} element={<CreateTicket />} />
<Route path={Pages.ticket.detail + ':id'} element={<TicketDetail />} />
<Route path={Pages.transactions} element={<TransactionList />} />
<Route path={Pages.receipts} element={<ReceiptsList />} />
</Routes>
</div>
</div>