crism
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
VITE_TOKEN_NAME = 'admin_token'
|
VITE_TOKEN_NAME = 'admin_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||||
VITE_BASE_URL = 'http://192.168.0.223:3500'
|
# VITE_BASE_URL = 'https://danak-dsc-api.run.danakcorp.com'
|
||||||
|
VITE_BASE_URL = 'http://192.168.0.207:3500'
|
||||||
Generated
+50
@@ -16,6 +16,7 @@
|
|||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"i18next": "^24.2.0",
|
"i18next": "^24.2.0",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"rc-rate": "^2.13.0",
|
"rc-rate": "^2.13.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
"@types/draft-js": "^0.11.18",
|
"@types/draft-js": "^0.11.18",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/react": "^18.3.17",
|
"@types/react": "^18.3.17",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@types/swiper": "^5.4.3",
|
"@types/swiper": "^5.4.3",
|
||||||
@@ -1855,6 +1857,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.7",
|
"version": "22.10.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
|
||||||
@@ -3602,6 +3614,12 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@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": {
|
"node_modules/jiti": {
|
||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
@@ -3865,6 +3883,38 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"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.46",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
|
||||||
|
"integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"i18next": "^24.2.0",
|
"i18next": "^24.2.0",
|
||||||
"iconsax-react": "^0.0.8",
|
"iconsax-react": "^0.0.8",
|
||||||
|
"moment-jalaali": "^0.10.4",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"rc-rate": "^2.13.0",
|
"rc-rate": "^2.13.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
"@types/draft-js": "^0.11.18",
|
"@types/draft-js": "^0.11.18",
|
||||||
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/react": "^18.3.17",
|
"@types/react": "^18.3.17",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@types/swiper": "^5.4.3",
|
"@types/swiper": "^5.4.3",
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ const DatePickerComponent: FC<Props> = (props: Props) => {
|
|||||||
const formattedDate = `${value.year}/${value.month.number}/${value.day}`;
|
const formattedDate = `${value.year}/${value.month.number}/${value.day}`;
|
||||||
props.onChange(formattedDate);
|
props.onChange(formattedDate);
|
||||||
}
|
}
|
||||||
}, [props, value]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.defaulValue && !value) {
|
if (props.defaulValue && !value) {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import DefaulModal from './DefaulModal'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from './Button'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isLoading?: boolean,
|
||||||
|
close: () => void,
|
||||||
|
isOpen: boolean,
|
||||||
|
onConfrim: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalConfrim: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaulModal
|
||||||
|
open={props.isOpen}
|
||||||
|
close={props.close}
|
||||||
|
title_header={t('confrim.subject')}
|
||||||
|
isHeader
|
||||||
|
>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='text-sm text-center'> {t('confrim.content')}</div>
|
||||||
|
|
||||||
|
<div className='flex gap-4 justify-center mt-10'>
|
||||||
|
<Button
|
||||||
|
label={t('confrim.yes')}
|
||||||
|
onClick={props.onConfrim}
|
||||||
|
isLoading={props.isLoading}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t('confrim.cancel')}
|
||||||
|
className='bg-transparent text-black border border-primary'
|
||||||
|
onClick={props.close}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalConfrim
|
||||||
@@ -7,7 +7,7 @@ const PageLoading: FC = () => {
|
|||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex bg-white w-fit mx-auto flex-col gap-4 items-center'>
|
<div className='flex bg-transparent w-fit mx-auto flex-col gap-4 items-center'>
|
||||||
<img src={Logo} alt='logo' className='w-28' />
|
<img src={Logo} alt='logo' className='w-28' />
|
||||||
<div className='text-xs text-gray-700'>
|
<div className='text-xs text-gray-700'>
|
||||||
{t('loading')}
|
{t('loading')}
|
||||||
|
|||||||
+4
-1
@@ -29,7 +29,10 @@ export const Pages = {
|
|||||||
list: "/announcement",
|
list: "/announcement",
|
||||||
detail: "/announcement/",
|
detail: "/announcement/",
|
||||||
},
|
},
|
||||||
criticisms: "/criticisms",
|
criticisms: {
|
||||||
|
list: "/criticisms/list",
|
||||||
|
detail: "/ccriticisms/detail/",
|
||||||
|
},
|
||||||
learning: "/learning",
|
learning: "/learning",
|
||||||
setting: "/setting",
|
setting: "/setting",
|
||||||
wallet: "/wallet",
|
wallet: "/wallet",
|
||||||
|
|||||||
+16
-2
@@ -375,8 +375,16 @@
|
|||||||
"subject": "موضوع",
|
"subject": "موضوع",
|
||||||
"enter_your_subject": "موضوع انتقاد خودرا وارد کنید",
|
"enter_your_subject": "موضوع انتقاد خودرا وارد کنید",
|
||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
"enter_description": "توضیحات شما"
|
"enter_description": "توضیحات شما",
|
||||||
|
"criticisms_list": "لیست انتقادات",
|
||||||
|
"number": "شماره",
|
||||||
|
"title_criticisms": "عنوان انتقاد",
|
||||||
|
"summary_criticisms": "خلاصه انتقاد",
|
||||||
|
"date_created": "تاریخ ثبت",
|
||||||
|
"criticism": "انتقاد",
|
||||||
|
"documents": "مستندات"
|
||||||
},
|
},
|
||||||
|
"detail": "جزییات",
|
||||||
"select_file": "انتخاب فایل",
|
"select_file": "انتخاب فایل",
|
||||||
"no_select_file": "هیچ فایلی انتخاب نشده است",
|
"no_select_file": "هیچ فایلی انتخاب نشده است",
|
||||||
"attachment": "فایل های ضمیمه",
|
"attachment": "فایل های ضمیمه",
|
||||||
@@ -485,5 +493,11 @@
|
|||||||
"date": "تاریخ"
|
"date": "تاریخ"
|
||||||
},
|
},
|
||||||
"loading": "در حال بارگذاری...",
|
"loading": "در حال بارگذاری...",
|
||||||
"success": "عملیات با موفقیت انجام شد."
|
"success": "عملیات با موفقیت انجام شد.",
|
||||||
|
"confrim": {
|
||||||
|
"subject": "حذف",
|
||||||
|
"content": "برای حذف این آیتم مطمئن هستید؟",
|
||||||
|
"yes": "بله",
|
||||||
|
"cancel": "لغو"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { FC, Fragment } from 'react'
|
||||||
|
import { useGetDetailCriticisms } from './hooks/useCriticismsData'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import PageLoading from '../../components/PageLoading'
|
||||||
|
import { Paperclip2 } from 'iconsax-react'
|
||||||
|
|
||||||
|
const CriticismsDetail: FC = () => {
|
||||||
|
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const getDetail = useGetDetailCriticisms(id ? id : '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
|
{
|
||||||
|
getDetail.isPending ?
|
||||||
|
<PageLoading />
|
||||||
|
:
|
||||||
|
<Fragment>
|
||||||
|
<div>
|
||||||
|
{t('criticisms.criticism')}
|
||||||
|
{' #' + getDetail.data?.data?.criticism?.numericId}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-1 mt-4 bg-white p-8 rounded-2xl'>
|
||||||
|
<div className='pb-6 border-b'>
|
||||||
|
<div>
|
||||||
|
{getDetail.data?.data?.criticism?.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className='mt-6 text-sm leading-6'>
|
||||||
|
{getDetail.data?.data?.criticism?.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6 text-sm'>
|
||||||
|
<div>
|
||||||
|
{t('criticisms.documents')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-4 text-xs flex flex-col gap-4'>
|
||||||
|
{
|
||||||
|
getDetail.data?.data?.criticism?.files?.map((item: { id: string, fileUrl: string }) => {
|
||||||
|
return (
|
||||||
|
<a href={item.fileUrl} target='_blank' key={item.id} className='flex gap-2 items-center text-[#0047FF]'>
|
||||||
|
<Paperclip2
|
||||||
|
className='size-6'
|
||||||
|
color='#0047FF'
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{item.fileUrl.split('/')[item.fileUrl.split('/').length - 1]}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CriticismsDetail
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { FC, useState } from 'react'
|
||||||
|
import { useDeleteCriticismsDetail, useGetCriticismsList } from './hooks/useCriticismsData'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Td from '../../components/Td'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Eye, Trash } from 'iconsax-react'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { CriticismsItemTypes } from './types/CriticismsTypes'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
import ModalConfrim from '../../components/ModalConfrim'
|
||||||
|
import Pagination from '../../components/Pagination'
|
||||||
|
|
||||||
|
const CriticismsList: FC = () => {
|
||||||
|
|
||||||
|
const [itemId, setItemId] = useState<string>('')
|
||||||
|
const [page, setPage] = useState<number>(1)
|
||||||
|
const [openConfrim, setOpenConfrim] = useState(false)
|
||||||
|
const getCriticismsList = useGetCriticismsList(page)
|
||||||
|
const deleteCriticisms = useDeleteCriticismsDetail()
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
const handleDelete = (id?: string, confrim: boolean = false) => {
|
||||||
|
if (confrim) {
|
||||||
|
|
||||||
|
deleteCriticisms.mutate(itemId, {
|
||||||
|
onSuccess: () => {
|
||||||
|
getCriticismsList.refetch()
|
||||||
|
setOpenConfrim(false)
|
||||||
|
setItemId('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (id) {
|
||||||
|
setItemId(id)
|
||||||
|
setOpenConfrim(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-4'>
|
||||||
|
<div>
|
||||||
|
{t('criticisms.criticisms_list')}
|
||||||
|
</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('criticisms.number')} />
|
||||||
|
<Td text={t('criticisms.title_criticisms')} />
|
||||||
|
<Td text={t('criticisms.summary_criticisms')} />
|
||||||
|
<Td text={t('criticisms.date_created')} />
|
||||||
|
<Td text={t('detail')} />
|
||||||
|
<Td text={''} />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
getCriticismsList.data?.data?.criticisms?.map((item: CriticismsItemTypes) => {
|
||||||
|
return (
|
||||||
|
<tr key={item.id} className='tr'>
|
||||||
|
<Td text={item.numericId} />
|
||||||
|
<Td text={item.title} />
|
||||||
|
<Td text={item.content.substring(0, 100)} />
|
||||||
|
<Td text={moment(item.createdAt).format('HH:mm jYYYY-jMM-jDD')} />
|
||||||
|
<Td text={''}>
|
||||||
|
<Link to={Pages.criticisms.detail + item.id}>
|
||||||
|
<Eye size={20} color='#8C90A3' />
|
||||||
|
</Link>
|
||||||
|
</Td>
|
||||||
|
<Td text=''>
|
||||||
|
<Trash onClick={() => handleDelete(item.id)} size={20} color='#8C90A3' className='cursor-pointer' />
|
||||||
|
</Td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div className='flex justify-end pb-3'>
|
||||||
|
<Pagination
|
||||||
|
currentPage={page}
|
||||||
|
totalPages={getCriticismsList.data?.data?.pager?.totalPages}
|
||||||
|
onPageChange={setPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ModalConfrim
|
||||||
|
isOpen={openConfrim}
|
||||||
|
close={() => setOpenConfrim(false)}
|
||||||
|
onConfrim={() => handleDelete('', true)}
|
||||||
|
isLoading={deleteCriticisms.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CriticismsList
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/CriticismsService";
|
||||||
|
|
||||||
|
export const useGetCriticismsList = (page: number) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["criticisms", page],
|
||||||
|
queryFn: () => api.getCriticismsList(page),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteCriticismsDetail = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (id: string) => api.deleteCriticisms(id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetDetailCriticisms = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["criticisms", id],
|
||||||
|
queryFn: () => api.getCriticismsDetail(id),
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import axios from "../../../config/axios";
|
||||||
|
|
||||||
|
export const getCriticismsList = async (page: number) => {
|
||||||
|
const { data } = await axios.get(`/criticisms?page=${page}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCriticismsDetail = async (id: string) => {
|
||||||
|
const { data } = await axios.get(`/criticisms/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteCriticisms = async (id: string) => {
|
||||||
|
const { data } = await axios.delete(`/criticisms/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type CriticismsItemTypes = {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
createdAt: Date;
|
||||||
|
isRead: boolean;
|
||||||
|
title: string;
|
||||||
|
numericId: string;
|
||||||
|
};
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
import { FC, useEffect } from 'react'
|
import { FC, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Input from '../../components/Input'
|
import Input from '../../components/Input'
|
||||||
import Select from '../../components/Select'
|
import Select from '../../components/Select'
|
||||||
import Quill from 'quill';
|
import Quill from 'quill';
|
||||||
import SwitchComponent from '../../components/Switch';
|
|
||||||
import CheckBoxComponent from '../../components/CheckBoxComponent';
|
|
||||||
import UploadBoxDraggble from '../../components/UploadBoxDraggble';
|
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
import { TickCircle } from 'iconsax-react';
|
import { TickCircle } from 'iconsax-react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { CreateServiceType } from './types/ServiceTypes';
|
||||||
|
import DatePickerComponent from '../../components/DatePicker';
|
||||||
|
import AddServiceSidebar from './components/AddServiceSidebar';
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
const AddService: FC = () => {
|
const AddService: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
|
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
new Quill('#editor', {
|
const quill = new Quill('#editor', {
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
modules: {
|
modules: {
|
||||||
toolbar: [
|
toolbar: [
|
||||||
@@ -27,8 +30,45 @@ const AddService: FC = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
quill.on('text-change', () => {
|
||||||
|
const html = editorRef.current?.querySelector('.ql-editor')?.innerHTML || '';
|
||||||
|
formik.setFieldValue('metaDescription', html);
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const formik = useFormik<CreateServiceType>({
|
||||||
|
initialValues: {
|
||||||
|
author: '',
|
||||||
|
categoryId: '',
|
||||||
|
description: '',
|
||||||
|
icon: '',
|
||||||
|
images: [],
|
||||||
|
isDanakSuggest: false,
|
||||||
|
link: '',
|
||||||
|
metaDescription: '',
|
||||||
|
name: '',
|
||||||
|
serviceLanguage: '',
|
||||||
|
softwareLanguage: '',
|
||||||
|
userCount: 0,
|
||||||
|
createDate: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string().required(t('errors.required')),
|
||||||
|
description: Yup.string().required(t('errors.required')),
|
||||||
|
author: Yup.string().required(t('errors.required')),
|
||||||
|
createDate: Yup.date().required(t('errors.required')),
|
||||||
|
userCount: Yup.number().required(t('errors.required')),
|
||||||
|
serviceLanguage: Yup.string().required(t('errors.required')),
|
||||||
|
metaDescription: Yup.string().required(t('errors.required')),
|
||||||
|
link: Yup.string().required(t('errors.required')),
|
||||||
|
}),
|
||||||
|
onSubmit(values) {
|
||||||
|
console.log(values);
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full mt-4'>
|
<div className='w-full mt-4'>
|
||||||
<div className='flex w-full justify-between items-center'>
|
<div className='flex w-full justify-between items-center'>
|
||||||
@@ -38,6 +78,7 @@ const AddService: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
className='px-5'
|
className='px-5'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
>
|
>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
<TickCircle
|
<TickCircle
|
||||||
@@ -56,25 +97,41 @@ const AddService: FC = () => {
|
|||||||
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||||
<Input
|
<Input
|
||||||
label={t('service.service_name')}
|
label={t('service.service_name')}
|
||||||
|
name='name'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||||
/>
|
/>
|
||||||
<div className='mt-8'>
|
<div className='mt-8'>
|
||||||
<Input
|
<Input
|
||||||
label={t('service.description_short')}
|
label={t('service.description_short')}
|
||||||
|
name='description'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 rowTwoInput'>
|
<div className='mt-8 rowTwoInput'>
|
||||||
<Input
|
<Input
|
||||||
label={t('service.company_developed')}
|
label={t('service.company_developed')}
|
||||||
|
name='author'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.author && formik.errors.author ? formik.errors.author : ''}
|
||||||
/>
|
/>
|
||||||
<Input
|
|
||||||
|
<DatePickerComponent
|
||||||
label={t('service.year_make')}
|
label={t('service.year_make')}
|
||||||
|
onChange={(d) => formik.setFieldValue('createDate', d)}
|
||||||
|
placeholder={t('select')}
|
||||||
|
error_text={formik.touched.createDate && formik.errors.createDate ? formik.errors.createDate : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8 rowTwoInput'>
|
<div className='mt-8 rowTwoInput'>
|
||||||
<Input
|
<Input
|
||||||
label={t('service.user_count')}
|
label={t('service.user_count')}
|
||||||
|
name='userCount'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.userCount && formik.errors.userCount ? formik.errors.userCount : ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -86,78 +143,21 @@ const AddService: FC = () => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
placeholder={t('select')}
|
placeholder={t('select')}
|
||||||
|
name='serviceLanguage'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.serviceLanguage && formik.errors.serviceLanguage ? formik.errors.serviceLanguage : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className='mt-8'>
|
||||||
<div id='editor' style={{ minHeight: '120px' }}></div>
|
<div ref={editorRef} id='editor' style={{ minHeight: '120px' }}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
<AddServiceSidebar
|
||||||
<div className='flex justify-between'>
|
formik={formik}
|
||||||
<div className='text-sm'>
|
|
||||||
{t('service.service_status')}
|
|
||||||
</div>
|
|
||||||
<div className='flex gap-1'>
|
|
||||||
<SwitchComponent active onChange={() => null} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8 items-center flex '>
|
|
||||||
<CheckBoxComponent
|
|
||||||
checked
|
|
||||||
onChange={() => null}
|
|
||||||
/>
|
/>
|
||||||
<div>{t('service.special_danak')}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
|
||||||
<Input
|
|
||||||
label={t('service.service_link')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
|
||||||
<div>{t('service.category')}</div>
|
|
||||||
<div className='mt-2 border border-border rounded-xl p-4'>
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<CheckBoxComponent
|
|
||||||
checked
|
|
||||||
onChange={() => null}
|
|
||||||
/>
|
|
||||||
<div>لورم ایپسوم</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<CheckBoxComponent
|
|
||||||
checked
|
|
||||||
onChange={() => null}
|
|
||||||
/>
|
|
||||||
<div>لورم ایپسوم</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8'>
|
|
||||||
<div>{t('service.service_icon')}</div>
|
|
||||||
<div className='mt-2'>
|
|
||||||
<UploadBoxDraggble
|
|
||||||
label={t('service.upload_icon_service')}
|
|
||||||
onChange={() => null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='mt-8'>
|
|
||||||
<div>{t('service.service_images')}</div>
|
|
||||||
<div className='mt-2'>
|
|
||||||
<UploadBoxDraggble
|
|
||||||
label={t('service.upload_images_service')}
|
|
||||||
onChange={() => null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import SwitchComponent from '../../../components/Switch'
|
||||||
|
import CheckBoxComponent from '../../../components/CheckBoxComponent'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import UploadBoxDraggble from '../../../components/UploadBoxDraggble'
|
||||||
|
import { FormikProps } from 'formik'
|
||||||
|
import { CreateServiceType, ServiceCategoryType } from '../types/ServiceTypes'
|
||||||
|
import { useGetCategoryParents } from '../hooks/useServiceData'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
formik: FormikProps<CreateServiceType>
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddServiceSidebar: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const getCategory = useGetCategoryParents()
|
||||||
|
|
||||||
|
const { formik } = props
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<div className='text-sm'>
|
||||||
|
{t('service.service_status')}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<SwitchComponent
|
||||||
|
active={formik.values.isDanakSuggest}
|
||||||
|
onChange={(e) => formik.setFieldValue('isDanakSuggest', e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8 items-center flex '>
|
||||||
|
<CheckBoxComponent
|
||||||
|
checked={formik.values.isDanakSuggest}
|
||||||
|
onChange={(e) => formik.setFieldValue('isDanakSuggest', e.target.checked)}
|
||||||
|
/>
|
||||||
|
<div>{t('service.special_danak')}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8'>
|
||||||
|
<Input
|
||||||
|
label={t('service.service_link')}
|
||||||
|
name='link'
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error_text={formik.touched.link && formik.errors.link ? formik.errors.link : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8'>
|
||||||
|
<div>{t('service.category')}</div>
|
||||||
|
<div className='mt-2 border border-border rounded-xl p-4'>
|
||||||
|
{
|
||||||
|
getCategory.data?.data?.categories?.map((item: ServiceCategoryType) => {
|
||||||
|
return (
|
||||||
|
<div key={item.id} className='flex items-center'>
|
||||||
|
<CheckBoxComponent
|
||||||
|
checked={formik.values.categoryId === item.id}
|
||||||
|
onChange={(e) => formik.setFieldValue('categoryId', e.target.checked ? item.id : '')}
|
||||||
|
/>
|
||||||
|
<div>{item.title}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-8'>
|
||||||
|
<div>{t('service.service_icon')}</div>
|
||||||
|
<div className='mt-2'>
|
||||||
|
<UploadBoxDraggble
|
||||||
|
label={t('service.upload_icon_service')}
|
||||||
|
onChange={() => null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-8'>
|
||||||
|
<div>{t('service.service_images')}</div>
|
||||||
|
<div className='mt-2'>
|
||||||
|
<UploadBoxDraggble
|
||||||
|
label={t('service.upload_images_service')}
|
||||||
|
onChange={() => null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddServiceSidebar
|
||||||
@@ -25,3 +25,19 @@ export type CreateServiceCategoryType = {
|
|||||||
export type ToggleStatusCategoryType = {
|
export type ToggleStatusCategoryType = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateServiceType = {
|
||||||
|
categoryId: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
userCount: number;
|
||||||
|
serviceLanguage: string;
|
||||||
|
softwareLanguage: string;
|
||||||
|
metaDescription: string;
|
||||||
|
link: string;
|
||||||
|
isDanakSuggest: boolean;
|
||||||
|
icon?: string;
|
||||||
|
images?: string[];
|
||||||
|
createDate: string;
|
||||||
|
};
|
||||||
|
|||||||
+4
-2
@@ -18,7 +18,6 @@ import TicketDetail from '../pages/ticket/Detail'
|
|||||||
import TransactionList from '../pages/transaction/List'
|
import TransactionList from '../pages/transaction/List'
|
||||||
import ReceiptsList from '../pages/receipts/List'
|
import ReceiptsList from '../pages/receipts/List'
|
||||||
import AnnouncementtList from '../pages/annoncement/List'
|
import AnnouncementtList from '../pages/annoncement/List'
|
||||||
import AddCriticisms from '../pages/criticisms/Add'
|
|
||||||
import LearningList from '../pages/learning/List'
|
import LearningList from '../pages/learning/List'
|
||||||
import Setting from '../pages/setting/Setting'
|
import Setting from '../pages/setting/Setting'
|
||||||
import Wallet from '../pages/wallet/Wallet'
|
import Wallet from '../pages/wallet/Wallet'
|
||||||
@@ -38,6 +37,8 @@ import CustomerList from '../pages/customer/List'
|
|||||||
import CreateCustomer from '../pages/customer/Create'
|
import CreateCustomer from '../pages/customer/Create'
|
||||||
import TicketCategory from '../pages/ticket/Category'
|
import TicketCategory from '../pages/ticket/Category'
|
||||||
import MessagesList from '../pages/messages/List'
|
import MessagesList from '../pages/messages/List'
|
||||||
|
import CriticismsList from '../pages/criticisms/List'
|
||||||
|
import CriticismsDetail from '../pages/criticisms/Detail'
|
||||||
|
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
@@ -80,7 +81,8 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.receipts.create} element={<CreateReceipt />} />
|
<Route path={Pages.receipts.create} element={<CreateReceipt />} />
|
||||||
<Route path={Pages.announcement.list} element={<AnnouncementtList />} />
|
<Route path={Pages.announcement.list} element={<AnnouncementtList />} />
|
||||||
<Route path={Pages.announcement.detail + ':id'} element={<AnnouncementDetail />} />
|
<Route path={Pages.announcement.detail + ':id'} element={<AnnouncementDetail />} />
|
||||||
<Route path={Pages.criticisms} element={<AddCriticisms />} />
|
<Route path={Pages.criticisms.list} element={<CriticismsList />} />
|
||||||
|
<Route path={Pages.criticisms.detail + ':id'} element={<CriticismsDetail />} />
|
||||||
<Route path={Pages.learning} element={<LearningList />} />
|
<Route path={Pages.learning} element={<LearningList />} />
|
||||||
<Route path={Pages.setting} element={<Setting />} />
|
<Route path={Pages.setting} element={<Setting />} />
|
||||||
<Route path={Pages.wallet} element={<Wallet />} />
|
<Route path={Pages.wallet} element={<Wallet />} />
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const SideBar: FC = () => {
|
|||||||
icon={<DocumentText variant={isActive('criticisms') ? 'Bold' : 'Outline'} color={isActive('criticisms') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
icon={<DocumentText variant={isActive('criticisms') ? 'Bold' : 'Outline'} color={isActive('criticisms') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
title={t('sidebar.criticisms')}
|
title={t('sidebar.criticisms')}
|
||||||
isActive={isActive('criticisms')}
|
isActive={isActive('criticisms')}
|
||||||
link={Pages.criticisms}
|
link={Pages.criticisms.list}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user