.
This commit is contained in:
parent
aabe3579ab
commit
5e4d58c112
@ -1,14 +1,18 @@
|
||||
import { DollarSign } from "lucide-react";
|
||||
|
||||
import { useLocalization } from "@/lib/hooks";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Separator } from "@/ui";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
export const QuotePricesResume = () => {
|
||||
const { getValues } = useFormContext();
|
||||
//const { formatNumber } = useLocalization({ locale: "es-ES" });
|
||||
const { formatNumber, formatCurrency, formatPercentage } = useLocalization();
|
||||
|
||||
const subtotal_price = getValues("subtotal_price");
|
||||
const discount = getValues("discount");
|
||||
console.log(getValues());
|
||||
|
||||
const subtotal_price = formatNumber(getValues("subtotal_price"));
|
||||
const discount = formatPercentage(getValues("discount"));
|
||||
const total_price = formatCurrency(getValues("total_price"));
|
||||
|
||||
return (
|
||||
<Card className='w-full'>
|
||||
@ -16,9 +20,9 @@ export const QuotePricesResume = () => {
|
||||
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>Importe neto</CardDescription>
|
||||
<CardTitle className='items-baseline gap-1 text-3xl tabular-nums'>
|
||||
{subtotal_price.amount / 100}
|
||||
<span className='text-base tracking-normal'>€</span>
|
||||
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
||||
{subtotal_price}
|
||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
@ -26,16 +30,16 @@ export const QuotePricesResume = () => {
|
||||
<div className='grid flex-1 h-16 grid-cols-2 gap-2 auto-rows-max'>
|
||||
<div className='grid gap-1 font-medium text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>Descuento</CardDescription>
|
||||
<CardTitle className='flex items-baseline gap-1 text-2xl tabular-nums'>
|
||||
{discount.amount / 100}
|
||||
<CardTitle className='flex items-baseline gap-1 text-xl tabular-nums'>
|
||||
{discount}
|
||||
<span className='text-base tracking-normal'>%</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>Imp. descuento</CardDescription>
|
||||
<CardTitle className='flex items-baseline gap-1 text-3xl tabular-nums'>
|
||||
{subtotal_price.amount / 100}
|
||||
<span className='text-base font-medium tracking-normal'>€</span>
|
||||
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
||||
{subtotal_price}
|
||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,15 +47,15 @@ export const QuotePricesResume = () => {
|
||||
<div className='grid flex-1 h-16 grid-cols-2 gap-2 auto-rows-max'>
|
||||
<div className='grid gap-1 font-medium text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>IVA</CardDescription>
|
||||
<CardTitle className='flex items-baseline gap-1 text-2xl tabular-nums'>
|
||||
{discount.amount / 100}
|
||||
<CardTitle className='flex items-baseline gap-1 text-xl tabular-nums'>
|
||||
{discount}
|
||||
<span className='text-base tracking-normal'>%</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>Importe IVA</CardDescription>
|
||||
<CardTitle className='flex items-baseline gap-1 text-3xl tabular-nums'>
|
||||
{subtotal_price.amount / 100}
|
||||
<CardTitle className='flex items-baseline gap-1 text-2xl tabular-nums'>
|
||||
{subtotal_price}
|
||||
<span className='text-base font-medium tracking-normal'>€</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
@ -60,9 +64,9 @@ export const QuotePricesResume = () => {
|
||||
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
||||
<div className='grid gap-0'>
|
||||
<CardDescription className='text-sm font-semibold'>Importe total</CardDescription>
|
||||
<CardTitle className='flex items-baseline gap-1 text-4xl tabular-nums'>
|
||||
{subtotal_price.amount / 100}
|
||||
<span className='text-base font-medium tracking-normal'>€</span>
|
||||
<CardTitle className='flex items-baseline gap-1 text-3xl tabular-nums'>
|
||||
{total_price}
|
||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -24,6 +24,34 @@ import { useQuotes } from "./hooks";
|
||||
|
||||
type QuoteDataForm = IGetQuote_Response_DTO;
|
||||
|
||||
const recalculateItemTotals = (items, setValue) => {
|
||||
let quoteSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
scale: 4,
|
||||
}).object;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const itemTotals = calculateItemTotals(item);
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
});
|
||||
|
||||
setValue("subtotal_price", quoteSubtotal.convertScale(2).toObject());
|
||||
};
|
||||
|
||||
const updateCurrency = (value, setQuoteCurrency) => {
|
||||
setQuoteCurrency(
|
||||
CurrencyData.createFromCode(value.currency_code ?? CurrencyData.DEFAULT_CURRENCY_CODE).object
|
||||
);
|
||||
};
|
||||
|
||||
const updateLanguage = (value, setQuoteLanguage) => {
|
||||
setQuoteLanguage(
|
||||
Language.createFromCode(value.lang_code ?? Language.DEFAULT_LANGUAGE_CODE).object
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const QuoteEdit = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -111,6 +139,7 @@ export const QuoteEdit = () => {
|
||||
mode: "onBlur",
|
||||
values: data,
|
||||
defaultValues,
|
||||
//shouldUnregister: true,
|
||||
});
|
||||
|
||||
const { watch, getValues, setValue, formState } = form;
|
||||
@ -146,7 +175,7 @@ export const QuoteEdit = () => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
/*useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { unsubscribe } = watch((_, { name, type }) => {
|
||||
const value = getValues();
|
||||
@ -183,7 +212,7 @@ export const QuoteEdit = () => {
|
||||
});
|
||||
|
||||
// Recálculo completo
|
||||
setValue("subtotal_price", quoteSubtotal.toObject());
|
||||
setValue("subtotal_price", quoteSubtotal.convertScale(2).toObject());
|
||||
}
|
||||
|
||||
if (name.endsWith("quantity") || name.endsWith("unit_price") || name.endsWith("discount")) {
|
||||
@ -197,11 +226,66 @@ export const QuoteEdit = () => {
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
|
||||
// Recálculo completo
|
||||
let quoteSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
scale: 4,
|
||||
}).object;
|
||||
|
||||
items &&
|
||||
items.map((item) => {
|
||||
const itemTotals = calculateItemTotals(item);
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
});
|
||||
|
||||
// Recálculo completo
|
||||
setValue("subtotal_price", quoteSubtotal.convertScale(2).toObject());
|
||||
}
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [watch, getValues, setValue]);
|
||||
}, [watch, getValues, setValue]);*/
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = watch((_, { name }) => {
|
||||
const value = getValues();
|
||||
|
||||
if (name) {
|
||||
switch (true) {
|
||||
case name === "currency_code":
|
||||
updateCurrency(value, setQuoteCurrency);
|
||||
break;
|
||||
|
||||
case name === "lang_code":
|
||||
updateLanguage(value, setQuoteLanguage);
|
||||
break;
|
||||
|
||||
case name === "items":
|
||||
recalculateItemTotals(value.items, setValue);
|
||||
break;
|
||||
|
||||
case name.endsWith("quantity") ||
|
||||
name.endsWith("unit_price") ||
|
||||
name.endsWith("discount"): {
|
||||
const [, indexString] = String(name).split(".");
|
||||
const index = parseInt(indexString);
|
||||
|
||||
const itemTotals = calculateItemTotals(value.items[index]);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
|
||||
recalculateItemTotals(value.items, setValue);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [watch, getValues, setValue, setQuoteCurrency, setQuoteLanguage]);
|
||||
|
||||
if (isSubmitting) {
|
||||
return <LoadingOverlay title='Guardando cotización' />;
|
||||
|
||||
@ -1,52 +1,88 @@
|
||||
/* https://github.com/mayank8aug/use-localization/blob/main/src/index.ts */
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { LocaleToCurrencyTable, rtlLangsList } from "./utils";
|
||||
import { IMoney, IPercentage, IQuantity } from "@/lib/types";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type UseLocalizationProps = {
|
||||
locale: string;
|
||||
};
|
||||
|
||||
export const useLocalization = (props: UseLocalizationProps) => {
|
||||
const adjustPrecision = ({ amount, scale }: { amount: number; scale: number }) => {
|
||||
const factor = 10 ** scale;
|
||||
return Number(amount) / factor;
|
||||
};
|
||||
|
||||
export const useLocalization = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return useCustomLocalization({
|
||||
locale: i18n.language,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCustomLocalization = (props: UseLocalizationProps) => {
|
||||
const { locale } = props;
|
||||
const [lang, loc] = locale.split("-");
|
||||
|
||||
//const { i18n } = useTranslation();
|
||||
|
||||
// Obtener el idioma actual
|
||||
// const currentLanguage = i18n.language;
|
||||
|
||||
const formatCurrency = useCallback(
|
||||
(value: number) => {
|
||||
(value: IMoney | null) => {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { amount, scale, currency_code } = value;
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency: LocaleToCurrencyTable[locale],
|
||||
}).format(value);
|
||||
currency: currency_code,
|
||||
currencyDisplay: "symbol",
|
||||
maximumFractionDigits: scale,
|
||||
}).format(amount === null ? 0 : amount);
|
||||
},
|
||||
[locale]
|
||||
);
|
||||
|
||||
const formatNumber = useCallback(
|
||||
(value: number) => {
|
||||
return new Intl.NumberFormat(locale).format(value);
|
||||
(value: IMoney | IPercentage | IQuantity | null) => {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { amount, scale } = value;
|
||||
|
||||
const result = new Intl.NumberFormat(locale, {
|
||||
minimumSignificantDigits: scale,
|
||||
maximumSignificantDigits: scale,
|
||||
minimumFractionDigits: scale,
|
||||
useGrouping: true,
|
||||
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));
|
||||
|
||||
console.log(value, result);
|
||||
|
||||
return result;
|
||||
},
|
||||
[locale]
|
||||
);
|
||||
|
||||
const flag = useMemo(
|
||||
() =>
|
||||
typeof String.fromCodePoint !== "undefined"
|
||||
? loc
|
||||
.toUpperCase()
|
||||
.replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397))
|
||||
: "",
|
||||
[loc]
|
||||
const formatPercentage = useCallback(
|
||||
(value: IPercentage | null) => {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { amount, scale } = value;
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: "decimal",
|
||||
minimumFractionDigits: scale,
|
||||
}).format(amount === null ? 0 : amount);
|
||||
},
|
||||
[locale]
|
||||
);
|
||||
|
||||
return {
|
||||
formatCurrency,
|
||||
formatNumber,
|
||||
flag,
|
||||
isRTL: rtlLangsList.includes(lang),
|
||||
formatPercentage,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
import {
|
||||
IMoney_Response_DTO,
|
||||
IPercentage_Response_DTO,
|
||||
IQuantity_Response_DTO,
|
||||
} from "@shared/contexts";
|
||||
import { IMoney_DTO, IPercentage_DTO, IQuantity_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IMoney extends IMoney_Response_DTO {}
|
||||
export interface IQuantity extends IQuantity_Response_DTO {}
|
||||
export interface IPercentage extends IPercentage_Response_DTO {}
|
||||
export interface IMoney extends IMoney_DTO {}
|
||||
export interface IQuantity extends IQuantity_DTO {}
|
||||
export interface IPercentage extends IPercentage_DTO {}
|
||||
|
||||
@ -26,9 +26,9 @@ export const GetQuotePresenter: IGetQuotePresenter = {
|
||||
validity: quote.validity.toString(),
|
||||
notes: quote.notes.toString(),
|
||||
|
||||
subtotal_price: quote.subtotalPrice.toObject(),
|
||||
discount: quote.discount.toObject(),
|
||||
total_price: quote.totalPrice.toObject(),
|
||||
subtotal_price: quote.subtotalPrice.convertScale(2).toObject(),
|
||||
discount: quote.discount.convertScale(2).toObject(),
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
items: quoteItemPresenter(quote.items, context),
|
||||
dealer_id: quote.dealerId.toString(),
|
||||
@ -45,10 +45,10 @@ const quoteItemPresenter = (
|
||||
? items.items.map((item: QuoteItem) => ({
|
||||
article_id: item.articleId.toString(),
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toObject(),
|
||||
unit_price: item.unitPrice.toObject(),
|
||||
subtotal_price: item.subtotalPrice.toObject(),
|
||||
discount: item.discount.toObject(),
|
||||
total_price: item.totalPrice.toObject(),
|
||||
quantity: item.quantity.convertScale(2).toObject(),
|
||||
unit_price: item.unitPrice.convertScale(4).toObject(),
|
||||
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
|
||||
discount: item.discount.convertScale(2).toObject(),
|
||||
total_price: item.totalPrice.convertScale(4).toObject(),
|
||||
}))
|
||||
: [];
|
||||
|
||||
@ -50,9 +50,9 @@ export class Quote_Model extends Model<
|
||||
declare notes: CreationOptional<string>;
|
||||
declare validity: CreationOptional<string>;
|
||||
|
||||
declare subtotal_price: CreationOptional<number>;
|
||||
declare subtotal_price: CreationOptional<number | null>;
|
||||
declare discount: CreationOptional<number | null>;
|
||||
declare total_price: CreationOptional<number>;
|
||||
declare total_price: CreationOptional<number | null>;
|
||||
|
||||
declare items: NonAttribute<QuoteItem_Model[]>;
|
||||
declare dealer: NonAttribute<Dealer_Model>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user