diff --git a/apps/server/package.json b/apps/server/package.json index 06f81c2d..591baa38 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -45,6 +45,7 @@ "@erp/core": "workspace:*", "@erp/customer-invoices": "workspace:*", "@erp/customers": "workspace:*", + "@erp/verifactu": "workspace:*", "@repo/rdx-logger": "workspace:*", "bcrypt": "^5.1.1", "cls-rtracer": "^2.6.3", diff --git a/apps/server/src/register-modules.ts b/apps/server/src/register-modules.ts index 6c7418fd..f318eba4 100644 --- a/apps/server/src/register-modules.ts +++ b/apps/server/src/register-modules.ts @@ -1,6 +1,6 @@ import customerInvoicesAPIModule from "@erp/customer-invoices/api"; import customersAPIModule from "@erp/customers/api"; -import verifactuAPIModule from "@erp/verifactu/api"; +//import verifactuAPIModule from "@erp/verifactu/api"; import { registerModule } from "./lib"; @@ -8,5 +8,5 @@ export const registerModules = () => { //registerModule(authAPIModule); registerModule(customersAPIModule); registerModule(customerInvoicesAPIModule); - registerModule(verifactuAPIModule); + // registerModule(verifactuAPIModule); }; diff --git a/modules/core/src/common/locales/en.json b/modules/core/src/common/locales/en.json index ef70255e..2737b410 100644 --- a/modules/core/src/common/locales/en.json +++ b/modules/core/src/common/locales/en.json @@ -2,6 +2,7 @@ "common": { "cancel": "Cancel", "save": "Save", + "saving": "Saving...", "required": "•" }, "components": { diff --git a/modules/core/src/web/components/form/taxes-multi-select-field.tsx b/modules/core/src/web/components/form/taxes-multi-select-field.tsx index 6d4e4509..08089016 100644 --- a/modules/core/src/web/components/form/taxes-multi-select-field.tsx +++ b/modules/core/src/web/components/form/taxes-multi-select-field.tsx @@ -47,7 +47,7 @@ export function TaxesMultiSelectField({ render={({ field }) => ( {label && ( -
+
{label} diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/cancel-form-button.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/cancel-form-button.tsx index 2e0ccd86..e9363e2e 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/cancel-form-button.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/cancel-form-button.tsx @@ -1,4 +1,5 @@ import { Button } from "@repo/shadcn-ui/components"; +import { XIcon } from "lucide-react"; import * as React from "react"; import { useCallback } from "react"; import { useNavigate } from "react-router-dom"; @@ -34,7 +35,6 @@ export const CancelFormButton = ({ const handleClick = useCallback(async () => { const ok = requestConfirm ? await requestConfirm() : true; - console.log("ok => ", ok); if (!ok) return; if (onCancel) { @@ -43,7 +43,6 @@ export const CancelFormButton = ({ } if (to) { - console.log("navego => ", to); navigate(to); } // si no hay ni onCancel ni to → no hace nada @@ -60,6 +59,7 @@ export const CancelFormButton = ({ aria-disabled={disabled} data-testid={dataTestId} > + {label ?? defaultLabel} ); diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx index 05c0c119..00f1fc5c 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/form-commit-button-group.tsx @@ -1,16 +1,46 @@ +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@repo/shadcn-ui/components"; import { cn } from "@repo/shadcn-ui/lib/utils"; +import { + ArrowLeftIcon, + CopyIcon, + EyeIcon, + MoreHorizontalIcon, + RotateCcwIcon, + Trash2Icon, +} from "lucide-react"; +import { useFormContext } from "react-hook-form"; import { CancelFormButton, CancelFormButtonProps } from "./cancel-form-button"; import { SubmitButtonProps, SubmitFormButton } from "./submit-form-button"; type Align = "start" | "center" | "end" | "between"; +type GroupSubmitButtonProps = Omit; + export type FormCommitButtonGroupProps = { className?: string; align?: Align; // default "end" gap?: string; // default "gap-2" reverseOrderOnMobile?: boolean; // default true (Cancel debajo en móvil) + + isLoading?: boolean; + disabled?: boolean; + preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading + cancel?: CancelFormButtonProps & { show?: boolean }; - submit?: SubmitButtonProps; // props directas a SubmitButton + submit?: GroupSubmitButtonProps; // props directas a SubmitButton + + onReset?: () => void; + onDelete?: () => void; + onPreview?: () => void; + onDuplicate?: () => void; + onBack?: () => void; }; const alignToJustify: Record = { @@ -25,10 +55,33 @@ export const FormCommitButtonGroup = ({ align = "end", gap = "gap-2", reverseOrderOnMobile = true, + + isLoading, + disabled = false, + preventDoubleSubmit = true, + cancel, submit, + + onReset, + onDelete, + onPreview, + onDuplicate, + onBack, }: FormCommitButtonGroupProps) => { const showCancel = cancel?.show ?? true; + const hasSecondaryActions = onReset || onPreview || onDuplicate || onBack || onDelete; + + // ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading + let rhfIsSubmitting = false; + try { + const ctx = useFormContext(); + rhfIsSubmitting = !!ctx?.formState?.isSubmitting; + } catch { + // No hay provider de RHF; ignorar + } + const busy = isLoading ?? rhfIsSubmitting; + const computedDisabled = !!(disabled || (preventDoubleSubmit && busy)); return (
- {showCancel && } {submit && } + {showCancel && } + + {/* Menú de acciones adicionales */} + {hasSecondaryActions && ( + + + + + + {onReset && ( + + + Deshacer cambios + + )} + {onPreview && ( + + + Vista previa + + )} + {onDuplicate && ( + + + Duplicar + + )} + {onBack && ( + + + Volver + + )} + {onDelete && ( + <> + + + + Eliminar + + + )} + + + )}
); }; diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/submit-form-button.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/submit-form-button.tsx index 966ea34c..5241c1a7 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/submit-form-button.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/components/submit-form-button.tsx @@ -1,5 +1,6 @@ import { Button } from "@repo/shadcn-ui/components"; -import { LoaderCircleIcon } from "lucide-react"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { LoaderCircleIcon, SaveIcon } from "lucide-react"; import * as React from "react"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "../../../i18n.ts"; @@ -8,12 +9,15 @@ export type SubmitButtonProps = { formId?: string; isLoading?: boolean; label?: string; + labelIsLoading?: string; variant?: React.ComponentProps["variant"]; size?: React.ComponentProps["size"]; className?: string; preventDoubleSubmit?: boolean; // Evita múltiples submits mientras loading + hasChanges?: boolean; + onClick?: React.MouseEventHandler; disabled?: boolean; children?: React.ReactNode; @@ -24,10 +28,12 @@ export const SubmitFormButton = ({ formId, isLoading, label, + labelIsLoading, variant = "default", size = "default", className, preventDoubleSubmit = true, + hasChanges = false, onClick, disabled, children, @@ -35,6 +41,7 @@ export const SubmitFormButton = ({ }: SubmitButtonProps) => { const { t } = useTranslation(); const defaultLabel = t ? t("common.save") : "Save"; + const defaultLabelIsLoading = t ? t("common.saving") : "Saving..."; // ⛳️ RHF opcional: auto-detectar isSubmitting si no se pasó isLoading let rhfIsSubmitting = false; @@ -65,20 +72,30 @@ export const SubmitFormButton = ({ form={formId} variant={variant} size={size} - className={className} disabled={computedDisabled} aria-busy={busy} aria-disabled={computedDisabled} data-state={dataState} onClick={handleClick} data-testid={dataTestId} + className={cn("min-w-[100px] font-medium", hasChanges && "ring-2 ring-primary/20", className)} > {children ? ( children ) : ( - {busy && )} diff --git a/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx b/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx index 6e3e142b..07316ba8 100644 --- a/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx @@ -1,11 +1,6 @@ +import { Description, Field, FieldGroup, Fieldset, Legend } from "@repo/rdx-ui/components"; + import { SelectField } from "@repo/rdx-ui/components"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@repo/shadcn-ui/components"; import { useFormContext } from "react-hook-form"; import { CURRENCY_OPTIONS, LANGUAGE_OPTIONS } from "../../constants"; import { useTranslation } from "../../i18n"; @@ -16,15 +11,12 @@ export const CustomerAdditionalConfigFields = () => { const { control } = useFormContext(); return ( - - - {t("form_groups.preferences.title")} - {t("form_groups.preferences.description")} - - -
+
+ {t("form_groups.preferences.title")} + {t("form_groups.preferences.description")} + + { description={t("form_fields.language_code.description")} items={[...LANGUAGE_OPTIONS]} /> + + { description={t("form_fields.currency_code.description")} items={[...CURRENCY_OPTIONS]} /> -
-
-
+ + + ); }; diff --git a/modules/customers/src/web/components/editor/customer-address-fields.tsx b/modules/customers/src/web/components/editor/customer-address-fields.tsx index 97347826..1e800e69 100644 --- a/modules/customers/src/web/components/editor/customer-address-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-address-fields.tsx @@ -1,18 +1,12 @@ import { Description, + Field, FieldGroup, Fieldset, Legend, SelectField, TextField, } from "@repo/rdx-ui/components"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@repo/shadcn-ui/components"; import { useFormContext } from "react-hook-form"; import { COUNTRY_OPTIONS } from "../../constants"; import { useTranslation } from "../../i18n"; @@ -26,7 +20,7 @@ export const CustomerAddressFields = () => {
{t("form_groups.address.title")} {t("form_groups.address.description")} - + { description={t("form_fields.postal_code.description")} /> - - - -
- ); - - return ( - - - {t("form_groups.address.title")} - {t("form_groups.address.description")} - - -
+ - - - - -
-
- + + { description={t("form_fields.country.description")} items={[...COUNTRY_OPTIONS]} /> -
-
-
+ + + ); }; diff --git a/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx index 733cfce7..b141e535 100644 --- a/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx @@ -35,7 +35,7 @@ export const CustomerBasicInfoFields = () => {
Identificación descripción - + { field.onChange(value === "false" ? "false" : "true"); }} defaultValue={field.value ? "true" : "false"} - className='flex items-center gap-8' + className='flex items-center gap-6' > @@ -106,16 +106,16 @@ export const CustomerBasicInfoFields = () => { placeholder={t("form_fields.reference.placeholder")} description={t("form_fields.reference.description")} /> - - + + + {
{t("form_groups.contact_info.title")} {t("form_groups.contact_info.description")} - + { typePreset='email' required /> + { } /> - + {t("common.more_details")}{" "} - - - + + + + + + + diff --git a/modules/customers/src/web/components/editor/customer-edit-form.tsx b/modules/customers/src/web/components/editor/customer-edit-form.tsx index 12eb4461..d2140826 100644 --- a/modules/customers/src/web/components/editor/customer-edit-form.tsx +++ b/modules/customers/src/web/components/editor/customer-edit-form.tsx @@ -22,7 +22,7 @@ export const CustomerEditForm = ({ formId, onSubmit, onError }: CustomerFormProp
-
+
diff --git a/modules/customers/src/web/pages/create/customer-create.tsx b/modules/customers/src/web/pages/create/customer-create.tsx index a6773a04..bf9c0b58 100644 --- a/modules/customers/src/web/pages/create/customer-create.tsx +++ b/modules/customers/src/web/pages/create/customer-create.tsx @@ -54,6 +54,10 @@ export const CustomerCreate = () => { // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario }; + const handleBack = () => { + navigate(-1); + }; + return ( <> @@ -69,14 +73,17 @@ export const CustomerCreate = () => {

handleBack()} />
{/* Alerta de error de actualización (si ha fallado el último intento) */} diff --git a/modules/customers/src/web/pages/update/customer-update.tsx b/modules/customers/src/web/pages/update/customer-update.tsx index 749eb2c4..dfcece0c 100644 --- a/modules/customers/src/web/pages/update/customer-update.tsx +++ b/modules/customers/src/web/pages/update/customer-update.tsx @@ -67,6 +67,12 @@ export const CustomerUpdate = () => { ); }; + const handleReset = () => form.reset(customerData ?? defaultCustomerFormData); + + const handleBack = () => { + navigate(-1); + }; + const handleError = (errors: FieldErrors) => { console.error("Errores en el formulario:", errors); // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario @@ -125,14 +131,18 @@ export const CustomerUpdate = () => {

handleBack()} + onReset={() => handleReset()} />
{/* Alerta de error de actualización (si ha fallado el último intento) */} diff --git a/modules/verifactu/src/api/infrastructure/index.ts b/modules/verifactu/src/api/infrastructure/index.ts index f25a4198..7be3bfce 100644 --- a/modules/verifactu/src/api/infrastructure/index.ts +++ b/modules/verifactu/src/api/infrastructure/index.ts @@ -1,3 +1,3 @@ //export * from "./mappers"; -//export * from "./sequelize"; export * from "./express"; +export * from "./sequelize"; diff --git a/packages/rdx-ui/src/components/form/DatePickerInputField.tsx b/packages/rdx-ui/src/components/form/DatePickerInputField.tsx index 6cc10260..0924cd3b 100644 --- a/packages/rdx-ui/src/components/form/DatePickerInputField.tsx +++ b/packages/rdx-ui/src/components/form/DatePickerInputField.tsx @@ -1,6 +1,7 @@ import { Calendar, FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -166,16 +167,9 @@ export function DatePickerInputField({

)} - {(inputError || description) && ( -

- {inputError || description} -

- )} + + {description || "\u00A0"} + diff --git a/packages/rdx-ui/src/components/form/NumberField.tsx b/packages/rdx-ui/src/components/form/NumberField.tsx index a8732185..c0c34b88 100644 --- a/packages/rdx-ui/src/components/form/NumberField.tsx +++ b/packages/rdx-ui/src/components/form/NumberField.tsx @@ -2,6 +2,7 @@ import { FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -53,9 +54,9 @@ export function NumberField({ -

+ {description || "\u00A0"} -

+ )} diff --git a/packages/rdx-ui/src/components/form/SelectField.tsx b/packages/rdx-ui/src/components/form/SelectField.tsx index ec6d10b8..7ec0169d 100644 --- a/packages/rdx-ui/src/components/form/SelectField.tsx +++ b/packages/rdx-ui/src/components/form/SelectField.tsx @@ -55,7 +55,7 @@ export function SelectField({ render={({ field }) => ( {label && ( -
+
{label} @@ -72,7 +72,7 @@ export function SelectField({ )} - + {description || "\u00A0"} diff --git a/packages/rdx-ui/src/components/form/TextAreaField.tsx b/packages/rdx-ui/src/components/form/TextAreaField.tsx index a5a09fb8..e828759f 100644 --- a/packages/rdx-ui/src/components/form/TextAreaField.tsx +++ b/packages/rdx-ui/src/components/form/TextAreaField.tsx @@ -2,6 +2,7 @@ import { FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -58,12 +59,17 @@ export function TextAreaField({
)} -