wholesale request

This commit is contained in:
hamid zarghami
2025-10-11 14:40:24 +03:30
parent 3fb0a15f3d
commit ec4fe28289
8 changed files with 278 additions and 4 deletions
+2 -2
View File
@@ -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 = 'http://192.168.1.122:4000' 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'
+1
View File
@@ -196,5 +196,6 @@ export const Pages = {
}, },
sellers: { sellers: {
list: "/sellers/list", list: "/sellers/list",
wholesaleRequests: "/sellers/wholesale-requests",
}, },
}; };
+94
View File
@@ -0,0 +1,94 @@
import { type FC, useState } from 'react'
import { useApproveWholesaleRequest, useGetWholesaleRequests } from './hooks/useSellerData'
import { type WholesaleRequest } from './types/Types'
import { type PagerType } from '../products/types/Types'
import PageLoading from '../../components/PageLoading'
import Error from '../../components/Error'
import PaginationUi from '../../components/PaginationUi'
import Td from '../../components/Td'
import WholesaleRequestTableRow from './components/WholesaleRequestTableRow'
import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/helpers/utils'
const WholeSaleRequest: FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const { data: wholesaleData, isLoading, error, refetch } = useGetWholesaleRequests(currentPage);
const approveWholesaleRequestMutation = useApproveWholesaleRequest();
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 handleApprove = async (id: string) => {
await approveWholesaleRequestMutation.mutateAsync(id, {
onSuccess: () => {
refetch();
toast.success('درخواست با موفقیت تایید شد');
},
onError: (error) => {
toast.error(extractErrorMessage(error));
}
});
};
const requests = wholesaleData?.results?.requests || [];
const pager = wholesaleData?.results?.pager;
return (
<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={'وضعیت'} />
<Td text={'تاریخ ایجاد'} />
<Td text={'عملیات'} />
</tr>
</thead>
<tbody>
{requests.length === 0 ? (
<tr className='tr'>
<td colSpan={7} className="text-center py-8 text-gray-500">
هیچ درخواست عمدهفروشی یافت نشد
</td>
</tr>
) : (
requests.map((request: WholesaleRequest) => (
<WholesaleRequestTableRow
key={request._id}
request={request}
onApprove={handleApprove}
/>
))
)}
</tbody>
</table>
</div>
<PaginationUi
pager={pager as unknown as PagerType}
onPageChange={(page) => {
setCurrentPage(page);
}}
/>
</div>
)
}
export default WholeSaleRequest
@@ -0,0 +1,111 @@
import { type FC, useState } from 'react'
import { type WholesaleRequest } from '../types/Types'
import StatusWithText from '../../../components/StatusWithText'
import Td from '../../../components/Td'
import Button from '../../../components/Button'
import ModalConfrim from '../../../components/ModalConfrim'
import { Calendar, Call, Card, User as UserIcon, Shop, TickCircle } from 'iconsax-react'
interface Props {
request: WholesaleRequest
onApprove: (id: string) => void
}
const WholesaleRequestTableRow: FC<Props> = ({ request, onApprove }) => {
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
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');
};
const handleApprove = () => {
setIsConfirmOpen(true);
};
const confirmApprove = () => {
onApprove(request._id);
setIsConfirmOpen(false);
};
return (
<>
<tr key={request._id} className='tr'>
<Td text="">
<div className="flex items-center gap-1">
<UserIcon size={16} color="#8C90A3" />
<span>{request.seller?.fullName || '-'}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-1">
<Shop size={16} color="#8C90A3" />
<span>{request.shop?.shopName || '-'}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-1">
<Card size={16} color="#8C90A3" />
<span>{request.seller?.email || '-'}</span>
</div>
</Td>
<Td text="">
<div className="flex items-center gap-1">
<Call size={16} color="#8C90A3" />
<span>{request.seller?.phoneNumber}</span>
</div>
</Td>
<Td text="">
<StatusWithText
variant={getStatusVariant(request.status)}
text={getStatusText(request.status)}
/>
</Td>
<Td text="">
<div className="flex items-center gap-1">
<Calendar size={16} color="#8C90A3" />
<span>{formatDate(request.createdAt)}</span>
</div>
</Td>
<Td text="">
{request.status === 'Pending' && (
<Button
variant="primary"
size="sm"
className="w-auto px-3"
onClick={handleApprove}
>
<div className="flex items-center gap-1">
<TickCircle color="#fff" size={14} />
تایید
</div>
</Button>
)}
</Td>
</tr>
<ModalConfrim
isOpen={isConfirmOpen}
close={() => setIsConfirmOpen(false)}
onConfrim={confirmApprove}
label="آیا از تایید این درخواست عمده‌فروشی اطمینان دارید؟"
/>
</>
)
}
export default WholesaleRequestTableRow
+13
View File
@@ -19,3 +19,16 @@ export const useApproveContract = () => {
mutationFn: api.approveContract, mutationFn: api.approveContract,
}); });
}; };
export const useGetWholesaleRequests = (page: number = 1) => {
return useQuery({
queryKey: ["wholesale-requests", page],
queryFn: () => api.getWholesaleRequests(page),
});
};
export const useApproveWholesaleRequest = () => {
return useMutation({
mutationFn: api.approveWholesaleRequest,
});
};
+18 -1
View File
@@ -1,5 +1,8 @@
import axios from "@/config/axios"; import axios from "@/config/axios";
import type { SellersResponse } from "../types/Types"; import type {
SellersResponse,
WholesaleRequestsResponse,
} from "../types/Types";
export const getSellers = async ( export const getSellers = async (
page: number = 1 page: number = 1
@@ -21,3 +24,17 @@ export const approveContract = async (sellerId: string) => {
); );
return data; return data;
}; };
export const getWholesaleRequests = async (
page: number = 1
): Promise<WholesaleRequestsResponse> => {
const { data } = await axios.get(
`/admin/sellers/wholesale-requests?page=${page}`
);
return data;
};
export const approveWholesaleRequest = async (id: string) => {
const { data } = await axios.patch(`/admin/sellers/wholesale-requests/${id}`);
return data;
};
+37
View File
@@ -85,3 +85,40 @@ export interface SellersResponse {
pager: Pager; pager: Pager;
}; };
} }
export interface WholesaleRequest {
_id: string;
seller: {
_id: string;
fullName: string;
dateOfBirth: string;
phoneNumber: string;
accountStatus: "Pending" | "Approved";
isRegisterCompleted: boolean;
businessType: string;
isWholesaler: boolean;
createdAt: string;
updatedAt: string;
email: string;
};
shop: Shop;
status: "Pending" | "Approved";
createdAt: string;
updatedAt: string;
}
export interface WholesaleRequestsResponse {
status: number;
success: boolean;
results: {
requests: WholesaleRequest[];
pager: {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: boolean;
};
};
}
+2 -1
View File
@@ -63,7 +63,7 @@ import Resumes from '@/pages/jobs/Resume'
import ContactUsList from '@/pages/contactUs/List' import ContactUsList from '@/pages/contactUs/List'
import Dashboard from '@/pages/dashboard/Dashboard' import Dashboard from '@/pages/dashboard/Dashboard'
import SellerList from '@/pages/seller/List' import SellerList from '@/pages/seller/List'
import WholeSaleRequest from '@/pages/seller/WholeSaleRequest'
const MainRouter: FC = () => { const MainRouter: FC = () => {
const { hasSubMenu } = useSharedStore() const { hasSubMenu } = useSharedStore()
@@ -160,6 +160,7 @@ const MainRouter: FC = () => {
<Route path="/dashboard/orders-chart" element={<Dashboard />} /> <Route path="/dashboard/orders-chart" element={<Dashboard />} />
<Route path={Pages.sellers.list} element={<SellerList />} /> <Route path={Pages.sellers.list} element={<SellerList />} />
<Route path={Pages.sellers.wholesaleRequests} element={<WholeSaleRequest />} />
</Routes> </Routes>
</div> </div>
</div> </div>