diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx index b4847d8..aadcda4 100644 --- a/src/config/Paths.tsx +++ b/src/config/Paths.tsx @@ -9,6 +9,10 @@ export const Paths = { myRequest: '/my-requests', newRequest: '/new-request', }, + orderDetails: '/order/', + order: { + myOrders: '/orders', + }, tickets: { list: '/tickets/list', create: '/tickets/create', diff --git a/src/pages/order/MyOrders.tsx b/src/pages/order/MyOrders.tsx new file mode 100644 index 0000000..c74e0dc --- /dev/null +++ b/src/pages/order/MyOrders.tsx @@ -0,0 +1,106 @@ +import Tabs from '@/components/Tabs' +import { useState, type FC } from 'react' +import { TabMyRequestsEnum } from './enum/OrderEnum' +import Filters from '@/components/Filters' +import { Eye } from 'iconsax-react' +import Table from '@/components/Table' +import { useGetMyOrders } from './hooks/useOrderData' +import moment from 'moment-jalaali' +import { Link } from 'react-router-dom' +import { Paths } from '@/config/Paths' + +const MyOrders: FC = () => { + const [page, setPage] = useState(1) + const [activeTab, setActiveTab] = useState( + TabMyRequestsEnum.ALL, + ) + const { data } = useGetMyOrders(page, activeTab) + + return ( +
+

سفارشات من

+ +
+ setActiveTab(tab as TabMyRequestsEnum)} + /> +
+ +
+ {}} + /> +
+ +
+ { + return ( +
{moment(item.createdAt).format('jYYYY-jMM-jDD')}
+ ) + }, + }, + { + key: 'status', + title: 'وضعیت سفارش', + }, + { + key: 'actions', + title: '', + render: (item) => { + return ( + + + + ) + }, + }, + ]} + data={data?.data} + pagination={{ + currentPage: page, + onPageChange: setPage, + totalPages: data?.meta?.totalPages || 1, + }} + /> + + + ) +} + +export default MyOrders diff --git a/src/pages/order/OrderDetail.tsx b/src/pages/order/OrderDetail.tsx new file mode 100644 index 0000000..ee166b9 --- /dev/null +++ b/src/pages/order/OrderDetail.tsx @@ -0,0 +1,166 @@ +import { type FC } from 'react' +import { Receipt1, Paperclip2, Calendar } from 'iconsax-react' +import { useParams } from 'react-router-dom' +import moment from 'moment-jalaali' +import { useGetOrderDetails } from './hooks/useOrderData' +import TicketSection from './components/TicketSection' +import { getFileNameAndExtensionFromUrl } from '@/config/func' +import { RequestStatusEnum } from './enum/OrderEnum' + +const statusLabels: Record = { + [RequestStatusEnum.CREATED]: 'ایجاد شده', + [RequestStatusEnum.INVOICED]: 'فاکتور شده', + [RequestStatusEnum.WAITING_to_CONFIRM_INVOICE]: 'در انتظار تایید فاکتور', + [RequestStatusEnum.DESIGN_INITIATED]: 'شروع طراحی', + [RequestStatusEnum.DESIGN_FINISHED]: 'طراحی تمام شده', + [RequestStatusEnum.DESIGN_REVISION]: 'بازنگری طراحی', + [RequestStatusEnum.READY_TO_PRINTING]: 'آماده چاپ', + [RequestStatusEnum.IN_PRINTING]: 'در حال چاپ', + [RequestStatusEnum.AFTER_PRINT_COVER]: 'پس از چاپ جلد', + [RequestStatusEnum.AFTER_PRINT_CARTONING]: 'کارتن‌سازی', + [RequestStatusEnum.AFTER_PRINT_BOXING]: 'بسته‌بندی', + [RequestStatusEnum.READY_FOR_DELIVERY]: 'آماده ارسال', + [RequestStatusEnum.DELIVERED]: 'تحویل شده', + [RequestStatusEnum.FINISHED]: 'پایان یافته', + [RequestStatusEnum.CANCELED]: 'لغو شده', +} + +const OrderDetail: FC = () => { + const { id } = useParams() + const { data } = useGetOrderDetails(id!) + const order = data?.data + + const handleOpenLink = (url: string) => { + window.open(url, '_blank') + } + + return ( +
+
+
سفارش #{order?.orderNumber}
+
+ +
+
+

اطلاعات سفارش

+ + + پیش فاکتور + +
+ +
+
+
+ {order?.product && ( +
+ +
+ )} +
+
+
عنوان:
+
+ {order?.title ?? '-'} +
+
+
+
محصول:
+
+ {order?.product?.title ?? '-'} +
+
+
+
تخمین زمان:
+
+ {order?.estimatedDays ?? '-'} روز +
+
+
+
وضعیت:
+
+ {order?.status + ? statusLabels[order.status] ?? order.status + : '-'} +
+
+
+ +
تاریخ ثبت:
+
+ {order?.createdAt + ? moment(order.createdAt).format('jYYYY/jMM/jDD HH:mm') + : '-'} +
+
+
+
+ + {order?.user && ( +
+
+ اطلاعات کاربر +
+
+
+ نام: + + {order.user.firstName} {order.user.lastName} + +
+
+ تلفن: + + {order.user.phone ?? '-'} + +
+ {order.user.addresse && ( +
+ آدرس: + + {order.user.addresse} + +
+ )} +
+
+ )} + + {order?.attachments && order.attachments.length > 0 && ( +
+
+ + اتچمنت‌ها +
+
+ {order.attachments.map((url) => { + const { fileName } = getFileNameAndExtensionFromUrl(url) + return ( + + ) + })} +
+
+ )} +
+
+
+ + +
+ ) +} + +export default OrderDetail diff --git a/src/pages/order/components/TicketSection.tsx b/src/pages/order/components/TicketSection.tsx new file mode 100644 index 0000000..0eab6a5 --- /dev/null +++ b/src/pages/order/components/TicketSection.tsx @@ -0,0 +1,233 @@ +import Button from '@/components/Button' +import UploadBox from '@/components/UploadBox' +import { Microphone, Paperclip2 } from 'iconsax-react' +import { useState, type FC } from 'react' +import { useVoiceRecorder } from '@/hooks/useVoiceRecorder' +import { Play, Pause } from 'iconsax-react' +import { useMultiUpload, useSingleUpload } from '@/pages/uploader/hooks/useUploader' +import type { AddTicketType } from '@/pages/request/type/Types' +import { useAddTicket, useGetTickets } from '../hooks/useOrderData' +import { useParams } from 'react-router-dom' +import { toast } from '@/shared/toast' +import { extractErrorMessage, getFileNameAndExtensionFromUrl } from '@/config/func' +import VoicePlayer from '@/components/VoicePlayer' + +const TicketSection: FC = () => { + const { id } = useParams() + const [message, setMessage] = useState('') + const [files, setFiles] = useState([]) + const addTicket = useAddTicket() + const { data, refetch } = useGetTickets(id!) + const singleUpload = useSingleUpload() + const multiUpload = useMultiUpload() + + const { + isRecording, + isPlaying, + audioUrl, + audioRef, + togglePlayPause, + startRecording, + stopRecording, + progress, + audioFile, + resetRecorder, + } = useVoiceRecorder() + + const handleSubmit = async () => { + const params: AddTicketType = { + attachments: [], + content: message, + } + if (audioFile) { + await singleUpload.mutateAsync(audioFile, { + onSuccess: (data) => { + params.attachments.push({ + type: 'voice', + url: data?.data?.url, + }) + }, + }) + } + if (files.length) { + await multiUpload.mutateAsync(files, { + onSuccess: (data) => { + data?.data?.map((item) => { + params.attachments.push({ + type: 'uploads_attach', + url: item.url, + }) + }) + }, + }) + } + + addTicket.mutate( + { requestId: id!, params }, + { + onSuccess: () => { + toast('پیام شما با موفقیت ارسال شد') + refetch() + setMessage('') + setFiles([]) + resetRecorder() + }, + onError: (error) => { + toast(extractErrorMessage(error), 'error') + }, + }, + ) + } + + const handleOpenLink = (url: string) => { + window.open(url, '_blank') + } + + return ( +
+ {data?.data?.map((item) => { + if (item.user) + return ( +
+
+ {item.content} +
+
+ {item.attachments + ?.filter((a) => a.type !== 'voice') + .map((attach) => ( +
handleOpenLink(attach.url)} + className="flex cursor-pointer items-center gap-1.5 text-[#0047FF]" + > + +
+ {getFileNameAndExtensionFromUrl(attach.url).fileName} +
+
+ ))} +
+ {item.attachments + ?.filter((a) => a.type === 'voice') + .map((voice) => ( + + ))} +
+ ) + if (item.admin) + return ( +
+
+
+
طراح :
+
+ {item.admin?.firstName + ' ' + item?.admin?.lastName} +
+
+
+ {item.content} +
+
+ {item.attachments + ?.filter((a) => a.type !== 'voice') + .map((attach) => ( +
handleOpenLink(attach.url)} + className="flex cursor-pointer items-center gap-1.5 text-[#0047FF]" + > + +
+ {getFileNameAndExtensionFromUrl(attach.url).fileName} +
+
+ ))} +
+ {item.attachments + ?.filter((a) => a.type === 'voice') + .map((voice) => ( + + ))} +
+
+ ) + })} + +
+
پیام شما
+
+