.
This commit is contained in:
parent
5a4b457384
commit
6be0ad686c
@ -1,6 +1,12 @@
|
|||||||
import { formatCurrency } from "@erp/core";
|
import { formatCurrency } from "@erp/core";
|
||||||
import { FieldDescription, FieldGroup, FieldLegend, FieldSet, Separator } from '@repo/shadcn-ui/components';
|
import {
|
||||||
import { cn } from '@repo/shadcn-ui/lib/utils';
|
FieldDescription,
|
||||||
|
FieldGroup,
|
||||||
|
FieldLegend,
|
||||||
|
FieldSet,
|
||||||
|
Separator,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { ReceiptIcon } from "lucide-react";
|
import { ReceiptIcon } from "lucide-react";
|
||||||
import { ComponentProps } from "react";
|
import { ComponentProps } from "react";
|
||||||
import { useFormContext, useWatch } from "react-hook-form";
|
import { useFormContext, useWatch } from "react-hook-form";
|
||||||
@ -20,46 +26,55 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
|||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const subtotal_amount = useWatch({
|
||||||
|
control,
|
||||||
|
name: "subtotal_amount",
|
||||||
|
defaultValue: 0,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet {...props}>
|
<FieldSet {...props}>
|
||||||
<FieldLegend className='hidden'>
|
<FieldLegend className='hidden'>
|
||||||
<ReceiptIcon className='size-6 text-muted-foreground' />{t("form_groups.totals.title")}
|
<ReceiptIcon className='size-6 text-muted-foreground' />
|
||||||
|
{t("form_groups.totals.title")}
|
||||||
</FieldLegend>
|
</FieldLegend>
|
||||||
|
|
||||||
<FieldDescription className='hidden'>{t("form_groups.totals.description")}</FieldDescription>
|
<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'>
|
<FieldGroup className='grid grid-cols-1 border rounded-lg bg-muted/10 p-4 gap-4'>
|
||||||
|
|
||||||
<div className='space-y-1.5'>
|
<div className='space-y-1.5'>
|
||||||
{/* Sección: Subtotal y Descuentos */}
|
{/* Sección: Subtotal y Descuentos */}
|
||||||
<div className="flex justify-between text-sm">
|
<div className='flex justify-between text-sm'>
|
||||||
<span className="text-muted-foreground">Subtotal sin descuento</span>
|
<span className='text-muted-foreground'>Subtotal sin descuento</span>
|
||||||
<span className="font-medium tabular-nums text-muted-foreground">
|
<span className='font-medium tabular-nums text-muted-foreground'>
|
||||||
{formatCurrency(getValues('subtotal_amount'), 2, currency_code, language_code)}
|
{formatCurrency(subtotal_amount, 2, currency_code, language_code)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between text-sm">
|
<div className='flex justify-between text-sm'>
|
||||||
<div className="flex items-center gap-3">
|
<div className='flex items-center gap-3'>
|
||||||
<span className="text-muted-foreground">Descuento global</span>
|
<span className='text-muted-foreground'>Descuento global</span>
|
||||||
<PercentageInputField
|
<PercentageInputField
|
||||||
control={control}
|
control={control}
|
||||||
name={"discount_percentage"}
|
name={"discount_percentage"}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
inputId={"header-discount-percentage"}
|
inputId={"header-discount-percentage"}
|
||||||
showSuffix={true}
|
showSuffix={true}
|
||||||
className={cn("w-20 text-right tabular-nums bg-background", "border-input border text-sm shadow-xs")}
|
className={cn(
|
||||||
|
"w-20 text-right tabular-nums bg-background",
|
||||||
|
"border-input border text-sm shadow-xs"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-destructive tabular-nums">-{formatCurrency(getValues("discount_amount"), 2, currency_code, language_code)}</span>
|
<span className='font-medium text-destructive tabular-nums'>
|
||||||
|
-{formatCurrency(getValues("discount_amount"), 2, currency_code, language_code)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Sección: Base Imponible */}
|
{/* Sección: Base Imponible */}
|
||||||
<div className="flex justify-between text-sm">
|
<div className='flex justify-between text-sm'>
|
||||||
<span className="text-foreground">Base imponible</span>
|
<span className='text-foreground'>Base imponible</span>
|
||||||
<span className="font-medium tabular-nums">
|
<span className='font-medium tabular-nums'>
|
||||||
{formatCurrency(getValues('taxable_amount'), 2, currency_code, language_code)}
|
{formatCurrency(getValues("taxable_amount"), 2, currency_code, language_code)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,10 +82,8 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Sección: Impuestos */}
|
{/* Sección: Impuestos */}
|
||||||
<div className="space-y-1.5">
|
<div className='space-y-1.5'>
|
||||||
<h3
|
<h3 className='text-xs font-semibold text-muted-foreground uppercase tracking-wide'>
|
||||||
className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"
|
|
||||||
>
|
|
||||||
Impuestos y retenciones
|
Impuestos y retenciones
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@ -88,9 +101,7 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
|||||||
if (taxesInGroup?.length === 0) return null;
|
if (taxesInGroup?.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div key={`tax-group-${group}`} className='space-y-1.5 leading-3'>
|
||||||
|
|
||||||
<div key={`tax-group-${group}`} className="space-y-1.5 leading-3">
|
|
||||||
{taxesInGroup?.map((item) => {
|
{taxesInGroup?.map((item) => {
|
||||||
const tax = taxCatalog.findByCode(item.tax_code).match(
|
const tax = taxCatalog.findByCode(item.tax_code).match(
|
||||||
(t) => t,
|
(t) => t,
|
||||||
@ -99,43 +110,36 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${group}:${item.tax_code}`}
|
key={`${group}:${item.tax_code}`}
|
||||||
className="flex items-center justify-between text-sm"
|
className='flex items-center justify-between text-sm'
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground text-sm">{tax?.name}</span>
|
<span className='text-muted-foreground text-sm'>{tax?.name}</span>
|
||||||
<span className="font-medium tabular-nums text-sm text-muted-foreground">
|
<span className='font-medium tabular-nums text-sm text-muted-foreground'>
|
||||||
{formatCurrency(
|
{formatCurrency(item.taxes_amount, 2, currency_code, language_code)}
|
||||||
item.taxes_amount,
|
|
||||||
2,
|
|
||||||
currency_code,
|
|
||||||
language_code
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="flex justify-between text-sm mt-3">
|
<div className='flex justify-between text-sm mt-3'>
|
||||||
<span className="text-foreground">Total de impuestos</span>
|
<span className='text-foreground'>Total de impuestos</span>
|
||||||
<span className="font-medium tabular-nums">
|
<span className='font-medium tabular-nums'>
|
||||||
{formatCurrency(getValues('taxes_amount'), 2, currency_code, language_code)}
|
{formatCurrency(getValues("taxes_amount"), 2, currency_code, language_code)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex justify-between text-sm ">
|
<div className='flex justify-between text-sm '>
|
||||||
<span className="font-bold text-foreground">Total de la factura</span>
|
<span className='font-bold text-foreground'>Total de la factura</span>
|
||||||
<span className="font-bold tabular-nums">
|
<span className='font-bold tabular-nums'>
|
||||||
{formatCurrency(getValues('total_amount'), 2, currency_code, language_code)}
|
{formatCurrency(getValues("total_amount"), 2, currency_code, language_code)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</FieldSet >
|
</FieldSet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,49 +1,47 @@
|
|||||||
import { DataTable, useWithRowSelection } from '@repo/rdx-ui/components';
|
import { DataTable, useWithRowSelection } from "@repo/rdx-ui/components";
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||||
import { useInvoiceContext } from '../../../context';
|
import { useInvoiceContext } from "../../../context";
|
||||||
import { useInvoiceAutoRecalc } from '../../../hooks';
|
import { useInvoiceAutoRecalc } from "../../../hooks";
|
||||||
import { useTranslation } from '../../../i18n';
|
import { useTranslation } from "../../../i18n";
|
||||||
import { InvoiceFormData, defaultCustomerInvoiceItemFormData } from '../../../schemas';
|
import { defaultCustomerInvoiceItemFormData, InvoiceFormData } from "../../../schemas";
|
||||||
import { debugIdCol } from './debug-id-col';
|
import { debugIdCol } from "./debug-id-col";
|
||||||
import { ItemRowEditor } from './item-row-editor';
|
import { ItemRowEditor } from "./item-row-editor";
|
||||||
import { useItemsColumns } from './use-items-columns';
|
import { useItemsColumns } from "./use-items-columns";
|
||||||
|
|
||||||
|
|
||||||
const createEmptyItem = () => defaultCustomerInvoiceItemFormData;
|
const createEmptyItem = () => defaultCustomerInvoiceItemFormData;
|
||||||
|
|
||||||
export const ItemsEditor = () => {
|
export const ItemsEditor = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const context = useInvoiceContext();
|
const context = useInvoiceContext();
|
||||||
const form = useFormContext<InvoiceFormData>();
|
const form = useFormContext<InvoiceFormData>();
|
||||||
const { control, getValues } = form;
|
const { control, getValues } = form;
|
||||||
|
|
||||||
useInvoiceAutoRecalc(form, context);
|
useInvoiceAutoRecalc(form, context);
|
||||||
|
|
||||||
const { fields, append, remove, move, insert, update } = useFieldArray({
|
const { fields, append, remove, move, insert, update } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "items",
|
name: "items",
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseColumns = useWithRowSelection(useItemsColumns(), true);
|
const baseColumns = useWithRowSelection(useItemsColumns(), true);
|
||||||
const columns = useMemo(
|
const columns = useMemo(() => [...baseColumns, debugIdCol], [baseColumns]);
|
||||||
() => [...baseColumns, debugIdCol],
|
|
||||||
[baseColumns]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-0">
|
<div className='space-y-0'>
|
||||||
<DataTable columns={columns as any} data={fields}
|
<DataTable
|
||||||
meta={{
|
columns={columns as any}
|
||||||
tableOps: {
|
data={fields}
|
||||||
onAdd: () => append({ ...createEmptyItem() }),
|
meta={{
|
||||||
//appendItem: (item: any) => append(item),
|
tableOps: {
|
||||||
},
|
onAdd: () => append({ ...createEmptyItem() }),
|
||||||
rowOps: {
|
//appendItem: (item: any) => append(item),
|
||||||
remove: (i: number) => remove(i),
|
},
|
||||||
move: (from: number, to: number) => move(from, to),
|
rowOps: {
|
||||||
//insertItem: (index: number, item: any) => insert(index, item),
|
remove: (i: number) => remove(i),
|
||||||
/*duplicateItems: (indexes: number[], table: Table<InvoiceFormData>) => {
|
move: (from: number, to: number) => move(from, to),
|
||||||
|
//insertItem: (index: number, item: any) => insert(index, item),
|
||||||
|
/*duplicateItems: (indexes: number[], table: Table<InvoiceFormData>) => {
|
||||||
const items = getValues("items") || [];
|
const items = getValues("items") || [];
|
||||||
// duplicate in descending order to keep indexes stable
|
// duplicate in descending order to keep indexes stable
|
||||||
[...indexes].sort((a, b) => b - a).forEach(i => {
|
[...indexes].sort((a, b) => b - a).forEach(i => {
|
||||||
@ -54,34 +52,33 @@ export const ItemsEditor = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},*/
|
},*/
|
||||||
/*deleteItems: (indexes: number[]) => {
|
/*deleteItems: (indexes: number[]) => {
|
||||||
// remove in descending order to avoid shifting issues
|
// remove in descending order to avoid shifting issues
|
||||||
[...indexes].sort((a, b) => b - a).forEach(i => remove(i));
|
[...indexes].sort((a, b) => b - a).forEach(i => remove(i));
|
||||||
},*/
|
},*/
|
||||||
//updateItem: (index: number, item: any) => update(index, item),
|
//updateItem: (index: number, item: any) => update(index, item),
|
||||||
},
|
},
|
||||||
bulkOps: {
|
bulkOps: {
|
||||||
duplicateSelected: (indexes, table) => {
|
duplicateSelected: (indexes, table) => {
|
||||||
const originalData = indexes.map((i) => {
|
const originalData = indexes.map((i) => {
|
||||||
const { id, ...original } = table.getRowModel().rows[i].original;
|
const { id, ...original } = table.getRowModel().rows[i].original;
|
||||||
return original;
|
return original;
|
||||||
});
|
});
|
||||||
|
|
||||||
insert(indexes[indexes.length - 1] + 1, originalData, { shouldFocus: true });
|
|
||||||
table.resetRowSelection();
|
|
||||||
},
|
|
||||||
removeSelected: (indexes) => indexes.sort((a, b) => b - a).forEach(remove),
|
|
||||||
moveSelectedUp: (indexes) => indexes.forEach((i) => move(i, i - 1)),
|
|
||||||
moveSelectedDown: (indexes) => [...indexes].reverse().forEach((i) => move(i, i + 1)),
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
enableRowSelection
|
|
||||||
enablePagination={false}
|
|
||||||
pageSize={999}
|
|
||||||
readOnly={false}
|
|
||||||
EditorComponent={ItemRowEditor}
|
|
||||||
/>
|
|
||||||
</div >
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
insert(indexes[indexes.length - 1] + 1, originalData, { shouldFocus: true });
|
||||||
|
table.resetRowSelection();
|
||||||
|
},
|
||||||
|
removeSelected: (indexes) => indexes.sort((a, b) => b - a).forEach(remove),
|
||||||
|
moveSelectedUp: (indexes) => indexes.forEach((i) => move(i, i - 1)),
|
||||||
|
moveSelectedDown: (indexes) => [...indexes].reverse().forEach((i) => move(i, i + 1)),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
enableRowSelection
|
||||||
|
enablePagination={false}
|
||||||
|
pageSize={999}
|
||||||
|
readOnly={false}
|
||||||
|
EditorComponent={ItemRowEditor}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user