From 7bafdf57ade4bfea9efcb9f4c020e39ae0384b7a Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 1 Feb 2025 16:41:30 +0330 Subject: [PATCH] admin ticket --- .env | 4 +- src/components/Textarea.tsx | 7 + src/components/UploadBox.tsx | 65 +++- src/langs/fa.json | 15 +- src/pages/ticket/Detail.tsx | 406 +++++++++++++++------- src/pages/ticket/TicketList.tsx | 87 +++-- src/pages/ticket/hooks/useTicketData.ts | 38 ++ src/pages/ticket/service/TicketServiec.ts | 25 ++ src/pages/ticket/types/TicketTypes.ts | 39 +++ 9 files changed, 504 insertions(+), 182 deletions(-) create mode 100644 src/pages/ticket/hooks/useTicketData.ts create mode 100644 src/pages/ticket/service/TicketServiec.ts create mode 100644 src/pages/ticket/types/TicketTypes.ts diff --git a/.env b/.env index 74f350e..50e5b0a 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ VITE_TOKEN_NAME = 'admin_token' VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token' -VITE_BASE_URL = 'https://danak-dsc-api.run.danakcorp.com' -# VITE_BASE_URL = 'http://192.168.0.207:3500' \ No newline at end of file +# VITE_BASE_URL = 'https://danak-dsc-api.run.danakcorp.com' +VITE_BASE_URL = 'http://192.168.0.221:3500' \ No newline at end of file diff --git a/src/components/Textarea.tsx b/src/components/Textarea.tsx index 65ad8ce..0546ac6 100644 --- a/src/components/Textarea.tsx +++ b/src/components/Textarea.tsx @@ -1,7 +1,9 @@ import { FC, InputHTMLAttributes } from 'react' +import Error from './Error' type Props = { label: string, + error_text?: string } & InputHTMLAttributes const Textarea: FC = (props: Props) => { @@ -18,6 +20,11 @@ const Textarea: FC = (props: Props) => { + { + props.error_text && + + } + ) } diff --git a/src/components/UploadBox.tsx b/src/components/UploadBox.tsx index a85f3ff..ceb1d74 100644 --- a/src/components/UploadBox.tsx +++ b/src/components/UploadBox.tsx @@ -1,22 +1,57 @@ -import { FC, useCallback, useState } from 'react' +import { FC, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useDropzone } from 'react-dropzone' +import { CloseCircle } from 'iconsax-react' type Props = { label: string, + isMultiple?: boolean, + onChange?: (file: File[]) => void; + isReset?: boolean; } const UploadBox: FC = (props: Props) => { const { t } = useTranslation('global') - const [file, setFile] = useState() + const [files, setFiles] = useState([]) const onDrop = useCallback((acceptedFiles: File[]) => { - setFile(acceptedFiles[0]) - }, []) + if (props.isMultiple) { + const array = [...files] + array.push(acceptedFiles[0]) + setFiles(array) + if (props.onChange) { + props.onChange(array); + } + } else { + setFiles([acceptedFiles[0]]) + if (props.onChange) { + props.onChange([acceptedFiles[0]]) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [files]) const { getRootProps, getInputProps } = useDropzone({ onDrop }) + const handleRemove = (index: number) => { + const array = [...files] + array.splice(index, 1) + setFiles(array) + if (props.onChange) { + props.onChange(array) + } + } + + useEffect(() => { + + if (props.isReset) { + setFiles([]) + } + + }, [props.isReset]) + + return (
@@ -28,15 +63,27 @@ const UploadBox: FC = (props: Props) => { {t('select_file')} -
+
{ - file ? - file.name - : - t('no_select_file') + files?.map((item, index) => ( +
+ handleRemove(index)} size={16} color='red' /> +
{item.name}
+
+ )) }
+
+ { + files?.map((item, index) => ( +
+ handleRemove(index)} size={16} color='red' /> +
{item.name}
+
+ )) + } +
) } diff --git a/src/langs/fa.json b/src/langs/fa.json index e07980c..f8949c3 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -208,8 +208,21 @@ "add_category": "اضافه کردن دسته بندی", "status_category": "وضعیت دسته بندی", "title_category": "عنوان دسته بندی", - "users": "کاربران" + "users": "کاربران", + "MEDIUM": "متوسط", + "LOW": "پایین", + "HIGH": "بالا", + "PENDING": "در انتظار", + "ANSWERED": "پاسخ داده شده", + "CLOSED": "بسته شده", + "asignto": "کارشناس پاسخگویی", + "category": "دسته بندی", + "low": "پایین", + "medium": "متوسط", + "high": "بالا" }, + "attach": "پیوست", + "date": "تاریخ", "status": "وضعیت", "title": "عنوان", "ads": { diff --git a/src/pages/ticket/Detail.tsx b/src/pages/ticket/Detail.tsx index dcd78e1..ba0b083 100644 --- a/src/pages/ticket/Detail.tsx +++ b/src/pages/ticket/Detail.tsx @@ -1,14 +1,89 @@ -import { FC } from 'react' +import { FC, Fragment, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '../../components/Input' import Textarea from '../../components/Textarea' import UploadBox from '../../components/UploadBox' import Button from '../../components/Button' -import { CloseCircle, InfoCircle } from 'iconsax-react' +import { CloseCircle, InfoCircle, Paperclip2 } from 'iconsax-react' +import { useNavigate, useParams } from 'react-router-dom' +import { useAddMessageTicket, useCloseTicket, useGetMessages } from './hooks/useTicketData' +import PageLoading from '../../components/PageLoading' +import moment from 'moment-jalaali' +import { useFormik } from 'formik' +import { AddMessageTicketType, TicketMessageType } from './types/TicketTypes' +import * as Yup from 'yup' +import { toast } from 'react-toastify' +import { ErrorType } from '../../helpers/types' +import { clx } from '../../helpers/utils' +import { Pages } from '../../config/Pages' +import { useMultiUpload } from '../service/hooks/useServiceData' const TicketDetail: FC = () => { const { t } = useTranslation('global') + const navigate = useNavigate() + const { id } = useParams() + const getMessages = useGetMessages(id ? id : '') + const [files, setFiles] = useState([]) + const addMessage = useAddMessageTicket(id ? id : '') + const multiUpload = useMultiUpload() + const [isResetFiles, setIsResetFiles] = useState(false) + const closeTicket = useCloseTicket() + + const formik = useFormik({ + initialValues: { + content: '', + attachmentUrls: [], + }, + validationSchema: Yup.object().shape({ + content: Yup.string().required(t('errors.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 + } + + addMessage.mutate(values, { + onSuccess: () => { + toast.success(t('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(t('success')) + navigate(Pages.ticket.list) + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error?.message[0]) + } + }) + } return (
@@ -16,139 +91,220 @@ const TicketDetail: FC = () => {
{t('ticket.ticket_number')}
12312
-
-
-
- - -
- -
- موضوع درخواست پشتیبانی -
- -
-
- لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد، -
-
- 10:07 | 1403/09/30 -
-
- -
-
-
-
- {t('ticket.expert')} -
-
سیما فرهادی
-
-
- لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد، -
-
- 10:07 | 1403/09/30 -
-
-
- -
-