onTabChange(item.value)} key={item.value} className={clx(
- 'h-[32px] flex items-center justify-center w-[122px] cursor-pointer rounded-full',
- activeTab === item.value && 'bg-primary'
+
handleChange(item.value)} key={item.value} className={clx(
+ 'h-[32px] flex items-center gap-2 justify-center min-w-[122px] px-3 cursor-pointer rounded-full',
+ currentActive === item.value && 'bg-primary'
)}>
+ {item.icon}
{item.label}
)
diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx
index 165ae24..952d10c 100644
--- a/src/config/Paths.tsx
+++ b/src/config/Paths.tsx
@@ -35,4 +35,10 @@ export const Paths = {
auth: {
login: '/auth/login',
},
+ ticket: {
+ list: "/tickets/list",
+ create: "/tickets/create",
+ detail: "/tickets/messages/",
+ category: "/tickets/category",
+ },
}
\ No newline at end of file
diff --git a/src/pages/ticket/Category.tsx b/src/pages/ticket/Category.tsx
new file mode 100644
index 0000000..c325f28
--- /dev/null
+++ b/src/pages/ticket/Category.tsx
@@ -0,0 +1,196 @@
+import { type FC } from 'react'
+import Select from '@/components/Select'
+import Input from '@/components/Input'
+import Td from '@/components/Td'
+import SwitchComponent from '@/components/Switch'
+import { useFormik } from 'formik'
+import type { CategoriesItemType, CreateCategoryType } from './types/TicketTypes'
+import * as Yup from 'yup'
+import Button from '@/components/Button'
+import { useCreateCategory, useGetCategories } from './hooks/useTicketData'
+import Textarea from '@/components/Textarea'
+import type { ErrorType } from '@/helpers/types'
+import { toast } from 'react-toastify'
+
+const TicketCategory: FC = () => {
+
+ // TODO: این hook باید بعداً وصل شود - مربوط به users است
+ // const getGroups = useGetGroups()
+ const createCategory = useCreateCategory()
+ const getCategories = useGetCategories()
+
+ const formik = useFormik
({
+ initialValues: {
+ title: '',
+ description: '',
+ userGroupId: '',
+ isActive: true
+ },
+ validationSchema: Yup.object({
+ title: Yup.string().required('این فیلد الزامی است'),
+ userGroupId: Yup.string().required('این فیلد الزامی است'),
+ description: Yup.string().required('این فیلد الزامی است'),
+ }),
+ onSubmit: (values: CreateCategoryType) => {
+ createCategory.mutate(values, {
+ onSuccess: () => {
+ formik.resetForm()
+ },
+ onError: (error: ErrorType) => {
+ toast.error(error.response?.data?.error.message[0])
+ }
+ })
+ }
+ })
+
+ return (
+
+
+ دستهبندی تیکتها
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ getCategories.data?.data?.ticketCategories?.map((item: CategoriesItemType) => (
+
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ افزودن دستهبندی
+
+
+
+
+ وضعیت دستهبندی
+
+
+
formik.setFieldValue('isActive', value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ // label: item.name,
+ // value: item.id
+ // }))
+ ]}
+ {...formik.getFieldProps('userGroupId')}
+ error_text={formik.touched.userGroupId && formik.errors.userGroupId ? formik.errors.userGroupId : ''}
+ placeholder='انتخاب کنید'
+ className='border'
+ />
+
+
+
+ formik.handleSubmit()}
+ isLoading={createCategory.isPending}
+ />
+
+
+ {/*
*/}
+
+
+
+ )
+}
+
+export default TicketCategory
\ No newline at end of file
diff --git a/src/pages/ticket/CreateTicket.tsx b/src/pages/ticket/CreateTicket.tsx
new file mode 100644
index 0000000..c1c182e
--- /dev/null
+++ b/src/pages/ticket/CreateTicket.tsx
@@ -0,0 +1,236 @@
+import { type FC } from 'react'
+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 { TickCircle, TickSquare } from 'iconsax-react'
+import { useCreateTicket, useGetCategories } from './hooks/useTicketData'
+import { useFormik } from 'formik'
+import * as Yup from 'yup'
+import type { ErrorType } from '@/helpers/types'
+import { useNavigate } from 'react-router-dom'
+import type { CreateTicketAdminType } from './types/TicketTypes'
+import { toast } from 'react-toastify'
+
+const CreateTicket: FC = () => {
+ const navigate = useNavigate()
+ // const [files, setFiles] = useState([])
+ const getCategories = useGetCategories()
+ const createTicket = useCreateTicket()
+
+ const formik = useFormik({
+ initialValues: {
+ subject: '',
+ message: '',
+ categoryId: '',
+ priority: 'LOW',
+ attachmentUrls: [],
+ userId: ''
+ },
+ validationSchema: Yup.object({
+ subject: Yup.string().required('این فیلد الزامی است'),
+ message: Yup.string().required('این فیلد الزامی است'),
+ categoryId: Yup.string().required('این فیلد الزامی است'),
+ priority: Yup.string().required('این فیلد الزامی است'),
+ }),
+ onSubmit: async (values) => {
+ // TODO: آپلود فایل باید بعداً وصل شود - نیاز به multiUpload API دارد
+ // if (files.length > 0) {
+ // const images = new FormData()
+ // files.forEach(file => {
+ // images.append('files', file)
+ // })
+ // await multiUpload.mutateAsync(images, {
+ // onSuccess: async (data) => {
+ // values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
+ // },
+ // onError: (error: ErrorType) => {
+ // toast.error(error.response?.data?.error?.message[0])
+ // }
+ // })
+ // } else {
+ // delete values.attachmentUrls
+ // }
+ delete values.attachmentUrls
+
+ if (values.danakServiceId === '') {
+ delete values.danakServiceId
+ }
+
+
+ createTicket.mutate(values, {
+ onSuccess: (data) => {
+ toast.success('تیکت با موفقیت ثبت شد')
+ formik.resetForm()
+ navigate(`/ticket/detail/${data.data?.ticketId}`)
+ },
+ onError: (error: ErrorType) => {
+ toast.error(error.response?.data?.error?.message[0])
+ }
+ })
+ }
+ })
+
+
+ return (
+
+
+ تیکت جدید
+
+
+ {
+ getCategories.isPending ?
+
در حال بارگذاری...
+ :
+
+
+
+ پیام خود را ثبت کنید
+
+
+
+ ({
+ // label: item.firstName + ' ' + item.lastName,
+ // value: item.id
+ // }))
+ ]}
+ {...formik.getFieldProps('userId')}
+ />
+
+
+
+
+
+
+
+
+
+
+ ({
+ label: item.title,
+ value: item.id
+ })
+ ) ?? []}
+ placeholder='انتخاب کنید'
+ className='border'
+ name='categoryId'
+ value={formik.values.categoryId}
+ onChange={formik.handleChange}
+ error_text={formik.touched.categoryId && formik.errors.categoryId ? formik.errors.categoryId : ''}
+ />
+
+
+
+
+
+
+ { }}
+ />
+
+
+
+
formik.handleSubmit()}
+ isLoading={createTicket.isPending}
+ >
+
+
+
+
+
+
+
+ راهنمای ارسال تیکت
+
+
+
+
+
+
+
+
+ سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
+
+
+
+
+
+
+
+ لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
+
+
+
+
+
+
+
+ پاسخگویی 24 ساعته تلفنی را تنها از داناک می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
+
+
+
+
+
+ }
+
+
+
+
+ )
+}
+
+export default CreateTicket
diff --git a/src/pages/ticket/Detail.tsx b/src/pages/ticket/Detail.tsx
new file mode 100644
index 0000000..dc27c01
--- /dev/null
+++ b/src/pages/ticket/Detail.tsx
@@ -0,0 +1,352 @@
+import { type FC, Fragment, useState } from 'react'
+import Input from '@/components/Input'
+import Textarea from '@/components/Textarea'
+import UploadBox from '@/components/UploadBox'
+import Button from '@/components/Button'
+import { CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react'
+import { useNavigate, useParams } from 'react-router-dom'
+import { useAddMessageTicket, useCloseTicket, useGetMessages } from './hooks/useTicketData'
+import moment from 'moment-jalaali'
+import { useFormik } from 'formik'
+import type { AddMessageTicketType, TicketMessageType } from './types/TicketTypes'
+import * as Yup from 'yup'
+import { toast } from 'react-toastify'
+import type { ErrorType } from '@/helpers/types'
+import { clx } from '@/helpers/utils'
+import { Paths } from '@/config/Paths'
+import ReferTicket from './components/ReferTicket'
+
+const TicketDetail: FC = () => {
+
+ const navigate = useNavigate()
+ const { id } = useParams()
+ const getMessages = useGetMessages(id ? id : '')
+ const [files, setFiles] = useState([])
+ const addMessage = useAddMessageTicket(id ? id : '')
+ const [isResetFiles, setIsResetFiles] = useState(false)
+ const closeTicket = useCloseTicket()
+
+ const formik = useFormik({
+ initialValues: {
+ content: '',
+ attachmentUrls: [],
+ },
+ validationSchema: Yup.object().shape({
+ content: Yup.string().required('این فیلد الزامیست'),
+ }),
+ onSubmit: async (values) => {
+ // if (files.length > 0) {
+ // const images = new FormData()
+ // files.forEach(file => {
+ // images.append('files', file)
+ // })
+ // await multiUpload.mutateAsync(images, {
+ // onSuccess: async (data) => {
+ // values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
+ // },
+ // onError: (error: ErrorType) => {
+ // toast.error(error.response?.data?.error?.message[0])
+ // }
+ // })
+ // } else {
+ // delete values.attachmentUrls
+ // }
+ delete values.attachmentUrls
+
+ addMessage.mutate(values, {
+ onSuccess: () => {
+ toast.success('پیام با موفقیت ارسال شد')
+ formik.resetForm()
+ setFiles([])
+ setIsResetFiles(true)
+ setTimeout(() => {
+ setIsResetFiles(false)
+ }, 1000);
+ },
+ onError: (error: ErrorType) => {
+ toast.error(error.response?.data?.error?.message[0])
+ }
+ })
+ },
+ })
+
+ const hadleCloseTicket = () => {
+ closeTicket.mutate(id ? id : '', {
+ onSuccess: () => {
+ toast.success('تیکت با موفقیت بسته شد')
+ navigate(Paths.ticket.list)
+ },
+ onError: (error: ErrorType) => {
+ toast.error(error.response?.data?.error?.message[0])
+ }
+ })
+ }
+
+ return (
+
+
+
شماره تیکت
+
{getMessages.data?.data?.ticket?.numericId || '12345'}
+
+ {
+ getMessages.isPending ?
+
در حال بارگذاری...
+ :
+
+
+
+
+
+
+
+
+
+
+ {getMessages.data?.data?.ticket?.subject || 'مشکل در دریافت فایل نهایی پروژه'}
+
+
+ {
+ getMessages.data?.data?.messages && getMessages.data.data.messages.length > 0 ? (
+ getMessages.data.data.messages.map((item: TicketMessageType) => {
+ if (item?.author?.roles[0]?.name === 'user') {
+ return (
+
+
+ {item.content}
+
+
+ {moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
+
+
+
+ {
+ item.attachments && item?.attachments?.map((item, index: number) => {
+ return (
+
+ )
+ })
+ }
+
+
+ )
+ } else {
+ return (
+
+
+
+
+ کارشناس
+
+
{item.author?.firstName + ' ' + item?.author?.lastName}
+
+
+ {item.content}
+
+
+ {moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
+
+
+
+ {
+ item.attachments && item?.attachments?.map((item, index: number) => {
+ return (
+
+ )
+ })
+ }
+
+
+
+
+
+ )
+ }
+ })
+ ) : (
+ <>
+
+
+ سلام، من فایل نهایی پروژهام رو دریافت نکردم. ممنون میشم پیگیری کنید.
+
+
+ 1403/08/01 14:30
+
+
+
+
+
+
+ کارشناس
+
+
رضا محمدی
+
+
+ سلام، بله حتما. فایل نهایی پروژه شما در حال بررسی نهایی است و تا پایان امروز برای شما ارسال خواهد شد.
+
+
+ 1403/08/01 15:45
+
+
+
+ >
+ )
+ }
+
+
+
+ {
+ getMessages.data?.data?.ticket?.status !== 'CLOSED' &&
+
+
+
+
+
+
+
+
+
+
+ formik.handleSubmit()}
+ isLoading={addMessage.isPending}
+ />
+
+
+ }
+
+
+
+ {
+ getMessages.data?.data?.ticket?.status !== 'CLOSED' &&
+
+ }
+
+
+
+ اطلاعات تیکت
+
+
+ {
+ getMessages.data?.data?.ticket?.status === 'PENDING' ? 'در انتظار' :
+ getMessages.data?.data?.ticket?.status === 'ANSWERED' ? 'پاسخ داده شده' :
+ 'بسته شده'
+ }
+
+
+
+
+
+
شماره تیکت
+
+ {getMessages.data?.data?.ticket?.numericId || '12345'}
+
+
+
+
ارجاع به
+
+ {getMessages.data?.data?.ticket?.assignedTo ? (getMessages.data?.data?.ticket?.assignedTo as any)?.firstName + ' ' + (getMessages.data?.data?.ticket?.assignedTo as any)?.lastName : 'رضا محمدی'}
+
+
+
+
تاریخ
+
+ {getMessages.data?.data?.ticket?.createdAt ? moment(getMessages.data?.data?.ticket?.createdAt).format('jYYYY/jMM/jDD HH:mm') : '1403/08/01 14:30'}
+
+
+
+
دستهبندی
+
+ {getMessages.data?.data?.ticket?.category?.title || 'پشتیبانی فنی'}
+
+
+
+
اولویت
+
+ {getMessages.data?.data?.ticket?.priority === 'low' ? 'کم' : getMessages.data?.data?.ticket?.priority === 'medium' ? 'متوسط' : getMessages.data?.data?.ticket?.priority === 'high' ? 'زیاد' : 'متوسط'}
+
+
+
+
+
+
+
+
+ بهروزرسانی از طریق پیامک
+
+
+
+
+ {
+
+ }
+
+
+ }
+
+ )
+}
+
+export default TicketDetail
diff --git a/src/pages/ticket/TicketList.tsx b/src/pages/ticket/TicketList.tsx
new file mode 100644
index 0000000..3defaaf
--- /dev/null
+++ b/src/pages/ticket/TicketList.tsx
@@ -0,0 +1,147 @@
+import { type FC, useEffect, useMemo, useState } from 'react'
+import { Eye, MessageRemove, MessageTick, MessageTime } from 'iconsax-react'
+import Tabs from '@/components/Tabs'
+import Table from '@/components/Table'
+import type { ColumnType } from '@/components/types/TableTypes'
+import { Link, useSearchParams } from 'react-router-dom'
+import { useGetTickets } from './hooks/useTicketData'
+import moment from 'moment-jalaali'
+import type { TicketItemType } from './types/TicketTypes'
+import OpenTicket from './components/OpenTicket'
+
+const TicketList: FC = () => {
+
+ const [page, setPage] = useState(1)
+ const [searchParams] = useSearchParams()
+ const [customerId, setCustomerId] = useState('')
+ const [activeTab, setActiveTab] = useState<'ANSWERED' | 'PENDING' | 'CLOSED' | ''>('PENDING')
+ const getTicket = useGetTickets(activeTab, customerId, page)
+
+ useEffect(() => {
+ const urlCustomerId = searchParams.get('user')
+ if (urlCustomerId) {
+ setCustomerId(urlCustomerId)
+ setActiveTab('')
+ }
+ }, [searchParams])
+
+ const columns = useMemo[]>(() => [
+ {
+ title: 'شماره',
+ key: 'numericId',
+ render: (item) => item.numericId + ''
+ },
+ {
+ title: 'عنوان',
+ key: 'subject'
+ },
+ {
+ title: 'کاربر',
+ key: 'user',
+ render: (item) => item.user.firstName + ' ' + item.user.lastName
+ },
+ {
+ title: 'ارجاع به',
+ key: 'assignedTo',
+ render: (item) => item?.assignedTo?.firstName + ' ' + item?.assignedTo?.lastName
+ },
+ {
+ title: 'دستهبندی',
+ key: 'category',
+ render: (item) => item?.category?.title
+ },
+ {
+ title: 'سرویس',
+ key: 'danakService',
+ render: (item) => item?.danakService?.name
+ },
+ {
+ title: 'تاریخ',
+ key: 'updatedAt',
+ render: (item) => (
+
+ {moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')}
+
+ )
+ },
+ {
+ title: 'وضعیت',
+ key: 'status',
+ render: (item) =>
+ item.status === 'PENDING' ? 'در حال بررسی' :
+ item.status === 'ANSWERED' ? 'پاسخ داده شده' :
+ item.status === 'CLOSED' ? 'بسته شده' : item.status
+ },
+ {
+ title: 'اولویت',
+ key: 'priority',
+ render: (item) =>
+ item.priority === 'LOW' ? 'کم' :
+ item.priority === 'MEDIUM' ? 'متوسط' :
+ item.priority === 'HIGH' ? 'زیاد' : item.priority
+ },
+ {
+ title: 'جزئیات',
+ key: 'actions',
+ render: (item) => (
+
+
+
+
+ {item.status === 'CLOSED' && (
+
+ )}
+
+ )
+ }
+ ], [getTicket.refetch])
+
+
+ return (
+
+
+
+
+ ,
+ label: 'در حال بررسی',
+ value: 'PENDING'
+ },
+ {
+ icon: ,
+ label: 'پاسخ داده شده',
+ value: 'ANSWERED'
+ },
+ {
+ icon: ,
+ label: 'بسته شده',
+ value: 'CLOSED'
+ },
+ ]}
+ onChange={(value) => setActiveTab(value as 'ANSWERED' | 'PENDING' | 'CLOSED')}
+ />
+
+
+
1 ? {
+ currentPage: page,
+ totalPages: getTicket.data?.data?.pager?.totalPages ?? 1,
+ onPageChange: setPage
+ } : undefined}
+ />
+
+ )
+}
+
+export default TicketList
\ No newline at end of file
diff --git a/src/pages/ticket/components/OpenTicket.tsx b/src/pages/ticket/components/OpenTicket.tsx
new file mode 100644
index 0000000..7110453
--- /dev/null
+++ b/src/pages/ticket/components/OpenTicket.tsx
@@ -0,0 +1,34 @@
+import { type FC } from 'react'
+import Button from '@/components/Button'
+import { useOpenTicket } from '../hooks/useTicketData'
+
+type Props = {
+ id: string,
+ refetch: () => void
+}
+
+const OpenTicket: FC = ({ id, refetch }) => {
+
+ const { mutate: openTicket, isPending } = useOpenTicket()
+
+ const handleOpenTicket = () => {
+ openTicket(id, {
+ onSuccess: () => {
+ refetch()
+ }
+ })
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default OpenTicket
\ No newline at end of file
diff --git a/src/pages/ticket/components/ReferTicket.tsx b/src/pages/ticket/components/ReferTicket.tsx
new file mode 100644
index 0000000..8562726
--- /dev/null
+++ b/src/pages/ticket/components/ReferTicket.tsx
@@ -0,0 +1,76 @@
+import { type FC } from 'react'
+import { useParams } from 'react-router-dom'
+import { useReferTicket } from '../hooks/useTicketData'
+import Button from '@/components/Button'
+import { toast } from 'react-toastify'
+import type { ErrorType } from '@/helpers/types'
+
+type Props = {
+ refetch: () => void
+}
+
+const ReferTicket: FC = ({ refetch }) => {
+
+ const { id } = useParams()
+ // const [userId, setUserId] = useState('')
+ // TODO: این hook باید بعداً وصل شود - مربوط به users است
+ // const getUsers = useGetUsers('', 'tickets')
+ const referTicket = useReferTicket()
+
+ const handleReferTicket = () => {
+ referTicket.mutate({ id: id as string, userId: '' }, {
+ onSuccess: () => {
+ refetch()
+ toast.success('تیکت با موفقیت ارجاع داده شد')
+ },
+ onError: (error: ErrorType) => {
+ toast.error(error.response?.data?.error.message[0])
+ }
+ })
+ }
+
+ return (
+
+
+
+
+ {/* TODO: این بخش باید بعداً وصل شود - نیاز به getUsers.data دارد */}
+ {/* {
+ getUsers.data?.data?.users?.map((item: UserItemType) => (
+
+
+ setUserId(e.target.checked ? item.id : '')}
+ />
+
+
+ {item.firstName + ' ' + item.lastName}
+
+
+ ))
+ } */}
+
+
+ لیست کاربران در حال حاضر در دسترس نیست
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ReferTicket
diff --git a/src/pages/ticket/hooks/useTicketData.ts b/src/pages/ticket/hooks/useTicketData.ts
new file mode 100644
index 0000000..96fc1e6
--- /dev/null
+++ b/src/pages/ticket/hooks/useTicketData.ts
@@ -0,0 +1,143 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+// import * as api from "../service/TicketServiec";
+import type {
+ AddMessageTicketType,
+ CreateCategoryType,
+ CreateTicketAdminType,
+} from "../types/TicketTypes";
+
+// Mock data برای نمایش UI
+const mockTicketsData = {
+ data: {
+ tickets: [],
+ pager: {
+ totalPages: 1,
+ currentPage: 1,
+ },
+ },
+};
+
+const mockMessagesData = {
+ data: {
+ ticket: {
+ id: "",
+ numericId: 0,
+ subject: "",
+ status: "PENDING",
+ priority: "LOW",
+ user: {
+ firstName: "",
+ lastName: "",
+ },
+ assignedTo: null,
+ category: {
+ title: "",
+ },
+ danakService: null,
+ createdAt: new Date().toISOString(),
+ },
+ messages: [],
+ supportPlan: null,
+ },
+};
+
+const mockCategoriesData = {
+ data: {
+ ticketCategories: [],
+ },
+};
+
+export const useGetTickets = (
+ status: string,
+ customerId?: string,
+ page?: number
+) => {
+ return useQuery({
+ queryKey: ["tickets", status, customerId, page],
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // queryFn: () => api.getTickets(status, customerId, page),
+ queryFn: () => Promise.resolve(mockTicketsData),
+ });
+};
+
+export const useGetMessages = (id: string) => {
+ return useQuery({
+ queryKey: ["ticket-messages", id],
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // queryFn: () => api.getMessages(id),
+ queryFn: () => Promise.resolve(mockMessagesData),
+ enabled: !!id,
+ });
+};
+
+export const useAddMessageTicket = (id: string) => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (variables: AddMessageTicketType) =>
+ // api.addMessageTicket(id, variables),
+ mutationFn: (_variables: AddMessageTicketType) =>
+ Promise.resolve({ data: { success: true } }),
+ onSuccess: () => {
+ queryClient.refetchQueries({
+ queryKey: ["ticket-messages", id],
+ });
+ },
+ });
+};
+
+export const useCloseTicket = () => {
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (id: string) => api.closeTicket(id),
+ mutationFn: (_id: string) => Promise.resolve({ data: { success: true } }),
+ });
+};
+
+export const useCreateCategory = () => {
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (variables: CreateCategoryType) =>
+ // api.createCategory(variables),
+ mutationFn: (_variables: CreateCategoryType) =>
+ Promise.resolve({ data: { success: true } }),
+ });
+};
+
+export const useGetCategories = () => {
+ return useQuery({
+ queryKey: ["categories"],
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // queryFn: () => api.getCategories(),
+ queryFn: () => Promise.resolve(mockCategoriesData),
+ });
+};
+
+export const useReferTicket = () => {
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (variables: { id: string; userId: string }) =>
+ // api.referTicket(variables.id, variables.userId),
+ mutationFn: (_variables: { id: string; userId: string }) =>
+ Promise.resolve({ data: { success: true } }),
+ });
+};
+
+export const useOpenTicket = () => {
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (id: string) => api.openTicket(id),
+ mutationFn: (_id: string) => Promise.resolve({ data: { success: true } }),
+ });
+};
+
+export const useCreateTicket = () => {
+ return useMutation({
+ // TODO: API call کامنت شده - باید بعداً وصل شود
+ // mutationFn: (variables: CreateTicketAdminType) =>
+ // api.createTicket(variables),
+ mutationFn: (_variables: CreateTicketAdminType) =>
+ Promise.resolve({ data: { ticketId: "mock-ticket-id" } }),
+ });
+};
diff --git a/src/pages/ticket/service/TicketServiec.ts b/src/pages/ticket/service/TicketServiec.ts
new file mode 100644
index 0000000..e889ccf
--- /dev/null
+++ b/src/pages/ticket/service/TicketServiec.ts
@@ -0,0 +1,82 @@
+// import axios from "@/config/axios";
+import type {
+ AddMessageTicketType,
+ CreateCategoryType,
+ CreateTicketAdminType,
+} from "../types/TicketTypes";
+
+// TODO: تمام API calls کامنت شدهاند - باید بعداً وصل شوند
+
+export const getTickets = async (
+ _status: string,
+ _customerId?: string,
+ _page?: number
+) => {
+ // const params = new URLSearchParams();
+ // if (status) params.append("status", status);
+ // if (customerId) params.append("userId", customerId);
+ // if (page) params.append("page", page.toString());
+ // const query = params.toString();
+ // const { data } = await axios.get(`/tickets/admin?${query}`);
+ // return data;
+ return { data: { tickets: [], pager: { totalPages: 1 } } };
+};
+
+export const getMessages = async (_id: string) => {
+ // const { data } = await axios.get(`/tickets/${id}/messages`);
+ // return data;
+ return {
+ data: {
+ ticket: null,
+ messages: [],
+ supportPlan: null,
+ },
+ };
+};
+
+export const addMessageTicket = async (
+ _id: string,
+ _params: AddMessageTicketType
+) => {
+ // const { data } = await axios.post(`/tickets/${id}/messages`, params);
+ // return data;
+ return { data: { success: true } };
+};
+
+export const closeTicket = async (_id: string) => {
+ // const { data } = await axios.post(`/tickets/${id}/close`);
+ // return data;
+ return { data: { success: true } };
+};
+
+export const createCategory = async (_params: CreateCategoryType) => {
+ // const { data } = await axios.post(`/tickets/category`, params);
+ // return data;
+ return { data: { success: true } };
+};
+
+export const getCategories = async () => {
+ // const { data } = await axios.get(`/tickets/categories`);
+ // return data;
+ return { data: { ticketCategories: [] } };
+};
+
+export const referTicket = async (_id: string, _userId: string) => {
+ // const { data } = await axios.post(`/tickets/${id}/refer`, {
+ // adminId: userId,
+ // });
+ // return data;
+ return { data: { success: true } };
+};
+
+export const openTicket = async (_id: string) => {
+ // const { data } = await axios.post(`/tickets/${id}/open`);
+ // return data;
+ return { data: { success: true } };
+};
+
+export const createTicket = async (_params: CreateTicketAdminType) => {
+ // const { data } = await axios.post(`/tickets/admin`, params);
+ // return data;
+ return { data: { ticketId: "mock-ticket-id" } };
+};
diff --git a/src/pages/ticket/types/TicketTypes.ts b/src/pages/ticket/types/TicketTypes.ts
new file mode 100644
index 0000000..24687b4
--- /dev/null
+++ b/src/pages/ticket/types/TicketTypes.ts
@@ -0,0 +1,91 @@
+export type TicketItemType = {
+ id: string;
+ numericId: number;
+ subject: string;
+ category: {
+ title: string;
+ };
+ user: {
+ id: string;
+ firstName: string;
+ lastName: string;
+ };
+ assignedTo: {
+ id: string;
+ firstName: string;
+ lastName: string;
+ };
+ updatedAt: string;
+ status: string;
+ priority: string;
+ danakService: {
+ id: string;
+ name: string;
+ description: string;
+ icon: string;
+ link: string;
+ isActive: boolean;
+ isDanakSuggest: boolean;
+ metaDescription: string;
+ serviceLanguage: string;
+ softwareLanguage: string;
+ title: string;
+ userCount: number;
+ createdAt: string;
+ updatedAt: string;
+ };
+};
+
+export type AddMessageTicketType = {
+ content: string;
+ attachmentUrls?: string[];
+};
+
+export type TicketMessageType = {
+ id: string;
+ content: string;
+ attachmentUrls: string[];
+ createdAt: string;
+ user: {
+ id: string;
+ name: string;
+ };
+ author: {
+ id: string;
+ firstName: string;
+ lastName: string;
+ roles: {
+ name: string;
+ }[];
+ };
+ attachments: {
+ attachmentUrl: string;
+ id: string;
+ }[];
+};
+
+export type CreateCategoryType = {
+ title: string;
+ description: string;
+ userGroupId: string;
+ isActive: boolean;
+};
+
+export type CategoriesItemType = {
+ id: string;
+ title: string;
+ description: string;
+ isActive: boolean;
+ createdAt: string;
+ updatedAt: string;
+};
+
+export type CreateTicketAdminType = {
+ subject: string;
+ priority: "LOW" | "MEDIUM" | "HIGH";
+ danakServiceId?: string;
+ categoryId: string;
+ message: string;
+ attachmentUrls?: string[];
+ userId: string;
+};
diff --git a/src/router/MainRouter.tsx b/src/router/MainRouter.tsx
index b2b29c4..771bf4c 100644
--- a/src/router/MainRouter.tsx
+++ b/src/router/MainRouter.tsx
@@ -13,6 +13,10 @@ import CreateProduct from '@/pages/product/Create'
import CreateFeature from '@/pages/features/Create'
import PrintService from '@/pages/service/PrintService'
import DetailPerfomaInvoice from '@/pages/invoice/DetailPerfomaInvoice'
+import TicketList from '@/pages/ticket/TicketList'
+import CreateTicket from '@/pages/ticket/CreateTicket'
+import TicketDetail from '@/pages/ticket/Detail'
+import TicketCategory from '@/pages/ticket/Category'
const MainRouter: FC = () => {
return (
@@ -34,6 +38,11 @@ const MainRouter: FC = () => {
} />
} />
} />
+
+ } />
+ } />
+ } />
+ } />