2024-07-19 15:22:13 +00:00
|
|
|
import { FormPercentageField } from "@/components";
|
2024-07-18 18:26:44 +00:00
|
|
|
import { useLocalization } from "@/lib/hooks";
|
2024-07-19 15:22:13 +00:00
|
|
|
import { Card, CardContent, CardDescription, CardTitle, Separator } from "@/ui";
|
2025-04-14 19:12:16 +00:00
|
|
|
import { CurrencyData, MoneyValue, Percentage } from "@shared/contexts";
|
2024-07-19 15:22:13 +00:00
|
|
|
import { t } from "i18next";
|
2024-08-16 14:57:44 +00:00
|
|
|
import { useMemo } from "react";
|
2025-04-14 19:12:16 +00:00
|
|
|
import { useFormContext, useWatch } from "react-hook-form";
|
2024-07-18 16:41:46 +00:00
|
|
|
|
2025-04-14 19:12:16 +00:00
|
|
|
const calculateQuoteTotals = (prices: any) => {
|
|
|
|
|
console.debug("calculateQuoteTotals", prices);
|
|
|
|
|
|
|
|
|
|
const discountOrError = Percentage.create(
|
|
|
|
|
prices[1] || {
|
|
|
|
|
amount: null,
|
|
|
|
|
scale: 2,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (discountOrError.isFailure) {
|
|
|
|
|
throw discountOrError.error;
|
|
|
|
|
}
|
|
|
|
|
const discount = discountOrError.object;
|
|
|
|
|
|
|
|
|
|
const taxOrError = Percentage.create(
|
|
|
|
|
prices[2] || {
|
|
|
|
|
amount: null,
|
|
|
|
|
scale: 2,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (taxOrError.isFailure) {
|
|
|
|
|
throw taxOrError.error;
|
|
|
|
|
}
|
|
|
|
|
const tax = taxOrError.object;
|
|
|
|
|
|
|
|
|
|
const subtotalOrError = MoneyValue.create(
|
|
|
|
|
prices[0] || {
|
|
|
|
|
amount: null,
|
|
|
|
|
scale: 2,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (subtotalOrError.isFailure) {
|
|
|
|
|
throw subtotalOrError.error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const subtotalPrice = subtotalOrError.object;
|
|
|
|
|
|
|
|
|
|
const discountPrice = subtotalPrice.percentage(discount.toNumber()).convertScale(2);
|
|
|
|
|
|
|
|
|
|
const priceBeforeTaxes = subtotalPrice.subtract(discountPrice).convertScale(2);
|
|
|
|
|
|
|
|
|
|
const taxesPrice = priceBeforeTaxes.percentage(tax.toNumber()).convertScale(2);
|
|
|
|
|
|
|
|
|
|
const totalPrice = priceBeforeTaxes.add(taxesPrice).convertScale(2);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
subtotalPrice: subtotalPrice.toObject(),
|
|
|
|
|
discount: discount.toObject(),
|
|
|
|
|
discountPrice: discountPrice.toObject(),
|
|
|
|
|
priceBeforeTaxes: priceBeforeTaxes.toObject(),
|
|
|
|
|
tax: tax.toObject(),
|
|
|
|
|
taxesPrice: taxesPrice.toObject(),
|
|
|
|
|
totalPrice: totalPrice.toObject(),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
|
2026-09-16 17:43:10 +00:00
|
|
|
const { formState, control } = useFormContext();
|
2024-07-19 15:59:50 +00:00
|
|
|
const { formatNumber } = useLocalization();
|
2024-07-18 16:41:46 +00:00
|
|
|
|
2025-04-14 19:12:16 +00:00
|
|
|
const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] });
|
|
|
|
|
|
|
|
|
|
const totals = calculateQuoteTotals(pricesWatch);
|
2024-07-18 16:41:46 +00:00
|
|
|
|
2025-04-14 19:12:16 +00:00
|
|
|
const subtotal_price = formatNumber(totals.subtotalPrice);
|
|
|
|
|
const discount_price = formatNumber(totals.discountPrice);
|
|
|
|
|
const tax_price = formatNumber(totals.taxesPrice);
|
|
|
|
|
const total_price = formatNumber(totals.totalPrice);
|
|
|
|
|
|
|
|
|
|
const currency_symbol = useMemo(() => currency.symbol || "", [currency]);
|
2024-08-16 14:57:44 +00:00
|
|
|
|
2024-07-18 16:41:46 +00:00
|
|
|
return (
|
2024-08-29 11:59:12 +00:00
|
|
|
<Card className='w-full bg-muted'>
|
2024-07-18 16:41:46 +00:00
|
|
|
<CardContent className='flex flex-row items-end gap-2 p-4 border-t'>
|
|
|
|
|
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
2024-08-29 11:59:12 +00:00
|
|
|
<div className='grid gap-1 font-semibold text-right text-muted-foreground'>
|
2024-08-16 14:57:44 +00:00
|
|
|
<CardDescription className='text-sm'>
|
|
|
|
|
{t("quotes.form_fields.subtotal_price.label")}
|
|
|
|
|
</CardDescription>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardTitle className='flex items-baseline justify-end text-2xl tabular-nums'>
|
2024-07-18 18:26:44 +00:00
|
|
|
{subtotal_price}
|
2025-04-14 19:12:16 +00:00
|
|
|
|
2024-08-16 14:57:44 +00:00
|
|
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
2024-07-18 16:41:46 +00:00
|
|
|
</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
2024-07-19 15:22:13 +00:00
|
|
|
<div className='grid flex-1 h-16 grid-cols-2 gap-6 auto-rows-max'>
|
2024-07-18 16:41:46 +00:00
|
|
|
<div className='grid gap-1 font-medium text-muted-foreground'>
|
2024-07-19 15:22:13 +00:00
|
|
|
<CardDescription className='text-sm'>
|
|
|
|
|
{t("quotes.form_fields.discount.label")}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
<FormPercentageField
|
|
|
|
|
scale={2}
|
|
|
|
|
disabled={formState.disabled}
|
|
|
|
|
placeholder={t("quotes.form_fields.discount.placeholder")}
|
2026-09-16 17:43:10 +00:00
|
|
|
name='discount'
|
2025-04-14 19:12:16 +00:00
|
|
|
onChange={(value) => {
|
|
|
|
|
console.log("discount", value);
|
|
|
|
|
}}
|
2024-07-19 15:22:13 +00:00
|
|
|
/>
|
2024-07-18 16:41:46 +00:00
|
|
|
</div>
|
|
|
|
|
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardDescription className='text-sm text-right'>
|
2024-08-16 14:57:44 +00:00
|
|
|
{t("quotes.form_fields.discount_price.label")}
|
|
|
|
|
</CardDescription>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardTitle className='flex items-baseline justify-end text-2xl tabular-nums'>
|
2024-07-19 15:22:13 +00:00
|
|
|
{discount_price}
|
2024-08-16 14:57:44 +00:00
|
|
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
2024-07-18 16:41:46 +00:00
|
|
|
</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
2024-07-19 15:22:13 +00:00
|
|
|
<div className='grid flex-1 h-16 grid-cols-2 gap-6 auto-rows-max'>
|
2024-07-18 16:41:46 +00:00
|
|
|
<div className='grid gap-1 font-medium text-muted-foreground'>
|
2024-07-19 15:22:13 +00:00
|
|
|
<CardDescription className='text-sm'>
|
|
|
|
|
{t("quotes.form_fields.tax.label")}
|
|
|
|
|
</CardDescription>
|
|
|
|
|
|
|
|
|
|
<FormPercentageField
|
|
|
|
|
scale={2}
|
|
|
|
|
disabled={formState.disabled}
|
|
|
|
|
placeholder={t("quotes.form_fields.tax.placeholder")}
|
2026-09-16 17:43:10 +00:00
|
|
|
name='tax'
|
2024-07-19 15:22:13 +00:00
|
|
|
/>
|
2024-07-18 16:41:46 +00:00
|
|
|
</div>
|
|
|
|
|
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardDescription className='text-sm text-right'>
|
2024-08-16 14:57:44 +00:00
|
|
|
{t("quotes.form_fields.tax_price.label")}
|
|
|
|
|
</CardDescription>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardTitle className='flex items-baseline justify-end gap-1 text-2xl tabular-nums'>
|
2024-07-19 15:22:13 +00:00
|
|
|
{tax_price}
|
2024-08-16 14:57:44 +00:00
|
|
|
<span className='text-base font-medium tracking-normal'>{currency_symbol}</span>
|
2024-07-18 16:41:46 +00:00
|
|
|
</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</div>{" "}
|
|
|
|
|
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
|
|
|
|
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
|
|
|
|
<div className='grid gap-0'>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardDescription className='text-sm font-semibold text-right text-foreground'>
|
2024-08-16 14:57:44 +00:00
|
|
|
{t("quotes.form_fields.total_price.label")}
|
|
|
|
|
</CardDescription>
|
2024-08-29 11:59:12 +00:00
|
|
|
<CardTitle className='flex items-baseline justify-end gap-1 text-3xl tabular-nums'>
|
2024-07-18 18:26:44 +00:00
|
|
|
{total_price}
|
2024-08-16 14:57:44 +00:00
|
|
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
2024-07-18 16:41:46 +00:00
|
|
|
</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
};
|