diff --git a/src/components/Select.tsx b/src/components/Select.tsx index 07c268e..8242dfe 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -13,7 +13,7 @@ type Props = { error_text?: string, placeholder?: string, label?: string, - value?: string, + value?: string | number, } & SelectHTMLAttributes const Select: FC = (props: Props) => { diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 6da8c8d..675b7da 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -194,4 +194,7 @@ export const Pages = { contactUs: { list: "/contact-us/list", }, + sellers: { + list: "/sellers/list", + }, }; diff --git a/src/index.css b/src/index.css index 632390c..f57a549 100644 --- a/src/index.css +++ b/src/index.css @@ -55,7 +55,7 @@ textarea::placeholder { @apply flex flex-col sm:flex-row sm:gap-5 gap-5; } .td { - @apply px-6 py-4 whitespace-nowrap text-xs; + @apply px-2 py-2 whitespace-nowrap text-xs; } .thead { @apply h-[69px] bg-white text-sm text-[#8C90A3] rounded-3xl overflow-hidden; diff --git a/src/pages/dashboard/components/DashboardOverview.tsx b/src/pages/dashboard/components/DashboardOverview.tsx index 6cbcfde..c06ee5e 100644 --- a/src/pages/dashboard/components/DashboardOverview.tsx +++ b/src/pages/dashboard/components/DashboardOverview.tsx @@ -9,33 +9,6 @@ const DashboardOverview: FC<{ data?: DashboardData }> = ({ data }) => { return (
- {/* دیباگ داده‌های API */} -
-

ℹ️ وضعیت اتصال به API

-
-
داده‌های واقعی API: {Array.isArray(data?.FiveDaysAgoSales) ? `✅ ${data.FiveDaysAgoSales.length} آیتم دریافت شد` : '❌ داده‌ای وجود ندارد'}
-
نمودار: {Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 ? '✅ نمایش داده‌های واقعی API' : '⏳ در انتظار داده‌های API'}
-
منطق نمایش: فقط از داده‌های واقعی API استفاده می‌شود
-
- {Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 && ( -
-
نمونه داده واقعی API:
-
-                            {JSON.stringify(data.FiveDaysAgoSales[0], null, 2)}
-                        
-
- )} -
-
🎯 نحوه عملکرد نمودار:
-
- ۱. فقط از داده‌های واقعی API استفاده می‌شود
- ۲. اگر داده وجود نداشته باشد، نمودار خالی نمایش داده می‌شود
- ۳. هیچ داده نمونه‌ای استفاده نمی‌شود
- ۴. ساختار داده‌های مورد انتظار: totalSales, totalOrders -
-
-
- {/* آمار کلی */} diff --git a/src/pages/seller/List.tsx b/src/pages/seller/List.tsx new file mode 100644 index 0000000..1348a9b --- /dev/null +++ b/src/pages/seller/List.tsx @@ -0,0 +1,95 @@ +import { type FC, useState } from 'react' +import { useGetSellers } from './hooks/useSellerData' +import { type Seller } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import SellerTableRow from './components/SellerTableRow' +import SellerContractModal from './components/SellerContractModal' + +const SellerList: FC = () => { + + const [currentPage, setCurrentPage] = useState(1); + const [selectedContract, setSelectedContract] = useState(); + const [isContractModalOpen, setIsContractModalOpen] = useState(false); + const { data: sellersData, isLoading, error } = useGetSellers(currentPage); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const sellers = sellersData?.results?.sellers || []; + const pager = sellersData?.results?.pager; + + const handleViewContract = (contract?: Seller['contracts']) => { + setSelectedContract(contract); + setIsContractModalOpen(true); + }; + + return ( +
+
+ + + + + + + {sellers.length === 0 ? ( + + + + ) : ( + sellers.map((seller: Seller) => ( + + )) + )} + +
+ + + + + + + + +
+ هیچ فروشنده‌ای یافت نشد +
+
+ + { + setCurrentPage(page); + }} + /> + + setIsContractModalOpen(false)} + contract={selectedContract} + /> +
+ ) +} + +export default SellerList \ No newline at end of file diff --git a/src/pages/seller/components/SellerContractModal.tsx b/src/pages/seller/components/SellerContractModal.tsx new file mode 100644 index 0000000..719de1b --- /dev/null +++ b/src/pages/seller/components/SellerContractModal.tsx @@ -0,0 +1,89 @@ +import { type FC } from 'react' +import DefaulModal from '../../../components/DefaulModal' +import Button from '../../../components/Button' +import { type Contract } from '../types/Types' +import { useApproveContract } from '../hooks/useSellerData' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' + +interface Props { + open: boolean + close: () => void + contract?: Contract +} + +const SellerContractModal: FC = ({ open, close, contract }) => { + const { mutate: approveContract, isPending } = useApproveContract(); + + const handleApproveContract = () => { + if (contract) { + approveContract(contract.seller, { + onSuccess: () => { + close(); + toast.success('قرارداد با موفقیت تایید شد'); + }, + onError: (error) => { + toast.error(extractErrorMessage(error)); + } + }); + } + }; + + return ( + +
+ {contract?.content ? ( +
+
+
+ ) : ( +
+ قراردادی برای نمایش وجود ندارد +
+ )} + + {contract && ( +
+
+
+ شماره قرارداد: {contract.contractNumber} +
+
+ وضعیت امضا: {contract.signed ? 'امضا شده' : 'امضا نشده'} +
+ {contract.signed && contract.singedAt && ( +
+ تاریخ امضا: {contract.singedAt} +
+ )} +
+ +
+ +
+
+ )} +
+ + ) +} + +export default SellerContractModal diff --git a/src/pages/seller/components/SellerTableRow.tsx b/src/pages/seller/components/SellerTableRow.tsx new file mode 100644 index 0000000..f86c2f2 --- /dev/null +++ b/src/pages/seller/components/SellerTableRow.tsx @@ -0,0 +1,97 @@ +import { type FC } from 'react' +import { type Seller } from '../types/Types' +import StatusWithText from '../../../components/StatusWithText' +import Td from '../../../components/Td' +import SellerToggle from './SellerToggle' +import { Calendar, Call, Card, User as UserIcon, Shop, Eye } from 'iconsax-react' + +interface Props { + seller: Seller + onViewContract: (contract?: Seller['contracts']) => void +} + +const SellerTableRow: FC = ({ seller, onViewContract }) => { + const getStatusVariant = (status: string) => { + switch (status) { + case 'Approved': return 'success'; + case 'Pending': return 'warning'; + default: return 'warning'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'Approved': return 'تایید شده'; + case 'Pending': return 'در انتظار'; + default: return status; + } + }; + + const formatDate = (dateString: string) => { + if (!dateString) return '-'; + return new Date(dateString).toLocaleDateString('fa-IR'); + }; + + return ( + + +
+ + {seller.fullName || '-'} +
+ + +
+ + {seller.shop?.shopName || '-'} +
+ + +
+ + {seller.email || '-'} +
+ + +
+ + {seller.phoneNumber} +
+ + + + + + +
+ + {formatDate(seller.createdAt)} +
+ + + {seller.contracts ? ( + + ) : ( + - + )} + + + + + + ) +} + +export default SellerTableRow diff --git a/src/pages/seller/components/SellerToggle.tsx b/src/pages/seller/components/SellerToggle.tsx new file mode 100644 index 0000000..f10930e --- /dev/null +++ b/src/pages/seller/components/SellerToggle.tsx @@ -0,0 +1,46 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import SwitchComponent from '../../../components/Switch' +import * as api from '../service/SellerService' +import { extractErrorMessage } from '@/helpers/utils' +import { toast } from 'react-toastify' + +interface SellerToggleProps { + sellerId: string + isActive: boolean +} + +const SellerToggle: React.FC = ({ sellerId, isActive }) => { + const queryClient = useQueryClient() + + const toggleMutation = useMutation({ + mutationFn: api.toggleSellerStatus, + onSuccess: () => { + // Refresh sellers data after successful toggle + queryClient.invalidateQueries({ queryKey: ['sellers'] }) + }, + onError: (error) => { + console.error('Error toggling seller status:', error) + } + }) + + const handleToggle = async () => { + + await toggleMutation.mutateAsync(sellerId, { + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + + } + + return ( + + ) +} + +export default SellerToggle diff --git a/src/pages/seller/hooks/useSellerData.ts b/src/pages/seller/hooks/useSellerData.ts new file mode 100644 index 0000000..fbe6ae6 --- /dev/null +++ b/src/pages/seller/hooks/useSellerData.ts @@ -0,0 +1,21 @@ +import * as api from "../service/SellerService"; +import { useMutation, useQuery } from "@tanstack/react-query"; + +export const useGetSellers = (page: number = 1) => { + return useQuery({ + queryKey: ["sellers", page], + queryFn: () => api.getSellers(page), + }); +}; + +export const useToggleSellerStatus = () => { + return useMutation({ + mutationFn: api.toggleSellerStatus, + }); +}; + +export const useApproveContract = () => { + return useMutation({ + mutationFn: api.approveContract, + }); +}; diff --git a/src/pages/seller/service/SellerService.ts b/src/pages/seller/service/SellerService.ts new file mode 100644 index 0000000..0ce4f5d --- /dev/null +++ b/src/pages/seller/service/SellerService.ts @@ -0,0 +1,23 @@ +import axios from "@/config/axios"; +import type { SellersResponse } from "../types/Types"; + +export const getSellers = async ( + page: number = 1 +): Promise => { + const { data } = await axios.get(`/admin/sellers?page=${page}`); + return data; +}; + +export const toggleSellerStatus = async (sellerId: string) => { + const { data } = await axios.patch( + `/admin/sellers/approve-register/${sellerId}` + ); + return data; +}; + +export const approveContract = async (sellerId: string) => { + const { data } = await axios.patch( + `/admin/sellers/approve-contract/${sellerId}` + ); + return data; +}; diff --git a/src/pages/seller/types/Types.ts b/src/pages/seller/types/Types.ts new file mode 100644 index 0000000..eccf35e --- /dev/null +++ b/src/pages/seller/types/Types.ts @@ -0,0 +1,87 @@ +// Seller Types +export interface SellerDocument { + _id: string; + seller: string; + type: string; + __v: number; + comment: string | null; + createdAt: string; + docsUrl: string; + status: "Approved" | "Pending"; + updatedAt: string; +} + +export interface ShopRate { + totalRate: number; + totalCount: number; + onTimeShip: number; + returns: number; + cancel: number; +} + +export interface Shop { + _id: string; + shopName: string | null; + shopDescription: string | null; + telephoneNumber: string | null; + shopPostalCode: string | null; + address: string | null; + logo: string | null; + rate: ShopRate; + shipmentMethod: number[]; + isChatActive: boolean; + owner: string; + ownerRef: string; + createdAt: string; + updatedAt: string; + shopCode: number; + __v: number; +} + +export interface Contract { + _id: string; + content: string; + seller: string; + contractNumber: string; + signed: boolean; + singedAt: string; + createdAt: string; + updatedAt: string; + __v: number; +} + +export interface Seller { + _id: string; + fullName: string; + dateOfBirth: string | null; + phoneNumber: string; + accountStatus: "Pending" | "Approved"; + isRegisterCompleted: boolean; + businessType: string | null; + isWholesaler: boolean; + createdAt: string; + updatedAt: string; + __v: number; + email?: string; + documents?: SellerDocument[]; + shop: Shop; + contracts?: Contract; +} + +export interface Pager { + page: number; + limit: number; + totalItems: number; + totalPages: number; + prevPage: boolean | null; + nextPage: string | null; +} + +export interface SellersResponse { + status: number; + success: boolean; + results: { + sellers: Seller[]; + pager: Pager; + }; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index c739b8f..4d5f3b3 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -62,6 +62,7 @@ import JobsList from '@/pages/jobs/List' import Resumes from '@/pages/jobs/Resume' import ContactUsList from '@/pages/contactUs/List' import Dashboard from '@/pages/dashboard/Dashboard' +import SellerList from '@/pages/seller/List' const MainRouter: FC = () => { @@ -157,6 +158,8 @@ const MainRouter: FC = () => { } /> } /> } /> + + } />