document seller
This commit is contained in:
@@ -53,6 +53,7 @@ const SellerList: FC = () => {
|
|||||||
<Td text={'نوع کسبوکار'} />
|
<Td text={'نوع کسبوکار'} />
|
||||||
<Td text={'تاریخ ایجاد'} />
|
<Td text={'تاریخ ایجاد'} />
|
||||||
<Td text={'قرارداد'} />
|
<Td text={'قرارداد'} />
|
||||||
|
<Td text={'مدارک'} />
|
||||||
<Td text={'فعال/غیرفعال'} />
|
<Td text={'فعال/غیرفعال'} />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { type FC, useState } from 'react'
|
||||||
|
import DefaulModal from '../../../components/DefaulModal'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import StatusWithText from '../../../components/StatusWithText'
|
||||||
|
import { type SellerDocument } from '../types/Types'
|
||||||
|
import { useChangeStatusDocument } from '../hooks/useSellerData'
|
||||||
|
import { Eye, Import, CloseCircle } from 'iconsax-react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
close: () => void
|
||||||
|
documents: SellerDocument[]
|
||||||
|
sellerId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentsModal: FC<Props> = ({ open, close, documents, sellerId }) => {
|
||||||
|
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
||||||
|
const changeStatusMutation = useChangeStatusDocument()
|
||||||
|
|
||||||
|
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 isImage = (url: string) => {
|
||||||
|
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']
|
||||||
|
const lowerUrl = url.toLowerCase()
|
||||||
|
return imageExtensions.some(ext => lowerUrl.includes(ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = (url: string, fileName: string) => {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
link.target = '_blank'
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApprove = async (documentId: string) => {
|
||||||
|
try {
|
||||||
|
await changeStatusMutation.mutateAsync({
|
||||||
|
sellerId,
|
||||||
|
params: {
|
||||||
|
docsId: documentId,
|
||||||
|
status: 'Approved'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Refresh documents or update local state
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در تایید مدرک:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReject = async (documentId: string) => {
|
||||||
|
try {
|
||||||
|
await changeStatusMutation.mutateAsync({
|
||||||
|
sellerId,
|
||||||
|
params: {
|
||||||
|
docsId: documentId,
|
||||||
|
status: 'REJECTED'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Refresh documents or update local state
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در رد مدرک:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaulModal
|
||||||
|
open={open}
|
||||||
|
close={close}
|
||||||
|
isHeader
|
||||||
|
title_header="مدارک فروشنده"
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<div className="space-y-4 mt-5">
|
||||||
|
{documents.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-gray-500">
|
||||||
|
هیچ مدرکی یافت نشد
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
documents.map((document) => (
|
||||||
|
<div
|
||||||
|
key={document._id}
|
||||||
|
className="border border-gray-200 rounded-lg p-4 space-y-3"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium">{document.type}</span>
|
||||||
|
<StatusWithText
|
||||||
|
variant={getStatusVariant(document.status)}
|
||||||
|
text={getStatusText(document.status)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{isImage(document.docsUrl) && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedImage(document.docsUrl)}
|
||||||
|
className="flex items-center gap-2 px-3 py-1 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors"
|
||||||
|
>
|
||||||
|
<Eye color="#8C90A3" size={16} />
|
||||||
|
<span className="text-sm">مشاهده</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => handleDownload(document.docsUrl, `${document.type}_${document._id}`)}
|
||||||
|
className="flex items-center gap-2 px-3 py-1 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 transition-colors"
|
||||||
|
>
|
||||||
|
<Import color="#8C90A3" size={16} />
|
||||||
|
<span className="text-sm">دانلود</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isImage(document.docsUrl) && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<img
|
||||||
|
src={document.docsUrl}
|
||||||
|
alt={document.type}
|
||||||
|
className="w-full max-h-48 object-contain rounded-lg border"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.style.display = 'none'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{document.comment && (
|
||||||
|
<div className="text-sm text-gray-600 bg-gray-50 p-2 rounded">
|
||||||
|
{document.comment}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
|
ایجاد شده: {new Date(document.createdAt).toLocaleDateString('fa-IR')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{document.status === 'Pending' && (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => handleApprove(document._id)}
|
||||||
|
isLoading={changeStatusMutation.isPending}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
تایید
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => handleReject(document._id)}
|
||||||
|
isLoading={changeStatusMutation.isPending}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
رد
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Image Preview Modal */}
|
||||||
|
{selectedImage && (
|
||||||
|
<DefaulModal
|
||||||
|
open={!!selectedImage}
|
||||||
|
close={() => setSelectedImage(null)}
|
||||||
|
width={800}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedImage(null)}
|
||||||
|
className="absolute top-2 right-2 z-10 bg-black/50 text-white rounded-full p-2 hover:bg-black/70 transition-colors"
|
||||||
|
>
|
||||||
|
<CloseCircle size={24} />
|
||||||
|
</button>
|
||||||
|
<img
|
||||||
|
src={selectedImage}
|
||||||
|
alt="پیشنمایش مدرک"
|
||||||
|
className="w-full max-h-[70vh] object-contain rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
)}
|
||||||
|
</DefaulModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocumentsModal
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
import { type Seller } from '../types/Types'
|
import { type Seller } from '../types/Types'
|
||||||
import StatusWithText from '../../../components/StatusWithText'
|
import StatusWithText from '../../../components/StatusWithText'
|
||||||
import Td from '../../../components/Td'
|
import Td from '../../../components/Td'
|
||||||
import SellerToggle from './SellerToggle'
|
import SellerToggle from './SellerToggle'
|
||||||
|
import DocumentsModal from './DocumentsModal'
|
||||||
import { Calendar, Call, Card, User as UserIcon, Shop, Eye } from 'iconsax-react'
|
import { Calendar, Call, Card, User as UserIcon, Shop, Eye } from 'iconsax-react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -11,6 +12,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SellerTableRow: FC<Props> = ({ seller, onViewContract }) => {
|
const SellerTableRow: FC<Props> = ({ seller, onViewContract }) => {
|
||||||
|
const [documentsModalOpen, setDocumentsModalOpen] = useState(false)
|
||||||
|
|
||||||
const getStatusVariant = (status: string) => {
|
const getStatusVariant = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Approved': return 'success';
|
case 'Approved': return 'success';
|
||||||
@@ -33,6 +36,13 @@ const SellerTableRow: FC<Props> = ({ seller, onViewContract }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<DocumentsModal
|
||||||
|
open={documentsModalOpen}
|
||||||
|
close={() => setDocumentsModalOpen(false)}
|
||||||
|
documents={seller.documents || []}
|
||||||
|
sellerId={seller._id}
|
||||||
|
/>
|
||||||
<tr key={seller._id} className='tr'>
|
<tr key={seller._id} className='tr'>
|
||||||
<Td text="">
|
<Td text="">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -84,6 +94,15 @@ const SellerTableRow: FC<Props> = ({ seller, onViewContract }) => {
|
|||||||
<span className="text-gray-400 text-sm">-</span>
|
<span className="text-gray-400 text-sm">-</span>
|
||||||
)}
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<button
|
||||||
|
onClick={() => setDocumentsModalOpen(true)}
|
||||||
|
className="flex items-center gap-1 hover:bg-gray-50 px-2 py-1 rounded transition-colors"
|
||||||
|
>
|
||||||
|
<Card size={16} color="#8C90A3" />
|
||||||
|
<span>مدارک ({seller.documents?.length || 0})</span>
|
||||||
|
</button>
|
||||||
|
</Td>
|
||||||
<Td text="">
|
<Td text="">
|
||||||
<SellerToggle
|
<SellerToggle
|
||||||
sellerId={seller._id}
|
sellerId={seller._id}
|
||||||
@@ -91,6 +110,7 @@ const SellerTableRow: FC<Props> = ({ seller, onViewContract }) => {
|
|||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as api from "../service/SellerService";
|
import * as api from "../service/SellerService";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import type {
|
import type {
|
||||||
|
ChangeStatusDocumentType,
|
||||||
CreateCategoryLearning,
|
CreateCategoryLearning,
|
||||||
CreateLearningType,
|
CreateLearningType,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
@@ -146,3 +147,19 @@ export const useUpdateContract = () => {
|
|||||||
mutationFn: api.updateContract,
|
mutationFn: api.updateContract,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useChangeStatusDocument = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
sellerId,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
sellerId: string;
|
||||||
|
params: ChangeStatusDocumentType;
|
||||||
|
}) => api.changeStatusDocument(sellerId, params),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["sellers"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
GetLearningResponse,
|
GetLearningResponse,
|
||||||
CreateLearningType,
|
CreateLearningType,
|
||||||
UpdateContractType,
|
UpdateContractType,
|
||||||
|
ChangeStatusDocumentType,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getSellers = async (
|
export const getSellers = async (
|
||||||
@@ -158,3 +159,14 @@ export const updateContract = async (params: UpdateContractType) => {
|
|||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const changeStatusDocument = async (
|
||||||
|
sellerId: string,
|
||||||
|
params: ChangeStatusDocumentType
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.patch(
|
||||||
|
`/admin/sellers/document/${sellerId}`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -380,3 +380,8 @@ export interface UpdateContractType {
|
|||||||
id: string;
|
id: string;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChangeStatusDocumentType {
|
||||||
|
docsId: string;
|
||||||
|
status: "Approved" | "REJECTED";
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user