Files
negareh-admin/src/pages/invoice/Update.tsx
T
morteza d6a5697998
deploy to danak / build_and_deploy (push) Has been cancelled
up
2026-06-26 11:04:03 +03:30

328 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState, type FC } from "react";
import Button from "@/components/Button";
import { Add, 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 { useFormik } from "formik";
import type { CreatePreInvoiceItemType, UpdatePreInvoiceType } from "./types/Types";
import InvoiceItemRow from "./components/InvoiceItemRow";
import { Checkbox } from "@/components/ui/checkbox";
import UploadBox from "@/components/UploadBox";
import Textarea from "@/components/Textarea";
import DatePickerComponent from "@/components/DatePicker";
import { useGetInvoiceDetail, useUpdateInvoice } from "./hooks/useInvoiceData";
import { useMultiUpload } from "../uploader/hooks/useUploader";
import moment from "moment-jalaali";
import { getItemLineTotal } from "./utils/invoiceItem";
const createEmptyItem = (): CreatePreInvoiceItemType => ({
productId: "",
quantity: 1,
unitPrice: 0,
discount: 0,
discountPercent: null,
description: "",
});
const UpdateInvoice: FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { data: invoiceData, isLoading: isLoadingInvoice } =
useGetInvoiceDetail(id);
const { mutate: submitUpdate, isPending } = useUpdateInvoice();
const multiUpload = useMultiUpload();
const invoice = invoiceData?.data;
const [items, setItems] = useState<CreatePreInvoiceItemType[]>([
createEmptyItem(),
]);
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
const formik = useFormik<
Pick<
UpdatePreInvoiceType,
"enableTax" | "approvalDeadline" | "paymentMethod" | "description"
>
>({
initialValues: {
enableTax: false,
approvalDeadline: "",
paymentMethod: "",
description: "",
},
onSubmit: async (values) => {
const validItems = items.filter(
(i) => i.productId && i.quantity > 0 && i.unitPrice >= 0
);
if (validItems.length === 0) {
toast(
"حداقل یک آیتم با محصول، تعداد و مبلغ واحد معتبر اضافه کنید.",
"error"
);
return;
}
const attachments: { type: string; url: string }[] =
invoice?.attachments?.map((a) => ({ type: a.type, url: a.url })) ?? [];
if (attachmentFiles.length) {
const uploadResult = await multiUpload.mutateAsync(attachmentFiles);
uploadResult?.data?.forEach((item) => {
attachments.push({
type: "uploads_attach",
url: item.url,
});
});
}
const params: UpdatePreInvoiceType = {
items: validItems.map((i) => ({
...(i.id ? { id: i.id } : {}),
productId: i.productId,
quantity: i.quantity,
unitPrice: i.unitPrice,
...(i.discountPercent != null && i.discountPercent > 0
? { discountPercent: i.discountPercent }
: { discount: i.discount || 0 }),
description: i.description || "",
})),
enableTax: values.enableTax,
...(values.approvalDeadline
? {
approvalDeadline: moment(
values.approvalDeadline,
"jYYYY/jMM/jDD"
).format("YYYY-MM-DD"),
}
: {}),
...(values.paymentMethod?.trim()
? { paymentMethod: values.paymentMethod.trim() }
: {}),
description: values.description,
...(attachments.length ? { attachments } : {}),
};
if (!id) return;
submitUpdate(
{ id, params },
{
onSuccess: () => {
toast("پیش فاکتور با موفقیت ویرایش شد", "success");
navigate(Paths.perfomaInvoice.list);
},
onError: (error) => {
toast(extractErrorMessage(error), "error");
},
}
);
},
});
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,
value: string | number | null
) => {
setItems((prev) =>
prev.map((item, itemIndex) =>
itemIndex === index ? { ...item, [field]: value } : item
)
);
};
const addItem = () => {
const current = items[items.length - 1];
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 + getItemLineTotal(i), 0);
if (isLoadingInvoice || !invoice) {
return (
<div className="mt-5 flex items-center justify-center min-h-[200px]">
<div className="text-[#888888]">در حال بارگذاری...</div>
</div>
);
}
return (
<div className="mt-5">
<div className="justify-between items-center flex">
<h1 className="text-lg font-light">
ویرایش پیش فاکتور #{invoice.invoiceNumber}
</h1>
<Button
className="w-fit px-5"
onClick={() => formik.handleSubmit()}
isLoading={isPending || multiUpload.isPending}
>
<div className="flex gap-2 items-center">
<TickSquare size={20} color="black" />
<div>ذخیره تغییرات</div>
</div>
</Button>
</div>
<div className="bg-white rounded-3xl p-6 mt-8">
<div className="font-light">اطلاعات پیش فاکتور</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>
</div>
</div>
<div className="mt-6">
{items.map((item, index) => (
<InvoiceItemRow
key={index}
item={item}
index={index}
onChange={handleItemChange}
onRemove={() => removeItem(index)}
canRemove={items.length > 1}
/>
))}
<div className="mt-4 flex justify-center">
<button
type="button"
onClick={addItem}
className="h-10 px-4 rounded-xl bg-primary flex items-center gap-2 hover:opacity-90 transition-opacity"
aria-label="افزودن آیتم"
>
<Add size={18} color="black" />
<span className="text-[13px] font-light">افزودن آیتم</span>
</button>
</div>
</div>
<div className="mt-6 flex gap-2 items-center">
<Checkbox
id="enableTax"
checked={formik.values.enableTax}
onCheckedChange={(checked) =>
formik.setFieldValue("enableTax", !!checked)
}
/>
<label
htmlFor="enableTax"
className="text-[13px] cursor-pointer select-none"
>
مالیات بر ارزش افزوده (۱۰ درصد)
</label>
</div>
<div className="rowTwoInput mt-6">
<DatePickerComponent
label="مهلت تایید"
placeholder="انتخاب تاریخ"
defaultValue={formik.values.approvalDeadline || undefined}
onChange={(date) => {
formik.setFieldValue("approvalDeadline", date);
formik.setFieldTouched("approvalDeadline", true);
}}
error_text={
formik.errors.approvalDeadline && formik.touched.approvalDeadline
? formik.errors.approvalDeadline
: ""
}
/>
</div>
<div className="mt-6">
<Textarea
label="روش پرداخت"
name="paymentMethod"
placeholder="روش پرداخت"
value={formik.values.paymentMethod}
onChange={(e) => formik.setFieldValue("paymentMethod", e.target.value)}
onBlur={formik.handleBlur}
/>
</div>
<div className="mt-6">
<Textarea
label="توضیحات"
placeholder="توضیحات پیش فاکتور را وارد کنید..."
value={formik.values.description}
onChange={(e) => formik.setFieldValue("description", e.target.value)}
onBlur={formik.handleBlur}
name="description"
/>
</div>
<div className="mt-6">
<UploadBox
isMultiple
label="فایل ضمیمه"
onChange={setAttachmentFiles}
/>
</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]">
{totalAmount.toLocaleString("fa-IR")} ریال
</div>
</div>
</div>
</div>
);
};
export default UpdateInvoice;