146 lines
5.3 KiB
TypeScript
146 lines
5.3 KiB
TypeScript
import { formatCurrency } from "@erp/core";
|
|
import {
|
|
FieldDescription,
|
|
FieldGroup,
|
|
FieldLegend,
|
|
FieldSet,
|
|
Separator,
|
|
} from "@repo/shadcn-ui/components";
|
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
import { ReceiptIcon } from "lucide-react";
|
|
import { ComponentProps } from "react";
|
|
import { useFormContext, useWatch } from "react-hook-form";
|
|
import { useInvoiceContext } from "../../context";
|
|
import { useTranslation } from "../../i18n";
|
|
import { InvoiceFormData } from "../../schemas";
|
|
import { PercentageInputField } from "./items/percentage-input-field";
|
|
|
|
export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
|
const { t } = useTranslation();
|
|
const { control, getValues } = useFormContext<InvoiceFormData>();
|
|
const { currency_code, language_code, readOnly, taxCatalog } = useInvoiceContext();
|
|
|
|
const displayTaxes = useWatch({
|
|
control,
|
|
name: "taxes",
|
|
defaultValue: [],
|
|
});
|
|
|
|
const subtotal_amount = useWatch({
|
|
control,
|
|
name: "subtotal_amount",
|
|
defaultValue: 0,
|
|
});
|
|
|
|
return (
|
|
<FieldSet {...props}>
|
|
<FieldLegend className='hidden'>
|
|
<ReceiptIcon className='size-6 text-muted-foreground' />
|
|
{t("form_groups.totals.title")}
|
|
</FieldLegend>
|
|
|
|
<FieldDescription className='hidden'>{t("form_groups.totals.description")}</FieldDescription>
|
|
<FieldGroup className='grid grid-cols-1 border rounded-lg bg-muted/10 p-4 gap-4'>
|
|
<div className='space-y-1.5'>
|
|
{/* Sección: Subtotal y Descuentos */}
|
|
<div className='flex justify-between text-sm'>
|
|
<span className='text-muted-foreground'>Subtotal sin descuento</span>
|
|
<span className='font-medium tabular-nums text-muted-foreground'>
|
|
{formatCurrency(subtotal_amount, 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
|
|
<div className='flex justify-between text-sm'>
|
|
<div className='flex items-center gap-3'>
|
|
<span className='text-muted-foreground'>Descuento global</span>
|
|
<PercentageInputField
|
|
control={control}
|
|
name={"discount_percentage"}
|
|
readOnly={readOnly}
|
|
inputId={"header-discount-percentage"}
|
|
showSuffix={true}
|
|
className={cn(
|
|
"w-20 text-right tabular-nums bg-background",
|
|
"border-input border text-sm shadow-xs"
|
|
)}
|
|
/>
|
|
</div>
|
|
<span className='font-medium text-destructive tabular-nums'>
|
|
-{formatCurrency(getValues("discount_amount"), 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Sección: Base Imponible */}
|
|
<div className='flex justify-between text-sm'>
|
|
<span className='text-foreground'>Base imponible</span>
|
|
<span className='font-medium tabular-nums'>
|
|
{formatCurrency(getValues("taxable_amount"), 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Sección: Impuestos */}
|
|
<div className='space-y-1.5'>
|
|
<h3 className='text-xs font-semibold text-muted-foreground uppercase tracking-wide'>
|
|
Impuestos y retenciones
|
|
</h3>
|
|
|
|
{taxCatalog.groups().map((group) => {
|
|
// Filtra impuestos de ese grupo
|
|
const taxesInGroup = displayTaxes?.filter((item) => {
|
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
|
(t) => t,
|
|
() => undefined
|
|
);
|
|
return tax?.group === group;
|
|
});
|
|
|
|
// Si el grupo no tiene impuestos, no renderiza nada
|
|
if (taxesInGroup?.length === 0) return null;
|
|
|
|
return (
|
|
<div key={`tax-group-${group}`} className='space-y-1.5 leading-3'>
|
|
{taxesInGroup?.map((item) => {
|
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
|
(t) => t,
|
|
() => undefined
|
|
);
|
|
return (
|
|
<div
|
|
key={`${group}:${item.tax_code}`}
|
|
className='flex items-center justify-between text-sm'
|
|
>
|
|
<span className='text-muted-foreground text-sm'>{tax?.name}</span>
|
|
<span className='font-medium tabular-nums text-sm text-muted-foreground'>
|
|
{formatCurrency(item.taxes_amount, 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
<div className='flex justify-between text-sm mt-3'>
|
|
<span className='text-foreground'>Total de impuestos</span>
|
|
<span className='font-medium tabular-nums'>
|
|
{formatCurrency(getValues("taxes_amount"), 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className='flex justify-between text-sm '>
|
|
<span className='font-bold text-foreground'>Total de la factura</span>
|
|
<span className='font-bold tabular-nums'>
|
|
{formatCurrency(getValues("total_amount"), 2, currency_code, language_code)}
|
|
</span>
|
|
</div>
|
|
</FieldGroup>
|
|
</FieldSet>
|
|
);
|
|
};
|