TTMT.ManageWebGUI/src/components/forms/dynamic-submit-form.tsx

161 lines
3.8 KiB
TypeScript

import { useForm } from "@tanstack/react-form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { type ReactNode } from "react";
interface FormBuilderProps<T extends Record<string, any>> {
defaultValues: T;
onSubmit: (values: T) => Promise<void> | void;
submitLabel?: string;
cancelLabel?: string;
onCancel?: () => void;
showCancel?: boolean;
children: (form: any) => ReactNode;
}
export function FormBuilder<T extends Record<string, any>>({
defaultValues,
onSubmit,
submitLabel = "Submit",
cancelLabel = "Hủy",
onCancel,
showCancel = false,
children,
}: FormBuilderProps<T>) {
const form = useForm({
defaultValues,
onSubmit: async ({ value }) => {
try {
await onSubmit(value as T);
} catch (error) {
console.error("Submit error:", error);
toast.error("Có lỗi xảy ra!");
}
},
});
return (
<form
className="space-y-4"
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
{children(form)}
<div className="flex justify-end gap-2">
{showCancel && onCancel && (
<Button type="button" variant="outline" onClick={onCancel}>
{cancelLabel}
</Button>
)}
<Button type="submit">{submitLabel}</Button>
</div>
</form>
);
}
interface FormFieldProps<T, K extends keyof T> {
form: any;
name: K;
label: string;
type?: string;
placeholder?: string;
required?: boolean;
}
export function FormField<T extends Record<string, any>, K extends keyof T>({
form,
name,
label,
type = "text",
placeholder,
required,
}: FormFieldProps<T, K>) {
return (
<form.Field name={name as string}>
{(field: any) => (
<div>
<Label>
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</Label>
<Input
type={type}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={placeholder}
/>
</div>
)}
</form.Field>
);
}
export function FormTextarea<T extends Record<string, any>, K extends keyof T>({
form,
name,
label,
placeholder,
required,
}: Omit<FormFieldProps<T, K>, "type">) {
return (
<form.Field name={name as string}>
{(field: any) => (
<div>
<Label>
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</Label>
<textarea
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={placeholder}
/>
</div>
)}
</form.Field>
);
}
export function FormSelect<T extends Record<string, any>, K extends keyof T>({
form,
name,
label,
options,
required,
}: {
form: any;
name: K;
label: string;
options: { value: string; label: string }[];
required?: boolean;
}) {
return (
<form.Field name={name as string}>
{(field: any) => (
<div>
<Label>
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</Label>
<select
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
)}
</form.Field>
);
}