base create order

This commit is contained in:
hamid zarghami
2026-06-23 10:12:56 +03:30
parent 6975e2d0b0
commit 223b1b5e8f
15 changed files with 1205 additions and 2 deletions
@@ -0,0 +1,308 @@
import { useEffect, useMemo } from 'react'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { toast } from 'react-toastify'
import { useNavigate } from 'react-router-dom'
import { useCreateOrder } from './useOrderData'
import { useGetFoods } from '@/pages/food/hooks/useFoodData'
import { useGetRestaurantShipmentMethods } from '@/pages/shipmentMethod/hooks/useShipmentMethodData'
import { useGetRestaurantPaymentMethods } from '@/pages/paymentMethods/hooks/usePaymentMethodData'
import { DeliveryMethodEnum } from '@/pages/shipmentMethod/enum/Enum'
import { PaymentMethodEnum } from '@/pages/paymentMethods/enum/Enum'
import type {
CreateOrderType,
CreateOrderItem,
CreateOrderUserAddress,
CreateOrderCarAddress,
} from '../types/Types'
import type { Food } from '@/pages/food/types/Types'
import type { RestaurantShipmentMethod } from '@/pages/shipmentMethod/types/Types'
import type { RestaurantPaymentMethod } from '@/pages/paymentMethods/types/Types'
import { Pages } from '@/config/Pages'
import type { ErrorType } from '@/helpers/types'
import { extractErrorMessage } from '@/config/func'
export interface CreateOrderFormValues {
userPhone: string
firstName: string
lastName: string
items: CreateOrderItem[]
deliveryMethodId: string
paymentMethodId: string
description: string
tableNumber: string
userAddress: CreateOrderUserAddress
carAddress: CreateOrderCarAddress
paymentDesc: string
}
const emptyUserAddress = (): CreateOrderUserAddress => ({
fullName: '',
phone: '',
city: '',
province: '',
address: '',
postalCode: '',
latitude: 0,
longitude: 0,
})
const emptyCarAddress = (): CreateOrderCarAddress => ({
carModel: '',
carColor: '',
plateNumber: '',
phone: '',
})
export const getFoodUnitPrice = (food: Food) => {
const discount = food.discount || 0
return Math.max(food.price - discount, 0)
}
export const useCreateOrderForm = () => {
const navigate = useNavigate()
const { mutate: createOrder, isPending: isCreating } = useCreateOrder()
const { data: foodsData } = useGetFoods({ limit: 500 })
const { data: deliveryMethodsData } = useGetRestaurantShipmentMethods({ limit: 100 })
const { data: paymentMethodsData } = useGetRestaurantPaymentMethods({ limit: 100 })
const foods = useMemo(() => foodsData?.data || [], [foodsData?.data])
const deliveryMethods = useMemo(
() => (deliveryMethodsData?.data || []).filter((method) => method.enabled),
[deliveryMethodsData?.data]
)
const paymentMethods = useMemo(
() =>
(paymentMethodsData?.data || []).filter(
(method) => method.enabled && method.method === PaymentMethodEnum.Cash
),
[paymentMethodsData?.data]
)
const formik = useFormik<CreateOrderFormValues>({
initialValues: {
userPhone: '',
firstName: '',
lastName: '',
items: [],
deliveryMethodId: '',
paymentMethodId: '',
description: '',
tableNumber: '',
userAddress: emptyUserAddress(),
carAddress: emptyCarAddress(),
paymentDesc: '',
},
validationSchema: Yup.object().shape({
userPhone: Yup.string().required('شماره تلفن الزامی است'),
firstName: Yup.string().required('نام الزامی است'),
lastName: Yup.string().required('نام خانوادگی الزامی است'),
deliveryMethodId: Yup.string().required('روش تحویل الزامی است'),
paymentMethodId: Yup.string().required('روش پرداخت الزامی است'),
items: Yup.array()
.of(
Yup.object().shape({
foodId: Yup.string().required(),
quantity: Yup.number().min(1).required(),
})
)
.min(1, 'حداقل یک آیتم الزامی است'),
}),
onSubmit: (values) => {
const selectedDelivery = deliveryMethods.find(
(method) => method.id === values.deliveryMethodId
)
const selectedPayment = paymentMethods.find(
(method) => method.id === values.paymentMethodId
)
if (selectedDelivery?.method === DeliveryMethodEnum.DeliveryCourier) {
const address = values.userAddress
if (
!address.fullName ||
!address.phone ||
!address.city ||
!address.province ||
!address.address
) {
toast.error('لطفاً آدرس تحویل را کامل کنید')
return
}
}
if (selectedDelivery?.method === DeliveryMethodEnum.DeliveryCar) {
const car = values.carAddress
if (!car.carModel || !car.carColor || !car.plateNumber || !car.phone) {
toast.error('لطفاً اطلاعات خودرو را کامل کنید')
return
}
}
const submitOrder = () => {
const payload: CreateOrderType = {
userPhone: values.userPhone,
firstName: values.firstName,
lastName: values.lastName,
items: values.items,
deliveryMethodId: values.deliveryMethodId,
paymentMethodId: values.paymentMethodId,
}
if (values.description.trim()) {
payload.description = values.description.trim()
}
if (values.tableNumber.trim()) {
payload.tableNumber = values.tableNumber.trim()
}
if (selectedDelivery?.method === DeliveryMethodEnum.DeliveryCourier) {
payload.userAddress = {
...values.userAddress,
latitude: Number(values.userAddress.latitude) || 0,
longitude: Number(values.userAddress.longitude) || 0,
}
}
if (selectedDelivery?.method === DeliveryMethodEnum.DeliveryCar) {
payload.carAddress = values.carAddress
}
createOrder(payload, {
onSuccess: (response) => {
toast.success('سفارش با موفقیت ثبت شد')
const orderId = response?.data?.id
if (orderId) {
navigate(Pages.orders.detail + orderId)
} else {
navigate(Pages.orders.list)
}
},
onError: (error: ErrorType) => {
toast.error(extractErrorMessage(error))
},
})
}
if (!selectedPayment) {
toast.error('روش پرداخت نقدی یافت نشد')
return
}
submitOrder()
},
})
const cashPaymentMethod = paymentMethods[0]
useEffect(() => {
if (cashPaymentMethod && formik.values.paymentMethodId !== cashPaymentMethod.id) {
formik.setFieldValue('paymentMethodId', cashPaymentMethod.id)
}
}, [cashPaymentMethod, formik.values.paymentMethodId, formik.setFieldValue])
const selectedDeliveryMethod = deliveryMethods.find(
(method) => method.id === formik.values.deliveryMethodId
)
const selectedPaymentMethod = paymentMethods.find(
(method) => method.id === formik.values.paymentMethodId
)
const isCourierDelivery =
selectedDeliveryMethod?.method === DeliveryMethodEnum.DeliveryCourier
const isCarDelivery =
selectedDeliveryMethod?.method === DeliveryMethodEnum.DeliveryCar
const isDineIn = selectedDeliveryMethod?.method === DeliveryMethodEnum.DineIn
const foodsMap = useMemo(
() => new Map(foods.map((food) => [food.id, food])),
[foods]
)
const orderSummary = useMemo(() => {
const lineItems = formik.values.items
.map((item) => {
const food = foodsMap.get(item.foodId)
if (!food) return null
const unitPrice = getFoodUnitPrice(food)
return {
foodId: item.foodId,
title: food.title,
image: food.images?.[0] || '',
quantity: item.quantity,
unitPrice,
totalPrice: unitPrice * item.quantity,
}
})
.filter(Boolean)
const subTotal = lineItems.reduce((sum, item) => sum + (item?.totalPrice || 0), 0)
const deliveryFee = selectedDeliveryMethod?.deliveryFee || 0
const totalItems = lineItems.reduce((sum, item) => sum + (item?.quantity || 0), 0)
return {
lineItems,
subTotal,
deliveryFee,
total: subTotal + deliveryFee,
totalItems,
}
}, [formik.values.items, foodsMap, selectedDeliveryMethod])
const addFoodToOrder = (foodId: string) => {
const existingIndex = formik.values.items.findIndex((item) => item.foodId === foodId)
if (existingIndex >= 0) {
const updated = [...formik.values.items]
updated[existingIndex] = {
...updated[existingIndex],
quantity: updated[existingIndex].quantity + 1,
}
formik.setFieldValue('items', updated)
return
}
formik.setFieldValue('items', [...formik.values.items, { foodId, quantity: 1 }])
}
const updateItemQuantity = (foodId: string, quantity: number) => {
if (quantity < 1) {
formik.setFieldValue(
'items',
formik.values.items.filter((item) => item.foodId !== foodId)
)
return
}
formik.setFieldValue(
'items',
formik.values.items.map((item) =>
item.foodId === foodId ? { ...item, quantity } : item
)
)
}
const removeItem = (foodId: string) => {
formik.setFieldValue(
'items',
formik.values.items.filter((item) => item.foodId !== foodId)
)
}
return {
formik,
foods,
deliveryMethods,
selectedDeliveryMethod,
selectedPaymentMethod,
cashPaymentMethod,
isCourierDelivery,
isCarDelivery,
isDineIn,
orderSummary,
addFoodToOrder,
updateItemQuantity,
removeItem,
isSubmitting: isCreating,
}
}
export type { RestaurantShipmentMethod, RestaurantPaymentMethod }
+10
View File
@@ -2,6 +2,16 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/OrderService";
import type { GetOrdersParams, ChangeOrderStatusParams, RefundPaymentParams } from "../service/OrderService";
export const useCreateOrder = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.createOrder,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["orders"] });
},
});
};
export const useGetOrders = (params?: GetOrdersParams) => {
return useQuery({
queryKey: ["orders", params],