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;
|
|
|
|
|
retail_price: IMoney;
|
|
|
|
|
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);
|
|
|
|
|
quoteSubtotal = quoteSubtotal.add(itemTotals.total);
|
|
|
|
|
|
|
|
|
|
setValue(`items.${index}.price`, itemTotals.price.toObject());
|
|
|
|
|
setValue(`items.${index}.total`, itemTotals.total.toObject());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
|
|
setValue(`items.${index}.price`, itemTotals.price.toObject());
|
|
|
|
|
setValue(`items.${index}.total`, itemTotals.total.toObject());
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
label={"subtotal"}
|
|
|
|
|
disabled={form.formState.disabled}
|
|
|
|
|
{...form.register("subtotal")}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
};
|