This commit is contained in:
David Arranz 2026-07-15 13:10:26 +02:00
parent 0129c5963c
commit a810312f01
32 changed files with 149 additions and 77 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges-server", "name": "@erp/factuges-server",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsup src/index.ts --config tsup.config.ts", "build": "tsup src/index.ts --config tsup.config.ts",

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/factuges-web", "name": "@erp/factuges-web",
"private": true, "private": true,
"version": "0.9.0", "version": "0.9.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit", "typecheck": "tsc -p tsconfig.json --noEmit",

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/auth", "name": "@erp/auth",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/catalogs", "name": "@erp/catalogs",
"description": "Catalogs module", "description": "Catalogs module",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/companies", "name": "@erp/companies",
"description": "Companies module", "description": "Companies module",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/core", "name": "@erp/core",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customer-invoices", "name": "@erp/customer-invoices",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,5 +1,5 @@
import type { UniqueID } from "@repo/rdx-ddd"; import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { import {
type IProformaCreateProps, type IProformaCreateProps,
@ -55,11 +55,11 @@ export class ProformaCreator implements IProformaCreator {
return Result.fail(numberResult.error); return Result.fail(numberResult.error);
} }
const invoiceNumber = numberResult.data; const proformaReference = numberResult.data;
// 2. Crear agregado // 2. Crear agregado
const proformaResult = Proforma.create( const proformaResult = Proforma.create(
{ ...resolvedProps.data, proformaReference: invoiceNumber, companyId }, { ...resolvedProps.data, proformaReference, companyId },
id id
); );
@ -81,11 +81,17 @@ export class ProformaCreator implements IProformaCreator {
private async resolveCreateProps( private async resolveCreateProps(
props: ProformaCreateInputProps props: ProformaCreateInputProps
): Promise<Result<Omit<IProformaCreateProps, "invoiceNumber">, Error>> { ): Promise<Result<Omit<IProformaCreateProps, "proformaReference">, Error>> {
// TODO: Esto hay que cambiarlo en el futuro para buscar valores por defecto, desde la empresa o desde el cliente
const _newProps = {
...props,
taxRegimeCode: props.taxRegimeCode.isNone() ? Maybe.some("01") : props.taxRegimeCode,
};
// Tax Regime => comprobar que existe // Tax Regime => comprobar que existe
const taxRegimeResult = await this.deps.taxResolver.ensureTaxRegimeByCode({ const taxRegimeResult = await this.deps.taxResolver.ensureTaxRegimeByCode({
companyId: props.companyId, companyId: _newProps.companyId,
code: props.taxRegimeCode, code: _newProps.taxRegimeCode,
}); });
if (taxRegimeResult.isFailure) { if (taxRegimeResult.isFailure) {
@ -94,8 +100,8 @@ export class ProformaCreator implements IProformaCreator {
// Payment method => comprobar que existe // Payment method => comprobar que existe
const paymentMethodResult = await this.deps.paymentResolver.ensurePaymentMethodById({ const paymentMethodResult = await this.deps.paymentResolver.ensurePaymentMethodById({
companyId: props.companyId, companyId: _newProps.companyId,
id: props.paymentMethodId, id: _newProps.paymentMethodId,
}); });
if (paymentMethodResult.isFailure) { if (paymentMethodResult.isFailure) {
@ -107,8 +113,8 @@ export class ProformaCreator implements IProformaCreator {
for (const item of props.items) { for (const item of props.items) {
const taxes = await this.deps.taxResolver.ensureItemTaxes({ const taxes = await this.deps.taxResolver.ensureItemTaxes({
companyId: props.companyId, companyId: _newProps.companyId,
atDate: props.proformaDate, atDate: _newProps.proformaDate,
taxCodes: item.taxes, taxCodes: item.taxes,
}); });
@ -123,7 +129,7 @@ export class ProformaCreator implements IProformaCreator {
} }
return Result.ok({ return Result.ok({
...props, ..._newProps,
items: resolvedItems, items: resolvedItems,
}); });
} }

View File

@ -8,6 +8,7 @@ import {
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { import {
focusFirstInputFormError, focusFirstInputFormError,
getFirstFormErrorMessage,
showErrorToast, showErrorToast,
showSuccessToast, showSuccessToast,
showWarningToast, showWarningToast,
@ -15,6 +16,7 @@ import {
import { useEffect, useId, useMemo, useState } from "react"; import { useEffect, useId, useMemo, useState } from "react";
import type { FieldErrors } from "react-hook-form"; import type { FieldErrors } from "react-hook-form";
import { useTranslation } from "../../../i18n";
import type { Proforma } from "../../shared"; import type { Proforma } from "../../shared";
import { import {
buildInvoiceSeriesSelectItems, buildInvoiceSeriesSelectItems,
@ -42,6 +44,7 @@ const normalizeSubmitError = (error: unknown): Error | ValidationErrorCollection
}; };
export const useCreateProformaController = (options?: UseCreateProformaControllerOptions) => { export const useCreateProformaController = (options?: UseCreateProformaControllerOptions) => {
const { t } = useTranslation();
const formId = useId(); const formId = useId();
const [selectedCustomer, setSelectedCustomer] = useState<CustomerSelectionOption | null>(null); const [selectedCustomer, setSelectedCustomer] = useState<CustomerSelectionOption | null>(null);
@ -70,9 +73,7 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
const defaultSeries = useMemo(() => { const defaultSeries = useMemo(() => {
const invoiceSeries = seriesQuery.data ?? []; const invoiceSeries = seriesQuery.data ?? [];
return ( return invoiceSeries.find((series) => series.isDefault)?.code ?? invoiceSeries[0]?.code ?? "";
invoiceSeries.find((series) => series.isDefault)?.code ?? invoiceSeries[0]?.code ?? ""
);
}, [seriesQuery.data]); }, [seriesQuery.data]);
useEffect(() => { useEffect(() => {
@ -140,8 +141,13 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
if (isValidationErrorCollection(normalizedError)) { if (isValidationErrorCollection(normalizedError)) {
applyValidationErrorCollection(form, normalizedError); applyValidationErrorCollection(form, normalizedError);
focusFirstInputFormError(form); focusFirstInputFormError(form.formState.errors, form);
showWarningToast("Revisa los campos", "Hay errores de validación en el formulario."); const errorMessage = getFirstFormErrorMessage(form.formState.errors, form);
showWarningToast(
"Revisa los campos",
errorMessage ??
t("forms.validation.message", "Hay errores de validación en el formulario.")
);
return; return;
} }
@ -149,8 +155,12 @@ export const useCreateProformaController = (options?: UseCreateProformaControlle
} }
}, },
(_errors: FieldErrors<ProformaCreateForm>) => { (_errors: FieldErrors<ProformaCreateForm>) => {
focusFirstInputFormError(form); focusFirstInputFormError(_errors, form);
showWarningToast("Revisa los campos", "Hay errores de validación en el formulario."); const errorMessage = getFirstFormErrorMessage(_errors, form);
showWarningToast(
"Revisa los campos",
errorMessage ?? t("forms.validation.message", "Hay errores de validación en el formulario.")
);
} }
); );

View File

@ -4,9 +4,13 @@ import { buildProformaUpdateDefault } from "../utils";
import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter"; import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter";
interface FiscalDefaults { interface FiscalState {
taxMode: ProformaTaxMode; taxMode: ProformaTaxMode;
hasTaxPercentage: boolean;
hasRecPercentage: boolean;
hasRetentionPercentage: boolean;
defaultTaxPercentage: number | null; defaultTaxPercentage: number | null;
defaultRecPercentage: number | null; defaultRecPercentage: number | null;
defaultRetentionPercentage: number | null; defaultRetentionPercentage: number | null;
@ -18,7 +22,7 @@ interface FiscalDefaults {
export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => { export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => {
const proformaDefaults = buildProformaUpdateDefault(); const proformaDefaults = buildProformaUpdateDefault();
const fiscalDefaults = resolveFiscalDefaults(proforma, proformaDefaults); const fiscalState = resolveFiscalState(proforma, proformaDefaults);
return { return {
proformaReference: proforma.proformaReference, proformaReference: proforma.proformaReference,
@ -40,18 +44,17 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
globalDiscountPercentage: globalDiscountPercentage:
proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage, proforma.globalDiscountPercentage ?? proformaDefaults.globalDiscountPercentage,
taxMode: fiscalDefaults.taxMode, taxMode: fiscalState.taxMode,
taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode, taxRegimeCode: proforma.taxRegimeCode ?? proformaDefaults.taxRegimeCode,
hasTaxPercentage: hasTaxPercentage: fiscalState.hasTaxPercentage,
fiscalDefaults.taxMode === "single" && fiscalDefaults.defaultTaxPercentage !== null, taxPercentage: fiscalState.defaultTaxPercentage,
taxPercentage: fiscalDefaults.defaultTaxPercentage,
hasRecPercentage: fiscalDefaults.defaultRecPercentage !== null, hasRecPercentage: fiscalState.hasRecPercentage,
recPercentage: fiscalDefaults.defaultRecPercentage, recPercentage: fiscalState.defaultRecPercentage,
hasRetentionPercentage: fiscalDefaults.defaultRetentionPercentage !== null, hasRetentionPercentage: fiscalState.defaultRetentionPercentage !== null,
retentionPercentage: fiscalDefaults.defaultRetentionPercentage, retentionPercentage: fiscalState.defaultRetentionPercentage,
paymentMethodId: proforma.paymentMethodId, paymentMethodId: proforma.paymentMethodId,
paymentTermId: proforma.paymentTermId, paymentTermId: proforma.paymentTermId,
@ -60,20 +63,26 @@ export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpd
}; };
}; };
const resolveFiscalDefaults = ( const resolveFiscalState = (
proforma: Proforma, proforma: Proforma,
proformaDefaults: ProformaUpdateForm proformaDefaults: ProformaUpdateForm
): FiscalDefaults => { ): FiscalState => {
// Caso habitual: una sola combinación de impuestos // Caso habitual: una sola combinación de impuestos
if (proforma.taxes.length === 1) { if (proforma.taxes.length === 1) {
const taxSummary = proforma.taxes[0]; const taxSummary = proforma.taxes[0];
const hasTaxPercentage = taxSummary.ivaCode !== null;
const hasRecPercentage = taxSummary.recCode !== null;
const hasRetentionPercentage = taxSummary.retentionCode !== null;
return { return {
taxMode: "single", taxMode: "single",
defaultTaxPercentage: taxSummary.ivaCode === null ? null : taxSummary.ivaPercentage, defaultTaxPercentage: hasTaxPercentage ? taxSummary.ivaPercentage : null,
defaultRecPercentage: taxSummary.recCode === null ? null : taxSummary.recPercentage, defaultRecPercentage: hasRecPercentage ? taxSummary.recPercentage : null,
defaultRetentionPercentage: defaultRetentionPercentage: hasRetentionPercentage ? taxSummary.retentionPercentage : null,
taxSummary.retentionCode === null ? null : taxSummary.retentionPercentage, hasTaxPercentage,
hasRecPercentage,
hasRetentionPercentage,
}; };
} }
@ -81,17 +90,26 @@ const resolveFiscalDefaults = (
if (proforma.taxes.length === 0) { if (proforma.taxes.length === 0) {
return { return {
taxMode: "single", taxMode: "single",
defaultTaxPercentage: proformaDefaults.taxPercentage, defaultTaxPercentage: null,
defaultRecPercentage: proformaDefaults.recPercentage, defaultRecPercentage: null,
defaultRetentionPercentage: proformaDefaults.retentionPercentage, defaultRetentionPercentage: null,
hasTaxPercentage: false,
hasRecPercentage: false,
hasRetentionPercentage: false,
}; };
} }
// Proforma con varias combinaciones de impuestos // Proforma con varias combinaciones de impuestos
const taxSummary = proforma.taxes[0];
const hasRecPercentage = taxSummary.recCode !== null;
return { return {
taxMode: "perLine", taxMode: "perLine",
defaultTaxPercentage: proformaDefaults.taxPercentage, defaultTaxPercentage: null,
defaultRecPercentage: proformaDefaults.recPercentage, defaultRecPercentage: hasRecPercentage ? taxSummary.recPercentage : null,
defaultRetentionPercentage: proformaDefaults.retentionPercentage, defaultRetentionPercentage: null,
hasTaxPercentage: false,
hasRecPercentage,
hasRetentionPercentage: false,
}; };
}; };

View File

@ -5,6 +5,7 @@ import type { CustomerSelectionOption } from "@erp/customers";
import { type ValidationErrorCollection, isValidationErrorCollection } from "@repo/rdx-ddd"; import { type ValidationErrorCollection, isValidationErrorCollection } from "@repo/rdx-ddd";
import { import {
focusFirstInputFormError, focusFirstInputFormError,
getFirstFormErrorMessage,
showErrorToast, showErrorToast,
showSuccessToast, showSuccessToast,
showWarningToast, showWarningToast,
@ -14,7 +15,6 @@ import type { FieldErrors } from "react-hook-form";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
import type { UpdateProformaByIdParams } from "../../shared"; import type { UpdateProformaByIdParams } from "../../shared";
import type { Proforma } from "../../shared/entities";
import { import {
buildInvoiceSeriesSelectItems, buildInvoiceSeriesSelectItems,
ensureCurrentValueInSelectItems, ensureCurrentValueInSelectItems,
@ -22,6 +22,7 @@ import {
useProformaGetQuery, useProformaGetQuery,
useProformaUpdateMutation, useProformaUpdateMutation,
} from "../../shared"; } from "../../shared";
import type { Proforma } from "../../shared/entities";
import { mapProformaToProformaUpdateForm, mapProformaToSelectedCustomer } from "../adapters"; import { mapProformaToProformaUpdateForm, mapProformaToSelectedCustomer } from "../adapters";
import { type ProformaUpdateForm, ProformaUpdateFormSchema } from "../entities"; import { type ProformaUpdateForm, ProformaUpdateFormSchema } from "../entities";
import { import {
@ -147,8 +148,8 @@ export const useUpdateProformaController = (
if (!formHasAnyDirty(form.formState.dirtyFields)) { if (!formHasAnyDirty(form.formState.dirtyFields)) {
showWarningToast( showWarningToast(
t("proformas.update.no_changes.title"), t("pages.update.no_changes.title", "Sin cambios"),
t("proformas.update.no_changes.message") t("pages.update.no_changes.message", "No has realizado ningún cambio.")
); );
return; return;
} }
@ -181,8 +182,8 @@ export const useUpdateProformaController = (
if (options?.successToasts !== false) { if (options?.successToasts !== false) {
showSuccessToast( showSuccessToast(
t("proformas.update.success.title"), t("pages.update.success.title", "Guardado"),
t("proformas.update.success.message") t("pages.update.success.message", "Los cambios se han guardado correctamente.")
); );
} }
@ -208,7 +209,7 @@ export const useUpdateProformaController = (
console.log("Errores de validación aplicados al form:", form.formState.errors); console.log("Errores de validación aplicados al form:", form.formState.errors);
focusFirstInputFormError(form); focusFirstInputFormError(form.formState.errors, form);
if (options?.errorToasts !== false) { if (options?.errorToasts !== false) {
showWarningToast( showWarningToast(
@ -230,11 +231,12 @@ export const useUpdateProformaController = (
} }
}, },
(_errors: FieldErrors<ProformaUpdateForm>) => { (_errors: FieldErrors<ProformaUpdateForm>) => {
focusFirstInputFormError(form); focusFirstInputFormError(_errors, form);
const errorMessage = getFirstFormErrorMessage(_errors, form);
showWarningToast( showWarningToast(
t("forms.validation.title", "Revisa los campos"), t("forms.validation.title", "Revisa los campos"),
t("forms.validation.message", "Hay errores de validación en el formulario.") errorMessage ?? t("forms.validation.message", "Hay errores de validación en el formulario.")
); );
} }
); );

View File

@ -230,6 +230,14 @@ export const useUpdateProformaItemsController = ({
); );
const itemErrors = React.useMemo<ProformaItemError[]>(() => { const itemErrors = React.useMemo<ProformaItemError[]>(() => {
//console.log("itemErrors => ", errors.items);
if (!errors.items) return [];
if (errors.items.root) {
return [errors.items.root];
//console.error("Errores de validación en items.root:", errors.items.root);
}
if (!Array.isArray(errors.items)) return []; if (!Array.isArray(errors.items)) return [];
return errors.items.map((error) => error ?? {}); return errors.items.map((error) => error ?? {});

View File

@ -19,6 +19,7 @@ import { ProformaItemUpdateFormSchema } from "./proforma-item-update-form.schema
export const ProformaUpdateFormSchema = z export const ProformaUpdateFormSchema = z
.object({ .object({
proformaReference: z.string(),
series: z.string(), series: z.string(),
proformaDate: z.string().min(1), proformaDate: z.string().min(1),
@ -50,7 +51,9 @@ export const ProformaUpdateFormSchema = z
paymentMethodId: z.string().nullable(), paymentMethodId: z.string().nullable(),
paymentTermId: z.string().nullable(), paymentTermId: z.string().nullable(),
items: z.array(ProformaItemUpdateFormSchema).min(1), items: z
.array(ProformaItemUpdateFormSchema)
.min(1, "Debe haber al menos un item en la proforma"),
}) })
.refine( .refine(
(formValues) => { (formValues) => {

View File

@ -1,4 +1,5 @@
import { FormSectionCard } from "@repo/rdx-ui/components"; import { FormSectionCard } from "@repo/rdx-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { ListIcon } from "lucide-react"; import { ListIcon } from "lucide-react";
import type { ComponentProps } from "react"; import type { ComponentProps } from "react";
@ -24,10 +25,13 @@ export const ProformaUpdateItemsEditor = ({
...props ...props
}: ProformaUpdateItemsEditorProps) => { }: ProformaUpdateItemsEditorProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { totals } = totalsCtrl; const { totals } = totalsCtrl;
const { itemErrors } = itemsCtrl;
return ( return (
<FormSectionCard <FormSectionCard
className={cn(itemErrors.length ? "bg-red-100" : "")}
disabled={disabled} disabled={disabled}
icon={<ListIcon className="size-3" />} icon={<ListIcon className="size-3" />}
title={t("form_groups.items.title", "Líneas de detalle")} title={t("form_groups.items.title", "Líneas de detalle")}

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/customers", "name": "@erp/customers",
"description": "Customers", "description": "Customers",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -274,12 +274,13 @@ export default (database: Sequelize) => {
{ name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, // <- para consulta get { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, // <- para consulta get
{ name: "idx_factuges", fields: ["factuges_id"], unique: true }, // <- para el proceso python { name: "idx_factuges", fields: ["factuges_id"], unique: true }, // <- para el proceso python
// Para búsquedas simples => se hace con el "CriteriaToSequelizeConverter" // Quiksearch => "CriteriaToSequelizeConverter" construye la sentencia SQL de búsqueda FULLTEXT
/*{ // a partir de este índice. Las columnas deben coincidir con los campos de búsqueda rápida definidos en el repositorio.
name: "ft_customer", {
name: "idx_qs_customer",
type: "FULLTEXT", type: "FULLTEXT",
fields: ["name", "trade_name", "reference", "tin", "email_primary", "mobile_primary"], fields: ["name", "trade_name", "reference", "tin", "email_primary", "mobile_primary"],
},*/ },
], ],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope whereMergeStrategy: "and", // <- cómo tratar el merge de un scope

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/factuges", "name": "@erp/factuges",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/identity", "name": "@erp/identity",
"description": "Identity module", "description": "Identity module",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/supplier-invoices", "name": "@erp/supplier-invoices",
"description": "Supplier invoices", "description": "Supplier invoices",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "@erp/suppliers", "name": "@erp/suppliers",
"description": "Suppliers", "description": "Suppliers",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,7 @@
{ {
"name": "uecko-erp-2025", "name": "uecko-erp-2025",
"private": true, "private": true,
"version": "0.9.0", "version": "0.9.1",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"modules/*", "modules/*",

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/i18next", "name": "@repo/i18next",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-criteria", "name": "@repo/rdx-criteria",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ddd", "name": "@repo/rdx-ddd",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-logger", "name": "@repo/rdx-logger",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-ui", "name": "@repo/rdx-ui",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,7 +1,9 @@
import type { FieldValues, Path, UseFormReturn } from "react-hook-form"; import type { FieldErrors, FieldValues, Path, UseFormReturn } from "react-hook-form";
export const focusFirstInputFormError = <T extends FieldValues>(form: UseFormReturn<T>) => { export const focusFirstInputFormError = <T extends FieldValues>(
const errors = form.formState.errors; errors: FieldErrors<T>,
form: UseFormReturn<T>
) => {
const firstKey = Object.keys(errors)[0] as keyof T | undefined; const firstKey = Object.keys(errors)[0] as keyof T | undefined;
if (firstKey) { if (firstKey) {

View File

@ -0,0 +1,17 @@
import type { FieldErrors, FieldValues, UseFormReturn } from "react-hook-form";
export const getFirstFormErrorMessage = <T extends FieldValues>(
errors: FieldErrors<T>,
form: UseFormReturn<T>
) => {
const firstKey = Object.keys(errors)[0] as keyof T | undefined;
const firstError = errors[firstKey] as
| { message?: string; root?: { message?: string } }
| undefined;
return firstError?.message
? firstError.message
: firstError?.root?.message
? firstError.root?.message
: undefined;
};

View File

@ -1,2 +1,3 @@
export * from "./focus-first-input-form-error.ts"; export * from "./focus-first-input-form-error.ts";
export * from "./get-first-form-error-message.ts";
export * from "./prevent-enter-key-submit-form.ts"; export * from "./prevent-enter-key-submit-form.ts";

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/rdx-utils", "name": "@repo/rdx-utils",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/shadcn-ui", "name": "@repo/shadcn-ui",
"version": "0.9.0", "version": "0.9.1",
"type": "module", "type": "module",
"private": true, "private": true,
"exports": { "exports": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@repo/typescript-config", "name": "@repo/typescript-config",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"