banner mobile

This commit is contained in:
hamid zarghami
2025-11-09 09:40:28 +03:30
parent 24689bf932
commit 9631f7e5da
5 changed files with 101 additions and 30 deletions
+6 -2
View File
@@ -9,6 +9,7 @@ type Props = {
isMultiple?: boolean, isMultiple?: boolean,
onChange?: (file: File[]) => void; onChange?: (file: File[]) => void;
isReset?: boolean; isReset?: boolean;
disabled?: boolean;
} }
const UploadBox: FC<Props> = (props: Props) => { const UploadBox: FC<Props> = (props: Props) => {
@@ -17,6 +18,9 @@ const UploadBox: FC<Props> = (props: Props) => {
const [files, setFiles] = useState<File[]>([]) const [files, setFiles] = useState<File[]>([])
const onDrop = useCallback((acceptedFiles: File[]) => { const onDrop = useCallback((acceptedFiles: File[]) => {
if (props.disabled) {
return
}
if (props.isMultiple) { if (props.isMultiple) {
const array = [...files] const array = [...files]
array.push(acceptedFiles[0]) array.push(acceptedFiles[0])
@@ -31,8 +35,8 @@ const UploadBox: FC<Props> = (props: Props) => {
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [files]) }, [files, props.disabled, props.isMultiple, props.onChange])
const { getRootProps, getInputProps } = useDropzone({ onDrop }) const { getRootProps, getInputProps } = useDropzone({ onDrop, disabled: props.disabled })
const handleRemove = (index: number) => { const handleRemove = (index: number) => {
const array = [...files] const array = [...files]
+18 -11
View File
@@ -1,14 +1,14 @@
import { useState, type FC } from 'react' import { useState, type FC } from 'react'
import PageTitle from '../../components/PageTitle' import PageTitle from '@/components/PageTitle'
import { useDeleteBanner, useGetBanners, useUpdateBanner } from './hooks/useSettingData' import PageLoading from '@/components/PageLoading'
import { type BannerItem, type UpdateBannerType } from './types/Types' import Error from '@/components/Error'
import PageLoading from '../../components/PageLoading' import Td from '@/components/Td'
import Error from '../../components/Error' import Button from '@/components/Button'
import Td from '../../components/Td' import TrashWithConfrim from '@/components/TrashWithConfrim'
import Switch from '@/components/Switch'
import { useDeleteBanner, useGetBanners, useUpdateBanner } from '@/pages/setting/hooks/useSettingData'
import { type BannerItem, type UpdateBannerType } from '@/pages/setting/types/Types'
import { Calendar, Add, Edit, Eye } from 'iconsax-react' import { Calendar, Add, Edit, Eye } from 'iconsax-react'
import Button from '../../components/Button'
import TrashWithConfrim from '../../components/TrashWithConfrim'
import Switch from '../../components/Switch'
import CreateBannerModal from './components/CreateBannerModal' import CreateBannerModal from './components/CreateBannerModal'
import EditBannerModal from './components/EditBannerModal' import EditBannerModal from './components/EditBannerModal'
@@ -55,7 +55,8 @@ const Banners: FC = () => {
title: banner.title, title: banner.title,
linkUrl: banner.linkUrl, linkUrl: banner.linkUrl,
isActive: !banner.isActive, isActive: !banner.isActive,
order: banner.order order: banner.order,
displayLocation: banner.displayLocation
} }
updateBanner.mutate({ id: banner._id, params: updateData }) updateBanner.mutate({ id: banner._id, params: updateData })
@@ -87,6 +88,7 @@ const Banners: FC = () => {
<Td text={'عنوان'} /> <Td text={'عنوان'} />
<Td text={'متن جایگزین'} /> <Td text={'متن جایگزین'} />
<Td text={'لینک'} /> <Td text={'لینک'} />
<Td text={'محل نمایش'} />
<Td text={'ترتیب'} /> <Td text={'ترتیب'} />
<Td text={'وضعیت'} /> <Td text={'وضعیت'} />
<Td text={'تاریخ ایجاد'} /> <Td text={'تاریخ ایجاد'} />
@@ -96,7 +98,7 @@ const Banners: FC = () => {
<tbody> <tbody>
{banners.length === 0 ? ( {banners.length === 0 ? (
<tr className='tr'> <tr className='tr'>
<td colSpan={8} className="text-center py-8 text-gray-500"> <td colSpan={9} className="text-center py-8 text-gray-500">
هیچ بنری یافت نشد هیچ بنری یافت نشد
</td> </td>
</tr> </tr>
@@ -137,6 +139,11 @@ const Banners: FC = () => {
</a> </a>
</div> </div>
</Td> </Td>
<Td text="">
<span className="capitalize">
{banner.displayLocation === 'desktop' ? 'دسکتاپ' : 'موبایل'}
</span>
</Td>
<Td text={banner.order.toString()} /> <Td text={banner.order.toString()} />
<Td text=""> <Td text="">
<Switch <Switch
@@ -1,15 +1,16 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { type FC } from 'react' import { type FC } from 'react'
import Button from '../../../components/Button' import Button from '@/components/Button'
import Input from '../../../components/Input' import Input from '@/components/Input'
import DefaulModal from '../../../components/DefaulModal' import DefaulModal from '@/components/DefaulModal'
import { useCreateBanner } from '../hooks/useSettingData' import UploadBox from '@/components/UploadBox'
import { useUploadSingle } from '../../../pages/uploader/hooks/useUploaderData' import Switch from '@/components/Switch'
import { type CreateBannerType } from '../types/Types' import Select, { type ItemsSelectType } from '@/components/Select'
import { useCreateBanner } from '@/pages/setting/hooks/useSettingData'
import { useUploadSingle } from '@/pages/uploader/hooks/useUploaderData'
import { type CreateBannerType } from '@/pages/setting/types/Types'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/helpers/utils' import { extractErrorMessage } from '@/helpers/utils'
import UploadBox from '../../../components/UploadBox'
import Switch from '../../../components/Switch'
interface CreateBannerModalProps { interface CreateBannerModalProps {
isOpen: boolean isOpen: boolean
@@ -17,14 +18,24 @@ interface CreateBannerModalProps {
onSuccess: () => void onSuccess: () => void
} }
type CreateBannerFormState = Omit<CreateBannerType, 'displayLocation'> & {
displayLocation: '' | 'desktop' | 'mobile'
}
const displayLocationOptions: ItemsSelectType[] = [
{ value: 'desktop', label: 'دسکتاپ' },
{ value: 'mobile', label: 'موبایل' }
]
const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => { const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => {
const [formData, setFormData] = useState<CreateBannerType>({ const [formData, setFormData] = useState<CreateBannerFormState>({
imageUrl: '', imageUrl: '',
altText: '', altText: '',
title: '', title: '',
linkUrl: '', linkUrl: '',
isActive: true, isActive: true,
order: 1, order: 1,
displayLocation: '',
}) })
const [imageFile, setImageFile] = useState<File[]>([]) const [imageFile, setImageFile] = useState<File[]>([])
@@ -40,6 +51,11 @@ const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => {
return return
} }
if (!formData.displayLocation) {
toast.error('لطفا محل نمایش بنر را مشخص کنید')
return
}
if (imageFile.length === 0) { if (imageFile.length === 0) {
toast.error('لطفا تصویر بنر را انتخاب کنید') toast.error('لطفا تصویر بنر را انتخاب کنید')
return return
@@ -68,6 +84,7 @@ const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => {
linkUrl: formData.linkUrl, linkUrl: formData.linkUrl,
isActive: formData.isActive, isActive: formData.isActive,
order: formData.order, order: formData.order,
displayLocation: formData.displayLocation,
}, { }, {
onError: (error) => { onError: (error) => {
toast.error(extractErrorMessage(error)) toast.error(extractErrorMessage(error))
@@ -89,6 +106,7 @@ const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => {
linkUrl: '', linkUrl: '',
isActive: true, isActive: true,
order: 1, order: 1,
displayLocation: '',
}) })
setImageFile([]) setImageFile([])
onClose() onClose()
@@ -97,12 +115,25 @@ const CreateBannerModal: FC<CreateBannerModalProps> = ({ isOpen, onClose }) => {
return ( return (
<DefaulModal open={isOpen} close={handleClose} isHeader={true} title_header="افزودن بنر جدید"> <DefaulModal open={isOpen} close={handleClose} isHeader={true} title_header="افزودن بنر جدید">
<form onSubmit={handleSubmit} className="w-[500px] mt-4 space-y-6"> <form onSubmit={handleSubmit} className="w-[500px] mt-4 space-y-6">
{/* محل نمایش */}
<div>
<Select
label='محل نمایش'
placeholder='انتخاب محل نمایش'
items={displayLocationOptions}
value={formData.displayLocation}
onChange={(e) => setFormData(prev => ({ ...prev, displayLocation: e.target.value as CreateBannerFormState['displayLocation'] }))}
required
/>
</div>
{/* تصویر بنر */} {/* تصویر بنر */}
<div> <div>
<UploadBox <UploadBox
label='تصویر بنر' label='تصویر بنر'
isMultiple={false} isMultiple={false}
onChange={(files) => setImageFile(files)} onChange={(files) => setImageFile(files)}
disabled={!formData.displayLocation}
/> />
</div> </div>
@@ -1,15 +1,16 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { type FC } from 'react' import { type FC } from 'react'
import Button from '../../../components/Button' import Button from '@/components/Button'
import Input from '../../../components/Input' import Input from '@/components/Input'
import DefaulModal from '../../../components/DefaulModal' import DefaulModal from '@/components/DefaulModal'
import { useUpdateBanner } from '../hooks/useSettingData' import UploadBox from '@/components/UploadBox'
import { useUploadSingle } from '../../../pages/uploader/hooks/useUploaderData' import Switch from '@/components/Switch'
import { type BannerItem, type UpdateBannerType } from '../types/Types' import Select, { type ItemsSelectType } from '@/components/Select'
import { useUpdateBanner } from '@/pages/setting/hooks/useSettingData'
import { useUploadSingle } from '@/pages/uploader/hooks/useUploaderData'
import { type BannerItem, type UpdateBannerType } from '@/pages/setting/types/Types'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/helpers/utils' import { extractErrorMessage } from '@/helpers/utils'
import UploadBox from '../../../components/UploadBox'
import Switch from '../../../components/Switch'
interface EditBannerModalProps { interface EditBannerModalProps {
banner: BannerItem banner: BannerItem
@@ -18,6 +19,11 @@ interface EditBannerModalProps {
onSuccess: () => void onSuccess: () => void
} }
const displayLocationOptions: ItemsSelectType[] = [
{ value: 'desktop', label: 'دسکتاپ' },
{ value: 'mobile', label: 'موبایل' }
]
const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, onSuccess }) => { const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, onSuccess }) => {
const [formData, setFormData] = useState<UpdateBannerType>({ const [formData, setFormData] = useState<UpdateBannerType>({
imageUrl: '', imageUrl: '',
@@ -26,6 +32,7 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
linkUrl: '', linkUrl: '',
isActive: true, isActive: true,
order: 1, order: 1,
displayLocation: 'desktop',
}) })
const [imageFile, setImageFile] = useState<File[]>([]) const [imageFile, setImageFile] = useState<File[]>([])
@@ -42,6 +49,7 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
linkUrl: banner.linkUrl, linkUrl: banner.linkUrl,
isActive: banner.isActive, isActive: banner.isActive,
order: banner.order, order: banner.order,
displayLocation: banner.displayLocation,
}) })
} }
}, [banner]) }, [banner])
@@ -59,6 +67,11 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
return return
} }
if (!formData.displayLocation) {
toast.error('لطفا محل نمایش بنر را مشخص کنید')
return
}
try { try {
let finalImageUrl = formData.imageUrl let finalImageUrl = formData.imageUrl
@@ -83,6 +96,7 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
linkUrl: formData.linkUrl, linkUrl: formData.linkUrl,
isActive: formData.isActive, isActive: formData.isActive,
order: formData.order, order: formData.order,
displayLocation: formData.displayLocation,
} }
}, { }, {
onError: (error) => { onError: (error) => {
@@ -106,6 +120,7 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
linkUrl: '', linkUrl: '',
isActive: true, isActive: true,
order: 1, order: 1,
displayLocation: 'desktop',
}) })
setImageFile([]) setImageFile([])
onClose() onClose()
@@ -114,6 +129,17 @@ const EditBannerModal: FC<EditBannerModalProps> = ({ banner, isOpen, onClose, on
return ( return (
<DefaulModal open={isOpen} close={handleClose} isHeader={true} title_header="ویرایش بنر"> <DefaulModal open={isOpen} close={handleClose} isHeader={true} title_header="ویرایش بنر">
<form onSubmit={handleSubmit} className="w-[500px] mt-4 space-y-6"> <form onSubmit={handleSubmit} className="w-[500px] mt-4 space-y-6">
{/* محل نمایش */}
<div>
<Select
label='محل نمایش'
items={displayLocationOptions}
value={formData.displayLocation}
onChange={(e) => setFormData(prev => ({ ...prev, displayLocation: e.target.value as UpdateBannerType['displayLocation'] }))}
required
/>
</div>
{/* تصویر بنر */} {/* تصویر بنر */}
<div> <div>
<div className="mb-4"> <div className="mb-4">
+3
View File
@@ -220,6 +220,7 @@ export interface BannerItem {
linkUrl: string; linkUrl: string;
isActive: boolean; isActive: boolean;
order: number; order: number;
displayLocation: "desktop" | "mobile";
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
@@ -241,6 +242,7 @@ export interface CreateBannerType {
linkUrl: string; linkUrl: string;
isActive: boolean; isActive: boolean;
order: number; order: number;
displayLocation: "desktop" | "mobile";
} }
export interface UpdateBannerType { export interface UpdateBannerType {
@@ -250,6 +252,7 @@ export interface UpdateBannerType {
linkUrl: string; linkUrl: string;
isActive: boolean; isActive: boolean;
order: number; order: number;
displayLocation: "desktop" | "mobile";
} }
export interface SliderItem { export interface SliderItem {