From 8a0a62b6d7bf1a22baa0436c23ad4aa802e745d0 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 21 Feb 2026 11:38:39 +0330 Subject: [PATCH] design invoice --- src/App.tsx | 2 + src/config/Paths.tsx | 5 +- src/pages/invoice/Create.tsx | 236 ++++++++++++++++++ src/pages/invoice/ProformaInvoice.tsx | 12 +- .../invoice/components/InvoiceItemRow.tsx | 125 ++++++++++ src/pages/invoice/hooks/useInvoiceData.ts | 14 ++ src/pages/invoice/service/InvoiceService.ts | 7 + src/pages/invoice/types/Types.ts | 16 ++ src/pages/requests/Detail.tsx | 13 +- src/router/MainRouter.tsx | 2 + 10 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 src/pages/invoice/Create.tsx create mode 100644 src/pages/invoice/components/InvoiceItemRow.tsx create mode 100644 src/pages/invoice/hooks/useInvoiceData.ts create mode 100644 src/pages/invoice/service/InvoiceService.ts create mode 100644 src/pages/invoice/types/Types.ts diff --git a/src/App.tsx b/src/App.tsx index 2ae71ce..030f1f9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import 'react-toastify/dist/ReactToastify.css' import { BrowserRouter } from 'react-router-dom' import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ToastContainer } from 'react-toastify' +import Toast from '@/components/Toast' import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func' import MainRouter from './router/MainRouter' import AuthRouter from './router/AuthRouter' @@ -131,6 +132,7 @@ const App: FC = () => { {isLoggedIn ? : } + ({ + productId: "", + quantity: 1, + unitPrice: 0, + discount: 0, + description: "", +}); + +const CreateInvoice: FC = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const requestId = searchParams.get("requestId") ?? undefined; + + const { mutate: submitInvoice, isPending } = useCreatePreInvoice(); + const { data: usersData } = useGetUsers(); + const { data: requestData } = useGetRequestDetail(requestId || ""); + + const [items, setItems] = useState([createEmptyItem()]); + const [, setAttachmentFiles] = useState([]); + + const formik = useFormik>({ + initialValues: { + userId: "", + enableTax: false, + approvalDeadline: "", + notes: "", + }, + validationSchema: Yup.object({ + userId: Yup.string().required("انتخاب مشتری اجباری است."), + approvalDeadline: Yup.string().required("نحوه پرداخت اجباری است."), + }), + onSubmit: (values) => { + const validItems = items.filter( + (i) => i.productId && i.quantity > 0 && i.unitPrice >= 0 + ); + + if (validItems.length === 0) { + toast("حداقل یک آیتم با محصول، تعداد و مبلغ واحد معتبر اضافه کنید.", "error"); + return; + } + + const params: CreatePreInvoiceType = { + requestId, + userId: values.userId, + items: validItems.map((i) => ({ + productId: i.productId, + quantity: i.quantity, + unitPrice: i.unitPrice, + discount: i.discount || 0, + description: i.description || "", + })), + enableTax: values.enableTax, + approvalDeadline: values.approvalDeadline, + notes: values.notes, + }; + + submitInvoice(params, { + onSuccess: () => { + toast("پیش فاکتور با موفقیت ثبت شد", "success"); + navigate(Paths.perfomaInvoice.list); + }, + onError: (error) => { + toast(extractErrorMessage(error), "error"); + }, + }); + }, + }); + + useEffect(() => { + if (requestId && requestData?.data?.user?.id) { + formik.setFieldValue("userId", String(requestData.data.user.id)); + } + }, [requestId, requestData?.data?.user?.id]); + + const handleItemChange = ( + index: number, + field: keyof CreatePreInvoiceItemType, + value: string | number + ) => { + const next = [...items]; + next[index] = { ...next[index], [field]: value }; + setItems(next); + }; + + const addItem = (currentIndex: number) => { + const current = items[currentIndex]; + const hasProduct = !!current?.productId; + const hasQuantity = (current?.quantity ?? 0) > 0; + const hasUnitPrice = (current?.unitPrice ?? 0) > 0; + + if (!hasProduct || !hasQuantity || !hasUnitPrice) { + toast("لطفاً محصول، تعداد و مبلغ واحد را وارد کنید.", "error"); + return; + } + setItems([...items, createEmptyItem()]); + }; + + const removeItem = (index: number) => { + if (items.length <= 1) return; + setItems(items.filter((_, i) => i !== index)); + }; + + const totalAmount = items.reduce( + (sum, i) => sum + (i.quantity || 0) * (i.unitPrice || 0) - (i.discount || 0), + 0 + ); + + const users = usersData?.data ?? []; + + return ( +
+
+

پیش فاکتور جدید

+ +
+ +
+
اطلاعات پیش فاکتور
+ +
+