This commit is contained in:
hamid zarghami
2025-02-16 11:20:25 +03:30
parent 8f39afeb48
commit df8c000aa6
7 changed files with 349 additions and 80 deletions
+8 -1
View File
@@ -264,7 +264,14 @@
"status": "وضعیت",
"enter_your_title": "عنوان تبلیغ را وارد کنید",
"link": "لینک",
"enter_your_link": "لینک تبلیغ را وارد کنید"
"enter_your_link": "لینک تبلیغ را وارد کنید",
"HOMEPAGE_TOP": "بالای صفحه اصلی",
"SINGLE_SERVICE_PAGE": "صفحه سرویس تکی",
"MY_SERVICES_PAGE": "صفحه سرویس های من",
"OTHER_SERVICES_TOP": "بالای سایر سرویس ها",
"OTHER_SERVICES_LEFT": "سمت چپ سایر سرویس ها",
"SERVICE": "سرویس",
"file_error": "فایل مورد نظر باید انتخاب شود"
},
"discount": {
"list": "لیست تخفیفات",
+77 -59
View File
@@ -1,16 +1,28 @@
import { FC } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../components/Button'
import { Add, Eye } from 'iconsax-react'
import Td from '../../components/Td'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import SwitchComponent from '../../components/Switch'
import DatePickerComponent from '../../components/DatePicker'
import Select from '../../components/Select'
import Input from '../../components/Input'
import { useGetAdsList } from './hooks/useAdsData'
import { AdsItemType } from './types/AdsTypes'
import moment from 'moment-jalaali'
import ToggleStatusAds from './components/ToggleStatus'
import PageLoading from '../../components/PageLoading'
import Pagination from '../../components/Pagination'
const AddList: FC = () => {
const { t } = useTranslation('global')
const [search, setSearch] = useState<string>('')
const [isActive, setIsActive] = useState<boolean>(false)
const [since, setSince] = useState<string>('')
const [page, setPage] = useState<number>(1)
const getAds = useGetAdsList(page, search, isActive, since)
return (
<div className='mt-4 min-h-[500px]'>
<div className='flex justify-between items-center'>
@@ -37,9 +49,8 @@ const AddList: FC = () => {
<div className='w-[200px]'>
<DatePickerComponent
label={t('ads.date')}
onChange={() => { }}
onChange={(date) => setSince(moment(date, 'jYYYY-jMM-jDD').format('YYYY-MM-DD'))}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
<div className='w-[200px]'>
@@ -48,10 +59,15 @@ const AddList: FC = () => {
items={[
{
label: 'فعال',
value: 'active'
}
value: 'true'
},
{
label: 'غیر فعال',
value: 'false'
},
]}
className='bg-white border'
onChange={(e) => setIsActive(e.target.value === 'true' ? true : false)}
/>
</div>
</div>
@@ -61,65 +77,67 @@ const AddList: FC = () => {
variant='search'
placeholder={t('ads.search')}
className='bg-white w-full'
onChangeSearchFinal={(value) => setSearch(value)}
/>
</div>
</div>
</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('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('ads.status')} />
<Td text={''} />
</tr>
</thead>
<tbody>
<tr className='tr'>
<Td text={t('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
<tr className='tr'>
<Td text={t('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.discount + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
</tbody>
</table>
</div>
{
getAds.isPending ?
<div className='mt-5'>
<PageLoading />
</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('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('ads.status')} />
<Td text={''} />
</tr>
</thead>
<tbody>
{
getAds.data?.data?.ads?.map((item: AdsItemType, index: number) => {
return (
<tr key={item.id} className='tr'>
<Td text={String(index + 1)} />
<Td text={item.title} />
<Td text={t(`ads.${item.displayLocation}`)} />
<Td text={moment(item.startDate).format('jYYYY-jMM-jDD')} />
<Td text={moment(item.endDate).format('jYYYY-jMM-jDD')} />
<Td text={t('')}>
<ToggleStatusAds
defaultActive={item.isActive}
id={item.id}
/>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
)
})
}
</tbody>
</table>
</div>
}
<Pagination
currentPage={page}
onPageChange={(page) => setPage(page)}
totalPages={getAds.data?.data?.pager?.totalPages}
/>
</div>
)
}
+143 -20
View File
@@ -1,13 +1,78 @@
import { FC } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle, TickSquare, Link2 } from 'iconsax-react'
import { TickCircle, Link2 } from 'iconsax-react'
import DatePickerComponent from '../../components/DatePicker'
import ImageUploader from './components/ImageUploader'
import { useFormik } from 'formik'
import { AdsDisplayLocation, CreateAdsType } from './types/AdsTypes'
import * as Yup from 'yup'
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
import CheckBoxComponent from '../../components/CheckBoxComponent'
import { useSingleUpload } from '../service/hooks/useServiceData'
import { useCreateAds } from './hooks/useAdsData'
import { ErrorType } from '../../helpers/types'
import { toast } from 'react-toastify'
import { clx } from '../../helpers/utils'
import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import moment from 'moment-jalaali'
const CreateAd: FC = () => {
const navigate = useNavigate()
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const singleUpload = useSingleUpload()
const createAds = useCreateAds()
const formik = useFormik<CreateAdsType>({
initialValues: {
displayLocation: '',
endDate: '',
imageUrl: '',
isActive: true,
link: '',
serviceId: undefined,
startDate: '',
title: ''
},
validationSchema: Yup.object().shape({
title: Yup.string().required(t('errors.required')),
displayLocation: Yup.string().required(t('errors.required')),
endDate: Yup.string().required(t('errors.required')),
link: Yup.string().required(t('errors.required')),
startDate: Yup.string().required(t('errors.required'))
}),
onSubmit: async (values) => {
if (file) {
const formData = new FormData()
formData.append('file', file)
await singleUpload.mutateAsync(formData, {
onSuccess: (data) => {
values.imageUrl = data?.data?.url
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
}
})
createAds.mutate(values, {
onSuccess: () => {
toast.success(t('success'))
navigate(Pages.ads.list)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message[0])
}
})
} else {
toast.error(t('ads.file_error'))
}
}
})
return (
<div className='mt-4'>
<div className='flex justify-between items-center'>
@@ -16,6 +81,8 @@ const CreateAd: FC = () => {
</div>
<Button
className='w-[172px]'
onClick={() => formik.handleSubmit()}
isLoading={createAds.isPending || singleUpload.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
@@ -31,28 +98,35 @@ const CreateAd: FC = () => {
<Input
label={t('ads.title')}
placeholder={t('ads.enter_your_title')}
{...formik.getFieldProps('title')}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
</div>
<div className='mt-6 rowTwoInput'>
<DatePickerComponent
label={t('ads.startDate')}
onChange={() => { }}
onChange={(value) => formik.setFieldValue('startDate', moment(value, 'jYYYY-jMM-jDD').format('YYYY-MM-DD'))}
placeholder=''
defaulValue='1400-01-01'
error_text={formik.touched.startDate && formik.errors.startDate ? formik.errors.startDate : ''}
/>
<DatePickerComponent
label={t('ads.endDate')}
onChange={() => { }}
onChange={(value) => formik.setFieldValue('endDate', moment(value, 'jYYYY-jMM-jDD').format('YYYY-MM-DD'))}
placeholder=''
defaulValue='1400-01-01'
error_text={formik.touched.endDate && formik.errors.endDate ? formik.errors.endDate : ''}
/>
</div>
<div className='mt-6 relative'>
<Input
label={t('ads.link')}
placeholder={t('ads.enter_your_link')}
{...formik.getFieldProps('link')}
error_text={formik.touched.link && formik.errors.link ? formik.errors.link : ''}
/>
<Link2 size={20} color='blue' className='absolute left-4 bottom-[10px]' />
<Link2 size={20} color='blue' className={clx(
'absolute left-4 bottom-[10px] cursor-pointer',
formik.touched.link && formik.errors.link ? 'bottom-[31px]' : ''
)} />
</div>
</div>
<div className='min-h-[calc(100vh-201px)] bg-white w-full xl:w-sidebar py-10 px-5 h-fit rounded-3xl'>
@@ -65,39 +139,88 @@ const CreateAd: FC = () => {
{t('ads.deactive')}
</div>
<SwitchComponent
active={false}
onChange={() => { }}
active={formik.values.isActive}
onChange={(value) => formik.setFieldValue('isActive', value)}
/>
</div>
</div>
<p className='mt-6 text-sm'>{t('ads.location')}</p>
<div className='p-2 border border-[##D0D0D0] rounded-lg mt-1'>
<div className='flex items-start gap-2 py-2'>
<div className='flex items-center py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.HOMEPAGE_TOP}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.HOMEPAGE_TOP)}
/>
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
{t(`ads.${AdsDisplayLocation.HOMEPAGE_TOP}`)}
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div className='flex items-center py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.SINGLE_SERVICE_PAGE}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.SINGLE_SERVICE_PAGE)}
/>
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
{t(`ads.${AdsDisplayLocation.SINGLE_SERVICE_PAGE}`)}
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div className='flex items-center py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.MY_SERVICES_PAGE}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.MY_SERVICES_PAGE)}
/>
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
{t(`ads.${AdsDisplayLocation.MY_SERVICES_PAGE}`)}
</div>
</div>
<div className='flex items-center py-2'>
<div>
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.OTHER_SERVICES_TOP}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.OTHER_SERVICES_TOP)}
/>
</div>
<div className='text-description text-xs leading-5'>
{t(`ads.${AdsDisplayLocation.OTHER_SERVICES_TOP}`)}
</div>
</div>
<div className='flex items-center py-2'>
<div>
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.OTHER_SERVICES_LEFT}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.OTHER_SERVICES_LEFT)}
/>
</div>
<div className='text-description text-xs leading-5'>
{t(`ads.${AdsDisplayLocation.OTHER_SERVICES_LEFT}`)}
</div>
</div>
<div className='flex items-center py-2'>
<div>
<CheckBoxComponent
checked={formik.values.displayLocation === AdsDisplayLocation.SERVICE}
onChange={() => formik.setFieldValue('displayLocation', AdsDisplayLocation.SERVICE)}
/>
</div>
<div className='text-description text-xs leading-5'>
{t(`ads.${AdsDisplayLocation.SERVICE}`)}
</div>
</div>
</div>
<div className='mt-8'>
<UploadBoxDraggble
label={t('ads.upload_picture')}
onChange={(value) => setFile(value[0])}
/>
</div>
<ImageUploader />
</div>
</div>
+28
View File
@@ -0,0 +1,28 @@
import { FC, useState } from 'react'
import SwitchComponent from '../../../components/Switch'
import { useToggleStatusAds } from '../hooks/useAdsData'
type Props = {
defaultActive: boolean,
id: string
}
const ToggleStatusAds: FC<Props> = (props: Props) => {
const [isActive, setIsActive] = useState<boolean>(props.defaultActive)
const toggleStatus = useToggleStatusAds()
const handleChange = (value: boolean) => {
setIsActive(value)
toggleStatus.mutate(props.id)
}
return (
<SwitchComponent
active={isActive}
onChange={handleChange}
/>
)
}
export default ToggleStatusAds
+27
View File
@@ -0,0 +1,27 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import * as api from "../service/AdsService";
import { CreateAdsType } from "../types/AdsTypes";
export const useGetAdsList = (
page: number,
search: string,
isActive: boolean,
since: string
) => {
return useQuery({
queryKey: ["ads", page, search, isActive, since],
queryFn: () => api.getAdsList({ page, search, isActive, since }),
});
};
export const useCreateAds = () => {
return useMutation({
mutationFn: (variables: CreateAdsType) => api.createAds(variables),
});
};
export const useToggleStatusAds = () => {
return useMutation({
mutationFn: (variables: string) => api.toggleStatusAds(variables),
});
};
+33
View File
@@ -0,0 +1,33 @@
import axios from "../../../config/axios";
import { CreateAdsType } from "../types/AdsTypes";
interface GetAdsListParams {
page: number;
search?: string;
isActive?: boolean;
since?: string;
}
export const getAdsList = async ({
page,
search,
isActive,
since,
}: GetAdsListParams) => {
let query = `?page=${page}&q=${search}&isActive=${isActive}`;
if (since) {
query += `&since=${since}`;
}
const { data } = await axios.get(`/advertise/list${query}`);
return data;
};
export const createAds = async (params: CreateAdsType) => {
const { data } = await axios.post(`/advertise`, params);
return data;
};
export const toggleStatusAds = async (id: string) => {
const { data } = await axios.patch(`/advertise/toggle-status/${id}`);
return data;
};
+33
View File
@@ -0,0 +1,33 @@
export type CreateAdsType = {
title: string;
displayLocation: string;
startDate: string;
endDate: string;
isActive: boolean;
imageUrl: string;
link: string;
serviceId?: string;
};
export enum AdsDisplayLocation {
HOMEPAGE_TOP = "HOMEPAGE_TOP",
SINGLE_SERVICE_PAGE = "SINGLE_SERVICE_PAGE",
MY_SERVICES_PAGE = "MY_SERVICES_PAGE",
OTHER_SERVICES_TOP = "OTHER_SERVICES_TOP",
OTHER_SERVICES_LEFT = "OTHER_SERVICES_LEFT",
SERVICE = "SERVICE",
}
export type AdsItemType = {
id: string;
title: string;
displayLocation: AdsDisplayLocation;
startDate: string;
endDate: string;
isActive: boolean;
imageUrl: string;
link: string;
service?: string | null;
createdAt: string;
updatedAt: string;
};