announcement
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# VITE_BASE_URL = 'https://shop-api.dev.danakcorp.com'
|
VITE_BASE_URL = 'https://shop-api.dev.danakcorp.com'
|
||||||
VITE_BASE_URL = 'https://api.shinan.ir'
|
# VITE_BASE_URL = 'https://api.shinan.ir'
|
||||||
VITE_TOKEN_NAME = 'sh_admin_token'
|
VITE_TOKEN_NAME = 'sh_admin_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'sh_admin_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'sh_admin_refresh_token'
|
||||||
@@ -121,7 +121,7 @@ const MultiSelectSearchable: React.FC<MultiSelectSearchableProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="w-[var(--radix-popover-trigger-width)] max-w-[400px] p-0"
|
className="w-[var(--radix-popover-trigger-width)] max-w-[400px] p-0 z-[70]"
|
||||||
align="start"
|
align="start"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
// Prevent event bubbling that might cause navigation
|
// Prevent event bubbling that might cause navigation
|
||||||
@@ -154,7 +154,13 @@ const MultiSelectSearchable: React.FC<MultiSelectSearchableProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CommandList>
|
<CommandList className="max-h-[200px]">
|
||||||
|
{loading && options.length === 0 && (
|
||||||
|
<div className="flex items-center justify-center py-6">
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-900"></div>
|
||||||
|
<span className="ml-2 text-sm text-muted-foreground">در حال بارگذاری...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<CommandEmpty>موردی یافت نشد.</CommandEmpty>
|
<CommandEmpty>موردی یافت نشد.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
|
|||||||
@@ -198,5 +198,6 @@ export const Pages = {
|
|||||||
list: "/sellers/list",
|
list: "/sellers/list",
|
||||||
wholesaleRequests: "/sellers/wholesale-requests",
|
wholesaleRequests: "/sellers/wholesale-requests",
|
||||||
withdrawalRequests: "/sellers/withdrawal-requests",
|
withdrawalRequests: "/sellers/withdrawal-requests",
|
||||||
|
announcement: "/sellers/announcement",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -196,3 +196,11 @@ textarea::placeholder {
|
|||||||
.rmdp-container {
|
.rmdp-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||||
|
}
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
display: none; /* Safari and Chrome */
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import { useGetAnnouncementSeller } from './hooks/useSellerData'
|
||||||
|
import { type Notification } from './types/Types'
|
||||||
|
import PageLoading from '../../components/PageLoading'
|
||||||
|
import Error from '../../components/Error'
|
||||||
|
import PaginationUi from '../../components/PaginationUi'
|
||||||
|
import Td from '../../components/Td'
|
||||||
|
import AnnouncementTableRow from './components/AnnouncementTableRow'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import CreateAnnouncementModal from './components/CreateAnnouncementModal'
|
||||||
|
|
||||||
|
const Annoncement: FC = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
|
const { data: announcementsData, isLoading, error, refetch } = useGetAnnouncementSeller(currentPage)
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setIsModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModalSuccess = () => {
|
||||||
|
refetch()
|
||||||
|
setCurrentPage(1) // Reset to first page after creating new announcement
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<PageLoading />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<Error errorText="خطا در بارگذاری اطلاعیهها" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = announcementsData?.results?.notifications || []
|
||||||
|
const notificationPager = announcementsData?.results?.pager
|
||||||
|
|
||||||
|
// Convert NotificationPager to PagerType expected by PaginationUi
|
||||||
|
const pager = notificationPager ? {
|
||||||
|
...notificationPager,
|
||||||
|
nextPage: notificationPager.nextPage ? `${notificationPager.page + 1}` : null,
|
||||||
|
prevPage: notificationPager.prevPage
|
||||||
|
} : undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='flex justify-end mt-5'>
|
||||||
|
<Button
|
||||||
|
label="ایجاد اطلاعیه جدید"
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
className="w-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||||
|
<table className='w-full text-sm'>
|
||||||
|
<thead className='thead'>
|
||||||
|
<tr>
|
||||||
|
<Td text={'عنوان'} />
|
||||||
|
<Td text={'متن اطلاعیه'} />
|
||||||
|
<Td text={'نوع'} />
|
||||||
|
<Td text={'تاریخ ایجاد'} />
|
||||||
|
<Td text={'وضعیت'} />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{notifications.length === 0 ? (
|
||||||
|
<tr className='tr'>
|
||||||
|
<td colSpan={5} className="text-center py-8 text-gray-500">
|
||||||
|
هیچ اطلاعیهای یافت نشد
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
notifications.map((notification: Notification) => (
|
||||||
|
<AnnouncementTableRow
|
||||||
|
key={notification._id}
|
||||||
|
notification={notification}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaginationUi
|
||||||
|
pager={pager}
|
||||||
|
onPageChange={(page) => {
|
||||||
|
setCurrentPage(page)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CreateAnnouncementModal
|
||||||
|
open={isModalOpen}
|
||||||
|
close={handleModalClose}
|
||||||
|
onSuccess={handleModalSuccess}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Annoncement
|
||||||
@@ -7,15 +7,25 @@ import Td from '../../components/Td'
|
|||||||
import WithdrawalRequestTableRow from './components/WithdrawalRequestTableRow'
|
import WithdrawalRequestTableRow from './components/WithdrawalRequestTableRow'
|
||||||
import WithdrawalRequestModal from './components/WithdrawalRequestModal'
|
import WithdrawalRequestModal from './components/WithdrawalRequestModal'
|
||||||
|
|
||||||
interface WithdrawalRequestData {
|
export interface WithdrawalRequestData {
|
||||||
wallet: {
|
wallet: {
|
||||||
_id: string;
|
_id: string;
|
||||||
seller: {
|
seller: {
|
||||||
|
_id: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
email?: string;
|
dateOfBirth: string | null;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
|
accountStatus: "Pending" | "Approved";
|
||||||
|
isRegisterCompleted: boolean;
|
||||||
|
businessType: string | null;
|
||||||
|
isWholesaler: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
email?: string;
|
||||||
};
|
};
|
||||||
balance: number;
|
balance: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
withdrawals: unknown[];
|
withdrawals: unknown[];
|
||||||
lastPaidAmount: number | null;
|
lastPaidAmount: number | null;
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { type FC } from 'react'
|
||||||
|
import Td from '../../../components/Td'
|
||||||
|
import { Calendar, MessageText } from 'iconsax-react'
|
||||||
|
import StatusWithText from '../../../components/StatusWithText'
|
||||||
|
import type { Notification } from '../types/Types'
|
||||||
|
import { AnnouncementType } from '../types/Types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
notification: Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnnouncementTableRow: FC<Props> = ({ notification }) => {
|
||||||
|
const getTypeLabel = (type: string) => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
[AnnouncementType.Announcement]: 'اطلاعیه عمومی',
|
||||||
|
[AnnouncementType.Order]: 'سفارش',
|
||||||
|
[AnnouncementType.Product]: 'محصول',
|
||||||
|
[AnnouncementType.Shipment]: 'ارسال',
|
||||||
|
[AnnouncementType.Fine]: 'جریمه',
|
||||||
|
[AnnouncementType.Wallet]: 'کیف پول',
|
||||||
|
[AnnouncementType.Ticket]: 'تیکت',
|
||||||
|
[AnnouncementType.Chat]: 'چت',
|
||||||
|
[AnnouncementType.Contract]: 'قرارداد'
|
||||||
|
}
|
||||||
|
return typeMap[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypeVariant = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case AnnouncementType.Announcement: return 'warning'
|
||||||
|
case AnnouncementType.Order: return 'success'
|
||||||
|
case AnnouncementType.Product: return 'warning'
|
||||||
|
case AnnouncementType.Shipment: return 'success'
|
||||||
|
case AnnouncementType.Fine: return 'error'
|
||||||
|
case AnnouncementType.Wallet: return 'success'
|
||||||
|
case AnnouncementType.Ticket: return 'warning'
|
||||||
|
case AnnouncementType.Chat: return 'warning'
|
||||||
|
case AnnouncementType.Contract: return 'success'
|
||||||
|
default: return 'warning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const truncateMessage = (message: string, maxLength: number = 100) => {
|
||||||
|
if (message.length <= maxLength) return message
|
||||||
|
return message.substring(0, maxLength) + '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString('fa-IR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className='tr'>
|
||||||
|
<Td text="">
|
||||||
|
<div className="max-w-xs">
|
||||||
|
<div className="font-medium text-gray-900 truncate">
|
||||||
|
{notification.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div className="max-w-sm">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<MessageText size={16} color="#8C90A3" className="mt-0.5 flex-shrink-0" />
|
||||||
|
<span className="text-gray-600 text-sm leading-relaxed">
|
||||||
|
{truncateMessage(notification.message)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<StatusWithText
|
||||||
|
variant={getTypeVariant(notification.type)}
|
||||||
|
text={getTypeLabel(notification.type)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Calendar size={16} color="#8C90A3" />
|
||||||
|
<span className="text-gray-600 text-sm">
|
||||||
|
{formatDate(notification.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<StatusWithText
|
||||||
|
variant={notification.read ? 'success' : 'warning'}
|
||||||
|
text={notification.read ? 'خوانده شده' : 'خوانده نشده'}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnnouncementTableRow
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import { type FC, useState, useMemo } from 'react'
|
||||||
|
import DefaulModal from '@/components/DefaulModal'
|
||||||
|
import Input from '@/components/Input'
|
||||||
|
import Textarea from '@/components/Textarea'
|
||||||
|
import Select, { type ItemsSelectType } from '@/components/Select'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import MultiSelectSearchable, { type MultiSelectOption } from '@/components/MultiSelectSearchable'
|
||||||
|
import RadioGroup from '@/components/RadioGroup'
|
||||||
|
import { AnnouncementType, type CreateAnnouncementSeller, type CreateAnnouncementSpecificSeller } from '../types/Types'
|
||||||
|
import { useCreateAnnouncementSeller, useCreateAnnouncementSpecificSeller, useGetSellers } from '../hooks/useSellerData'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { extractErrorMessage } from '@/helpers/utils'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
close: () => void
|
||||||
|
onSuccess?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateAnnouncementModal: FC<Props> = ({ open, close, onSuccess }) => {
|
||||||
|
const createMutation = useCreateAnnouncementSeller()
|
||||||
|
const createSpecificMutation = useCreateAnnouncementSpecificSeller()
|
||||||
|
const [selectedSellers, setSelectedSellers] = useState<string[]>([])
|
||||||
|
const [sendToAll, setSendToAll] = useState(true)
|
||||||
|
|
||||||
|
// Get sellers for selection when modal is open
|
||||||
|
const { data: sellersData, isLoading: sellersLoading, error: sellersError } = useGetSellers(1)
|
||||||
|
|
||||||
|
const announcementTypes: ItemsSelectType[] = [
|
||||||
|
{ value: AnnouncementType.Announcement, label: 'اطلاعیه عمومی' },
|
||||||
|
{ value: AnnouncementType.Order, label: 'سفارش' },
|
||||||
|
{ value: AnnouncementType.Product, label: 'محصول' },
|
||||||
|
{ value: AnnouncementType.Shipment, label: 'ارسال' },
|
||||||
|
{ value: AnnouncementType.Fine, label: 'جریمه' },
|
||||||
|
{ value: AnnouncementType.Wallet, label: 'کیف پول' },
|
||||||
|
{ value: AnnouncementType.Ticket, label: 'تیکت' },
|
||||||
|
{ value: AnnouncementType.Chat, label: 'چت' },
|
||||||
|
{ value: AnnouncementType.Contract, label: 'قرارداد' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// Convert sellers to MultiSelectOption format
|
||||||
|
const sellerOptions: MultiSelectOption[] = useMemo(() => {
|
||||||
|
const sellers = sellersData?.results?.sellers || []
|
||||||
|
return sellers
|
||||||
|
.filter(seller => seller.fullName && seller.phoneNumber) // Filter out sellers with missing data
|
||||||
|
.map(seller => ({
|
||||||
|
value: seller._id,
|
||||||
|
label: `${seller.fullName} (${seller.phoneNumber})`
|
||||||
|
}))
|
||||||
|
}, [sellersData])
|
||||||
|
|
||||||
|
const formik = useFormik<CreateAnnouncementSeller>({
|
||||||
|
initialValues: {
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
type: AnnouncementType.Announcement
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
title: Yup.string().required('عنوان اطلاعیه الزامی است'),
|
||||||
|
message: Yup.string().required('متن اطلاعیه الزامی است'),
|
||||||
|
type: Yup.string().required('نوع اطلاعیه الزامی است')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (sendToAll) {
|
||||||
|
// Send to all sellers
|
||||||
|
createMutation.mutate(values, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('اطلاعیه برای همه فروشندگان ارسال شد')
|
||||||
|
close()
|
||||||
|
onSuccess?.()
|
||||||
|
formik.resetForm()
|
||||||
|
setSelectedSellers([])
|
||||||
|
setSendToAll(true)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(extractErrorMessage(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Send to specific sellers
|
||||||
|
if (selectedSellers.length === 0) {
|
||||||
|
toast.error('لطفا حداقل یک فروشنده انتخاب کنید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const specificValues: CreateAnnouncementSpecificSeller = {
|
||||||
|
...values,
|
||||||
|
sellerIds: selectedSellers
|
||||||
|
}
|
||||||
|
|
||||||
|
createSpecificMutation.mutate(specificValues, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(`اطلاعیه برای ${selectedSellers.length} فروشنده ارسال شد`)
|
||||||
|
close()
|
||||||
|
onSuccess?.()
|
||||||
|
formik.resetForm()
|
||||||
|
setSelectedSellers([])
|
||||||
|
setSendToAll(true)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(extractErrorMessage(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaulModal
|
||||||
|
open={open}
|
||||||
|
close={close}
|
||||||
|
isHeader={true}
|
||||||
|
title_header="ایجاد اطلاعیه جدید"
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<div className="space-y-6 w-[450px] mt-4">
|
||||||
|
<Input
|
||||||
|
label="عنوان اطلاعیه"
|
||||||
|
placeholder="عنوان اطلاعیه را وارد کنید"
|
||||||
|
{...formik.getFieldProps('title')}
|
||||||
|
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label="متن اطلاعیه"
|
||||||
|
placeholder="متن اطلاعیه را وارد کنید"
|
||||||
|
rows={4}
|
||||||
|
{...formik.getFieldProps('message')}
|
||||||
|
error_text={formik.touched.message && formik.errors.message ? formik.errors.message : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="نوع اطلاعیه"
|
||||||
|
items={announcementTypes}
|
||||||
|
placeholder="نوع اطلاعیه را انتخاب کنید"
|
||||||
|
{...formik.getFieldProps('type')}
|
||||||
|
error_text={formik.touched.type && formik.errors.type ? formik.errors.type : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">گیرندگان اطلاعیه</label>
|
||||||
|
<RadioGroup
|
||||||
|
items={[
|
||||||
|
{ label: 'ارسال به همه فروشندگان', value: true },
|
||||||
|
{ label: 'ارسال به فروشندگان خاص', value: false }
|
||||||
|
]}
|
||||||
|
selected={sendToAll}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSendToAll(value as boolean)
|
||||||
|
setSelectedSellers([])
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!sendToAll && (
|
||||||
|
<MultiSelectSearchable
|
||||||
|
label="انتخاب فروشندگان"
|
||||||
|
placeholder={sellersLoading ? "در حال بارگذاری فروشندگان..." : sellersError ? "خطا در بارگذاری فروشندگان" : sellerOptions.length === 0 ? "فروشندهای یافت نشد" : "فروشندگان مورد نظر را انتخاب کنید"}
|
||||||
|
options={sellerOptions.length > 0 ? sellerOptions : []}
|
||||||
|
value={selectedSellers}
|
||||||
|
onChange={setSelectedSellers}
|
||||||
|
error_text={sellersError ? 'خطا در بارگذاری فروشندگان' : selectedSellers.length === 0 && !sendToAll ? 'انتخاب حداقل یک فروشنده الزامی است' : ''}
|
||||||
|
loading={sellersLoading && sellerOptions.length === 0}
|
||||||
|
disabled={!!sellersError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-3 pt-4">
|
||||||
|
<Button
|
||||||
|
label="لغو"
|
||||||
|
variant="outline"
|
||||||
|
onClick={close}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="ایجاد اطلاعیه"
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={createMutation.isPending || createSpecificMutation.isPending}
|
||||||
|
disabled={!formik.isValid || createMutation.isPending || createSpecificMutation.isPending || (!sendToAll && selectedSellers.length === 0)}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateAnnouncementModal
|
||||||
@@ -1,21 +1,7 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import Td from '../../../components/Td'
|
import Td from '../../../components/Td'
|
||||||
import { Call, Card, User as UserIcon, Money, Eye } from 'iconsax-react'
|
import { Call, Card, User as UserIcon, Money, Eye } from 'iconsax-react'
|
||||||
|
import type { WithdrawalRequestData } from '../WithdrawalRequest'
|
||||||
interface WithdrawalRequestData {
|
|
||||||
wallet: {
|
|
||||||
_id: string;
|
|
||||||
seller: {
|
|
||||||
fullName: string;
|
|
||||||
email?: string;
|
|
||||||
phoneNumber: string;
|
|
||||||
};
|
|
||||||
balance: number;
|
|
||||||
};
|
|
||||||
withdrawals: unknown[];
|
|
||||||
lastPaidAmount: number | null;
|
|
||||||
lastPaidDate: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
request: WithdrawalRequestData
|
request: WithdrawalRequestData
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as api from "../service/SellerService";
|
import * as api from "../service/SellerService";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const useGetSellers = (page: number = 1) => {
|
export const useGetSellers = (page: number = 1, enabled: boolean = true) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["sellers", page],
|
queryKey: ["sellers", page],
|
||||||
queryFn: () => api.getSellers(page),
|
queryFn: () => api.getSellers(page),
|
||||||
|
enabled,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,3 +40,22 @@ export const useGetWithdrawalRequests = (page: number = 1) => {
|
|||||||
queryFn: () => api.getWithdrawalRequests(page),
|
queryFn: () => api.getWithdrawalRequests(page),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetAnnouncementSeller = (page: number = 1) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["announcement-seller", page],
|
||||||
|
queryFn: () => api.getAnnouncementSeller(page),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateAnnouncementSeller = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.createAnnouncementSeller,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateAnnouncementSpecificSeller = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.createAnnouncementToSpecificSellers,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import type {
|
|||||||
SellersResponse,
|
SellersResponse,
|
||||||
WholesaleRequestsResponse,
|
WholesaleRequestsResponse,
|
||||||
WithdrawalRequestsResponse,
|
WithdrawalRequestsResponse,
|
||||||
|
CreateAnnouncementSeller,
|
||||||
|
NotificationsResponse,
|
||||||
|
CreateAnnouncementSpecificSeller,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getSellers = async (
|
export const getSellers = async (
|
||||||
@@ -48,3 +51,29 @@ export const getWithdrawalRequests = async (
|
|||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAnnouncementSeller = async (
|
||||||
|
page: number = 1
|
||||||
|
): Promise<NotificationsResponse> => {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
`/admin/notification/allSeller?page=${page}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createAnnouncementSeller = async (
|
||||||
|
params: CreateAnnouncementSeller
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(`/admin/notification/allSeller`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createAnnouncementToSpecificSellers = async (
|
||||||
|
params: CreateAnnouncementSpecificSeller
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`/admin/notification/specific-sellers`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -159,3 +159,56 @@ export interface WithdrawalRequestsResponse {
|
|||||||
walletData: WithdrawalRequest[];
|
walletData: WithdrawalRequest[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateAnnouncementSeller {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
type: AnnouncementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAnnouncementSpecificSeller
|
||||||
|
extends CreateAnnouncementSeller {
|
||||||
|
sellerIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AnnouncementType {
|
||||||
|
Announcement = "announcement",
|
||||||
|
Order = "order",
|
||||||
|
Product = "product",
|
||||||
|
Shipment = "shipment",
|
||||||
|
Fine = "fine",
|
||||||
|
Wallet = "wallet",
|
||||||
|
Ticket = "ticket",
|
||||||
|
Chat = "chat",
|
||||||
|
Contract = "contract",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationPager {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
totalItems: number;
|
||||||
|
totalPages: number;
|
||||||
|
prevPage: boolean;
|
||||||
|
nextPage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Notification {
|
||||||
|
_id: string;
|
||||||
|
recipient: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
meta: unknown | null;
|
||||||
|
read: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationsResponse {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
results: {
|
||||||
|
pager: NotificationPager;
|
||||||
|
notifications: Notification[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import Dashboard from '@/pages/dashboard/Dashboard'
|
|||||||
import SellerList from '@/pages/seller/List'
|
import SellerList from '@/pages/seller/List'
|
||||||
import WholeSaleRequest from '@/pages/seller/WholeSaleRequest'
|
import WholeSaleRequest from '@/pages/seller/WholeSaleRequest'
|
||||||
import WithdrawalRequestPage from '@/pages/seller/WithdrawalRequest'
|
import WithdrawalRequestPage from '@/pages/seller/WithdrawalRequest'
|
||||||
|
import Annoncement from '@/pages/seller/Annoncement'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -163,6 +164,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.sellers.list} element={<SellerList />} />
|
<Route path={Pages.sellers.list} element={<SellerList />} />
|
||||||
<Route path={Pages.sellers.wholesaleRequests} element={<WholeSaleRequest />} />
|
<Route path={Pages.sellers.wholesaleRequests} element={<WholeSaleRequest />} />
|
||||||
<Route path={Pages.sellers.withdrawalRequests} element={<WithdrawalRequestPage />} />
|
<Route path={Pages.sellers.withdrawalRequests} element={<WithdrawalRequestPage />} />
|
||||||
|
<Route path={Pages.sellers.announcement} element={<Annoncement />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -513,8 +513,8 @@ const SellersSubMenu: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'ارسال اطلاعیه'}
|
title={'ارسال اطلاعیه'}
|
||||||
isActive={isActive('notifications')}
|
isActive={isActive('announcement')}
|
||||||
link="/sellers/notifications"
|
link={Pages.sellers.announcement}
|
||||||
/>
|
/>
|
||||||
<SubMenuItem
|
<SubMenuItem
|
||||||
title={'درخواست های ثبت محصول'}
|
title={'درخواست های ثبت محصول'}
|
||||||
|
|||||||
Reference in New Issue
Block a user