Presupuestador_web/client/src/app/quotes/edit.tsx

202 lines
6.3 KiB
TypeScript
Raw Normal View History

2024-07-09 16:21:12 +00:00
import { FormMoneyField, LoadingOverlay, SubmitButton } from "@/components";
import { calculateItemTotals } from "@/lib/calc";
2024-07-01 17:12:15 +00:00
import { useGetIdentity } from "@/lib/hooks";
2024-07-09 16:21:12 +00:00
import { useUrlId } from "@/lib/hooks/useUrlId";
2024-07-01 17:12:15 +00:00
import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
2024-07-09 16:21:12 +00:00
import { IUpdateQuote_Request_DTO, MoneyValue } from "@shared/contexts";
2024-07-01 17:12:15 +00:00
import { t } from "i18next";
2024-07-09 16:21:12 +00:00
import { ChevronLeftIcon } from "lucide-react";
import { useEffect, useState } from "react";
2024-07-01 17:12:15 +00:00
import { SubmitHandler, useForm } from "react-hook-form";
2024-07-09 16:21:12 +00:00
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
2024-07-01 17:12:15 +00:00
import { useQuotes } from "./hooks";
2024-07-09 16:21:12 +00:00
// simple typesafe helperfunction
type EndsWith<T, b extends string> = T extends `${infer f}${b}` ? T : never;
const endsWith = <T extends string, b extends string>(str: T, prefix: b): str is EndsWith<T, b> =>
str.endsWith(prefix);
interface QuoteDataForm extends IUpdateQuote_Request_DTO {
/*status: string;
2024-07-01 17:12:15 +00:00
date: string;
reference: string;
customer_information: string;
lang_code: string;
currency_code: string;
payment_method: string;
notes: string;
validity: string;
2024-07-09 16:21:12 +00:00
discount: IPercentage;
2024-07-01 17:12:15 +00:00
2024-07-09 16:21:12 +00:00
subtotal: IMoney;
items: {
quantity: IQuantity;
description: string;
2024-07-09 16:21:21 +00:00
unit_price: IMoney;
2024-07-09 16:21:12 +00:00
price: IMoney;
discount: IPercentage;
total: IMoney;
}[];*/
}
2024-07-01 17:12:15 +00:00
2024-07-09 16:21:12 +00:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const QuoteEdit = () => {
2024-07-01 17:12:15 +00:00
const [loading, setLoading] = useState(false);
2024-07-09 16:21:12 +00:00
const quoteId = useUrlId();
2024-07-01 17:12:15 +00:00
const { data: userIdentity } = useGetIdentity();
2024-07-09 16:21:12 +00:00
const { useOne, useUpdate } = useQuotes();
2024-07-01 17:12:15 +00:00
2024-07-09 16:21:12 +00:00
const { data, status } = useOne(quoteId);
const { mutate } = useUpdate(quoteId);
2024-07-01 17:12:15 +00:00
const form = useForm<QuoteDataForm>({
mode: "onBlur",
values: data,
defaultValues: {
date: "",
reference: "",
customer_information: "",
lang_code: "",
currency_code: "",
payment_method: "",
notes: "",
validity: "",
2024-07-09 16:21:12 +00:00
subtotal: "",
2024-07-01 17:12:15 +00:00
items: [],
},
});
const onSubmit: SubmitHandler<QuoteDataForm> = async (data) => {
2024-07-09 16:21:12 +00:00
console.debug(JSON.stringify(data));
2024-07-01 17:12:15 +00:00
try {
setLoading(true);
2024-07-09 16:21:12 +00:00
// Transformación del form -> typo de request
mutate(data, {
onError: (error) => {
alert(error);
},
//onSettled: () => {},
onSuccess: () => {
alert("guardado");
},
});
2024-07-01 17:12:15 +00:00
} finally {
setLoading(false);
}
};
2024-07-09 16:21:12 +00:00
const { watch, getValues, setValue } = form;
useEffect(() => {
const { unsubscribe } = watch((_, { name, type }) => {
const value = getValues();
console.debug({ name, type });
if (name) {
if (name === "items") {
const { items } = value;
let quoteSubtotal = MoneyValue.create().object;
// Recálculo líneas
items.map((item, index) => {
const itemTotals = calculateItemTotals(item);
2024-07-09 16:21:21 +00:00
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
2024-07-09 16:21:12 +00:00
2024-07-09 16:21:21 +00:00
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
2024-07-09 16:21:12 +00:00
});
console.log(quoteSubtotal.toFormat());
// Recálculo completo
setValue("subtotal", quoteSubtotal.toObject());
}
if (
endsWith(name, "quantity") ||
endsWith(name, "retail_price") ||
endsWith(name, "discount")
) {
const { items } = value;
const [, indexString, fieldName] = String(name).split(".");
const index = parseInt(indexString);
const itemTotals = calculateItemTotals(items[index]);
2024-07-09 16:21:21 +00:00
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
2024-07-09 16:21:12 +00:00
// Recálculo completo
}
}
});
return () => unsubscribe();
}, [watch, getValues, setValue]);
if (status !== "success") {
return <LoadingOverlay />;
}
2024-07-01 17:12:15 +00:00
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
<div className='flex items-center gap-4'>
<Button variant='outline' size='icon' className='h-7 w-7'>
2024-07-09 16:21:12 +00:00
<ChevronLeftIcon className='w-4 h-4' />
2024-07-01 17:12:15 +00:00
<span className='sr-only'>{t("quotes.common.back")}</span>
</Button>
<h1 className='flex-1 text-xl font-semibold tracking-tight shrink-0 whitespace-nowrap sm:grow-0'>
2024-07-09 16:21:12 +00:00
{t("quotes.edit.title")}
2024-07-01 17:12:15 +00:00
</h1>
<Badge variant='default' className='ml-auto sm:ml-0'>
2024-07-09 16:21:12 +00:00
{data.status}
2024-07-01 17:12:15 +00:00
</Badge>
<div className='items-center hidden gap-2 md:ml-auto md:flex'>
<Button variant='outline' size='sm'>
2024-07-09 16:21:12 +00:00
{t("common.cancel")}
2024-07-01 17:12:15 +00:00
</Button>
<SubmitButton variant={form.formState.isDirty ? "default" : "outline"} size='sm'>
2024-07-09 16:21:12 +00:00
{t("common.save")}
2024-07-01 17:12:15 +00:00
</SubmitButton>
</div>
</div>
2024-07-09 16:21:12 +00:00
<FormMoneyField
2024-07-09 16:21:21 +00:00
label={"subtotal_price"}
2024-07-09 16:21:12 +00:00
disabled={form.formState.disabled}
2024-07-09 16:21:21 +00:00
{...form.register("subtotal_price")}
2024-07-09 16:21:12 +00:00
/>
<Tabs defaultValue='items' className='space-y-4'>
2024-07-01 17:12:15 +00:00
<TabsList>
<TabsTrigger value='general'>{t("quotes.create.tabs.general")}</TabsTrigger>
<TabsTrigger value='items'>{t("quotes.create.tabs.items")}</TabsTrigger>
<TabsTrigger value='history'>{t("quotes.create.tabs.history")}</TabsTrigger>
</TabsList>
<TabsContent value='general'>
<QuoteGeneralCardEditor />
</TabsContent>
<TabsContent value='items'>
<QuoteDetailsCardEditor />
</TabsContent>
<TabsContent value='history'></TabsContent>
</Tabs>
<div className='flex items-center justify-center gap-2 md:hidden'>
<Button variant='outline' size='sm'>
{t("quotes.create.buttons.discard")}
</Button>
<Button size='sm'>{t("quotes.create.buttons.save_quote")}</Button>
</div>
</div>
</form>
</Form>
);
};