base create order
This commit is contained in:
@@ -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 }
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user