feat: add shipment management modal for admin
- Add ShipmentManagementModal component to manage shop shipment methods - Add button to Shop settings page to open shipment management modal - Update useShipmentData hooks to invalidate queries after add/remove operations - Modal shows active and available shipment methods for admin to manage
This commit is contained in:
@@ -8,10 +8,11 @@ import Button from '../../components/Button'
|
||||
import Input from '../../components/Input'
|
||||
import Textarea from '../../components/Textarea'
|
||||
import UploadBox from '../../components/UploadBox'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { TickCircle, Truck } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/helpers/utils'
|
||||
import { useSingleUpload } from '../category'
|
||||
import ShipmentManagementModal from './components/ShipmentManagementModal'
|
||||
|
||||
// Validation schema برای UpdateShopType
|
||||
const validationSchema = Yup.object().shape({
|
||||
@@ -26,6 +27,7 @@ const Shop: FC = () => {
|
||||
const updateShopMutation = useUpdateShop()
|
||||
const uploadSingle = useSingleUpload()
|
||||
const [logoFile, setLogoFile] = useState<File[]>([])
|
||||
const [isShipmentModalOpen, setIsShipmentModalOpen] = useState(false)
|
||||
|
||||
const formik = useFormik<UpdateShopType>({
|
||||
initialValues: {
|
||||
@@ -91,7 +93,17 @@ const Shop: FC = () => {
|
||||
<div className='flex items-center gap-3'>
|
||||
<h1>ویرایش اطلاعات شاپ</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='px-6'
|
||||
onClick={() => setIsShipmentModalOpen(true)}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Truck size={16} />
|
||||
<div>مدیریت روش ارسال</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
className='px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
@@ -160,6 +172,11 @@ const Shop: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ShipmentManagementModal
|
||||
open={isShipmentModalOpen}
|
||||
close={() => setIsShipmentModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import PageTitle from '../../components/PageTitle'
|
||||
import { useGetShipment, useDeleteShipment } from './hooks/useShipmentData'
|
||||
import { useGetShipment, useDeleteShipment, useGetAdminShop } from './hooks/useShipmentData'
|
||||
import { type Shipper } from './types/Types'
|
||||
import PageLoading from '@/components/PageLoading';
|
||||
import Error from '@/components/Error';
|
||||
@@ -14,7 +14,9 @@ import { Pages } from '@/config/Pages';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const ShippingList: FC = () => {
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
useGetAdminShop()
|
||||
const navigate = useNavigate();
|
||||
const { data, isLoading, error, refetch } = useGetShipment(currentPage);
|
||||
const deleteShipmentMutation = useDeleteShipment();
|
||||
|
||||
@@ -46,3 +46,30 @@ export const useUpdateShipment = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveShipmentFromAdmin = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.removeShipmentFromAdmin(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["admin-shop"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddShipmentToAdmin = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.addShipmentToAdmin(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["admin-shop"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAdminShop = () => {
|
||||
return useQuery({
|
||||
queryKey: ["admin-shop"],
|
||||
queryFn: () => api.getAdminShop(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
CreateShipper,
|
||||
Shipper,
|
||||
ShipmentByIdResponse,
|
||||
GetAdminShopResponse,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getShipment = async (
|
||||
@@ -37,3 +38,22 @@ export const updateShipment = async (
|
||||
const { data } = await axios.patch<Shipper>(`/admin/shipment/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const removeShipmentFromAdmin = async (id: string): Promise<void> => {
|
||||
const { data } = await axios.post(`/admin/shipment/shipment/deactivate`, {
|
||||
shipmentId: id,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const addShipmentToAdmin = async (id: string): Promise<void> => {
|
||||
const { data } = await axios.post(`/admin/shipment/shipment/activate`, {
|
||||
shipmentId: id,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getAdminShop = async (): Promise<GetAdminShopResponse> => {
|
||||
const { data } = await axios.get<GetAdminShopResponse>(`/admin/shop`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -54,3 +54,40 @@ export interface ShipmentByIdResponse {
|
||||
shipper: Shipper;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ShipmentMethod {
|
||||
_id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
costs: Cost[];
|
||||
deliveryType: string;
|
||||
deliveryTime: number;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ShopData {
|
||||
_id: string;
|
||||
shopName: string;
|
||||
shopDescription: string;
|
||||
telephoneNumber: string;
|
||||
shopPostalCode: string;
|
||||
address: string | null;
|
||||
logo: string;
|
||||
shipmentMethod: ShipmentMethod[];
|
||||
isChatActive: boolean;
|
||||
owner: string;
|
||||
ownerRef: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
shopCode: number;
|
||||
}
|
||||
|
||||
export interface GetAdminShopResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
data: ShopData;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user