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 { Add, TickSquare } from "iconsax-react";
import { Add, Link, TickSquare } from "iconsax-react";
import { toast } from "@/shared/toast";
import { useNavigate, useParams } from "react-router-dom";
import { Paths } from "@/config/Paths";
import { extractErrorMessage } from "@/config/func";
import { extractErrorMessage, getFileNameAndExtensionFromUrl } from "@/config/func";
import { useFormik } from "formik";
import type { CreatePreInvoiceItemType, UpdatePreInvoiceType } from "./types/Types";
import InvoiceItemRow from "./components/InvoiceItemRow";
@@ -16,6 +16,7 @@ import { useGetInvoiceDetail, useUpdateInvoice } from "./hooks/useInvoiceData";
import { useMultiUpload } from "../uploader/hooks/useUploader";
import moment from "moment-jalaali";
import { getItemLineTotal } from "./utils/invoiceItem";
import TrashWithConfrim from "@/components/TrashWithConfrim";
const createEmptyItem = (): CreatePreInvoiceItemType => ({
productId: "",
@@ -26,6 +27,17 @@ const createEmptyItem = (): CreatePreInvoiceItemType => ({
description: "",
});
const formatInvoiceUserName = (
firstName?: string | null,
lastName?: string | null,
phone?: string | null
) => {
if (firstName && lastName) {
return `${firstName} ${lastName}`;
}
return phone ?? "-";
};
const UpdateInvoice: FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
@@ -37,10 +49,55 @@ const UpdateInvoice: FC = () => {
const invoice = invoiceData?.data;
const [items, setItems] = useState<CreatePreInvoiceItemType[]>([
createEmptyItem(),
]);
const [items, setItems] = useState<CreatePreInvoiceItemType[]>([createEmptyItem()]);
const [currentAttachments, setCurrentAttachments] = useState<
{ type: string; url: string }[]
>([]);
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<
Pick<
@@ -48,12 +105,8 @@ const UpdateInvoice: FC = () => {
"enableTax" | "approvalDeadline" | "paymentMethod" | "description"
>
>({
initialValues: {
enableTax: false,
approvalDeadline: "",
paymentMethod: "",
description: "",
},
initialValues: initialFormValues,
enableReinitialize: true,
onSubmit: async (values) => {
const validItems = items.filter(
(i) => i.productId && i.quantity > 0 && i.unitPrice >= 0
@@ -67,8 +120,7 @@ const UpdateInvoice: FC = () => {
return;
}
const attachments: { type: string; url: string }[] =
invoice?.attachments?.map((a) => ({ type: a.type, url: a.url })) ?? [];
const attachments = [...currentAttachments];
if (attachmentFiles.length) {
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 = (
index: number,
field: keyof CreatePreInvoiceItemType,
@@ -184,7 +206,12 @@ const UpdateInvoice: FC = () => {
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 isSubmitting = isPending || multiUpload.isPending;
if (isLoadingInvoice || !invoice) {
return (
@@ -196,14 +223,17 @@ const UpdateInvoice: FC = () => {
return (
<div className="mt-5">
<div className="justify-between items-center flex">
<h1 className="text-lg font-light">
ویرایش پیش فاکتور #{invoice.invoiceNumber}
</h1>
<div className="justify-between items-center flex flex-wrap gap-4">
<div>
<h1 className="text-lg font-light">ویرایش پیش فاکتور #{invoice.invoiceNumber}</h1>
<p className="text-xs text-[#888888] mt-1">
اطلاعات مشتری، آیتمها و شرایط پرداخت را بهروزرسانی کنید.
</p>
</div>
<Button
className="w-fit px-5"
className="w-fit px-5 h-11"
onClick={() => formik.handleSubmit()}
isLoading={isPending || multiUpload.isPending}
isLoading={isSubmitting}
>
<div className="flex gap-2 items-center">
<TickSquare size={20} color="black" />
@@ -212,16 +242,24 @@ const UpdateInvoice: FC = () => {
</Button>
</div>
<div className="bg-white rounded-3xl p-6 mt-8">
<div className="font-light">اطلاعات پیش فاکتور</div>
<div className="bg-white rounded-3xl p-6 mt-8 border border-[#EEF2FF] shadow-sm">
<div className="font-light text-base">اطلاعات پیش فاکتور</div>
<div className="mt-6 rowTwoInput">
<div className="opacity-70">
<div className="text-[13px] text-[#888888] mb-1.5">مشتری</div>
<div className="text-[15px]">
{invoice.user?.firstName && invoice.user?.lastName
? `${invoice.user.firstName} ${invoice.user.lastName}`
: invoice.user?.phone ?? "-"}
<div className="mt-6 rounded-2xl bg-[#F8FAFF] border border-[#E8EEFF] p-4 rowTwoInput gap-4">
<div>
<div className="text-[12px] text-[#888888] mb-1">مشتری</div>
<div className="text-[14px] font-medium">
{formatInvoiceUserName(
invoice.user?.firstName,
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>
@@ -229,7 +267,7 @@ const UpdateInvoice: FC = () => {
<div className="mt-6">
{items.map((item, index) => (
<InvoiceItemRow
key={index}
key={item.id ?? index}
item={item}
index={index}
onChange={handleItemChange}
@@ -250,7 +288,7 @@ const UpdateInvoice: FC = () => {
</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
id="enableTax"
checked={formik.values.enableTax}
@@ -313,9 +351,32 @@ const UpdateInvoice: FC = () => {
/>
</div>
<div className="mt-6 bg-[#F5F7FC] h-12 px-4 flex justify-between items-center rounded-xl">
<div className="text-[#888888] text-[13px]">مبلغ کل:</div>
<div className="font-semibold text-[13px]">
{currentAttachments.length ? (
<div className="mt-6">
<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")} ریال
</div>
</div>