edit order

This commit is contained in:
hamid zarghami
2026-02-26 09:04:13 +03:30
parent d5a6342158
commit 54b86b718c
9 changed files with 195 additions and 113 deletions
+167 -96
View File
@@ -1,58 +1,81 @@
import { useEffect, useState, type FC } from 'react'
import Button from '@/components/Button'
import { TickSquare } from 'iconsax-react'
import { useOrderStore } from './store/OrderStore'
import { useGetOrderDetails, useUpdateOrder } from './hooks/useOrderData'
import { toast } from '@/shared/toast'
import { useNavigate } from 'react-router-dom'
import { useNavigate, useParams } from 'react-router-dom'
import { Paths } from '@/config/Paths'
import { extractErrorMessage } from '@/config/func'
import { useFormik } from 'formik'
import type { CreateBaseOrderType } from '../product/types/Types'
import { OrderStatusEnum } from './enum/OrderEnum'
import * as Yup from 'yup'
import Input from '@/components/Input'
import Select from '@/components/Select'
import Textarea from '@/components/Textarea'
import UploadBox from '@/components/UploadBox'
import { useGetUsers } from '../user/hooks/useUserData'
import { useParams } from 'react-router-dom'
import EditOrderComponent from './components/SingleOrderEdit'
import type { CreateOrderType } from './types/Types'
import { useGetCategory } from '@/pages/product/hooks/useProductData'
import { useGetProducts } from './hooks/useOrderData'
import type { UpdateOrderType } from './types/Types'
import type { OrderDetailDataType } from './types/Types'
import { useMultiUpload } from '@/pages/uploader/hooks/useUploader'
import { Link } from 'iconsax-react'
import TrashWithConfrim from '@/components/TrashWithConfrim'
import { getFileNameAndExtensionFromUrl } from '@/config/func'
const normalizeAttachments = (attachments: unknown[]): string[] => {
return attachments.map((att) =>
typeof att === 'string' ? att : (att as { url?: string })?.url ?? ''
).filter(Boolean)
}
const EditOrder: FC = () => {
const { id } = useParams()
const { data } = useGetOrderDetails(id!)
const navigate = useNavigate()
const { mutate: submitOrder, isPending } = useUpdateOrder()
const { data: users } = useGetUsers()
const [showOrders, setShowOrders] = useState([0])
const { items, setItems } = useOrderStore()
const { data: categories } = useGetCategory()
const { data: products } = useGetProducts()
const multiUpload = useMultiUpload()
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([])
const formik = useFormik<CreateBaseOrderType>({
const order = data?.data as OrderDetailDataType | undefined
const formik = useFormik<UpdateOrderType>({
initialValues: {
enableTax: true,
estimatedDays: 0,
paymentMethod: '',
status: OrderStatusEnum.CREATED,
userId: '',
invoiceItemId: '',
typeId: '',
estimatedDays: 0,
productId: '',
title: '',
attachments: [],
description: '',
},
validationSchema: Yup.object({
userId: Yup.string().required('این فیلد اجباری می باشد.'),
estimatedDays: Yup.number().required('این فیلد اجباری می باشد.'),
paymentMethod: Yup.string().required('این فیلد اجباری می باشد.'),
userId: Yup.string().required('انتخاب کاربر اجباری است.'),
typeId: Yup.string().required('انتخاب نوع اجباری است.'),
estimatedDays: Yup.number().required('تخمین روز اجباری است.').min(0, 'باید عدد مثبت باشد'),
productId: Yup.string().required('انتخاب محصول اجباری است.'),
title: Yup.string().required('عنوان اجباری است.'),
}),
onSubmit: (values) => {
values.estimatedDays = Number(values.estimatedDays)
onSubmit(values)
}
})
enableReinitialize: true,
onSubmit: async (values) => {
const attachments: string[] = [...values.attachments]
const onSubmit = (values: CreateBaseOrderType) => {
if (items.length > 0) {
const params = {
...values,
items: items
if (attachmentFiles.length > 0) {
const result = await multiUpload.mutateAsync(attachmentFiles)
result?.data?.forEach((item) => {
attachments.push(item.url)
})
}
const params: UpdateOrderType = {
...values,
estimatedDays: Number(values.estimatedDays),
attachments,
}
submitOrder({ orderId: id!, params }, {
onSuccess: () => {
toast('سفارش با موفقیت ویرایش شد', 'success')
@@ -62,46 +85,29 @@ const EditOrder: FC = () => {
toast(extractErrorMessage(error), 'error')
}
})
} else {
toast('حداقل یک سفارش باید اضافه کنید.', 'error')
}
}
})
useEffect(() => {
if (data && data?.data) {
if (order) {
formik.setValues({
enableTax: data?.data?.enableTax,
estimatedDays: data?.data?.estimatedDays as number,
paymentMethod: data?.data?.paymentMethod,
status: data?.data?.status as OrderStatusEnum,
userId: data?.data?.user.id,
userId: order.user?.id ?? '',
invoiceItemId: order.invoiceItem ?? '',
typeId: order.type?.id ?? '',
estimatedDays: order.estimatedDays ?? 0,
productId: order.product?.id ?? '',
title: order.title ?? '',
attachments: normalizeAttachments(order.attachments ?? []),
description: (order as OrderDetailDataType & { description?: string })?.description ?? '',
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
}, [order])
useEffect(() => {
if (data && data?.data) {
const items: CreateOrderType[] = []
data?.data?.items.map((item) => {
items.push({
id: item.id,
productId: item.product.id,
quantity: item.quantity,
attributes: item.attributes,
attachments: item.attachments,
discount: item.discount,
unitPrice: item.unitPrice,
description: item.description,
})
})
setItems(items)
const handleDeleteAttachment = (url: string) => {
const attachments = formik.values.attachments.filter((u) => u !== url)
formik.setFieldValue('attachments', attachments)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
return (
<div className='mt-5'>
@@ -112,7 +118,7 @@ const EditOrder: FC = () => {
<Button
className='w-fit px-5'
onClick={() => formik.handleSubmit()}
isLoading={isPending}
isLoading={isPending || multiUpload.isPending}
>
<div className='flex gap-2 items-center'>
<TickSquare size={20} color='black' />
@@ -122,59 +128,124 @@ const EditOrder: FC = () => {
</div>
<div className='bg-white rounded-3xl p-6 mt-8'>
<div className='font-light'>اطلاعات اولیه</div>
<div className='font-light'>اطلاعات سفارش</div>
<div className='rowTwoInput mt-6'>
<Select
items={
users?.data?.map((item) => ({
label: item.firstName ? `${item.firstName} ${item.lastName}` : item.phone,
value: item.id,
})) ?? []
}
label='کاربر'
placeholder='انتخاب کاربر'
{...formik.getFieldProps('userId')}
error_text={
formik.touched.userId && formik.errors.userId ? formik.errors.userId : ''
}
/>
<Select
items={
products?.data?.map((item) => ({
label: item.title,
value: String(item.id),
})) ?? []
}
label='محصول'
placeholder='انتخاب محصول'
{...formik.getFieldProps('productId')}
error_text={
formik.touched.productId && formik.errors.productId
? formik.errors.productId
: ''
}
/>
</div>
<div className='rowTwoInput mt-6'>
<Select
items={
categories?.data?.map((item) => ({
label: item.title,
value: String(item.id),
})) ?? []
}
label='نوع'
placeholder='انتخاب نوع'
{...formik.getFieldProps('typeId')}
error_text={
formik.touched.typeId && formik.errors.typeId ? formik.errors.typeId : ''
}
/>
<Input
label='تخمین روز'
type='number'
{...formik.getFieldProps('estimatedDays')}
error_text={formik.errors.estimatedDays && formik.touched.estimatedDays ? formik.errors.estimatedDays : ''}
error_text={
formik.touched.estimatedDays && formik.errors.estimatedDays
? formik.errors.estimatedDays
: ''
}
/>
</div>
<div className='mt-6'>
<Input
label='نوع پرداخت'
{...formik.getFieldProps('paymentMethod')}
error_text={formik.errors.paymentMethod && formik.touched.paymentMethod ? formik.errors.paymentMethod : ''}
/>
</div>
<div className='rowTwoInput mt-6'>
<Select
items={users?.data?.map((item) => {
return {
label: item.firstName ? item.firstName + ' ' + item.lastName : item.phone,
value: item.id
label='عنوان'
{...formik.getFieldProps('title')}
error_text={
formik.touched.title && formik.errors.title ? formik.errors.title : ''
}
}) || []}
label='انتخاب کاربر'
placeholder='انتخاب'
{...formik.getFieldProps('userId')}
error_text={formik.errors.userId && formik.touched.userId ? formik.errors.userId : ''}
/>
</div>
<Select
label='وضعیت سفارش'
placeholder='انتخاب'
items={Object.values(OrderStatusEnum).map((item) => {
return {
label: item,
value: item
<div className='mt-6'>
<Textarea
label='توضیحات'
placeholder='توضیحات را وارد کنید...'
{...formik.getFieldProps('description')}
error_text={
formik.touched.description && formik.errors.description
? formik.errors.description
: ''
}
})}
{...formik.getFieldProps('status')}
error_text={formik.errors.status && formik.touched.status ? formik.errors.status : ''}
/>
</div>
<div className='mt-6'>
<UploadBox
label='پیوست‌ها'
isMultiple
onChange={setAttachmentFiles}
/>
</div>
{items.map((item) => {
return (
<EditOrderComponent addNewItem={() => setShowOrders([...showOrders, showOrders.length])} key={item.productId} initialValues={item} />
)
})}
{formik.values.attachments?.length ? (
<div className='mt-5'>
<div className='text-sm mb-2'>پیوستهای فعلی</div>
<div className='flex flex-wrap gap-4'>
{formik.values.attachments.map((url) => (
<div
key={url}
className='flex mt-2 px-2 py-2 rounded-lg items-center gap-2 text-xs text-[#0037FF] border border-[#0037FF]'
>
<Link size={16} color='#0037FF' />
<div>{getFileNameAndExtensionFromUrl(url).fileName}</div>
<div className='w-9 flex justify-end'>
<TrashWithConfrim
colorIcon='red'
onDelete={() => handleDeleteAttachment(url)}
/>
</div>
</div>
))}
</div>
</div>
) : null}
</div>
</div>
)
}
+3 -3
View File
@@ -2,8 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import * as api from "../service/OrderService";
import type {
AddTicketType,
CreateFinalOrderType,
OrderPrintFormType,
UpdateOrderType,
} from "../types/Types";
export const useGetOrders = (params?: api.GetOrdersParams) => {
@@ -74,7 +74,7 @@ export const useUpdateOrder = () => {
params,
}: {
orderId: string;
params: CreateFinalOrderType;
params: UpdateOrderType;
}) => api.updateOrder(orderId, params),
});
};
@@ -86,7 +86,7 @@ export const useGetProducts = () => {
});
};
export const useGetAttributes = (id?: number) => {
export const useGetAttributes = (id?: string) => {
return useQuery({
queryKey: ["attributes", id],
queryFn: () => api.getAttributes(id),
+4 -3
View File
@@ -9,6 +9,7 @@ import type {
OrderPrintFormType,
OrderPrintResponseType,
TicketsResponseType,
UpdateOrderType,
} from "../types/Types";
import type {
FieldResponseType,
@@ -28,7 +29,7 @@ const cleanParams = (params?: GetOrdersParams) => {
if (!params) return undefined;
return Object.fromEntries(
Object.entries(params).filter(
(entry) => entry[1] != null && entry[1] !== '',
(entry) => entry[1] != null && entry[1] !== "",
) as [string, string][],
);
};
@@ -81,7 +82,7 @@ export const submitOrder = async (params: CreateFinalOrderType) => {
export const updateOrder = async (
orderId: string,
params: CreateFinalOrderType,
params: UpdateOrderType,
) => {
const { data } = await axios.patch(`/admin/orders/${orderId}`, params);
return data;
@@ -92,7 +93,7 @@ export const getProducts = async () => {
return data;
};
export const getAttributes = async (productId?: number) => {
export const getAttributes = async (productId?: string) => {
const { data } = await axios.get<FieldResponseType>(
`/admin/entity/${productId}/field`,
);
+11
View File
@@ -238,6 +238,17 @@ export interface CreateFinalOrderType extends CreateBaseOrderType {
items: CreateOrderType[];
}
export type UpdateOrderType = {
userId: string;
invoiceItemId: string;
typeId: string;
estimatedDays: number;
productId: string;
title: string;
attachments: string[];
description: string;
};
export type ConvertToOrderItemType = {
invoiceItemId: string;
productId?: string;
+1 -1
View File
@@ -15,7 +15,7 @@ const ProductList: FC = () => {
const { data: products, refetch } = useGetProducts()
const { mutate: deleteProduct, isPending: isDeleting } = useDeleteProduct()
const handleDelete = (id: number) => {
const handleDelete = (id: string) => {
deleteProduct(id, {
onSuccess: () => {
refetch()
+1 -1
View File
@@ -88,7 +88,7 @@ export const useUpdateProduct = () => {
export const useDeleteProduct = () => {
return useMutation({
mutationFn: (id: number) => api.deleteProduct(id),
mutationFn: (id: string) => api.deleteProduct(id),
});
};
+1 -1
View File
@@ -46,7 +46,7 @@ export const updateProduct = async (id: string, params:CreateProductType) => {
return data;
};
export const deleteProduct = async (id: number) => {
export const deleteProduct = async (id: string) => {
const { data } = await axios.delete(`/admin/products/${id}`);
return data;
};
+1 -1
View File
@@ -122,7 +122,7 @@ const CreateUser: FC = () => {
<div className="mt-6">
<Input
label="حداکثر اعتبار (ریال)"
label="حداکثر اعتبار (تومان)"
type="number"
min={0}
placeholder="۰"
+2 -3
View File
@@ -27,8 +27,7 @@ const initialValues: CreateUserType = {
const validationSchema = Yup.object({
phone: Yup.string()
.required("شماره موبایل اجباری است")
.matches(/^09[0-9]{9}$/, "شماره موبایل معتبر نیست"),
.required("شماره موبایل اجباری است"),
firstName: Yup.string().required("نام اجباری است"),
lastName: Yup.string().required("نام خانوادگی اجباری است"),
address: Yup.string(),
@@ -169,7 +168,7 @@ const UpdateUser: FC = () => {
<div className="mt-6">
<Input
label="حداکثر اعتبار (ریال)"
label="حداکثر اعتبار (تومان)"
type="number"
min={0}
placeholder="۰"