diff --git a/src/helpers/func.ts b/src/helpers/func.ts
index 8d890a8..d32a934 100644
--- a/src/helpers/func.ts
+++ b/src/helpers/func.ts
@@ -53,3 +53,11 @@ export const formatFaNumber = (value?: number | null): string => {
}
return value.toLocaleString("fa-IR");
};
+
+export const formatPrice = (price: number): string => {
+ return new Intl.NumberFormat("fa-IR", {
+ style: "currency",
+ currency: "IRR",
+ minimumFractionDigits: 0,
+ }).format(price);
+};
diff --git a/src/langs/fa.json b/src/langs/fa.json
index e508db9..746d037 100644
--- a/src/langs/fa.json
+++ b/src/langs/fa.json
@@ -840,5 +840,18 @@
"payment_methods_list": "لیست روش های پرداخت",
"payment_methods_add": "افزودن روش پرداخت",
"payment_methods_update": "ویرایش روش پرداخت"
+ },
+ "order_status": {
+ "pending": "در انتظار تایید",
+ "confirmed": "تایید شده",
+ "preparing": "در حال آماده سازی",
+ "rejectedByRestaurant": "رد شده توسط رستوران",
+ "cancelledByUser": "لغو شده توسط کاربر",
+ "cancelledBySystem": "لغو شده توسط سیستم",
+ "readyForCustomerPickup": "آماده برای دریافت مشتری",
+ "readyForDineIn": "آماده برای سرو در محل",
+ "readyForDeliveryCar": "آماده برای تحویل با ماشین",
+ "readyForDeliveryCourier": "آماده برای تحویل با پیک",
+ "delivered": "تحویل داده شده"
}
}
diff --git a/src/pages/orders/List.tsx b/src/pages/orders/List.tsx
index 551c473..3d26af7 100644
--- a/src/pages/orders/List.tsx
+++ b/src/pages/orders/List.tsx
@@ -1,231 +1,52 @@
-import { type FC, useState } from 'react'
-import Button from '@/components/Button'
+import { type FC } from 'react'
+import { useTranslation } from 'react-i18next'
import Table from '@/components/Table'
import Filters from '@/components/Filters'
-import { Add, Eye } from 'iconsax-react'
-import type { RowDataType, ActionType } from '@/components/types/TableTypes'
-import type { FilterValues } from '@/components/Filters'
-import { Link } from 'react-router-dom'
-import { Pages } from '@/config/Pages'
-
-interface OrderData extends RowDataType {
- id: number
- orderNumber: string
- customerName: string
- orderAmount: string
- orderTime: string
- deliveryType: string
- paymentType: string
- status: string
-}
+import { useGetOrders } from './hooks/useGetOrders'
+import { useOrderFilters } from './hooks/useOrderFilters'
+import { getOrderTableColumns } from './components/OrderTableColumns'
+import { useOrderFiltersFields } from './components/OrderFiltersFields'
+import type { Order } from './types/Types'
+import type { RowDataType } from '@/components/types/TableTypes'
const OrdersList: FC = () => {
- const [currentPage, setCurrentPage] = useState(1)
- const totalPages = 2
+ const { t } = useTranslation('global')
+ const {
+ filters,
+ currentPage,
+ apiParams,
+ handleFiltersChange,
+ handlePageChange,
+ limit,
+ } = useOrderFilters()
- const handlePageChange = (page: number) => {
- setCurrentPage(page)
- }
+ const { data: ordersData, isLoading } = useGetOrders(apiParams)
- const handleFiltersChange = (newFilters: FilterValues) => {
- console.log('فیلترها:', newFilters)
- }
+ const orders = ordersData?.data || []
+ const columns = getOrderTableColumns({ t })
+ const filterFields = useOrderFiltersFields()
- const handleRowActions = (item: OrderData): ActionType[] => {
- return [
- {
- label: 'ویرایش',
- onClick: () => {
- console.log('ویرایش سفارش:', item.id)
- },
- },
- {
- label: 'حذف',
- onClick: () => {
- console.log('حذف سفارش:', item.id)
- },
- },
- ]
- }
-
- const getStatusColor = (status: string) => {
- switch (status) {
- case 'در حال آماده سازی':
- return 'text-orange-500'
- case 'تایید نشده':
- return 'text-red-500'
- case 'تحویل داده شده':
- return 'text-green-500'
- default:
- return ''
- }
- }
-
- const columns = [
- {
- key: 'orderNumber',
- title: 'شماره سفارش',
- },
- {
- key: 'customerName',
- title: 'نام مشتری',
- },
- {
- key: 'orderAmount',
- title: 'مبلغ سفارش',
- },
- {
- key: 'orderTime',
- title: 'زمان سفارش',
- },
- {
- key: 'deliveryType',
- title: 'نوع تحویل',
- },
- {
- key: 'paymentType',
- title: 'نوع پرداخت',
- },
- {
- key: 'status',
- title: 'وضعیت',
- render: (item: OrderData) => {
- return (
-
- {item.status}
-
- )
- },
- },
- {
- key: 'details',
- title: 'جزییات',
- render: (item: OrderData) => {
- return (
-
-
-
- )
- },
- },
- ]
-
- const data: OrderData[] = [
- {
- id: 1,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'تحویل با پیک',
- paymentType: 'پرداخت آنلاین',
- status: 'در حال آماده سازی',
- },
- {
- id: 2,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'تحویل با پیک',
- paymentType: 'پرداخت آنلاین',
- status: 'در انتظار تایید',
- },
- {
- id: 3,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'تحویل با پیک',
- paymentType: 'پرداخت آنلاین',
- status: 'تایید نشده',
- },
- {
- id: 4,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'تحویل با پیک',
- paymentType: 'پرداخت با کیف پول',
- status: 'تحویل داده شده',
- },
- {
- id: 5,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'سرو در رستوران',
- paymentType: 'پرداخت در محل',
- status: 'تحویل داده شده',
- },
- {
- id: 6,
- orderNumber: '12345',
- customerName: 'مهرداد مظفری',
- orderAmount: '۴۲۰۰۰۰ تومان',
- orderTime: '۱۴۰۳/۱۱/۲۵ ۱۳:۰۰:۰۰',
- deliveryType: 'تحویل در ماشین',
- paymentType: 'پرداخت آنلاین',
- status: 'تحویل داده شده',
- },
- ]
+ const totalPages = Math.ceil(orders.length / limit) || 1
return (
-
columns={columns}
- data={data}
- rowActions={handleRowActions}
+ data={orders as (Order & RowDataType)[]}
+ isloading={isLoading}
pagination={{
currentPage,
totalPages,
diff --git a/src/pages/orders/components/OrderFiltersFields.tsx b/src/pages/orders/components/OrderFiltersFields.tsx
new file mode 100644
index 0000000..cb4494c
--- /dev/null
+++ b/src/pages/orders/components/OrderFiltersFields.tsx
@@ -0,0 +1,39 @@
+import { useMemo } from 'react'
+import type { FieldType } from '@/components/Filters'
+
+export const useOrderFiltersFields = (): FieldType[] => {
+ return useMemo(() => [
+ {
+ type: 'input',
+ name: 'search',
+ placeholder: 'جستجو (شماره سفارش یا نام مشتری)',
+ },
+ {
+ type: 'select',
+ name: 'status',
+ placeholder: 'وضعیت',
+ defaultValue: '',
+ options: [
+ { label: 'همه', value: '' },
+ { label: 'در انتظار تایید', value: 'PENDING' },
+ { label: 'در حال آماده سازی', value: 'PREPARING' },
+ { label: 'آماده تحویل', value: 'READY' },
+ { label: 'تحویل داده شده', value: 'DELIVERED' },
+ { label: 'لغو شده', value: 'CANCELLED' },
+ { label: 'تایید نشده', value: 'REJECTED' },
+ ],
+ },
+ {
+ type: 'select',
+ name: 'deliveryType',
+ placeholder: 'نوع تحویل',
+ defaultValue: '',
+ options: [
+ { label: 'همه', value: '' },
+ { label: 'تحویل با پیک', value: 'DELIVERY' },
+ { label: 'سرو در رستوران', value: 'DINE_IN' },
+ { label: 'تحویل در ماشین', value: 'PICKUP' },
+ ],
+ },
+ ], [])
+}
diff --git a/src/pages/orders/components/OrderTableColumns.tsx b/src/pages/orders/components/OrderTableColumns.tsx
new file mode 100644
index 0000000..02ca3c1
--- /dev/null
+++ b/src/pages/orders/components/OrderTableColumns.tsx
@@ -0,0 +1,109 @@
+import { Eye } from 'iconsax-react'
+import type { ColumnType } from '@/components/types/TableTypes'
+import type { Order } from '../types/Types'
+import { formatPrice, formatOptionalDate } from '@/helpers/func'
+import Status from '@/components/Status'
+import { Link } from 'react-router-dom'
+import { Pages } from '@/config/Pages'
+import { formatFaNumber } from '@/helpers/func'
+import type { TFunction } from 'i18next'
+
+interface GetOrderTableColumnsParams {
+ onDelete?: (id: string) => void
+ isDeleting?: boolean
+ t: TFunction
+}
+
+const getStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'pending' => {
+ const variantMap: Record = {
+ 'pending': 'pending',
+ 'confirmed': 'info',
+ 'preparing': 'warning',
+ 'rejectedByRestaurant': 'error',
+ 'cancelledByUser': 'error',
+ 'cancelledBySystem': 'error',
+ 'readyForCustomerPickup': 'info',
+ 'readyForDineIn': 'info',
+ 'readyForDeliveryCar': 'info',
+ 'readyForDeliveryCourier': 'info',
+ 'delivered': 'success',
+ }
+ return variantMap[status] || 'pending'
+}
+
+export const getOrderTableColumns = ({ t }: GetOrderTableColumnsParams): ColumnType[] => {
+ return [
+ {
+ key: 'orderNumber',
+ title: 'شماره سفارش',
+ render: (item: Order) => {
+ return formatFaNumber(item.orderNumber)
+ }
+ },
+ {
+ key: 'customerName',
+ title: 'نام مشتری',
+ render: (item: Order) => {
+ const name = [item.user.firstName, item.user.lastName].filter(Boolean).join(' ')
+ return name || '-'
+ }
+ },
+ {
+ key: 'total',
+ title: 'مبلغ سفارش',
+ render: (item: Order) => {
+ return formatPrice(item.total)
+ }
+ },
+ {
+ key: 'createdAt',
+ title: 'زمان سفارش',
+ render: (item: Order) => {
+ return formatOptionalDate(item.createdAt, {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ })
+ }
+ },
+ {
+ key: 'deliveryMethod',
+ title: 'نوع تحویل',
+ render: (item: Order) => {
+ return item.deliveryMethod?.title || '-'
+ }
+ },
+ {
+ key: 'paymentMethod',
+ title: 'نوع پرداخت',
+ render: (item: Order) => {
+ return item.paymentMethod?.title || '-'
+ }
+ },
+ {
+ key: 'status',
+ title: 'وضعیت',
+ render: (item: Order) => {
+ return (
+
+ )
+ }
+ },
+ {
+ key: 'details',
+ title: '',
+ render: (item: Order) => {
+ return (
+
+
+
+ )
+ }
+ },
+ ]
+}
diff --git a/src/pages/orders/enum/Enum.ts b/src/pages/orders/enum/Enum.ts
new file mode 100644
index 0000000..fb9347e
--- /dev/null
+++ b/src/pages/orders/enum/Enum.ts
@@ -0,0 +1,17 @@
+export const enum OrderStatus {
+ Pending = "pending",
+
+ Confirmed = "confirmed",
+ Preparing = "preparing",
+
+ RejectedByRestaurant = "rejectedByRestaurant",
+ CancelledByUser = "cancelledByUser",
+ CancelledBySystem = "cancelledBySystem",
+
+ ReadyForCustomerPickup = "readyForCustomerPickup",
+ ReadyForDineIn = "readyForDineIn",
+ ReadyForDeliveryCar = "readyForDeliveryCar",
+ ReadyForDeliveryCourier = "readyForDeliveryCourier",
+
+ Delivered = "delivered",
+}
diff --git a/src/pages/orders/hooks/useGetOrders.ts b/src/pages/orders/hooks/useGetOrders.ts
new file mode 100644
index 0000000..931a4f6
--- /dev/null
+++ b/src/pages/orders/hooks/useGetOrders.ts
@@ -0,0 +1,10 @@
+import { useQuery } from "@tanstack/react-query";
+import * as api from "../service/OrderService";
+import type { GetOrdersParams } from "../service/OrderService";
+
+export const useGetOrders = (params?: GetOrdersParams) => {
+ return useQuery({
+ queryKey: ["orders", params],
+ queryFn: () => api.getOrders(params),
+ });
+};
diff --git a/src/pages/orders/hooks/useOrderFilters.ts b/src/pages/orders/hooks/useOrderFilters.ts
new file mode 100644
index 0000000..3d99ed0
--- /dev/null
+++ b/src/pages/orders/hooks/useOrderFilters.ts
@@ -0,0 +1,50 @@
+import { useState, useMemo } from "react";
+import type { FilterValues } from "@/components/Filters";
+
+const DEFAULT_PAGE = 1;
+const DEFAULT_LIMIT = 10;
+
+export const useOrderFilters = () => {
+ const [filters, setFilters] = useState({});
+ const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE);
+ const [limit] = useState(DEFAULT_LIMIT);
+
+ const apiParams = useMemo(() => {
+ const params: Record = {
+ page: currentPage,
+ limit: limit,
+ };
+
+ if (filters.search) {
+ params.search = filters.search as string;
+ }
+
+ if (filters.status) {
+ params.status = filters.status as string;
+ }
+
+ if (filters.deliveryType) {
+ params.deliveryType = filters.deliveryType as string;
+ }
+
+ return params;
+ }, [filters, currentPage, limit]);
+
+ const handleFiltersChange = (newFilters: FilterValues) => {
+ setFilters(newFilters);
+ setCurrentPage(DEFAULT_PAGE);
+ };
+
+ const handlePageChange = (page: number) => {
+ setCurrentPage(page);
+ };
+
+ return {
+ filters,
+ currentPage,
+ apiParams,
+ handleFiltersChange,
+ handlePageChange,
+ limit,
+ };
+};
diff --git a/src/pages/orders/service/OrderService.ts b/src/pages/orders/service/OrderService.ts
new file mode 100644
index 0000000..1b5c829
--- /dev/null
+++ b/src/pages/orders/service/OrderService.ts
@@ -0,0 +1,19 @@
+import axios from "@/config/axios";
+import type { GetOrdersResponse } from "../types/Types";
+
+export interface GetOrdersParams {
+ page?: number;
+ limit?: number;
+ search?: string;
+ status?: string;
+ deliveryType?: string;
+}
+
+export const getOrders = async (
+ params?: GetOrdersParams
+): Promise => {
+ const { data } = await axios.get("/admin/orders", {
+ params,
+ });
+ return data;
+};
diff --git a/src/pages/orders/types/Types.ts b/src/pages/orders/types/Types.ts
new file mode 100644
index 0000000..bd73843
--- /dev/null
+++ b/src/pages/orders/types/Types.ts
@@ -0,0 +1,171 @@
+import type { IResponse } from "@/types/response.types";
+import type { RowDataType } from "@/components/types/TableTypes";
+
+export interface OrderUser {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ firstName: string;
+ lastName: string;
+ birthDate: string;
+ marriageDate: string;
+ referrer: string | null;
+ isActive: boolean;
+ gender: boolean;
+ wallet: number;
+ points: number;
+ phone: string;
+}
+
+export interface ServiceArea {
+ type: string;
+ coordinates: number[][][];
+}
+
+export interface OrderRestaurant {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ name: string;
+ slug: string;
+ logo: string | null;
+ address: string | null;
+ menuColor: string | null;
+ latitude: number | null;
+ longitude: number | null;
+ serviceArea: ServiceArea;
+ isActive: boolean;
+ establishedYear: number | null;
+ phoneNumber: string | null;
+ phone: string;
+ instagram: string | null;
+ telegram: string | null;
+ whatsapp: string | null;
+ description: string | null;
+ seoTitle: string | null;
+ seoDescription: string | null;
+ tagNames: string | null;
+ images: string[] | null;
+ vat: number;
+ domain: string;
+}
+
+export interface OrderDeliveryMethod {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ title: string;
+ method: string;
+ restaurant: string;
+ deliveryFee: number;
+ minOrderPrice: number;
+ description: string;
+ enabled: boolean;
+ order: number;
+}
+
+export interface OrderAddress {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ user: string;
+ title: string;
+ address: string;
+ city: string;
+ province: string;
+ postalCode: string;
+ latitude: number;
+ longitude: number;
+ phone: string;
+ isDefault: boolean;
+}
+
+export interface OrderPaymentMethod {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ restaurant: string;
+ title: string;
+ method: string;
+ gateway: string | null;
+ description: string;
+ enabled: boolean;
+ order: number;
+ merchantId: string | null;
+}
+
+export interface OrderFood {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ restaurant: string;
+ category: string;
+ title: string;
+ desc: string;
+ content: string | null;
+ price: number;
+ points: number | null;
+ order: number | null;
+ prepareTime: number | null;
+ sat: boolean;
+ sun: boolean;
+ mon: boolean;
+ breakfast: boolean;
+ noon: boolean;
+ dinner: boolean;
+ stock: number;
+ stockDefault: number;
+ isActive: boolean;
+ images: string[];
+ inPlaceServe: boolean;
+ pickupServe: boolean;
+ rate: number;
+ discount: number;
+ isSpecialOffer: boolean;
+}
+
+export interface OrderItem {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ order: string;
+ food: OrderFood;
+ quantity: number;
+ unitPrice: number;
+ discount: number;
+ totalPrice: number;
+}
+
+export interface Order extends RowDataType {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ user: OrderUser;
+ restaurant: OrderRestaurant;
+ deliveryMethod: OrderDeliveryMethod;
+ address: OrderAddress;
+ paymentMethod: OrderPaymentMethod;
+ orderNumber: number;
+ couponDiscount: number;
+ couponDetail: unknown | null;
+ itemsDiscount: number;
+ totalDiscount: number;
+ subTotal: number;
+ tax: number;
+ deliveryFee: number;
+ total: number;
+ totalItems: number;
+ status: string;
+ paymentStatus: string;
+ items: OrderItem[];
+}
+
+export type GetOrdersResponse = IResponse;