.
This commit is contained in:
parent
c813081ce1
commit
9f870bbd76
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@ -1,12 +1,10 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"mfeckies.handlebars-formatter",
|
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"biomejs.biome",
|
"biomejs.biome",
|
||||||
"cweijan.vscode-mysql-client2",
|
"cweijan.vscode-mysql-client2",
|
||||||
"ms-vscode.vscode-json",
|
"ms-vscode.vscode-json",
|
||||||
"formulahendry.auto-rename-tag",
|
"formulahendry.auto-rename-tag",
|
||||||
"cweijan.dbclient-jdbc",
|
"cweijan.dbclient-jdbc"
|
||||||
"nabous.handlebars-preview-plus"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,9 +44,8 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"handlebars": "^4.7.8",
|
|
||||||
"http-status": "^2.1.0",
|
"http-status": "^2.1.0",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.577.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime-types": "^3.0.1",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
|
||||||
import { join } from "node:path";
|
|
||||||
|
|
||||||
import type { IRendererTemplateResolver } from "../../../application";
|
|
||||||
|
|
||||||
import { FastReportTemplateNotFoundError } from "./fastreport";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resuelve rutas de plantillas para desarrollo y producción.
|
|
||||||
*/
|
|
||||||
export abstract class RendererTemplateResolver implements IRendererTemplateResolver {
|
|
||||||
constructor(protected readonly rootPath: string) {}
|
|
||||||
|
|
||||||
/** Une partes de ruta relativas al rootPath */
|
|
||||||
protected resolveJoin(parts: string[]): string {
|
|
||||||
return join(this.rootPath, ...parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Devuelve el directorio donde residen las plantillas de un módulo/empresa
|
|
||||||
* según el entorno (dev/prod).
|
|
||||||
*/
|
|
||||||
protected resolveTemplateDirectory(module: string, companySlug: string): string {
|
|
||||||
const isDev = process.env.NODE_ENV === "development";
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
// <root>/<module>/templates/<companySlug>/
|
|
||||||
return this.resolveJoin([module, "templates", companySlug]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// <root>/templates/<module>/<companySlug>/
|
|
||||||
//return this.resolveJoin(["templates", module, companySlug]);
|
|
||||||
return this.resolveJoin([module]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Resuelve una ruta de recurso relativa al directorio de plantilla */
|
|
||||||
protected resolveAssetPath(templateDir: string, relative: string): string {
|
|
||||||
return join(templateDir, relative);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Devuelve la ruta absoluta del fichero de plantilla.
|
|
||||||
*/
|
|
||||||
public resolveTemplatePath(module: string, companySlug: string, templateName: string): string {
|
|
||||||
const dir = this.resolveTemplateDirectory(module, companySlug);
|
|
||||||
const filePath = this.resolveAssetPath(dir, templateName);
|
|
||||||
|
|
||||||
if (!existsSync(filePath)) {
|
|
||||||
throw new FastReportTemplateNotFoundError(
|
|
||||||
`Template not found: module=${module} company=${companySlug} name=${templateName}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Lee el contenido de un fichero plantilla */
|
|
||||||
protected readTemplateFile(templatePath: string): string {
|
|
||||||
return readFileSync(templatePath, "utf8");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,44 @@
|
|||||||
import { DevTool } from '@hookform/devtools';
|
import { Suspense, lazy, useState } from "react";
|
||||||
import { useState } from "react";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
const HookFormDevTool = lazy(async () => {
|
||||||
|
const mod = await import("@hookform/devtools");
|
||||||
|
return { default: mod.DevTool };
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormDebugProps = {
|
||||||
|
enabled?: boolean;
|
||||||
|
placement?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FormDebug({ enabled = false, placement = "top-right" }: FormDebugProps) {
|
||||||
|
return enabled ? <FormDebugInner placement={placement} /> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormDebugInner({
|
||||||
|
placement,
|
||||||
|
}: {
|
||||||
|
placement: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||||
|
}) {
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<HookFormDevTool control={control} placement={placement} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Renderiza una propiedad recursivamente con expansión
|
// Renderiza una propiedad recursivamente con expansión
|
||||||
function DebugField({ label, oldValue, newValue }: { label?: string; oldValue: any; newValue: any }) {
|
function DebugField({
|
||||||
|
label,
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
}: {
|
||||||
|
label?: string;
|
||||||
|
oldValue: any;
|
||||||
|
newValue: any;
|
||||||
|
}) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const isObject = typeof newValue === "object" && newValue !== null;
|
const isObject = typeof newValue === "object" && newValue !== null;
|
||||||
@ -12,8 +47,8 @@ function DebugField({ label, oldValue, newValue }: { label?: string; oldValue: a
|
|||||||
return (
|
return (
|
||||||
<li className="ml-4">
|
<li className="ml-4">
|
||||||
{label && <span className="font-medium">{label}: </span>}
|
{label && <span className="font-medium">{label}: </span>}
|
||||||
<span className="text-gray-500 line-through">{String(oldValue)}</span>{" "}
|
<span className="text-gray-500 line-through">{String(oldValue)}</span> ➝{" "}
|
||||||
➝ <span className="text-green-600">{String(newValue)}</span>
|
<span className="text-green-600">{String(newValue)}</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -21,58 +56,19 @@ function DebugField({ label, oldValue, newValue }: { label?: string; oldValue: a
|
|||||||
return (
|
return (
|
||||||
<li className="ml-4">
|
<li className="ml-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={() => setOpen((v) => !v)}
|
|
||||||
className="text-left font-medium text-blue-600 hover:underline focus:outline-none"
|
className="text-left font-medium text-blue-600 hover:underline focus:outline-none"
|
||||||
|
onClick={() => setOpen((v) => !v)}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{open ? "▼" : "▶"} {label}
|
{open ? "▼" : "▶"} {label}
|
||||||
</button>
|
</button>
|
||||||
{open && (
|
{open && (
|
||||||
<ul className="ml-4 border-l pl-2 mt-1 space-y-1">
|
<ul className="ml-4 border-l pl-2 mt-1 space-y-1">
|
||||||
{Object.keys(newValue).map((key) => (
|
{Object.keys(newValue).map((key) => (
|
||||||
<DebugField
|
<DebugField key={key} label={key} newValue={newValue[key]} oldValue={oldValue?.[key]} />
|
||||||
key={key}
|
|
||||||
label={key}
|
|
||||||
oldValue={oldValue?.[key]}
|
|
||||||
newValue={newValue[key]}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormDebug = () => {
|
|
||||||
const { control } = useFormContext();
|
|
||||||
//const { watch, formState } = useFormContext();
|
|
||||||
//const { isDirty, dirtyFields, defaultValues } = formState;
|
|
||||||
//const currentValues = watch();
|
|
||||||
|
|
||||||
return <DevTool control={control} placement="top-right" />
|
|
||||||
|
|
||||||
/*return (
|
|
||||||
<div className="absolute right-4 bottom-4 z-50 p-4 border rounded bg-red-50">
|
|
||||||
<p>
|
|
||||||
<strong>¿Formulario modificado?</strong> {isDirty ? "Sí" : "No"}
|
|
||||||
</p>
|
|
||||||
<div className="mt-2">
|
|
||||||
<strong>Campos modificados:</strong>
|
|
||||||
{Object.keys(dirtyFields).length > 0 ? (
|
|
||||||
<ul className="list-disc list-inside mt-1">
|
|
||||||
{Object.keys(dirtyFields).map((key) => (
|
|
||||||
<DebugField
|
|
||||||
key={key}
|
|
||||||
label={key}
|
|
||||||
oldValue={defaultValues?.[key]}
|
|
||||||
newValue={currentValues[key]}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p>Ninguno</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);*/
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { FieldValues, UseFormProps, UseFormReturn, useForm } from "react-hook-form";
|
import { type FieldValues, type UseFormProps, type UseFormReturn, useForm } from "react-hook-form";
|
||||||
import * as z4 from "zod/v4/core";
|
import type * as z4 from "zod/v4/core";
|
||||||
|
|
||||||
type UseHookFormProps<TFields extends FieldValues = FieldValues, TContext = any> = UseFormProps<
|
type UseHookFormProps<TFields extends FieldValues = FieldValues, TContext = any> = UseFormProps<
|
||||||
TFields,
|
TFields,
|
||||||
@ -24,6 +24,9 @@ export function useHookForm<TFields extends FieldValues = FieldValues, TContext
|
|||||||
resolver: zodResolver(resolverSchema),
|
resolver: zodResolver(resolverSchema),
|
||||||
defaultValues: initialValues,
|
defaultValues: initialValues,
|
||||||
disabled,
|
disabled,
|
||||||
|
|
||||||
|
mode: "onBlur",
|
||||||
|
reValidateMode: "onBlur",
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -57,7 +60,9 @@ export function useHookForm<TFields extends FieldValues = FieldValues, TContext
|
|||||||
lastAppliedRef.current = next;
|
lastAppliedRef.current = next;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void apply();
|
|
||||||
|
apply();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -52,9 +52,8 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"handlebars": "^4.7.8",
|
|
||||||
"libphonenumber-js": "^1.12.7",
|
"libphonenumber-js": "^1.12.7",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.577.0",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { PageHeader, SimpleSearchInput } from "@erp/core/components";
|
import { ErrorAlert, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
||||||
import { ErrorAlert } from "@erp/customers/components";
|
|
||||||
import { AppContent, AppHeader, BackHistoryButton, LogoVerifactu } from "@repo/rdx-ui/components";
|
import { AppContent, AppHeader, BackHistoryButton, LogoVerifactu } from "@repo/rdx-ui/components";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
"@tanstack/react-query": "^5.90.6",
|
"@tanstack/react-query": "^5.90.6",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.577.0",
|
||||||
"react-data-table-component": "^7.7.0",
|
"react-data-table-component": "^7.7.0",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-i18next": "^16.2.4",
|
"react-i18next": "^16.2.4",
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import type { ICatalogs } from "@erp/core/api";
|
import type { ICatalogs } from "@erp/core/api";
|
||||||
|
|
||||||
import { CreateCustomerInputMapper, type ICreateCustomerInputMapper } from "../mappers";
|
import {
|
||||||
|
CreateCustomerInputMapper,
|
||||||
|
type ICreateCustomerInputMapper,
|
||||||
|
type IUpdateCustomerInputMapper,
|
||||||
|
UpdateCustomerInputMapper,
|
||||||
|
} from "../mappers";
|
||||||
|
|
||||||
export interface ICustomerInputMappers {
|
export interface ICustomerInputMappers {
|
||||||
createInputMapper: ICreateCustomerInputMapper;
|
createInputMapper: ICreateCustomerInputMapper;
|
||||||
|
updateInputMapper: IUpdateCustomerInputMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildCustomerInputMappers = (catalogs: ICatalogs): ICustomerInputMappers => {
|
export const buildCustomerInputMappers = (catalogs: ICatalogs): ICustomerInputMappers => {
|
||||||
@ -11,9 +17,10 @@ export const buildCustomerInputMappers = (catalogs: ICatalogs): ICustomerInputMa
|
|||||||
|
|
||||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
||||||
const createInputMapper = new CreateCustomerInputMapper({ taxCatalog });
|
const createInputMapper = new CreateCustomerInputMapper({ taxCatalog });
|
||||||
//const updateCustomerInputMapper = new UpdateCustomerInputMapper();
|
const updateInputMapper = new UpdateCustomerInputMapper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createInputMapper,
|
createInputMapper,
|
||||||
|
updateInputMapper,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import type { ICustomerRepository } from "../repositories";
|
||||||
|
import { CustomerUpdater, type ICustomerUpdater } from "../services";
|
||||||
|
|
||||||
|
export const buildCustomerUpdater = (params: {
|
||||||
|
repository: ICustomerRepository;
|
||||||
|
}): ICustomerUpdater => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new CustomerUpdater({
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -2,4 +2,5 @@ export * from "./customer-creator.di";
|
|||||||
export * from "./customer-finder.di";
|
export * from "./customer-finder.di";
|
||||||
export * from "./customer-input-mappers.di";
|
export * from "./customer-input-mappers.di";
|
||||||
export * from "./customer-snapshot-builders.di";
|
export * from "./customer-snapshot-builders.di";
|
||||||
|
export * from "./customer-updater.di";
|
||||||
export * from "./customer-use-cases.di";
|
export * from "./customer-use-cases.di";
|
||||||
|
|||||||
@ -65,14 +65,7 @@ export class UpdateCustomerUseCase {
|
|||||||
return Result.fail(updateResult.error);
|
return Result.fail(updateResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const customerOrError = await this.service.updateCustomerInCompany(
|
return Result.ok(this.fullSnapshotBuilder.toOutput(updateResult.data));
|
||||||
companyId,
|
|
||||||
updateResult.data,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
const customer = customerOrError.data;
|
|
||||||
const dto = presenter.toOutput(customer);
|
|
||||||
return Result.ok(dto);
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,10 @@ import {
|
|||||||
buildCustomerFinder,
|
buildCustomerFinder,
|
||||||
buildCustomerInputMappers,
|
buildCustomerInputMappers,
|
||||||
buildCustomerSnapshotBuilders,
|
buildCustomerSnapshotBuilders,
|
||||||
|
buildCustomerUpdater,
|
||||||
buildGetCustomerByIdUseCase,
|
buildGetCustomerByIdUseCase,
|
||||||
buildListCustomersUseCase,
|
buildListCustomersUseCase,
|
||||||
|
buildUpdateCustomerUseCase,
|
||||||
} from "../../application";
|
} from "../../application";
|
||||||
|
|
||||||
import { buildCustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
import { buildCustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
||||||
@ -49,6 +51,7 @@ export function buildCustomersDependencies(params: ModuleParams): CustomersInter
|
|||||||
const inputMappers = buildCustomerInputMappers(catalogs);
|
const inputMappers = buildCustomerInputMappers(catalogs);
|
||||||
const finder = buildCustomerFinder({ repository });
|
const finder = buildCustomerFinder({ repository });
|
||||||
const creator = buildCustomerCreator({ repository });
|
const creator = buildCustomerCreator({ repository });
|
||||||
|
const updater = buildCustomerUpdater({ repository });
|
||||||
|
|
||||||
const snapshotBuilders = buildCustomerSnapshotBuilders();
|
const snapshotBuilders = buildCustomerSnapshotBuilders();
|
||||||
//const documentGeneratorPipeline = buildCustomerDocumentService(params);
|
//const documentGeneratorPipeline = buildCustomerDocumentService(params);
|
||||||
@ -80,7 +83,7 @@ export function buildCustomersDependencies(params: ModuleParams): CustomersInter
|
|||||||
|
|
||||||
updateCustomer: () =>
|
updateCustomer: () =>
|
||||||
buildUpdateCustomerUseCase({
|
buildUpdateCustomerUseCase({
|
||||||
creator,
|
updater,
|
||||||
dtoMapper: inputMappers.updateInputMapper,
|
dtoMapper: inputMappers.updateInputMapper,
|
||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-
|
|||||||
|
|
||||||
import { UpdateCustomerByIdRequestSchema } from "../../../common";
|
import { UpdateCustomerByIdRequestSchema } from "../../../common";
|
||||||
import type { Customer } from "../api";
|
import type { Customer } from "../api";
|
||||||
|
import type { CustomerData } from "../types";
|
||||||
|
|
||||||
import { toValidationErrors } from "./toValidationErrors";
|
import { toValidationErrors } from "./toValidationErrors";
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ type UpdateCustomerContext = {};
|
|||||||
|
|
||||||
type UpdateCustomerPayload = {
|
type UpdateCustomerPayload = {
|
||||||
id: string;
|
id: string;
|
||||||
data: Partial<CustomerFormData>;
|
data: Partial<CustomerData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCustomerUpdateMutation = () => {
|
export const useCustomerUpdateMutation = () => {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./api";
|
export * from "./api";
|
||||||
export * from "./hooks";
|
export * from "./hooks";
|
||||||
|
export * from "./types";
|
||||||
|
|||||||
3
modules/customers/src/web/common/types.ts
Normal file
3
modules/customers/src/web/common/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { Customer } from "./api";
|
||||||
|
|
||||||
|
export type CustomerData = Customer;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import { FormDebug } from "@erp/core/components";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
import { CustomerAdditionalConfigFields } from "./customer-additional-config-fields";
|
import { CustomerAdditionalConfigFields } from "./customer-additional-config-fields";
|
||||||
@ -16,8 +15,7 @@ type CustomerFormProps = {
|
|||||||
export const CustomerEditForm = ({ formId, onSubmit, className, focusRef }: CustomerFormProps) => {
|
export const CustomerEditForm = ({ formId, onSubmit, className, focusRef }: CustomerFormProps) => {
|
||||||
return (
|
return (
|
||||||
<form id={formId} noValidate onSubmit={onSubmit}>
|
<form id={formId} noValidate onSubmit={onSubmit}>
|
||||||
<FormDebug />
|
<section className={cn("space-y-12 p-6", className)}>
|
||||||
<section className={cn("space-y-6 p-6", className)}>
|
|
||||||
<CustomerBasicInfoFields focusRef={focusRef} />
|
<CustomerBasicInfoFields focusRef={focusRef} />
|
||||||
<CustomerAddressFields />
|
<CustomerAddressFields />
|
||||||
<CustomerContactFields />
|
<CustomerContactFields />
|
||||||
|
|||||||
@ -45,8 +45,8 @@ export const CustomerTaxesMultiSelect = (props: CustomerTaxesMultiSelect) => {
|
|||||||
animation={0}
|
animation={0}
|
||||||
autoFilter={true}
|
autoFilter={true}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full -mt-0.5 px-1 py-0.5 rounded-md border min-h-8 h-auto items-center justify-between bg-background hover:bg-inherit [&_svg]:pointer-events-auto",
|
"flex w-full -mt-0.5 px-1 py-0.5 rounded-md border border-input min-h-8 h-auto items-center justify-between hover:bg-inherit [&_svg]:pointer-events-auto",
|
||||||
"hover:border-ring hover:ring-ring/50 hover:ring-[2px]",
|
"hover:border-ring hover:ring-ring/50 hover:ring-[2px] font-medium bg-muted/50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
export * from "./customer.api.schema";
|
export * from "./customer.api.schema";
|
||||||
export * from "./customer.form.schema";
|
export * from "./customer.form.schema";
|
||||||
export * from "./customer-resume.form.schema";
|
|
||||||
|
|||||||
@ -6,12 +6,8 @@ import { type FieldErrors, FormProvider } from "react-hook-form";
|
|||||||
|
|
||||||
import { useCustomerGetQuery, useCustomerUpdateMutation } from "../../common";
|
import { useCustomerGetQuery, useCustomerUpdateMutation } from "../../common";
|
||||||
import { useTranslation } from "../../i18n";
|
import { useTranslation } from "../../i18n";
|
||||||
import {
|
import { type Customer, CustomerFormSchema, defaultCustomerFormData } from "../../schemas";
|
||||||
type Customer,
|
import type { CustomerFormData } from "../types";
|
||||||
type CustomerFormData,
|
|
||||||
CustomerFormSchema,
|
|
||||||
defaultCustomerFormData,
|
|
||||||
} from "../../schemas";
|
|
||||||
|
|
||||||
export interface UseCustomerUpdateControllerOptions {
|
export interface UseCustomerUpdateControllerOptions {
|
||||||
onUpdated?(updated: Customer): void;
|
onUpdated?(updated: Customer): void;
|
||||||
|
|||||||
@ -1,3 +1,89 @@
|
|||||||
import type { Customer } from "../api";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
export type CustomerData = Customer;
|
export const CustomerFormSchema = z.object({
|
||||||
|
reference: z.string().optional(),
|
||||||
|
|
||||||
|
is_company: z.string().default("true"),
|
||||||
|
name: z
|
||||||
|
.string({
|
||||||
|
error: "El nombre es obligatorio",
|
||||||
|
})
|
||||||
|
.min(1, "El nombre no puede estar vacío"),
|
||||||
|
trade_name: z.string().optional(),
|
||||||
|
tin: z.string().optional(),
|
||||||
|
default_taxes: z.array(z.string()).default([]),
|
||||||
|
|
||||||
|
street: z.string().optional(),
|
||||||
|
street2: z.string().optional(),
|
||||||
|
city: z.string().optional(),
|
||||||
|
province: z.string().optional(),
|
||||||
|
postal_code: z.string().optional(),
|
||||||
|
country: z
|
||||||
|
.string({
|
||||||
|
error: "El país es obligatorio",
|
||||||
|
})
|
||||||
|
.min(1, "El país no puede estar vacío")
|
||||||
|
.toLowerCase() // asegura minúsculas
|
||||||
|
.default("es"),
|
||||||
|
|
||||||
|
email_primary: z.string().optional(),
|
||||||
|
email_secondary: z.string().optional(),
|
||||||
|
phone_primary: z.string().optional(),
|
||||||
|
phone_secondary: z.string().optional(),
|
||||||
|
mobile_primary: z.string().optional(),
|
||||||
|
mobile_secondary: z.string().optional(),
|
||||||
|
|
||||||
|
fax: z.string().optional(),
|
||||||
|
website: z.string().optional(),
|
||||||
|
|
||||||
|
legal_record: 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"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CustomerFormData = z.infer<typeof CustomerFormSchema>;
|
||||||
|
|
||||||
|
export const defaultCustomerFormData: CustomerFormData = {
|
||||||
|
reference: "",
|
||||||
|
|
||||||
|
is_company: "true",
|
||||||
|
name: "",
|
||||||
|
trade_name: "",
|
||||||
|
tin: "",
|
||||||
|
default_taxes: ["iva_21"],
|
||||||
|
|
||||||
|
street: "",
|
||||||
|
street2: "",
|
||||||
|
city: "",
|
||||||
|
province: "",
|
||||||
|
postal_code: "",
|
||||||
|
country: "es",
|
||||||
|
|
||||||
|
email_primary: "",
|
||||||
|
email_secondary: "",
|
||||||
|
phone_primary: "",
|
||||||
|
phone_secondary: "",
|
||||||
|
mobile_primary: "",
|
||||||
|
mobile_secondary: "",
|
||||||
|
|
||||||
|
fax: "",
|
||||||
|
website: "",
|
||||||
|
|
||||||
|
legal_record: "",
|
||||||
|
language_code: "es",
|
||||||
|
currency_code: "EUR",
|
||||||
|
};
|
||||||
|
|||||||
@ -31,6 +31,8 @@ export const CustomerUpdatePage = () => {
|
|||||||
FormProvider,
|
FormProvider,
|
||||||
} = useCustomerUpdateController(initialCustomerId, {});
|
} = useCustomerUpdateController(initialCustomerId, {});
|
||||||
|
|
||||||
|
console.log("venga!!!");
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <CustomerEditorSkeleton />;
|
return <CustomerEditorSkeleton />;
|
||||||
}
|
}
|
||||||
@ -68,7 +70,7 @@ export const CustomerUpdatePage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnsavedChangesProvider isDirty={form.formState.isDirty}>
|
<UnsavedChangesProvider isDirty={false}>
|
||||||
<AppHeader>
|
<AppHeader>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
backIcon
|
backIcon
|
||||||
@ -106,7 +108,7 @@ export const CustomerUpdatePage = () => {
|
|||||||
|
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<CustomerEditForm
|
<CustomerEditForm
|
||||||
className="bg-white rounded-xl border shadow-xl max-w-7xl mx-auto mt-6" // para que el botón del header pueda hacer submit
|
className="bg-white rounded-xl border shadow-xl max-w-7xl mx-auto mt-6 " // para que el botón del header pueda hacer submit
|
||||||
formId={formId}
|
formId={formId}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth
|
|||||||
import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api";
|
import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api";
|
||||||
import type { ProformaPublicServices } from "@erp/customer-invoices/api";
|
import type { ProformaPublicServices } from "@erp/customer-invoices/api";
|
||||||
import type { CustomerPublicServices } from "@erp/customers/api";
|
import type { CustomerPublicServices } from "@erp/customers/api";
|
||||||
|
import { CreateProformaFromFactugesRequestSchema } from "@erp/factuges/common";
|
||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import { CreateProformaFromFactugesRequestSchema } from "../../../common/dto/request/create-proforma-from-factuges.request.dto";
|
|
||||||
import type { FactugesInternalDeps } from "../di/factuges.di";
|
import type { FactugesInternalDeps } from "../di/factuges.di";
|
||||||
|
|
||||||
import { CreateProformaFromFactugesController } from "./controllers";
|
import { CreateProformaFromFactugesController } from "./controllers";
|
||||||
|
|||||||
@ -25,10 +25,11 @@
|
|||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
"@typescript-eslint/parser": "^8.56.1",
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
|
"baseline-browser-mapping": "^2.10.8",
|
||||||
"change-case": "^5.4.4",
|
"change-case": "^5.4.4",
|
||||||
"inquirer": "^12.10.0",
|
"inquirer": "^12.10.0",
|
||||||
"plop": "^4.0.4",
|
"plop": "^4.0.5",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^6.0.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"turbo": "^2.5.8",
|
"turbo": "^2.5.8",
|
||||||
"typescript": "5.9.3"
|
"typescript": "5.9.3"
|
||||||
|
|||||||
@ -1,32 +1,43 @@
|
|||||||
import {
|
import {
|
||||||
|
Field,
|
||||||
|
FieldDescription,
|
||||||
|
FieldError,
|
||||||
|
FieldLabel,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { Control, FieldPath, FieldValues, useController, useFormState } from "react-hook-form";
|
import {
|
||||||
|
type Control,
|
||||||
|
Controller,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
useController,
|
||||||
|
useFormState,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
import { useTranslation } from "../../locales/i18n.ts";
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
type SelectFieldProps<TFormValues extends FieldValues> = {
|
type SelectFieldProps<TFormValues extends FieldValues> = {
|
||||||
control: Control<TFormValues>;
|
control: Control<TFormValues>;
|
||||||
name: FieldPath<TFormValues>;
|
name: FieldPath<TFormValues>;
|
||||||
items: Array<{ value: string; label: string }>;
|
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
|
||||||
description?: string;
|
description?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
|
||||||
|
placeholder?: string;
|
||||||
|
items: Array<{ value: string; label: string }>;
|
||||||
|
|
||||||
|
orientation?: "vertical" | "horizontal" | "responsive";
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
inputClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SelectField<TFormValues extends FieldValues>({
|
export function SelectField<TFormValues extends FieldValues>({
|
||||||
@ -39,61 +50,61 @@ export function SelectField<TFormValues extends FieldValues>({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
required = false,
|
required = false,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
|
||||||
|
orientation = "vertical",
|
||||||
|
|
||||||
className,
|
className,
|
||||||
|
inputClassName,
|
||||||
}: SelectFieldProps<TFormValues>) {
|
}: SelectFieldProps<TFormValues>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { isSubmitting, isValidating } = useFormState({ control, name });
|
const { isSubmitting } = useFormState({ control, name });
|
||||||
const { field, fieldState } = useController({ control, name });
|
const { field, fieldState } = useController({ control, name });
|
||||||
|
|
||||||
const isDisabled = disabled || readOnly;
|
const isDisabled = disabled || readOnly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
render={({ field }) => (
|
render={({ field, fieldState }) => {
|
||||||
<FormItem className={cn("space-y-0", className)}>
|
return (
|
||||||
{label && (
|
<Field
|
||||||
<div className='mb-1 flex justify-between'>
|
className={cn("gap-1", className)}
|
||||||
<div className='flex items-center gap-2'>
|
data-invalid={fieldState.invalid}
|
||||||
<FormLabel htmlFor={name} className='m-0'>
|
orientation={orientation}
|
||||||
{label}
|
>
|
||||||
</FormLabel>
|
{label && <FieldLabel htmlFor={name}>{label}</FieldLabel>}
|
||||||
{required && (
|
|
||||||
<span className='text-xs text-destructive'>{t("common.required")}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{/* Punto “unsaved” */}
|
|
||||||
{fieldState.isDirty && (
|
|
||||||
<span className='text-[10px] text-muted-foreground'>{t("common.modified")}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={isDisabled}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger className='w-full bg-background h-8'>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={placeholder}
|
|
||||||
className=' placeholder:font-normal placeholder:italic '
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SelectItem key={`key-${item.value}`} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<FormDescription className={cn("text-xs truncate", !description && "invisible")}>
|
<Select defaultValue={field.value} disabled={isDisabled} onValueChange={field.onChange}>
|
||||||
{description || "\u00A0"}
|
<FormControl>
|
||||||
</FormDescription>
|
<SelectTrigger
|
||||||
<FormMessage />
|
className={cn(
|
||||||
</FormItem>
|
"w-full h-8",
|
||||||
)}
|
"font-medium bg-muted/50 hover:bg-inherit hover:border-ring hover:ring-ring/50 hover:ring-[2px]",
|
||||||
|
inputClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SelectValue
|
||||||
|
className={"placeholder:font-normal placeholder:italic"}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SelectItem key={`key-${item.value}`} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<FieldDescription>{description || "\u00A0"}</FieldDescription>
|
||||||
|
<FieldError errors={[fieldState.error]} />
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
// DatePickerField.tsx
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Field,
|
Field,
|
||||||
FieldDescription,
|
FieldDescription,
|
||||||
@ -7,19 +5,26 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
Textarea,
|
Textarea,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import {
|
||||||
|
type Control,
|
||||||
|
Controller,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
useFormState,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
import { cn } from '@repo/shadcn-ui/lib/utils';
|
import type { CommonInputProps } from "./types.js";
|
||||||
import { Control, Controller, FieldPath, FieldValues, useFormState } from "react-hook-form";
|
|
||||||
import { CommonInputProps } from "./types.js";
|
|
||||||
|
|
||||||
type TextAreaFieldProps<TFormValues extends FieldValues> = CommonInputProps & {
|
type TextAreaFieldProps<TFormValues extends FieldValues> = CommonInputProps & {
|
||||||
control: Control<TFormValues>;
|
control: Control<TFormValues>;
|
||||||
name: FieldPath<TFormValues>;
|
name: FieldPath<TFormValues>;
|
||||||
|
|
||||||
label?: string;
|
label?: string;
|
||||||
|
required?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
orientation?: "vertical" | "horizontal" | "responsive",
|
orientation?: "vertical" | "horizontal" | "responsive";
|
||||||
|
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
};
|
};
|
||||||
@ -32,14 +37,14 @@ export function TextAreaField<TFormValues extends FieldValues>({
|
|||||||
required = false,
|
required = false,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
|
||||||
orientation = 'vertical',
|
orientation = "vertical",
|
||||||
|
|
||||||
className,
|
className,
|
||||||
inputClassName,
|
inputClassName,
|
||||||
|
|
||||||
...inputRest
|
...inputRest
|
||||||
}: TextAreaFieldProps<TFormValues>) {
|
}: TextAreaFieldProps<TFormValues>) {
|
||||||
const { isSubmitting, isValidating } = useFormState({ control, name });
|
const { isSubmitting } = useFormState({ control, name });
|
||||||
const disabled = isSubmitting || inputRest.disabled;
|
const disabled = isSubmitting || inputRest.disabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,25 +53,30 @@ export function TextAreaField<TFormValues extends FieldValues>({
|
|||||||
name={name}
|
name={name}
|
||||||
render={({ field, fieldState }) => {
|
render={({ field, fieldState }) => {
|
||||||
return (
|
return (
|
||||||
<Field data-invalid={fieldState.invalid} orientation={orientation} className={cn("gap-1", className)}>
|
<Field
|
||||||
{label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>}
|
className={cn("gap-1", className)}
|
||||||
|
data-invalid={fieldState.invalid}
|
||||||
|
orientation={orientation}
|
||||||
|
>
|
||||||
|
{label && <FieldLabel htmlFor={name}>{label}</FieldLabel>}
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={field.ref}
|
|
||||||
id={name}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
onChange={field.onChange}
|
|
||||||
onBlur={field.onBlur}
|
|
||||||
aria-invalid={fieldState.invalid}
|
aria-invalid={fieldState.invalid}
|
||||||
aria-busy={isValidating}
|
id={name}
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
onChange={field.onChange}
|
||||||
|
ref={field.ref}
|
||||||
|
value={field.value ?? ""}
|
||||||
{...inputRest}
|
{...inputRest}
|
||||||
disabled={disabled}
|
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
className={cn("bg-background", inputClassName)}
|
className={cn(
|
||||||
|
"font-medium bg-muted/50 hover:bg-inherit hover:border-ring hover:ring-ring/50 hover:ring-[2px]",
|
||||||
|
inputClassName
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FieldDescription>{description || "\u00A0"}</FieldDescription>
|
||||||
{false && <FieldDescription className='text-xs'>{description || "\u00A0"}</FieldDescription>}
|
|
||||||
<FieldError errors={[fieldState.error]} />
|
<FieldError errors={[fieldState.error]} />
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
@ -74,4 +84,3 @@ export function TextAreaField<TFormValues extends FieldValues>({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
import {
|
import { Field, FieldDescription, FieldError, FieldLabel, Input } from "@repo/shadcn-ui/components";
|
||||||
Field,
|
|
||||||
FieldDescription,
|
|
||||||
FieldError,
|
|
||||||
FieldLabel,
|
|
||||||
Input
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { Control, Controller, FieldPath, FieldValues, useFormState } from "react-hook-form";
|
import {
|
||||||
import { CommonInputProps } from "./types.js";
|
type Control,
|
||||||
|
Controller,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
useFormState,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
|
import type { CommonInputProps } from "./types.js";
|
||||||
type Normalizer = (value: string) => string;
|
|
||||||
|
|
||||||
type TextFieldProps<TFormValues extends FieldValues> = CommonInputProps & {
|
type TextFieldProps<TFormValues extends FieldValues> = CommonInputProps & {
|
||||||
control: Control<TFormValues>;
|
control: Control<TFormValues>;
|
||||||
@ -20,7 +17,7 @@ type TextFieldProps<TFormValues extends FieldValues> = CommonInputProps & {
|
|||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
orientation?: "vertical" | "horizontal" | "responsive",
|
orientation?: "vertical" | "horizontal" | "responsive";
|
||||||
|
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
};
|
};
|
||||||
@ -33,14 +30,14 @@ export function TextField<TFormValues extends FieldValues>({
|
|||||||
required = false,
|
required = false,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
|
||||||
orientation = 'vertical',
|
orientation = "vertical",
|
||||||
|
|
||||||
className,
|
className,
|
||||||
inputClassName,
|
inputClassName,
|
||||||
|
|
||||||
...inputRest
|
...inputRest
|
||||||
}: TextFieldProps<TFormValues>) {
|
}: TextFieldProps<TFormValues>) {
|
||||||
const { isSubmitting, isValidating } = useFormState({ control, name });
|
const { isSubmitting } = useFormState({ control, name });
|
||||||
const disabled = isSubmitting || inputRest.disabled;
|
const disabled = isSubmitting || inputRest.disabled;
|
||||||
|
|
||||||
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
@ -56,24 +53,30 @@ export function TextField<TFormValues extends FieldValues>({
|
|||||||
name={name}
|
name={name}
|
||||||
render={({ field, fieldState }) => {
|
render={({ field, fieldState }) => {
|
||||||
return (
|
return (
|
||||||
<Field data-invalid={fieldState.invalid} orientation={orientation} className={cn("gap-1", className)}>
|
<Field
|
||||||
{label && <FieldLabel className='text-xs text-muted-foreground text-nowrap' htmlFor={name}>{label}</FieldLabel>}
|
className={cn("gap-1", className)}
|
||||||
|
data-invalid={fieldState.invalid}
|
||||||
|
orientation={orientation}
|
||||||
|
>
|
||||||
|
{label && <FieldLabel htmlFor={name}>{label}</FieldLabel>}
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
ref={field.ref}
|
|
||||||
id={name}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
onChange={field.onChange}
|
|
||||||
onBlur={field.onBlur}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
aria-invalid={fieldState.invalid}
|
aria-invalid={fieldState.invalid}
|
||||||
aria-busy={isValidating}
|
id={name}
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
onChange={field.onChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
ref={field.ref}
|
||||||
|
value={field.value ?? ""}
|
||||||
{...inputRest}
|
{...inputRest}
|
||||||
disabled={disabled}
|
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
className={cn("bg-background", inputClassName)}
|
className={cn(
|
||||||
|
"font-medium bg-muted/50 hover:bg-inherit hover:border-ring hover:ring-ring/50 hover:ring-[2px]",
|
||||||
|
inputClassName
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
{false && <FieldDescription className='text-xs'>{description || "\u00A0"}</FieldDescription>}
|
<FieldDescription>{description || "\u00A0"}</FieldDescription>
|
||||||
<FieldError errors={[fieldState.error]} />
|
<FieldError errors={[fieldState.error]} />
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import * as React from "react"
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import type * as React from "react";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils"
|
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
|
||||||
data-slot="input"
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
data-slot="input"
|
||||||
|
type={type}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
@ -125,12 +125,12 @@
|
|||||||
--secondary-foreground: oklch(0.9543 0.0166 250.8425);
|
--secondary-foreground: oklch(0.9543 0.0166 250.8425);
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: oklch(0.97 0 0);
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.6427 0.1407 253.94);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.97 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.575 0.1533 256.4357); /* oklch(0.708 0 0); */
|
||||||
--chart-1: oklch(0.809 0.105 251.813);
|
--chart-1: oklch(0.809 0.105 251.813);
|
||||||
--chart-2: oklch(0.623 0.214 259.815);
|
--chart-2: oklch(0.623 0.214 259.815);
|
||||||
--chart-3: oklch(0.546 0.245 262.881);
|
--chart-3: oklch(0.546 0.245 262.881);
|
||||||
|
|||||||
1894
pnpm-lock.yaml
1894
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -12,4 +12,5 @@ onlyBuiltDependencies:
|
|||||||
- '@tailwindcss/oxide'
|
- '@tailwindcss/oxide'
|
||||||
- bcrypt
|
- bcrypt
|
||||||
- core-js-pure
|
- core-js-pure
|
||||||
|
- msw
|
||||||
- sharp
|
- sharp
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user