diff --git a/src/config/Pages.ts b/src/config/Pages.ts index fedc06c..3e91500 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -140,4 +140,15 @@ export const Pages = { update: "/products/warranty/update/", }, }, + orders: { + native: "/orders/native", + sellers: "/orders/seller", + admin: "/orders/native", + completed: "/orders/native?status=Completed", + cancelled: "/orders/native?status=Cancelled", + pending: "/orders/native?status=Pending", + detail: "/orders/native/", + cancellationCategories: "/orders/cancellation-categories", + returnCategories: "/orders/return-categories", + }, }; diff --git a/src/pages/orders/CancellationCategories.tsx b/src/pages/orders/CancellationCategories.tsx new file mode 100644 index 0000000..0e55b8c --- /dev/null +++ b/src/pages/orders/CancellationCategories.tsx @@ -0,0 +1,116 @@ +import { type FC, useState } from 'react' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import TrashWithConfrim from '../../components/TrashWithConfrim' +import { Edit } from 'iconsax-react' +import { Link } from 'react-router-dom' +import Button from '@/components/Button' +import { useNavigate } from 'react-router-dom' +import { type CancellationCategoryType, type PagerType } from './types/Types' + +const CancellationCategories: FC = () => { + const [currentPage, setCurrentPage] = useState(1); + // TODO: Add category data fetching hook + const isLoading = false; + const error = null; + const refetch = () => { }; + // TODO: Add delete mutation + const deleteCategoryMutation = { isPending: false, mutateAsync: async (_id: string) => { } }; + + const navigate = useNavigate(); + + // Mock data for now + const categories: CancellationCategoryType[] = []; + const pager: PagerType = { page: currentPage, limit: 10, totalPages: 1, totalItems: 0, prevPage: null, nextPage: null }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const handleDeleteCategory = async (categoryId: string) => { + await deleteCategoryMutation.mutateAsync(categoryId); + refetch() + }; + + return ( +
+
+
+
+ + + + + + + {categories.length === 0 ? ( + + + + ) : ( + categories.map((category: any) => ( + + + + + )) + )} + +
+ + + +
+ هیچ دسته‌بندی کنسلی یافت نشد +
+ + +
+ + {category.status} + +
+
+
+ + + + + handleDeleteCategory(category.id)} + isLoading={deleteCategoryMutation.isPending} + /> +
+
+
+ + { + setCurrentPage(page); + }} + /> +
+ ) +} + +export default CancellationCategories diff --git a/src/pages/orders/Native.tsx b/src/pages/orders/Native.tsx new file mode 100644 index 0000000..c0de438 --- /dev/null +++ b/src/pages/orders/Native.tsx @@ -0,0 +1,141 @@ +import { type FC, useState } from 'react' +import { useSearchParams } from 'react-router-dom' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import { Eye } from 'iconsax-react' +import { Link } from 'react-router-dom' +import { type NativeOrderType } from '@/pages/orders/types/Types' +import { useGetNativeOrders } from '@/pages/orders/hooks/useOrderData' +import { Pages } from '@/config/Pages' + +const STATUS_CONFIG = { + Cancelled: { label: 'کنسل شده', color: 'bg-red-100 text-red-800' }, + wait_payment: { label: 'در انتظار پرداخت', color: 'bg-orange-100 text-orange-800' }, + process_by_sellers: { label: 'در حال پردازش توسط فروشنده', color: 'bg-blue-100 text-blue-800' }, + cancelled_system: { label: 'کنسل شده توسط سیستم', color: 'bg-red-100 text-red-800' }, + Delivered: { label: 'تحویل داده شده', color: 'bg-green-100 text-green-800' }, +} as const + +const getStatusLabel = (status: string): string => STATUS_CONFIG[status as keyof typeof STATUS_CONFIG]?.label || status + +const getStatusColor = (status: string): string => { + const statusConfig = STATUS_CONFIG[status as keyof typeof STATUS_CONFIG] + return statusConfig && 'color' in statusConfig ? statusConfig.color : 'bg-gray-100 text-gray-800' +} + +const Native: FC = () => { + const [searchParams] = useSearchParams() + const status = searchParams.get('status') || '' + const [currentPage, setCurrentPage] = useState(1) + + const { data, isLoading, error } = useGetNativeOrders(currentPage, status) + + const orders = data?.results?.orders || [] + const pager = data?.results?.pager ? { + ...data.results.pager, + prevPage: data.results.pager.prevPage ? true : null, + nextPage: data.results.pager.nextPage ? 'next' : null + } : { + page: 1, + limit: 10, + totalPages: 1, + totalItems: 0, + prevPage: null, + nextPage: null + } + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const getTableHeaders = () => { + const headers = [ + 'شماره سفارش', + 'مشتری', + 'مبلغ', + 'وضعیت' + ] + + headers.push('تاریخ') + headers.push('عملیات') + + return headers.map(header => ) + } + + const getTableRow = (order: NativeOrderType) => { + const statusColor = getStatusColor(order.orderStatus) + + return ( + + + + + + + {getStatusLabel(order.orderStatus)} + + + + +
+ + + +
+ + + ) + } + + const columnCount = 6 + + return ( +
+

{getStatusLabel(status)}

+ +
+ + + + {getTableHeaders()} + + + + {orders.length === 0 ? ( + + + + ) : ( + orders.map(order => getTableRow(order)) + )} + +
+ هیچ سفارشی یافت نشد +
+
+ + {orders.length > 0 && ( + + )} +
+ ) +} + +export default Native diff --git a/src/pages/orders/ReturnCategories.tsx b/src/pages/orders/ReturnCategories.tsx new file mode 100644 index 0000000..54ad9e8 --- /dev/null +++ b/src/pages/orders/ReturnCategories.tsx @@ -0,0 +1,116 @@ +import { type FC, useState } from 'react' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import TrashWithConfrim from '../../components/TrashWithConfrim' +import { Edit } from 'iconsax-react' +import { Link } from 'react-router-dom' +import Button from '@/components/Button' +import { useNavigate } from 'react-router-dom' +import { type ReturnCategoryType, type PagerType } from './types/Types' + +const ReturnCategories: FC = () => { + const [currentPage, setCurrentPage] = useState(1); + // TODO: Add category data fetching hook + const isLoading = false; + const error = null; + const refetch = () => { }; + // TODO: Add delete mutation + const deleteCategoryMutation = { isPending: false, mutateAsync: async (_id: string) => { } }; + + const navigate = useNavigate(); + + // Mock data for now + const categories: ReturnCategoryType[] = []; + const pager: PagerType = { page: currentPage, limit: 10, totalPages: 1, totalItems: 0, prevPage: null, nextPage: null }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const handleDeleteCategory = async (categoryId: string) => { + await deleteCategoryMutation.mutateAsync(categoryId); + refetch() + }; + + return ( +
+
+
+
+ + + + + + + {categories.length === 0 ? ( + + + + ) : ( + categories.map((category: any) => ( + + + + + )) + )} + +
+ + + +
+ هیچ دسته‌بندی مرجوعی یافت نشد +
+ + +
+ + {category.status} + +
+
+
+ + + + + handleDeleteCategory(category.id)} + isLoading={deleteCategoryMutation.isPending} + /> +
+
+
+ + { + setCurrentPage(page); + }} + /> +
+ ) +} + +export default ReturnCategories diff --git a/src/pages/orders/Seller.tsx b/src/pages/orders/Seller.tsx new file mode 100644 index 0000000..a569430 --- /dev/null +++ b/src/pages/orders/Seller.tsx @@ -0,0 +1,141 @@ +import { type FC, useState } from 'react' +import { useSearchParams } from 'react-router-dom' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import { Eye } from 'iconsax-react' +import { Link } from 'react-router-dom' +import { type NativeOrderType } from '@/pages/orders/types/Types' +import { useGetSellerOrders } from '@/pages/orders/hooks/useOrderData' +import { Pages } from '@/config/Pages' + +const STATUS_CONFIG = { + Cancelled: { label: 'کنسل شده', color: 'bg-red-100 text-red-800' }, + wait_payment: { label: 'در انتظار پرداخت', color: 'bg-orange-100 text-orange-800' }, + process_by_sellers: { label: 'در حال پردازش توسط فروشنده', color: 'bg-blue-100 text-blue-800' }, + cancelled_system: { label: 'کنسل شده توسط سیستم', color: 'bg-red-100 text-red-800' }, + Delivered: { label: 'تحویل داده شده', color: 'bg-green-100 text-green-800' }, +} as const + +const getStatusLabel = (status: string): string => STATUS_CONFIG[status as keyof typeof STATUS_CONFIG]?.label || status + +const getStatusColor = (status: string): string => { + const statusConfig = STATUS_CONFIG[status as keyof typeof STATUS_CONFIG] + return statusConfig && 'color' in statusConfig ? statusConfig.color : 'bg-gray-100 text-gray-800' +} + +const OrdersSeller: FC = () => { + const [searchParams] = useSearchParams() + const status = searchParams.get('status') || '' + const [currentPage, setCurrentPage] = useState(1) + + const { data, isLoading, error } = useGetSellerOrders(currentPage, status) + + const orders = data?.results?.orders || [] + const pager = data?.results?.pager ? { + ...data.results.pager, + prevPage: data.results.pager.prevPage ? true : null, + nextPage: data.results.pager.nextPage ? 'next' : null + } : { + page: 1, + limit: 10, + totalPages: 1, + totalItems: 0, + prevPage: null, + nextPage: null + } + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const getTableHeaders = () => { + const headers = [ + 'شماره سفارش', + 'مشتری', + 'مبلغ', + 'وضعیت' + ] + + headers.push('تاریخ') + headers.push('عملیات') + + return headers.map(header => ) + } + + const getTableRow = (order: NativeOrderType) => { + const statusColor = getStatusColor(order.orderStatus) + + return ( + + + + + + + {getStatusLabel(order.orderStatus)} + + + + +
+ + + +
+ + + ) + } + + const columnCount = 6 + + return ( +
+

{getStatusLabel(status)}

+ +
+ + + + {getTableHeaders()} + + + + {orders.length === 0 ? ( + + + + ) : ( + orders.map(order => getTableRow(order)) + )} + +
+ هیچ سفارشی یافت نشد +
+
+ + {orders.length > 0 && ( + + )} +
+ ) +} + +export default OrdersSeller \ No newline at end of file diff --git a/src/pages/orders/enum/Enum.ts b/src/pages/orders/enum/Enum.ts new file mode 100644 index 0000000..e647cd1 --- /dev/null +++ b/src/pages/orders/enum/Enum.ts @@ -0,0 +1,22 @@ +export const enum PaymentStatus { + Pending = "Pending", + Cancelled = "Cancelled", + Completed = "Completed", +} +export const enum OrdersStatus { + wait_payment = "wait_payment", + process_by_seller = "process_by_sellers", + cancelled_system = "cancelled_system", + Cancelled = "cancelled", + Delivered = "Delivered", +} + +export const enum OrderItemsStatus { + Processing = "Processing", + Shipped = "Shipped", + Delivered = "Delivered", + cancelled_shop = "cancelled_shop", + cancelled_system = "cancelled_system", + cancelled_user = "cancelled_user", + Returned = "Returned", +} diff --git a/src/pages/orders/hooks/useOrderData.ts b/src/pages/orders/hooks/useOrderData.ts new file mode 100644 index 0000000..4c0d406 --- /dev/null +++ b/src/pages/orders/hooks/useOrderData.ts @@ -0,0 +1,24 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/OrderService"; + +export const useGetNativeOrders = ( + page: number = 1, + status: string = "all" +) => { + const { data, isLoading, error } = useQuery({ + queryKey: ["orders", page, status], + queryFn: () => api.getNativeOrders(page, status), + }); + return { data, isLoading, error }; +}; + +export const useGetSellerOrders = ( + page: number = 1, + status: string = "all" +) => { + const { data, isLoading, error } = useQuery({ + queryKey: ["orders", page, status], + queryFn: () => api.getSellerOrders(page, status), + }); + return { data, isLoading, error }; +}; diff --git a/src/pages/orders/service/OrderService.ts b/src/pages/orders/service/OrderService.ts new file mode 100644 index 0000000..676e7a5 --- /dev/null +++ b/src/pages/orders/service/OrderService.ts @@ -0,0 +1,22 @@ +import axios from "../../../config/axios"; +import type { NativeOrdersResponseType } from "../types/Types"; + +export const getNativeOrders = async ( + page: number = 1, + status: string +): Promise => { + const { data } = await axios.get("/admin/orders/native", { + params: { page, status }, + }); + return data; +}; + +export const getSellerOrders = async ( + page: number = 1, + status: string +): Promise => { + const { data } = await axios.get("/admin/orders/seller", { + params: { page, status }, + }); + return data; +}; diff --git a/src/pages/orders/types/Types.ts b/src/pages/orders/types/Types.ts new file mode 100644 index 0000000..edefea4 --- /dev/null +++ b/src/pages/orders/types/Types.ts @@ -0,0 +1,245 @@ +// تایپ‌های مربوط به سفارشات + +export type OrderType = { + _id: string; + orderNumber: string; + customer: { + name: string; + }; + seller?: { + name: string; + }; + amount: number; + status: "Completed" | "Cancelled" | "Pending" | string; + date: string; + cancellationReason?: string; + cancelledDate?: string; +}; + +export type PaymentType = { + _id: string; + transaction_id: string | null; + payment_method: PaymentMethodType; + priceDetails: PriceDetailsType; + totalPrice: number; + paymentStatus: string; + createdAt: string; +}; + +export type PaymentMethodType = { + _id: string; + title_fa: string; + title_en: string; + description: string; + provider: string; + isActive: boolean; + type: string; +}; + +export type PriceDetailsType = { + shipping_cost: number; + process_cost: number; + total_retail_price: number; + total_payable_price: number; + total_discount: number; + coupon_discount: number; +}; + +export type OrderItemsType = { + _id: string; + order_id: number; + shop: ShopType; + shipmentItems: ShipmentItemType[]; + shipper: ShipperType; + trackCode: string | null; + itemsCount: number; + totalPaymentPrice: number; + totalRetailPrice: number; + totalSellingPrice: number; + totalShipmentCost: number; + postingDate: string | null; + postingReceipt: string | null; + status: string; +}; + +export type ShipmentItemType = { + _id: string; + product: ProductType; + variant: VariantType; + quantity: number; + cancelled_quantity: number; + returned_quantity: number; + selling_price: number; + retail_price: number; + discount_percent: number; + shipmentCost: number; +}; + +export type ProductType = { + _id: number; + title_fa: string; + title_en: string; + seoTitle: string | null; + seoDescription: string | null; + source: string; + description: string; + metaDescription: string; + tags: string[]; + advantages: string[]; + disAdvantages: string[]; + imagesUrl: ImagesUrlType; + isFake: string; + voice: string | null; + specifications: SpecificationType[]; + brand: BrandType; + category: CategoryType; + variants: string[]; +}; + +export type ImagesUrlType = { + cover: string; + list: string[]; +}; + +export type SpecificationType = { + title: string; + values: string[]; +}; + +export type BrandType = { + _id: string; +}; + +export type CategoryType = { + _id: string; +}; + +export type VariantType = { + _id: string; + market_status: string; + price: PriceType; + stock: number; + postingTime: number; + isFreeShip: boolean; + isWholeSale: boolean; + shop: ShopType; + saleFormat: SaleFormatType; + warranty: number; + size?: number; + meterage?: number; +}; + +export type PriceType = { + order_limit: number; + retailPrice: number; + selling_price: number; + is_specialSale: boolean; + discount_percent: number; + specialSale_order_limit: number | null; + specialSale_quantity: number | null; + specialSale_endDate: string | null; +}; + +export type SaleFormatType = { + wholeSale: unknown[]; +}; + +export type ShopType = { + _id: string; +}; + +export type ShipperType = { + _id: number; + name: string; + description: string; + deliveryTime: number; + deliveryType: string; +}; + +export type ShipmentAddressType = { + address: string; + city: string; + province: string; + plaque: string; + postalCode: string; + phone: string; +}; + +export type CancellationCategoryType = { + _id: string; + title: string; + description: string; + status: string; + deleted: boolean; +}; + +export type ReturnCategoryType = { + _id: string; + title: string; + description: string; + status: string; + deleted: boolean; +}; + +// تایپ‌های صفحه‌بندی +export type PagerType = { + page: number; + limit: number; + totalItems: number; + totalPages: number; + prevPage: boolean; + nextPage: boolean; +}; + +export type OrdersResponseType = { + status: number; + success: boolean; + results: { + orders: OrderType[]; + pager: PagerType; + }; +}; + +export type CategoriesResponseType = { + status: number; + success: boolean; + results: { + categories: CancellationCategoryType[] | ReturnCategoryType[]; + pager: PagerType; + }; +}; + +export type CreateCategoryType = { + title: string; + description: string; +}; + +// تایپ‌های مربوط به سفارشات بومی +export type PriceRangeType = { + minPrice: number; + maxPrice: number; +}; + +export type UserType = { + fullName: string; +}; + +export type NativeOrderType = { + _id: number; + user: UserType; + payment: PaymentType; + orderItems: OrderItemsType; + shipmentAddress: ShipmentAddressType; + orderStatus: string; + createdAt: string; +}; + +export type NativeOrdersResponseType = { + status: number; + success: boolean; + results: { + pager: PagerType; + orders: NativeOrderType[]; + priceRange: PriceRangeType; + }; +}; diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 27e5075..e2e84cd 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -19,6 +19,10 @@ import BrandList from '@/pages/brand/List' import BrandCreate from '@/pages/brand/Create' import WarrantyList from '@/pages/warranty/List' import CreateWarranty from '@/pages/warranty/Create' +import Native from '@/pages/orders/Native' +import OrdersSeller from '@/pages/orders/Seller' +import CancellationCategories from '@/pages/orders/CancellationCategories' +import ReturnCategories from '@/pages/orders/ReturnCategories' const MainRouter: FC = () => { @@ -50,6 +54,13 @@ const MainRouter: FC = () => { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index 9cfbbbf..3adc196 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -37,7 +37,7 @@ const SideBar: FC = () => { const location = useLocation() const isActive = (name: string) => { - return location.pathname.includes(name) + return window.location.href.includes(name) } useEffect(() => { @@ -389,7 +389,7 @@ const ProductsSubMenu: FC = () => { const OrdersSubMenu: FC = () => { const isActive = (name: string) => { - return location.pathname.includes(name) + return window.location.href.includes(name) } return ( @@ -408,28 +408,33 @@ const OrdersSubMenu: FC = () => {
+