From 65c3e1f3244f6c68f99a056b69bb2cb99acde20c Mon Sep 17 00:00:00 2001 From: david Date: Sun, 12 Apr 2026 23:10:53 +0200 Subject: [PATCH] =?UTF-8?q?Edici=C3=B3n=20de=20Items=20de=20proformas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../proformas/pages/update/ui/blocks/index.ts | 1 - .../pages/update/ui/blocks/items/index.ts | 1 - .../ui/blocks/items/proforma-items-editor.tsx | 22 -- .../web/proformas/update/controllers/index.ts | 1 + .../use-update-proforma-controller.ts | 8 + .../use-update-proforma-items-controller.ts | 229 ++++++++++++++++++ .../proforma-item-update-form.entity.ts | 3 +- .../proforma-item-update-form.schema.ts | 26 ++ .../proforma-update-form-default.entity.ts | 2 + .../entities/proforma-update-form.entity.ts | 4 +- .../entities/proforma-update-form.schema.ts | 84 +------ .../web/proformas/update/ui/blocks/index.ts | 1 + .../ui/blocks}/items-editor/index.ts | 0 .../blocks}/items-editor/item-row-editor.tsx | 0 .../ui/blocks}/items-editor/items-editor.tsx | 15 -- .../editors/proforma-update-editor-form.tsx | 9 +- .../proforma-update-item-row-editor.tsx | 107 ++++++++ .../editors/proforma-update-items-editor.tsx | 62 +++++ .../editors/proforma-update-items-totals.tsx | 29 +++ .../update/ui/pages/proforma-update-page.tsx | 1 + .../build-proforma-item-update-default.ts | 14 ++ .../src/web/proformas/update/utils/index.ts | 1 + 22 files changed, 497 insertions(+), 123 deletions(-) delete mode 100644 modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts delete mode 100644 modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts rename modules/customer-invoices/src/web/proformas/{pages/update/ui/components => update/ui/blocks}/items-editor/index.ts (100%) rename modules/customer-invoices/src/web/proformas/{pages/update/ui/components => update/ui/blocks}/items-editor/item-row-editor.tsx (100%) rename modules/customer-invoices/src/web/proformas/{pages/update/ui/components => update/ui/blocks}/items-editor/items-editor.tsx (79%) create mode 100644 modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-item-row-editor.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-totals.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts index c9c839bc..b4cd11db 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts @@ -1,3 +1,2 @@ -export * from "./items"; export * from "./proforma-basic-info-fields"; export * from "./proforma-totals"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts deleted file mode 100644 index d183d2e4..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./proforma-items-editor"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx deleted file mode 100644 index 2e42d5cb..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FieldDescription, FieldGroup, FieldLegend, FieldSet } from "@repo/shadcn-ui/components"; -import type { ComponentProps } from "react"; - -import { useTranslation } from "../../../../../../i18n"; -import { ItemsEditor } from "../../components"; - -export const ProformaItems = (props: ComponentProps<"fieldset">) => { - const { t } = useTranslation(); - - return ( -
- - {t("form_groups.items.title")} - - {t("form_groups.items.description")} - - - - -
- ); -}; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/index.ts b/modules/customer-invoices/src/web/proformas/update/controllers/index.ts index f8022c89..2fcdad69 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/index.ts @@ -1,2 +1,3 @@ export * from "./use-update-proforma-controller"; +export * from "./use-update-proforma-items-controller"; export * from "./use-update-proforma-page-controller"; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts index 9238fe50..1251f5ba 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts @@ -20,6 +20,8 @@ import { focusFirstProformaUpdateError, } from "../utils"; +import { useUpdateProformaItemsController } from "./use-update-proforma-items-controller"; + export interface UseUpdateProformaControllerOptions { onUpdated?(updated: Proforma): void; successToasts?: boolean; @@ -172,11 +174,17 @@ export const useUpdateProformaController = ( submitHandler(event); }; + const itemsCtrl = useUpdateProformaItemsController({ + form, + }); + return { // form formId, form, + itemsCtrl, + // handlers del form onSubmit, resetForm, diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts new file mode 100644 index 00000000..5fed0f57 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts @@ -0,0 +1,229 @@ +import * as React from "react"; +import { + type FieldArrayWithId, + type UseFormReturn, + useFieldArray, + useWatch, +} from "react-hook-form"; + +import type { ProformaItemUpdateForm, ProformaUpdateForm } from "../entities"; +import { buildProformaItemUpdateDefault } from "../utils/build-proforma-item-update-default"; + +export interface ProformaItemAmounts { + subtotal: number; + discountAmount: number; + total: number; +} + +export interface ProformaItemsTotals { + subtotal: number; + discountAmount: number; + total: number; +} + +export type ProformaItemField = FieldArrayWithId; + +export interface UseUpdateProformaItemsControllerResult { + fields: ProformaItemField[]; + items: ProformaItemUpdateForm[]; + + hasItems: boolean; + itemCount: number; + + appendItem: () => void; + removeItem: (index: number) => void; + duplicateItem: (index: number) => void; + moveItemUp: (index: number) => void; + moveItemDown: (index: number) => void; + + getItemAmounts: (index: number) => ProformaItemAmounts; + totals: ProformaItemsTotals; +} + +interface UseUpdateProformaItemsControllerParams { + form: UseFormReturn; +} + +const roundCurrency = (value: number): number => { + return Math.round(value * 100) / 100; +}; + +const normalizeItemPositions = (items: ProformaItemUpdateForm[]): ProformaItemUpdateForm[] => { + return items.map((item, index) => ({ + ...item, + position: index, + })); +}; + +const calculateItemAmounts = ( + item?: Pick +): ProformaItemAmounts => { + if (!item) { + return { + subtotal: 0, + discountAmount: 0, + total: 0, + }; + } + + const subtotal = roundCurrency(item.quantity * item.unitAmount); + const discountAmount = roundCurrency(subtotal * (item.discountPercentage / 100)); + const total = roundCurrency(subtotal - discountAmount); + + return { + subtotal, + discountAmount, + total, + }; +}; + +const calculateItemsTotals = (items: ProformaItemUpdateForm[]): ProformaItemsTotals => { + return items.reduce( + (acc, item) => { + const amounts = calculateItemAmounts(item); + + return { + subtotal: roundCurrency(acc.subtotal + amounts.subtotal), + discountAmount: roundCurrency(acc.discountAmount + amounts.discountAmount), + total: roundCurrency(acc.total + amounts.total), + }; + }, + { + subtotal: 0, + discountAmount: 0, + total: 0, + } + ); +}; + +export const useUpdateProformaItemsController = ({ + form, +}: UseUpdateProformaItemsControllerParams): UseUpdateProformaItemsControllerResult => { + const { control, getValues, setValue } = form; + + const { fields, append } = useFieldArray({ + control, + name: "items", + keyName: "fieldId", + }); + + const watchedItems = useWatch({ + control, + name: "items", + }); + + const items = React.useMemo(() => watchedItems ?? [], [watchedItems]); + + const replaceItems = React.useCallback( + (nextItems: ProformaItemUpdateForm[]) => { + setValue("items", normalizeItemPositions(nextItems), { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + }, + [setValue] + ); + + const appendItem = React.useCallback(() => { + const nextPosition = getValues("items")?.length ?? 0; + + append(buildProformaItemUpdateDefault(nextPosition), { + shouldFocus: false, + }); + }, [append, getValues]); + + const removeItem = React.useCallback( + (index: number) => { + const currentItems = getValues("items") ?? []; + + if (index < 0 || index >= currentItems.length) { + return; + } + + replaceItems(currentItems.filter((_, currentIndex) => currentIndex !== index)); + }, + [getValues, replaceItems] + ); + + const duplicateItem = React.useCallback( + (index: number) => { + const currentItems = getValues("items") ?? []; + const item = currentItems[index]; + + if (!item) { + return; + } + + const duplicatedItem: ProformaItemUpdateForm = { + ...item, + id: buildProformaItemUpdateDefault(index + 1).id, + position: index + 1, + }; + + const nextItems = [...currentItems]; + nextItems.splice(index + 1, 0, duplicatedItem); + + replaceItems(nextItems); + }, + [getValues, replaceItems] + ); + + const moveItemUp = React.useCallback( + (index: number) => { + const currentItems = getValues("items") ?? []; + + if (index <= 0 || index >= currentItems.length) { + return; + } + + const nextItems = [...currentItems]; + [nextItems[index - 1], nextItems[index]] = [nextItems[index], nextItems[index - 1]]; + + replaceItems(nextItems); + }, + [getValues, replaceItems] + ); + + const moveItemDown = React.useCallback( + (index: number) => { + const currentItems = getValues("items") ?? []; + + if (index < 0 || index >= currentItems.length - 1) { + return; + } + + const nextItems = [...currentItems]; + [nextItems[index], nextItems[index + 1]] = [nextItems[index + 1], nextItems[index]]; + + replaceItems(nextItems); + }, + [getValues, replaceItems] + ); + + const getItemAmounts = React.useCallback( + (index: number): ProformaItemAmounts => { + return calculateItemAmounts(items[index]); + }, + [items] + ); + + const totals = React.useMemo(() => calculateItemsTotals(items), [items]); + + return { + fields, + items, + + hasItems: items.length > 0, + itemCount: items.length, + + appendItem, + removeItem, + duplicateItem, + moveItemUp, + moveItemDown, + + getItemAmounts, + totals, + }; +}; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts index 9c8cc110..5bd9406e 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts @@ -1,9 +1,8 @@ export interface ProformaItemUpdateForm { id: string; - position: string; + position: number; description: string; quantity: number; unitAmount: number; discountPercentage: number; - taxes: string; } diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts new file mode 100644 index 00000000..1a07d100 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts @@ -0,0 +1,26 @@ +import { z } from "zod/v4"; + +/** + * Este esquema es para validar los datos de los items de proformas. + * No tiene por qué coincidir con el shape de la entidad ni con el de la API. + * Solo define los campos que se muestran en el editor y sus validaciones. + * + * Reglas: + * - no meter transformaciones silenciosas raras en el esquema (ej: .toUpperCase()) + * - nombres en camelCase + * - tipos orientados a UI/form + * - sin campos de solo lectura que no se editen + * - sin shape DTO + * - sin detalles impuestos por el widget + */ + +export const ProformaItemUpdateFormSchema = z.object({ + id: z.uuid(), + position: z.number().int().nonnegative(), + description: z.string().trim(), + quantity: z.number().positive(), + unitAmount: z.number().nonnegative(), + discountPercentage: z.number().min(0).max(100), +}); + +export type ProformaItemUpdateForm = z.infer; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form-default.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form-default.entity.ts index e3ec1aa1..a53581dd 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form-default.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form-default.entity.ts @@ -18,4 +18,6 @@ export const defaultProformaUpdateForm: ProformaUpdateForm = { paymentMethod: "", globalDiscountPercentage: 0, + + items: [], }; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts index 610b3eb5..f2d28221 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts @@ -13,6 +13,8 @@ * - sin detalles impuestos por el widget */ +import type { ProformaItemUpdateForm } from "./proforma-item-update-form.schema"; + export interface ProformaUpdateForm { series: string; @@ -32,5 +34,5 @@ export interface ProformaUpdateForm { paymentMethod: string; - //items: ProformaItemForm[]; + items: ProformaItemUpdateForm[]; } diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts index 4b6b7156..295e66bc 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts @@ -1,5 +1,7 @@ import { z } from "zod/v4"; +import { ProformaItemUpdateFormSchema } from "./proforma-item-update-form.schema"; + /** * Este esquema es para validar los datos del formulario de actualización de proformas. * No tiene por qué coincidir con el shape de la entidad ni con el de la API. @@ -32,86 +34,8 @@ export const ProformaUpdateFormSchema = z.object({ globalDiscountPercentage: z.number().default(0), paymentMethod: z.string().default(""), + + items: z.array(ProformaItemUpdateFormSchema), }); export type ProformaUpdateFormSchemaType = z.infer; - -const ProformaItemFormSchema = z.object({ - description: z.string().max(2000).optional().default(""), - quantity: z.any(), //NumericStringSchema.optional(), - unit_amount: z.any(), //NumericStringSchema.optional(), - - subtotal_amount: z.any(), //z.number(), - discount_percentage: z.any(), //NumericStringSchema.optional(), - discount_amount: z.number(), - taxable_amount: z.number(), - - tax_codes: z.array(z.string()).default([]), - - taxes_amount: z.number(), - total_amount: z.number(), -}); - -const ProformaFormSchema = z.object({ - invoice_number: z.string().optional(), - series: z.string().optional(), - - invoice_date: z.string().optional(), - operation_date: z.string().optional(), - - customer_id: z.string().optional(), - recipient: z - .object({ - id: z.string().optional(), - name: z.string().optional(), - tin: z.string().optional(), - street: z.string().optional(), - street2: z.string().optional(), - city: z.string().optional(), - province: z.string().optional(), - postal_code: z.string().optional(), - country: z.string().optional(), - }) - .optional(), - - reference: z.string().optional(), - description: z.string().optional(), - notes: z.string().optional(), - - language_code: z - .string({ - error: "El idioma es obligatorio", - }) - .min(1, "Debe indicar un idioma") - .toUpperCase() // asegura mayúsculas - .default("es"), - - currency_code: z - .string({ - error: "La moneda es obligatoria", - }) - .min(1, "La moneda no puede estar vacía") - .toUpperCase() // asegura mayúsculas - .default("EUR"), - - taxes: z - .array( - z.object({ - tax_code: z.string(), - tax_label: z.string(), - taxable_amount: z.number(), - taxes_amount: z.number(), - }) - ) - .optional(), - - items: z.array(ProformaItemFormSchema).optional(), - - subtotal_amount: z.number(), - items_discount_amount: z.number(), - discount_percentage: z.number(), - discount_amount: z.number(), - taxable_amount: z.number(), - taxes_amount: z.number(), - total_amount: z.number(), -}); diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts index c3c8cd30..f7577927 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts @@ -1,3 +1,4 @@ +export * from "./items-editor"; export * from "./proforma-basic-info-fields"; export * from "./proforma-form-field-shell"; export * from "./proforma-header-fields-card"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts similarity index 100% rename from modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/index.ts rename to modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx similarity index 100% rename from modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx rename to modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/items-editor.tsx similarity index 79% rename from modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx rename to modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/items-editor.tsx index ad5df7c4..cf5430b8 100644 --- a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/items-editor.tsx @@ -1,18 +1,3 @@ -/** biome-ignore-all lint/complexity/noForEach: */ -/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: */ - -import { useProformaGridColumns } from "@erp/customer-invoices/web/proformas/hooks"; -import { DataTable, useWithRowSelection } from "@repo/rdx-ui/components"; -import { useMemo } from "react"; -import { useFieldArray, useFormContext } from "react-hook-form"; - -import { useProformaAutoRecalc } from "../../../../../../hooks"; -import { useTranslation } from "../../../../../../i18n"; -import { type ProformaFormData, defaultProformaItemFormData } from "../../../../../types"; -import { useProformaContext } from "../../../context"; - -import { ItemRowEditor } from "./item-row-editor"; - const createEmptyItem = () => defaultProformaItemFormData; export const ItemsEditor = () => { diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx index 223629c5..31ce0fd8 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx @@ -7,20 +7,24 @@ import { ProformaUpdateRecipientEditor } from "."; import { useTranslation } from "../../../../i18n"; import type { Proforma } from "../../../shared/entities"; +import type { UseUpdateProformaItemsControllerResult } from "../../controllers"; import { ProformaUpdateHeaderEditor } from "./proforma-update-header-editor"; +import { ProformaUpdateItemsEditor } from "./proforma-update-items-editor"; type ProformaUpdateEditorProps = { formId: string; proforma?: Proforma; isSubmitting: boolean; - onSubmit: React.FormEventHandler; + onSubmit: React.SubmitEventHandler; onReset: () => void; selectedCustomer?: CustomerSelectionOption | null; onChangeCustomerClick: () => void; onCreateCustomerClick: () => void; + itemsCtrl: UseUpdateProformaItemsControllerResult; + className?: string; }; @@ -32,6 +36,7 @@ export const ProformaUpdateEditorForm = ({ selectedCustomer, onChangeCustomerClick, onCreateCustomerClick, + itemsCtrl, className, }: ProformaUpdateEditorProps) => { const { t } = useTranslation(); @@ -47,6 +52,8 @@ export const ProformaUpdateEditorForm = ({ selectedCustomer={selectedCustomer} /> + +
+ + + + + + +
+ + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx new file mode 100644 index 00000000..c1f603f3 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-editor.tsx @@ -0,0 +1,62 @@ +import { Button } from "@repo/shadcn-ui/components"; +import { PlusIcon } from "lucide-react"; +import type { ComponentProps } from "react"; + +import { useTranslation } from "../../../../i18n"; +import type { UseUpdateProformaItemsControllerResult } from "../../controllers/use-update-proforma-items-controller"; +import { ProformaSectionCard } from "../blocks"; + +import { ProformaUpdateItemRowEditor } from "./proforma-update-item-row-editor"; +import { ProformaUpdateItemsTotals } from "./proforma-update-items-totals"; + +interface ProformaUpdateItemsEditorProps extends ComponentProps<"fieldset"> { + itemsCtrl: UseUpdateProformaItemsControllerResult; +} + +export const ProformaUpdateItemsEditor = ({ + itemsCtrl, + disabled, + ...props +}: ProformaUpdateItemsEditorProps) => { + const { t } = useTranslation(); + + return ( + +
+
+ +
+ + {itemsCtrl.hasItems ? ( +
+ {itemsCtrl.fields.map((field, index) => ( + 0} + index={index} + key={field.fieldId} + onDuplicate={() => itemsCtrl.duplicateItem(index)} + onMoveDown={() => itemsCtrl.moveItemDown(index)} + onMoveUp={() => itemsCtrl.moveItemUp(index)} + onRemove={() => itemsCtrl.removeItem(index)} + /> + ))} +
+ ) : ( +
+ {t("form_groups.items.empty", "Todavía no hay líneas en la proforma.")} +
+ )} + + +
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-totals.tsx b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-totals.tsx new file mode 100644 index 00000000..139785dd --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-items-totals.tsx @@ -0,0 +1,29 @@ +import { useTranslation } from "../../../../i18n"; +import type { ProformaItemsTotals } from "../../controllers"; + +interface ProformaUpdateItemsTotalsProps { + totals: ProformaItemsTotals; +} + +export const ProformaUpdateItemsTotals = ({ totals }: ProformaUpdateItemsTotalsProps) => { + const { t } = useTranslation(); + + return ( +
+
+ {t("form_groups.items.totals.subtotal", "Subtotal")} + {totals.subtotal} +
+ +
+ {t("form_groups.items.totals.discount", "Descuento")} + {totals.discountAmount} +
+ +
+ {t("form_groups.items.totals.total", "Total")} + {totals.total} +
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx b/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx index 9208b991..9bc8a0cd 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx @@ -101,6 +101,7 @@ export const ProformaUpdatePage = () => { null} diff --git a/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts new file mode 100644 index 00000000..41d80167 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/utils/build-proforma-item-update-default.ts @@ -0,0 +1,14 @@ +import { UniqueID } from "@repo/rdx-ddd"; + +import type { ProformaItemUpdateForm } from "../entities"; + +export const buildProformaItemUpdateDefault = (position: number): ProformaItemUpdateForm => { + return { + id: UniqueID.generateNewID().toString(), + position, + description: "", + quantity: 1, + unitAmount: 0, + discountPercentage: 0, + }; +}; diff --git a/modules/customer-invoices/src/web/proformas/update/utils/index.ts b/modules/customer-invoices/src/web/proformas/update/utils/index.ts index a9f354f3..5bfdadc2 100644 --- a/modules/customer-invoices/src/web/proformas/update/utils/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/utils/index.ts @@ -1,3 +1,4 @@ +export * from "./build-proforma-item-update-default"; export * from "./build-proforma-update-patch"; export * from "./build-update-proforma-by-id-params"; export * from "./focus-first-proforma-update-error";