.
This commit is contained in:
parent
a32ba80bb6
commit
1ac0533de5
@ -1,11 +1,17 @@
|
|||||||
import { CancelButton, FormDatePickerField, FormTextAreaField, FormTextField } from "@/components";
|
import {
|
||||||
|
BackHistoryButton,
|
||||||
|
FormDatePickerField,
|
||||||
|
FormTextAreaField,
|
||||||
|
FormTextField,
|
||||||
|
} from "@/components";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
|
||||||
import { ChevronLeft } from "lucide-react";
|
import { ChevronLeft } from "lucide-react";
|
||||||
|
|
||||||
import { SubmitButton } from "@/components";
|
import { SubmitButton } from "@/components";
|
||||||
import { Button, Form } from "@/ui";
|
import { Button, Form } from "@/ui";
|
||||||
import { SubmitHandler, useForm } from "react-hook-form";
|
import { FieldErrors, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuotes } from "./hooks";
|
import { useQuotes } from "./hooks";
|
||||||
|
|
||||||
type QuoteDataForm = {
|
type QuoteDataForm = {
|
||||||
@ -32,14 +38,15 @@ export const QuoteCreate = () => {
|
|||||||
//const { data: userIdentity } = useGetIdentity();
|
//const { data: userIdentity } = useGetIdentity();
|
||||||
//console.log(userIdentity);
|
//console.log(userIdentity);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const { useMutation } = useQuotes();
|
const { useMutation } = useQuotes();
|
||||||
const { mutate } = useMutation;
|
const { mutate } = useMutation();
|
||||||
|
|
||||||
const form = useForm<QuoteDataForm>({
|
const form = useForm<QuoteDataForm>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
reference: "",
|
date: new Date(Date.now()).toUTCString(),
|
||||||
date: Date.now().toLocaleString(),
|
|
||||||
customer_information: "",
|
customer_information: "",
|
||||||
|
reference: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,15 +55,25 @@ export const QuoteCreate = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
//setLoading(true);
|
//setLoading(true);
|
||||||
mutate(formData);
|
mutate(formData, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
navigate(`/quotes/edit/${data.id}`, { relative: "path", replace: true });
|
||||||
|
},
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
//setLoading(false);
|
//setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onErrors: SubmitErrorHandler<QuoteDataForm> = async (
|
||||||
|
errors: FieldErrors<QuoteDataForm>
|
||||||
|
) => {
|
||||||
|
console.log(errors);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form onSubmit={form.handleSubmit(onSubmit, onErrors)}>
|
||||||
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
|
<div className='mx-auto grid max-w-[90rem] flex-1 auto-rows-max gap-6'>
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-4'>
|
||||||
<Button variant='outline' size='icon' className='h-7 w-7'>
|
<Button variant='outline' size='icon' className='h-7 w-7'>
|
||||||
@ -70,47 +87,35 @@ export const QuoteCreate = () => {
|
|||||||
|
|
||||||
<div className='grid max-w-lg gap-6'>
|
<div className='grid max-w-lg gap-6'>
|
||||||
<FormTextField
|
<FormTextField
|
||||||
|
className='row-span-2'
|
||||||
|
name='reference'
|
||||||
|
required
|
||||||
label={t("quotes.create.form_fields.reference.label")}
|
label={t("quotes.create.form_fields.reference.label")}
|
||||||
description={t("quotes.create.form_fields.reference.desc")}
|
description={t("quotes.create.form_fields.reference.desc")}
|
||||||
disabled={form.formState.disabled}
|
|
||||||
placeholder={t("quotes.create.form_fields.reference.placeholder")}
|
placeholder={t("quotes.create.form_fields.reference.placeholder")}
|
||||||
{...form.register("reference", {
|
|
||||||
required: false,
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormDatePickerField
|
<FormDatePickerField
|
||||||
required
|
required
|
||||||
label={t("quotes.create.form_fields.date.label")}
|
label={t("quotes.create.form_fields.date.label")}
|
||||||
description={t("quotes.create.form_fields.date.desc")}
|
description={t("quotes.create.form_fields.date.desc")}
|
||||||
disabled={form.formState.disabled}
|
|
||||||
placeholder={t("quotes.create.form_fields.date.placeholder")}
|
placeholder={t("quotes.create.form_fields.date.placeholder")}
|
||||||
{...form.register("date", {
|
name='date'
|
||||||
required: true,
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='grid grid-cols-1 grid-rows-2 gap-6'>
|
<FormTextAreaField
|
||||||
<FormTextAreaField
|
rows={4}
|
||||||
className='row-span-2'
|
className='row-span-2'
|
||||||
required
|
name='customer_information'
|
||||||
label={t("quotes.create.form_fields.customer_information.label")}
|
required
|
||||||
description={t("quotes.create.form_fields.customer_information.desc")}
|
label={t("quotes.create.form_fields.customer_information.label")}
|
||||||
disabled={form.formState.disabled}
|
description={t("quotes.create.form_fields.customer_information.desc")}
|
||||||
placeholder={t("quotes.create.form_fields.customer_information.placeholder")}
|
placeholder={t("quotes.create.form_fields.customer_information.placeholder")}
|
||||||
{...form.register("customer_information", {
|
/>
|
||||||
required: true,
|
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-center gap-2'>
|
<div className='flex items-center justify-start gap-2'>
|
||||||
<CancelButton
|
<BackHistoryButton size='sm' label={t("quotes.create.buttons.discard")} url='/quotes' />
|
||||||
variant='outline'
|
|
||||||
size='sm'
|
|
||||||
label={t("quotes.create.buttons.discard")}
|
|
||||||
></CancelButton>
|
|
||||||
|
|
||||||
<SubmitButton size='sm' label={t("common.continue")}></SubmitButton>
|
<SubmitButton size='sm' label={t("common.continue")}></SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,32 +19,36 @@ export const useQuotes = (params?: UseQuotesGetParamsType) => {
|
|||||||
const keys = useQueryKey();
|
const keys = useQueryKey();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useQuery: useOne<IGetQuote_Response_DTO>({
|
useQuery: () =>
|
||||||
queryKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
useOne<IGetQuote_Response_DTO>({
|
||||||
queryFn: () =>
|
queryKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
||||||
dataSource.getOne({
|
queryFn: () =>
|
||||||
resource: "quotes",
|
dataSource.getOne({
|
||||||
id: "",
|
resource: "quotes",
|
||||||
}),
|
id: "",
|
||||||
...params,
|
}),
|
||||||
}),
|
...params,
|
||||||
useMutation: useSave<ICreateQuote_Response_DTO, TDataSourceError, ICreateQuote_Request_DTO>({
|
}),
|
||||||
mutationKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
useMutation: () =>
|
||||||
mutationFn: (data) => {
|
useSave<ICreateQuote_Response_DTO, TDataSourceError, ICreateQuote_Request_DTO>({
|
||||||
let { id } = data;
|
mutationKey: keys().data().resource("quotes").action("one").id("").params().get(),
|
||||||
|
mutationFn: (data) => {
|
||||||
|
let { id, status } = data;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
id = UniqueID.generateNewID().object.toString();
|
id = UniqueID.generateNewID().object.toString();
|
||||||
}
|
status = "draft";
|
||||||
|
}
|
||||||
|
|
||||||
return dataSource.createOne({
|
return dataSource.createOne({
|
||||||
resource: "quotes",
|
resource: "quotes",
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
id,
|
status,
|
||||||
},
|
id,
|
||||||
});
|
},
|
||||||
},
|
});
|
||||||
}),
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,12 +4,9 @@ export interface CancelButtonProps extends ButtonProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CancelButton = ({
|
export const CancelButton = ({ label = "Cancelar", ...props }: CancelButtonProps): JSX.Element => {
|
||||||
label = "Cancelar",
|
|
||||||
...props
|
|
||||||
}: CancelButtonProps): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<Button type="button" variant="secondary" {...props}>
|
<Button type='button' variant='secondary' {...props}>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,9 +18,7 @@ const customButtonVariants = cva("", {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface CustomButtonProps
|
export interface CustomButtonProps extends ButtonProps, VariantProps<typeof customButtonVariants> {
|
||||||
extends ButtonProps,
|
|
||||||
VariantProps<typeof customButtonVariants> {
|
|
||||||
icon: LucideIcon; // Propiedad para proporcionar el icono personalizado
|
icon: LucideIcon; // Propiedad para proporcionar el icono personalizado
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Button, ButtonProps } from "@/ui";
|
import { Button, ButtonProps } from "@/ui";
|
||||||
import { ChevronLeft } from "lucide-react";
|
import { ChevronLeft } from "lucide-react";
|
||||||
import { To, useNavigate } from "react-router-dom";
|
import { To, useNavigate } from "react-router-dom";
|
||||||
|
import { CustomButton } from "../Buttons/CustomButton";
|
||||||
|
|
||||||
export interface BackHistoryButtonProps extends ButtonProps {
|
export interface BackHistoryButtonProps extends ButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -15,16 +16,29 @@ export const BackHistoryButton = ({
|
|||||||
}: BackHistoryButtonProps): JSX.Element => {
|
}: BackHistoryButtonProps): JSX.Element => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomButton
|
||||||
|
type='button'
|
||||||
|
label={label}
|
||||||
|
icon={ChevronLeft}
|
||||||
|
variant='ghost'
|
||||||
|
onClick={() => {
|
||||||
|
url ? navigate(url) : navigate(-1);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant='ghost'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
url ? navigate(url) : navigate(-1);
|
url ? navigate(url) : navigate(-1);
|
||||||
}}
|
}}
|
||||||
size={size}
|
size={size}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-4 h-4" />
|
<ChevronLeft className='w-4 h-4' />
|
||||||
<span className={size === "icon" ? "sr-only" : "ml-2"}>{label}</span>
|
<span className={size === "icon" ? "sr-only" : "ml-2"}>{label}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
FormDescription,
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
|
||||||
InputProps,
|
InputProps,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@ -28,14 +27,12 @@ import { CalendarIcon } from "lucide-react";
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
|
|
||||||
type FormDatePickerFieldProps<
|
type FormDatePickerFieldProps<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
> = InputProps &
|
> = InputProps & FormInputProps & Partial<FormLabelProps> & UseControllerProps<TFieldValues, TName>;
|
||||||
FormInputProps &
|
|
||||||
Partial<FormLabelProps> &
|
|
||||||
UseControllerProps<TFieldValues, TName> & {};
|
|
||||||
|
|
||||||
/*const loadDateFnsLocale = async (locale: Locale) => {
|
/*const loadDateFnsLocale = async (locale: Locale) => {
|
||||||
return await import(`date-fns/locale/${locale.code}/index.js`);
|
return await import(`date-fns/locale/${locale.code}/index.js`);
|
||||||
@ -45,19 +42,7 @@ export const FormDatePickerField = React.forwardRef<
|
|||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & FormDatePickerFieldProps
|
React.HTMLAttributes<HTMLDivElement> & FormDatePickerFieldProps
|
||||||
>((props: FormDatePickerFieldProps, ref) => {
|
>((props: FormDatePickerFieldProps, ref) => {
|
||||||
const {
|
const { label, placeholder, hint, description, required, className, name } = props;
|
||||||
label,
|
|
||||||
placeholder,
|
|
||||||
hint,
|
|
||||||
description,
|
|
||||||
required,
|
|
||||||
className,
|
|
||||||
disabled,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
errors,
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
} = props;
|
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
//const { locale } = loadDateFnsLocale();
|
//const { locale } = loadDateFnsLocale();
|
||||||
|
|
||||||
@ -72,11 +57,10 @@ export const FormDatePickerField = React.forwardRef<
|
|||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
rules={{ required }}
|
rules={{ required }}
|
||||||
disabled={disabled}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
render={({ field, fieldState, formState }) => (
|
render={({ field, fieldState, formState }) => (
|
||||||
<FormItem ref={ref} className={cn(className, "flex flex-col")}>
|
<FormItem ref={ref} className={cn(className, "flex flex-col")}>
|
||||||
{label && <FormLabel label={label} hint={hint} />}
|
{label && <FormLabel label={label} hint={hint} required={required} />}
|
||||||
|
|
||||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@ -90,6 +74,8 @@ export const FormDatePickerField = React.forwardRef<
|
|||||||
>
|
>
|
||||||
{field.value ? (
|
{field.value ? (
|
||||||
new Date(field.value).toLocaleDateString() //"en-US", DATE_OPTIONS)
|
new Date(field.value).toLocaleDateString() //"en-US", DATE_OPTIONS)
|
||||||
|
) : placeholder ? (
|
||||||
|
placeholder
|
||||||
) : (
|
) : (
|
||||||
<span>{t("common.pick_date")}</span>
|
<span>{t("common.pick_date")}</span>
|
||||||
)}
|
)}
|
||||||
@ -117,8 +103,9 @@ export const FormDatePickerField = React.forwardRef<
|
|||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
<FormMessage />
|
<FormErrorMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
30
client/src/components/Forms/FormErrorMessage.tsx
Normal file
30
client/src/components/Forms/FormErrorMessage.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { FormMessage, useFormField } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export const FormErrorMessage = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ children, ...props }, ref) => {
|
||||||
|
const { error } = useFormField();
|
||||||
|
let message = children;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
if (error.message) {
|
||||||
|
message = String(error?.message || error.root?.message);
|
||||||
|
} else {
|
||||||
|
if (error.type === "required") {
|
||||||
|
message = t("common.required_field");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(message);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormMessage ref={ref} {...props}>
|
||||||
|
{message}
|
||||||
|
</FormMessage>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FormErrorMessage.displayName = "FormErrorMessage";
|
||||||
@ -14,12 +14,14 @@ export const FormLabel = React.forwardRef<
|
|||||||
FormLabelProps &
|
FormLabelProps &
|
||||||
Pick<FormInputProps, "required">
|
Pick<FormInputProps, "required">
|
||||||
>(({ label, hint, required, ...props }, ref) => {
|
>(({ label, hint, required, ...props }, ref) => {
|
||||||
|
const { error } = UI.useFormField();
|
||||||
|
|
||||||
const _hint = hint ? hint : required ? "obligatorio" : undefined;
|
const _hint = hint ? hint : required ? "obligatorio" : undefined;
|
||||||
const _hintClassName = required ? "text-destructive" : "";
|
const _hintClassName = error ? "text-destructive font-semibold" : "";
|
||||||
return (
|
return (
|
||||||
<UI.FormLabel ref={ref} className='flex justify-between text-sm' {...props}>
|
<UI.FormLabel ref={ref} className='flex justify-between text-sm' {...props}>
|
||||||
<span className='block font-semibold'>{label}</span>
|
<span className={`block font-semibold ${_hintClassName}`}>{label}</span>
|
||||||
{_hint && <span className={`text-sm font-medium ${_hintClassName}`}>{_hint}</span>}
|
{_hint && <span className={`text-sm font-medium ${_hintClassName} `}>{_hint}</span>}
|
||||||
</UI.FormLabel>
|
</UI.FormLabel>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {
|
|||||||
FormDescription,
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
|
||||||
Textarea,
|
Textarea,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@ -17,6 +16,7 @@ import {
|
|||||||
UseControllerProps,
|
UseControllerProps,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
|
|
||||||
export type FormTextAreaFieldProps<
|
export type FormTextAreaFieldProps<
|
||||||
@ -41,12 +41,14 @@ export const FormTextAreaField = React.forwardRef<
|
|||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
hint,
|
hint,
|
||||||
description,
|
|
||||||
placeholder,
|
placeholder,
|
||||||
|
description,
|
||||||
|
|
||||||
required,
|
required,
|
||||||
disabled,
|
|
||||||
autoSize,
|
|
||||||
className,
|
className,
|
||||||
|
|
||||||
|
autoSize,
|
||||||
|
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@ -57,7 +59,6 @@ export const FormTextAreaField = React.forwardRef<
|
|||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
rules={{ required }}
|
rules={{ required }}
|
||||||
disabled={disabled}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
render={({ field, fieldState, formState }) => (
|
render={({ field, fieldState, formState }) => (
|
||||||
<FormItem ref={ref} className={cn(className, "flex flex-col space-y-3")}>
|
<FormItem ref={ref} className={cn(className, "flex flex-col space-y-3")}>
|
||||||
@ -65,17 +66,29 @@ export const FormTextAreaField = React.forwardRef<
|
|||||||
<FormControl className='grow'>
|
<FormControl className='grow'>
|
||||||
{autoSize ? (
|
{autoSize ? (
|
||||||
<AutosizeTextarea
|
<AutosizeTextarea
|
||||||
{...field}
|
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className='resize-y'
|
className={cn(
|
||||||
|
fieldState.error ? "border-destructive focus-visible:ring-destructive" : "",
|
||||||
|
"resize-y"
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
{...field}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Textarea {...field} placeholder={placeholder} className='resize-y' {...props} />
|
<Textarea
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={cn(
|
||||||
|
fieldState.error ? "border-destructive focus-visible:ring-destructive" : "",
|
||||||
|
"resize-y"
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
<FormMessage />
|
<FormErrorMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,23 +1,10 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import { FormControl, FormDescription, FormField, FormItem, Input, InputProps } from "@/ui";
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
Input,
|
|
||||||
InputProps,
|
|
||||||
} from "@/ui";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
import {
|
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
FieldErrors,
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
FieldPath,
|
|
||||||
FieldValues,
|
|
||||||
UseControllerProps,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
import { FormInputProps, FormInputWithIconProps } from "./FormProps";
|
||||||
|
|
||||||
@ -30,28 +17,25 @@ export type FormTextFieldProps<
|
|||||||
FormInputProps &
|
FormInputProps &
|
||||||
Partial<FormLabelProps> &
|
Partial<FormLabelProps> &
|
||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName> & {
|
UseControllerProps<TFieldValues, TName>;
|
||||||
errors?: FieldErrors<TFieldValues>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormTextField = React.forwardRef<
|
export const FormTextField = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & FormTextFieldProps
|
React.HTMLAttributes<HTMLDivElement> & FormTextFieldProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
|
name,
|
||||||
label,
|
label,
|
||||||
placeholder,
|
|
||||||
hint,
|
hint,
|
||||||
|
placeholder,
|
||||||
description,
|
description,
|
||||||
|
|
||||||
required,
|
required,
|
||||||
className,
|
className,
|
||||||
leadIcon,
|
leadIcon,
|
||||||
trailIcon,
|
trailIcon,
|
||||||
button,
|
button,
|
||||||
disabled,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
errors,
|
|
||||||
name,
|
|
||||||
type,
|
type,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -62,55 +46,64 @@ export const FormTextField = React.forwardRef<
|
|||||||
control={control}
|
control={control}
|
||||||
name={name}
|
name={name}
|
||||||
rules={{ required }}
|
rules={{ required }}
|
||||||
disabled={disabled}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
render={({ field, fieldState, formState }) => (
|
render={({ field, fieldState, formState }) => {
|
||||||
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
return (
|
||||||
{label && <FormLabel label={label} hint={hint} required={required} />}
|
<FormItem ref={ref} className={cn(className, "space-y-3")}>
|
||||||
<div className={cn(button ? "flex" : null)}>
|
{label && <FormLabel label={label} hint={hint} required={required} />}
|
||||||
<div
|
<div className={cn(button ? "flex" : null)}>
|
||||||
className={cn(
|
<div
|
||||||
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
className={cn(
|
||||||
)}
|
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
||||||
>
|
)}
|
||||||
{leadIcon && (
|
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
|
||||||
{React.createElement(
|
|
||||||
leadIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormControl
|
|
||||||
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
|
||||||
>
|
>
|
||||||
<Input type={type} placeholder={placeholder} disabled={disabled} {...field} />
|
{leadIcon && (
|
||||||
</FormControl>
|
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
||||||
|
{React.createElement(
|
||||||
|
leadIcon,
|
||||||
|
{
|
||||||
|
className: "h-5 w-5 text-muted-foreground",
|
||||||
|
"aria-hidden": true,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{trailIcon && (
|
<FormControl
|
||||||
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
||||||
{createElement(
|
>
|
||||||
trailIcon,
|
<Input
|
||||||
{
|
type={type}
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
placeholder={placeholder}
|
||||||
"aria-hidden": true,
|
className={cn(
|
||||||
},
|
fieldState.error ? "border-destructive focus-visible:ring-destructive" : ""
|
||||||
null
|
)}
|
||||||
)}
|
{...field}
|
||||||
</div>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
|
|
||||||
|
{trailIcon && (
|
||||||
|
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
||||||
|
{createElement(
|
||||||
|
trailIcon,
|
||||||
|
{
|
||||||
|
className: "h-5 w-5 text-muted-foreground",
|
||||||
|
"aria-hidden": true,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{button && <>{createElement(button)}</>}
|
||||||
</div>
|
</div>
|
||||||
{button && <>{createElement(button)}</>}
|
|
||||||
</div>
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
<FormErrorMessage />
|
||||||
<FormMessage />
|
</FormItem>
|
||||||
</FormItem>
|
);
|
||||||
)}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./FormDatePickerField";
|
export * from "./FormDatePickerField";
|
||||||
|
export * from "./FormErrorMessage";
|
||||||
export * from "./FormGroup";
|
export * from "./FormGroup";
|
||||||
export * from "./FormLabel";
|
export * from "./FormLabel";
|
||||||
export * from "./FormMoneyField";
|
export * from "./FormMoneyField";
|
||||||
|
|||||||
@ -21,8 +21,8 @@
|
|||||||
--secondary-foreground: 25 18% 30%;
|
--secondary-foreground: 25 18% 30%;
|
||||||
--accent: 25 23% 83%;
|
--accent: 25 23% 83%;
|
||||||
--accent-foreground: 25 23% 23%;
|
--accent-foreground: 25 23% 23%;
|
||||||
--destructive: 13 96% 20%;
|
--destructive: 0 72.2% 50.6%; /* 13 96% 20%; */
|
||||||
--destructive-foreground: 13 96% 80%;
|
--destructive-foreground: 0 85.7% 97.3%; /* 13 96% 80%; */
|
||||||
--ring: 25 31% 75%;
|
--ring: 25 31% 75%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,8 @@
|
|||||||
"open_menu": "Abrir el menú",
|
"open_menu": "Abrir el menú",
|
||||||
"duplicate_rows": "Duplicar",
|
"duplicate_rows": "Duplicar",
|
||||||
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
|
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
|
||||||
"pick_date": "Elige una fecha"
|
"pick_date": "Elige una fecha",
|
||||||
|
"required_field": "Este campo es obligatorio"
|
||||||
},
|
},
|
||||||
"main_menu": {
|
"main_menu": {
|
||||||
"home": "Inicio",
|
"home": "Inicio",
|
||||||
|
|||||||
@ -22,9 +22,7 @@ type FormFieldContextValue<
|
|||||||
name: TName;
|
name: TName;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||||
{} as FormFieldContextValue
|
|
||||||
);
|
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
@ -66,22 +64,19 @@ type FormItemContextValue = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||||
{} as FormItemContextValue
|
|
||||||
|
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
const id = React.useId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const id = React.useId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItemContext.Provider value={{ id }}>
|
|
||||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
||||||
</FormItemContext.Provider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
FormItem.displayName = "FormItem";
|
FormItem.displayName = "FormItem";
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
const FormLabel = React.forwardRef<
|
||||||
@ -105,18 +100,13 @@ const FormControl = React.forwardRef<
|
|||||||
React.ElementRef<typeof Slot>,
|
React.ElementRef<typeof Slot>,
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
React.ComponentPropsWithoutRef<typeof Slot>
|
||||||
>(({ ...props }, ref) => {
|
>(({ ...props }, ref) => {
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||||
useFormField();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@ -146,7 +136,8 @@ const FormMessage = React.forwardRef<
|
|||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
const { error, formMessageId } = useFormField();
|
const { error, formMessageId } = useFormField();
|
||||||
const body = error ? String(error?.message) : children;
|
|
||||||
|
const body = error && error.message ? String(error?.message || error.root?.message) : children;
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
@ -17,9 +16,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
Input.displayName = "Input"
|
Input.displayName = "Input";
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user