refactor
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-08 20:45:54 +03:30
parent 05d60a8067
commit dfddd8d129
+125 -64
View File
@@ -1,10 +1,10 @@
import { useEffect, useState, type FC } from "react"; import { useEffect, useMemo, useRef, useState, type FC } from "react";
import Button from "@/components/Button"; import Button from "@/components/Button";
import { Add, TickSquare } from "iconsax-react"; import { Add, Link, TickSquare } from "iconsax-react";
import { toast } from "@/shared/toast"; import { toast } from "@/shared/toast";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Paths } from "@/config/Paths"; import { Paths } from "@/config/Paths";
import { extractErrorMessage } from "@/config/func"; import { extractErrorMessage, getFileNameAndExtensionFromUrl } from "@/config/func";
import { useFormik } from "formik"; import { useFormik } from "formik";
import type { CreatePreInvoiceItemType, UpdatePreInvoiceType } from "./types/Types"; import type { CreatePreInvoiceItemType, UpdatePreInvoiceType } from "./types/Types";
import InvoiceItemRow from "./components/InvoiceItemRow"; import InvoiceItemRow from "./components/InvoiceItemRow";
@@ -16,6 +16,7 @@ import { useGetInvoiceDetail, useUpdateInvoice } from "./hooks/useInvoiceData";
import { useMultiUpload } from "../uploader/hooks/useUploader"; import { useMultiUpload } from "../uploader/hooks/useUploader";
import moment from "moment-jalaali"; import moment from "moment-jalaali";
import { getItemLineTotal } from "./utils/invoiceItem"; import { getItemLineTotal } from "./utils/invoiceItem";
import TrashWithConfrim from "@/components/TrashWithConfrim";
const createEmptyItem = (): CreatePreInvoiceItemType => ({ const createEmptyItem = (): CreatePreInvoiceItemType => ({
productId: "", productId: "",
@@ -26,6 +27,17 @@ const createEmptyItem = (): CreatePreInvoiceItemType => ({
description: "", description: "",
}); });
const formatInvoiceUserName = (
firstName?: string | null,
lastName?: string | null,
phone?: string | null
) => {
if (firstName && lastName) {
return `${firstName} ${lastName}`;
}
return phone ?? "-";
};
const UpdateInvoice: FC = () => { const UpdateInvoice: FC = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -37,10 +49,55 @@ const UpdateInvoice: FC = () => {
const invoice = invoiceData?.data; const invoice = invoiceData?.data;
const [items, setItems] = useState<CreatePreInvoiceItemType[]>([ const [items, setItems] = useState<CreatePreInvoiceItemType[]>([createEmptyItem()]);
createEmptyItem(), const [currentAttachments, setCurrentAttachments] = useState<
]); { type: string; url: string }[]
>([]);
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]); const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
const hydratedInvoiceId = useRef<string | null>(null);
const initialFormValues = useMemo(
() => ({
enableTax: invoice?.enableTax ?? false,
approvalDeadline: invoice?.approvalDeadline
? moment(invoice.approvalDeadline).format("jYYYY/jMM/jDD")
: "",
paymentMethod: invoice?.paymentMethod ?? "",
description: invoice?.description ?? "",
}),
[invoice]
);
const initialItems = useMemo<CreatePreInvoiceItemType[]>(
() =>
invoice?.items?.length
? invoice.items.map((item) => ({
id: item.id,
productId: item.product?.id ?? "",
quantity: item.quantity ?? 1,
unitPrice: item.unitPrice ?? 0,
discount: item.discount ?? 0,
discountPercent:
item.discountPercent != null && item.discountPercent > 0
? item.discountPercent
: null,
description: item.description ?? "",
}))
: [createEmptyItem()],
[invoice]
);
const initialAttachments = useMemo(
() => invoice?.attachments?.map((attachment) => ({ ...attachment })) ?? [],
[invoice]
);
useEffect(() => {
if (!invoice || hydratedInvoiceId.current === invoice.id) return;
setItems(initialItems);
setCurrentAttachments(initialAttachments);
hydratedInvoiceId.current = invoice.id;
}, [invoice, initialAttachments, initialItems]);
const formik = useFormik< const formik = useFormik<
Pick< Pick<
@@ -48,12 +105,8 @@ const UpdateInvoice: FC = () => {
"enableTax" | "approvalDeadline" | "paymentMethod" | "description" "enableTax" | "approvalDeadline" | "paymentMethod" | "description"
> >
>({ >({
initialValues: { initialValues: initialFormValues,
enableTax: false, enableReinitialize: true,
approvalDeadline: "",
paymentMethod: "",
description: "",
},
onSubmit: async (values) => { onSubmit: async (values) => {
const validItems = items.filter( const validItems = items.filter(
(i) => i.productId && i.quantity > 0 && i.unitPrice >= 0 (i) => i.productId && i.quantity > 0 && i.unitPrice >= 0
@@ -67,8 +120,7 @@ const UpdateInvoice: FC = () => {
return; return;
} }
const attachments: { type: string; url: string }[] = const attachments = [...currentAttachments];
invoice?.attachments?.map((a) => ({ type: a.type, url: a.url })) ?? [];
if (attachmentFiles.length) { if (attachmentFiles.length) {
const uploadResult = await multiUpload.mutateAsync(attachmentFiles); const uploadResult = await multiUpload.mutateAsync(attachmentFiles);
@@ -124,36 +176,6 @@ const UpdateInvoice: FC = () => {
}, },
}); });
useEffect(() => {
if (invoice) {
formik.setValues({
enableTax: invoice.enableTax ?? false,
approvalDeadline: invoice.approvalDeadline
? moment(invoice.approvalDeadline).format("jYYYY/jMM/jDD")
: "",
paymentMethod: invoice.paymentMethod ?? "",
description: invoice.description ?? "",
});
const mappedItems: CreatePreInvoiceItemType[] =
invoice.items?.length > 0
? invoice.items.map((i) => ({
id: i.id,
productId: i.product?.id ?? "",
quantity: i.quantity ?? 1,
unitPrice: i.unitPrice ?? 0,
discount: i.discount ?? 0,
discountPercent:
i.discountPercent != null && i.discountPercent > 0 ? i.discountPercent : null,
description: i.description ?? "",
}))
: [createEmptyItem()];
setItems(mappedItems);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [invoice]);
const handleItemChange = ( const handleItemChange = (
index: number, index: number,
field: keyof CreatePreInvoiceItemType, field: keyof CreatePreInvoiceItemType,
@@ -184,7 +206,12 @@ const UpdateInvoice: FC = () => {
setItems(items.filter((_, i) => i !== index)); setItems(items.filter((_, i) => i !== index));
}; };
const removeCurrentAttachment = (url: string) => {
setCurrentAttachments((prev) => prev.filter((attachment) => attachment.url !== url));
};
const totalAmount = items.reduce((sum, i) => sum + getItemLineTotal(i), 0); const totalAmount = items.reduce((sum, i) => sum + getItemLineTotal(i), 0);
const isSubmitting = isPending || multiUpload.isPending;
if (isLoadingInvoice || !invoice) { if (isLoadingInvoice || !invoice) {
return ( return (
@@ -196,14 +223,17 @@ const UpdateInvoice: FC = () => {
return ( return (
<div className="mt-5"> <div className="mt-5">
<div className="justify-between items-center flex"> <div className="justify-between items-center flex flex-wrap gap-4">
<h1 className="text-lg font-light"> <div>
ویرایش پیش فاکتور #{invoice.invoiceNumber} <h1 className="text-lg font-light">ویرایش پیش فاکتور #{invoice.invoiceNumber}</h1>
</h1> <p className="text-xs text-[#888888] mt-1">
اطلاعات مشتری، آیتمها و شرایط پرداخت را بهروزرسانی کنید.
</p>
</div>
<Button <Button
className="w-fit px-5" className="w-fit px-5 h-11"
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
isLoading={isPending || multiUpload.isPending} isLoading={isSubmitting}
> >
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<TickSquare size={20} color="black" /> <TickSquare size={20} color="black" />
@@ -212,16 +242,24 @@ const UpdateInvoice: FC = () => {
</Button> </Button>
</div> </div>
<div className="bg-white rounded-3xl p-6 mt-8"> <div className="bg-white rounded-3xl p-6 mt-8 border border-[#EEF2FF] shadow-sm">
<div className="font-light">اطلاعات پیش فاکتور</div> <div className="font-light text-base">اطلاعات پیش فاکتور</div>
<div className="mt-6 rowTwoInput"> <div className="mt-6 rounded-2xl bg-[#F8FAFF] border border-[#E8EEFF] p-4 rowTwoInput gap-4">
<div className="opacity-70"> <div>
<div className="text-[13px] text-[#888888] mb-1.5">مشتری</div> <div className="text-[12px] text-[#888888] mb-1">مشتری</div>
<div className="text-[15px]"> <div className="text-[14px] font-medium">
{invoice.user?.firstName && invoice.user?.lastName {formatInvoiceUserName(
? `${invoice.user.firstName} ${invoice.user.lastName}` invoice.user?.firstName,
: invoice.user?.phone ?? "-"} invoice.user?.lastName,
invoice.user?.phone
)}
</div>
</div>
<div>
<div className="text-[12px] text-[#888888] mb-1">تاریخ ایجاد</div>
<div className="text-[14px] font-medium">
{moment(invoice.createdAt).format("jYYYY/jMM/jDD")}
</div> </div>
</div> </div>
</div> </div>
@@ -229,7 +267,7 @@ const UpdateInvoice: FC = () => {
<div className="mt-6"> <div className="mt-6">
{items.map((item, index) => ( {items.map((item, index) => (
<InvoiceItemRow <InvoiceItemRow
key={index} key={item.id ?? index}
item={item} item={item}
index={index} index={index}
onChange={handleItemChange} onChange={handleItemChange}
@@ -250,7 +288,7 @@ const UpdateInvoice: FC = () => {
</div> </div>
</div> </div>
<div className="mt-6 flex gap-2 items-center"> <div className="mt-8 pt-6 border-t border-[#F0F0F0] flex gap-2 items-center">
<Checkbox <Checkbox
id="enableTax" id="enableTax"
checked={formik.values.enableTax} checked={formik.values.enableTax}
@@ -313,9 +351,32 @@ const UpdateInvoice: FC = () => {
/> />
</div> </div>
<div className="mt-6 bg-[#F5F7FC] h-12 px-4 flex justify-between items-center rounded-xl"> {currentAttachments.length ? (
<div className="text-[#888888] text-[13px]">مبلغ کل:</div> <div className="mt-6">
<div className="font-semibold text-[13px]"> <div className="text-[13px] text-[#888888] mb-2">پیوستهای فعلی</div>
<div className="flex flex-wrap gap-3">
{currentAttachments.map((attachment) => (
<div
key={attachment.url}
className="flex px-3 py-2 rounded-lg items-center gap-2 text-xs text-[#0037FF] border border-[#0037FF]"
>
<Link size={16} color="#0037FF" />
<div>{getFileNameAndExtensionFromUrl(attachment.url).fileName}</div>
<div className="w-8 flex justify-end">
<TrashWithConfrim
colorIcon="red"
onDelete={() => removeCurrentAttachment(attachment.url)}
/>
</div>
</div>
))}
</div>
</div>
) : null}
<div className="mt-6 bg-[#F5F7FC] h-14 px-4 flex justify-between items-center rounded-xl border border-[#E6EBFA]">
<div className="text-[#5F6782] text-[13px]">مبلغ کل آیتمها:</div>
<div className="font-semibold text-[14px] text-[#1F2A60]">
{totalAmount.toLocaleString("fa-IR")} ریال {totalAmount.toLocaleString("fa-IR")} ریال
</div> </div>
</div> </div>