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