admin shipment

This commit is contained in:
hamid zarghami
2025-10-27 11:53:20 +03:30
parent cf3e2b7d4b
commit 19a68f7188
6 changed files with 171 additions and 194 deletions
+138
View File
@@ -0,0 +1,138 @@
import { useState } from 'react';
import DefaulModal from '@/components/DefaulModal';
import Button from '@/components/Button';
import Select from '@/components/Select';
import { type Shipper, type ShipmentMethod } from '@/pages/shipment/types/Types';
import { useGetShipment, useGetAdminShop, useAddShipmentToAdmin, useRemoveShipmentFromAdmin } from '@/pages/shipment/hooks/useShipmentData';
import { toast } from 'react-toastify';
import { extractErrorMessage } from '@/helpers/utils';
interface ShipmentManagementModalProps {
isManagementModalOpen: boolean;
setIsManagementModalOpen: (open: boolean) => void;
}
export const ShipmentManagementModal = ({
isManagementModalOpen,
setIsManagementModalOpen,
}: ShipmentManagementModalProps) => {
const [selectedShipment, setSelectedShipment] = useState('');
const { data: shipmentData } = useGetShipment(1);
const { data: adminShopData, refetch: refetchAdminShop } = useGetAdminShop();
const addShipmentToAdminMutation = useAddShipmentToAdmin();
const removeShipmentFromAdminMutation = useRemoveShipmentFromAdmin();
const shipments = shipmentData?.results?.shipper || [];
const adminShipments: ShipmentMethod[] = adminShopData?.results?.data?.shipmentMethod || [];
// لیست روش‌های ارسال که هنوز به ادمین اضافه نشده‌اند
const availableShipments = shipments.filter((shipment: Shipper) =>
!adminShipments.some((adminShipment: ShipmentMethod) => adminShipment._id === shipment._id)
);
const handleAddShipmentToAdmin = async () => {
if (!selectedShipment) return;
await addShipmentToAdminMutation.mutateAsync(Number(selectedShipment), {
onError: (error) => {
toast.error(extractErrorMessage(error));
},
onSuccess: () => {
refetchAdminShop();
toast.success('روش ارسال با موفقیت اضافه شد');
}
});
};
const handleRemoveShipmentFromAdmin = async (shipmentId: string) => {
await removeShipmentFromAdminMutation.mutateAsync(Number(shipmentId), {
onError: (error) => {
toast.error(extractErrorMessage(error));
},
onSuccess: () => {
refetchAdminShop();
toast.success('روش ارسال با موفقیت حذف شد');
}
});
};
const getDeliveryTypeText = (type: string) => {
switch (type) {
case 'Standard':
return 'معمولی';
case 'SameDay':
return 'همان روز';
case 'Express':
return 'اکسپرس';
default:
return type;
}
};
return (
<DefaulModal
open={isManagementModalOpen}
close={() => setIsManagementModalOpen(false)}
title_header="مدیریت روش‌های ارسال ادمین"
isHeader={true}
width={800}
>
<div className="space-y-6 w-[400px] mt-5">
{/* بخش اضافه کردن روش ارسال جدید */}
<div className="border-b pb-4">
<h3 className="mb-3">اضافه کردن روش ارسال جدید</h3>
<div className="flex gap-3">
<Select
className="flex-1"
placeholder="انتخاب روش ارسال"
value={selectedShipment}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setSelectedShipment(e.target.value)}
items={availableShipments.map((shipment: Shipper) => ({
value: shipment._id.toString(),
label: shipment.name
}))}
/>
<Button
label="اضافه کردن"
onClick={handleAddShipmentToAdmin}
isLoading={addShipmentToAdminMutation.isPending}
disabled={!selectedShipment}
className="w-fit whitespace-nowrap"
/>
</div>
</div>
{/* بخش روش‌های ارسال فعال ادمین */}
<div>
<h3 className="mb-3">روشهای ارسال فعال ادمین</h3>
{adminShipments.length === 0 ? (
<p className="text-gray-500 text-center py-8">هیچ روش ارسالی فعالی وجود ندارد</p>
) : (
<div className="space-y-3">
{adminShipments.map((shipment: ShipmentMethod) => (
<div key={shipment._id} className="flex items-center justify-between p-4 bg-gray-50 rounded-xl">
<div>
<h4 className="font-medium">{shipment.name}</h4>
<p className="text-sm text-gray-600">{shipment.description}</p>
<p className="text-xs text-gray-500 mt-1">
نوع ارسال: {getDeliveryTypeText(shipment.deliveryType)} |
زمان ارسال: {shipment.deliveryTime} روز
</p>
</div>
<Button
variant="danger"
size="sm"
label="حذف"
onClick={() => handleRemoveShipmentFromAdmin(shipment._id.toString())}
isLoading={removeShipmentFromAdminMutation.isPending}
className="w-fit"
/>
</div>
))}
</div>
)}
</div>
</div>
</DefaulModal>
);
};
+2 -19
View File
@@ -8,11 +8,10 @@ import Button from '../../components/Button'
import Input from '../../components/Input' import Input from '../../components/Input'
import Textarea from '../../components/Textarea' import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox' import UploadBox from '../../components/UploadBox'
import { TickCircle, Truck } from 'iconsax-react' import { TickCircle } from 'iconsax-react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/helpers/utils' import { extractErrorMessage } from '@/helpers/utils'
import { useSingleUpload } from '../category' import { useSingleUpload } from '../category'
import ShipmentManagementModal from './components/ShipmentManagementModal'
// Validation schema برای UpdateShopType // Validation schema برای UpdateShopType
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
@@ -27,7 +26,6 @@ const Shop: FC = () => {
const updateShopMutation = useUpdateShop() const updateShopMutation = useUpdateShop()
const uploadSingle = useSingleUpload() const uploadSingle = useSingleUpload()
const [logoFile, setLogoFile] = useState<File[]>([]) const [logoFile, setLogoFile] = useState<File[]>([])
const [isShipmentModalOpen, setIsShipmentModalOpen] = useState(false)
const formik = useFormik<UpdateShopType>({ const formik = useFormik<UpdateShopType>({
initialValues: { initialValues: {
@@ -93,17 +91,7 @@ const Shop: FC = () => {
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<h1>ویرایش اطلاعات شاپ</h1> <h1>ویرایش اطلاعات شاپ</h1>
</div> </div>
<div className='flex gap-2'> <div>
<Button
variant='outline'
className='px-6'
onClick={() => setIsShipmentModalOpen(true)}
>
<div className='flex gap-2 items-center'>
<Truck size={16} />
<div>مدیریت روش ارسال</div>
</div>
</Button>
<Button <Button
className='px-6' className='px-6'
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
@@ -172,11 +160,6 @@ const Shop: FC = () => {
</div> </div>
</div> </div>
</div> </div>
<ShipmentManagementModal
open={isShipmentModalOpen}
close={() => setIsShipmentModalOpen(false)}
/>
</div> </div>
) )
} }
@@ -1,154 +0,0 @@
import { type FC, useState } from 'react'
import DefaulModal from '@/components/DefaulModal'
import Button from '@/components/Button'
import { useGetShipment } from '../../shipment/hooks/useShipmentData'
import { useGetAdminShop, useAddShipmentToAdmin, useRemoveShipmentFromAdmin } from '../../shipment/hooks/useShipmentData'
import PageLoading from '@/components/PageLoading'
import Error from '@/components/Error'
import { Add, Trash } from 'iconsax-react'
import { toast } from 'react-toastify'
interface Props {
open: boolean
close: () => void
}
const ShipmentManagementModal: FC<Props> = ({ open, close }) => {
const [currentPage] = useState(1)
const { data: shipmentsData, isLoading: shipmentsLoading, error: shipmentsError } = useGetShipment(currentPage)
const { data: adminShopData, isLoading: adminShopLoading, error: adminShopError, refetch: refetchAdminShop } = useGetAdminShop()
const addShipmentMutation = useAddShipmentToAdmin()
const removeShipmentMutation = useRemoveShipmentFromAdmin()
if (shipmentsLoading || adminShopLoading) {
return (
<DefaulModal open={open} close={close} isHeader title_header="مدیریت روش‌های ارسال">
<div className="flex justify-center items-center min-h-[200px]">
<PageLoading />
</div>
</DefaulModal>
)
}
if (shipmentsError || adminShopError) {
return (
<DefaulModal open={open} close={close} isHeader title_header="مدیریت روش‌های ارسال">
<div className="flex justify-center items-center min-h-[200px]">
<Error errorText="خطا در بارگذاری اطلاعات" />
</div>
</DefaulModal>
)
}
const allShipments = shipmentsData?.results?.shipper || []
const shopShipments = adminShopData?.results?.data?.shipmentMethod || []
const shopShipmentIds = shopShipments.map(s => s._id)
const availableShipments = allShipments.filter(shipment => !shopShipmentIds.includes(shipment._id))
const getDeliveryTypeText = (type: string) => {
switch (type) {
case 'Standard':
return 'معمولی'
case 'SameDay':
return 'همان روز'
case 'Express':
return 'اکسپرس'
default:
return type
}
}
const handleAddShipment = async (shipmentId: number) => {
try {
await addShipmentMutation.mutateAsync(shipmentId.toString())
toast.success('روش ارسال با موفقیت اضافه شد')
refetchAdminShop()
} catch {
toast.error('خطا در اضافه کردن روش ارسال')
}
}
const handleRemoveShipment = async (shipmentId: number) => {
try {
await removeShipmentMutation.mutateAsync(shipmentId.toString())
toast.success('روش ارسال با موفقیت حذف شد')
refetchAdminShop()
} catch {
toast.error('خطا در حذف روش ارسال')
}
}
return (
<DefaulModal open={open} close={close} isHeader title_header="مدیریت روش‌های ارسال" width={800}>
<div className="p-4">
{/* روش‌های ارسال فروشگاه */}
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3 text-green-600">روشهای ارسال فعال فروشگاه</h3>
{shopShipments.length === 0 ? (
<div className="text-center py-4 text-gray-500 bg-gray-50 rounded-lg">
هیچ روش ارسالی فعالی وجود ندارد
</div>
) : (
<div className="space-y-2 max-h-40 overflow-y-auto">
{shopShipments.map((shipment) => (
<div key={shipment._id} className="flex items-center justify-between bg-green-50 p-3 rounded-lg border">
<div className="flex-1">
<div className="font-medium">{shipment.name}</div>
<div className="text-sm text-gray-600">
{getDeliveryTypeText(shipment.deliveryType)} - {shipment.deliveryTime} روز
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleRemoveShipment(shipment._id)}
isLoading={removeShipmentMutation.isPending}
className="text-red-600 hover:text-red-700 border-red-300 hover:border-red-400"
>
<Trash size={16} />
</Button>
</div>
))}
</div>
)}
</div>
{/* روش‌های ارسال موجود برای اضافه کردن */}
<div>
<h3 className="text-lg font-semibold mb-3 text-blue-600">روشهای ارسال قابل اضافه کردن</h3>
{availableShipments.length === 0 ? (
<div className="text-center py-4 text-gray-500 bg-gray-50 rounded-lg">
همه روشهای ارسال اضافه شدهاند
</div>
) : (
<div className="space-y-2 max-h-40 overflow-y-auto">
{availableShipments.map((shipment) => (
<div key={shipment._id} className="flex items-center justify-between bg-blue-50 p-3 rounded-lg border">
<div className="flex-1">
<div className="font-medium">{shipment.name}</div>
<div className="text-sm text-gray-600">
{getDeliveryTypeText(shipment.deliveryType)} - {shipment.deliveryTime} روز
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleAddShipment(shipment._id)}
isLoading={addShipmentMutation.isPending}
className="text-green-600 hover:text-green-700 border-green-300 hover:border-green-400"
>
<Add size={16} />
</Button>
</div>
))}
</div>
)}
</div>
</div>
</DefaulModal>
)
}
export default ShipmentManagementModal
+25 -7
View File
@@ -1,6 +1,6 @@
import { type FC, useState } from 'react' import { type FC, useState } from 'react'
import PageTitle from '../../components/PageTitle' import PageTitle from '../../components/PageTitle'
import { useGetShipment, useDeleteShipment, useGetAdminShop } from './hooks/useShipmentData' import { useGetShipment, useDeleteShipment } from './hooks/useShipmentData'
import { type Shipper } from './types/Types' import { type Shipper } from './types/Types'
import PageLoading from '@/components/PageLoading'; import PageLoading from '@/components/PageLoading';
import Error from '@/components/Error'; import Error from '@/components/Error';
@@ -8,7 +8,8 @@ import Button from '@/components/Button';
import PaginationUi from '@/components/PaginationUi'; import PaginationUi from '@/components/PaginationUi';
import Td from '@/components/Td'; import Td from '@/components/Td';
import TrashWithConfrim from '@/components/TrashWithConfrim'; import TrashWithConfrim from '@/components/TrashWithConfrim';
import { Edit } from 'iconsax-react'; import { ShipmentManagementModal } from '@/components/ShipmentManagementModal';
import { Edit, Setting2 } from 'iconsax-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Pages } from '@/config/Pages'; import { Pages } from '@/config/Pages';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -16,11 +17,15 @@ import { useNavigate } from 'react-router-dom';
const ShippingList: FC = () => { const ShippingList: FC = () => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
useGetAdminShop() const [isManagementModalOpen, setIsManagementModalOpen] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const { data, isLoading, error, refetch } = useGetShipment(currentPage); const { data, isLoading, error, refetch } = useGetShipment(currentPage);
const deleteShipmentMutation = useDeleteShipment(); const deleteShipmentMutation = useDeleteShipment();
const shipments = data?.results?.shipper || [];
const pager = data?.results?.pager;
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex justify-center items-center min-h-[400px]"> <div className="flex justify-center items-center min-h-[400px]">
@@ -42,9 +47,6 @@ const ShippingList: FC = () => {
refetch(); refetch();
}; };
const shipments = data?.results?.shipper || [];
const pager = data?.results?.pager;
const getDeliveryTypeText = (type: string) => { const getDeliveryTypeText = (type: string) => {
switch (type) { switch (type) {
case 'Standard': case 'Standard':
@@ -61,7 +63,17 @@ const ShippingList: FC = () => {
return ( return (
<div> <div>
<PageTitle /> <PageTitle />
<div className='flex justify-end mt-5'> <div className='flex justify-end gap-3 mt-5'>
<Button
onClick={() => setIsManagementModalOpen(true)}
variant='outline'
className='w-fit'
>
<div className="flex items-center gap-2">
<Setting2 color='#6B7280' size={16} />
مدیریت روشهای ارسال ادمین
</div>
</Button>
<Button <Button
label='افزودن روش ارسال' label='افزودن روش ارسال'
onClick={() => navigate(`${Pages.shipment.create}`)} onClick={() => navigate(`${Pages.shipment.create}`)}
@@ -138,6 +150,12 @@ const ShippingList: FC = () => {
setCurrentPage(page); setCurrentPage(page);
}} }}
/> />
{/* مودال مدیریت روش‌های ارسال ادمین */}
<ShipmentManagementModal
isManagementModalOpen={isManagementModalOpen}
setIsManagementModalOpen={setIsManagementModalOpen}
/>
</div> </div>
) )
} }
+2 -10
View File
@@ -48,22 +48,14 @@ export const useUpdateShipment = () => {
}; };
export const useRemoveShipmentFromAdmin = () => { export const useRemoveShipmentFromAdmin = () => {
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: (id: string) => api.removeShipmentFromAdmin(id), mutationFn: api.removeShipmentFromAdmin,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["admin-shop"] });
},
}); });
}; };
export const useAddShipmentToAdmin = () => { export const useAddShipmentToAdmin = () => {
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: (id: string) => api.addShipmentToAdmin(id), mutationFn: api.addShipmentToAdmin,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["admin-shop"] });
},
}); });
}; };
+4 -4
View File
@@ -39,15 +39,15 @@ export const updateShipment = async (
return data; return data;
}; };
export const removeShipmentFromAdmin = async (id: string): Promise<void> => { export const removeShipmentFromAdmin = async (id: number): Promise<void> => {
const { data } = await axios.post(`/admin/shipment/shipment/deactivate`, { const { data } = await axios.patch(`/admin/shipment/deactivate`, {
shipmentId: id, shipmentId: id,
}); });
return data; return data;
}; };
export const addShipmentToAdmin = async (id: string): Promise<void> => { export const addShipmentToAdmin = async (id: number): Promise<void> => {
const { data } = await axios.post(`/admin/shipment/shipment/activate`, { const { data } = await axios.patch(`/admin/shipment/activate`, {
shipmentId: id, shipmentId: id,
}); });
return data; return data;