diff --git a/.env b/.env index faa6055..7bb0cf5 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ VITE_TOKEN_NAME = 'dsc_token' VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token' -VITE_BASE_URL = 'http://192.168.0.235:3500' \ No newline at end of file +VITE_BASE_URL = 'http://192.168.0.221:3500' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5e6686a..eef4972 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "formik": "^2.4.6", "i18next": "^24.2.0", "iconsax-react": "^0.0.8", + "moment-jalaali": "^0.10.4", "rc-rate": "^2.13.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -33,6 +34,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/moment-jalaali": "^0.7.9", "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", "@types/swiper": "^5.4.3", @@ -1841,6 +1843,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/moment-jalaali": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@types/moment-jalaali/-/moment-jalaali-0.7.9.tgz", + "integrity": "sha512-gsDOoAzRnCfQTbfdlUrCvX6R0wIto6CvwfvV2C3j4qJLK+DEiTK8Rl/xlOCBO9C6qeUfX8oyZ2UfjnXJTOvHSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "moment": ">=2.14.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -3492,6 +3504,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jalaali-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.7.tgz", + "integrity": "sha512-gE+YHWSbygYAoJa+Xg8LWxGILqFOxZSBQQw39ghel01fVFUxV7bjL0x1JFsHcLQ3uPjvn81HQMa+kxwyPWnxGQ==", + "license": "MIT" + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -3743,6 +3761,38 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-jalaali": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz", + "integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==", + "license": "MIT", + "dependencies": { + "jalaali-js": "^1.2.7", + "moment": "^2.29.4", + "moment-timezone": "^0.5.46" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.47", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", + "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 6f87376..d41b5dd 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "formik": "^2.4.6", "i18next": "^24.2.0", "iconsax-react": "^0.0.8", + "moment-jalaali": "^0.10.4", "rc-rate": "^2.13.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -35,6 +36,7 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/moment-jalaali": "^0.7.9", "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", "@types/swiper": "^5.4.3", diff --git a/src/assets/images/logo_orig.svg b/src/assets/images/logo_orig.svg new file mode 100644 index 0000000..a764edd --- /dev/null +++ b/src/assets/images/logo_orig.svg @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 78b3796..2c62aed 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -7,7 +7,7 @@ type Props = { className?: string; isLoading?: boolean, } & ButtonHTMLAttributes & - XOR<{ children: ReactNode }, { label: string, isLoading?: boolean }>; + XOR<{ children: ReactNode }, { label: string }>; const Button: FC = memo((props: Props) => { diff --git a/src/components/PageLoading.tsx b/src/components/PageLoading.tsx new file mode 100644 index 0000000..4d24026 --- /dev/null +++ b/src/components/PageLoading.tsx @@ -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 ( +
+ logo +
+ {t('loading')} +
+
+ ) +} + +export default PageLoading \ No newline at end of file diff --git a/src/components/Select.tsx b/src/components/Select.tsx index a907ec3..0367e11 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -45,7 +45,7 @@ const Select: FC = (props: Props) => { { props.error_text && props.error_text !== '' ? diff --git a/src/components/Textarea.tsx b/src/components/Textarea.tsx index 65ad8ce..360dfec 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) => { @@ -17,6 +19,10 @@ const Textarea: FC = (props: Props) => { > + { + props.error_text && + + } ) diff --git a/src/components/UploadBox.tsx b/src/components/UploadBox.tsx index a85f3ff..451380e 100644 --- a/src/components/UploadBox.tsx +++ b/src/components/UploadBox.tsx @@ -1,22 +1,51 @@ -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) + props.onChange(array) + } else { + setFiles([acceptedFiles[0]]) + 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) + props.onChange(array) + } + + useEffect(() => { + + if (props.isReset) { + setFiles([]) + } + + }, [props.isReset]) + + return (
@@ -28,15 +57,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 cf858d3..1cc16d9 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -148,8 +148,20 @@ "send_answer": "ارسال پاسخ", "close_ticket": "بستن تیکت", "info_ticket": "اطلاعات تیکت", - "update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود" + "update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود", + "low": "پایین", + "medium": "متوسط", + "high": "بالا", + "category": "دسته بندی", + "asignto": "کارشناس پاسخگویی", + "MEDIUM": "متوسط", + "LOW": "پایین", + "HIGH": "بالا", + "PENDING": "در انتظار", + "ANSWERED": "پاسخ داده شده", + "CLOSED": "بسته شده" }, + "date": "تاریخ", "receip": { "receip_list": "لیست صورتحساب ها", "dont_pay": "پرداخت نشده", @@ -288,5 +300,6 @@ "username_save_success": "نام کاربری با موفقیت ذخیره شد" }, "email": "ایمیل", - "save": "ذخیره" + "save": "ذخیره", + "select": "انتخاب" } diff --git a/src/pages/ticket/CreateTicket.tsx b/src/pages/ticket/CreateTicket.tsx index 5747cc5..03b42e0 100644 --- a/src/pages/ticket/CreateTicket.tsx +++ b/src/pages/ticket/CreateTicket.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '../../components/Input' import Select from '../../components/Select' @@ -6,10 +6,77 @@ import Textarea from '../../components/Textarea' import UploadBox from '../../components/UploadBox' import Button from '../../components/Button' import { TickSquare } from 'iconsax-react' +import { useCreateTicket, useGetCategoriesTicket, useMultiUpload } from './hooks/useTicketData' +import { useGetProfile } from '../profile/hooks/useProfileData' +import PageLoading from '../../components/PageLoading' +import { useFormik } from 'formik' +import { CreateTicketType } from './types/TicketTypes' +import * as Yup from 'yup' +import { toast } from 'react-toastify' +import { ErrorType } from '../../helpers/types' +import { useNavigate } from 'react-router-dom' +import { Pages } from '../../config/Pages' const CreateTicket: FC = () => { const { t } = useTranslation('global') + const navigate = useNavigate() + const [files, setFiles] = useState([]) + const getProfile = useGetProfile() + const getCategories = useGetCategoriesTicket() + const multiUpload = useMultiUpload() + const createTicket = useCreateTicket() + + const formik = useFormik({ + initialValues: { + subject: '', + message: '', + categoryId: '', + priority: 'LOW', + danakServiceId: '', + title: '', + attachmentUrls: [] + }, + validationSchema: Yup.object({ + subject: Yup.string().required(t('errors.required')), + message: Yup.string().required(t('errors.required')), + categoryId: Yup.string().required(t('errors.required')), + priority: 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 + } + + + createTicket.mutate(values, { + onSuccess: (data) => { + toast.success(t('success')) + formik.resetForm() + navigate(Pages.ticket.detail + data.data?.ticketId) + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error?.message[0]) + } + }) + } + }) + + console.log(formik.errors); + return (
@@ -17,111 +84,157 @@ const CreateTicket: FC = () => { {t('ticket.new_ticket')}
-
-
-
- {t('ticket.submit_your_message')} -
- -
- - -
- - -
- - -
-
-