import React, { useState } from "react"; import { Button } from "../button"; import { Card, CardContent, CardHeader, CardTitle } from "../card"; import { cn } from "@repo/ui/lib/utils"; // Base interface for form fields export interface FormField { key: string; label: string; type: | "text" | "number" | "email" | "password" | "textarea" | "url" | "tel" | "date" | "select" | "checkbox"; required?: boolean; placeholder?: string; options?: { value: string; label: string }[]; // For select fields validation?: { min?: number; max?: number; pattern?: string; minLength?: number; maxLength?: number; }; className?: string; disabled?: boolean; helpText?: string; } // Generic form data interface export interface FormData { [key: string]: string | number | boolean; } interface CreateFormProps { title: string; fields: FormField[]; onSubmit: (data: FormData) => Promise; onCancel?: () => void; loading?: boolean; submitText?: string; cancelText?: string; className?: string; initialData?: FormData; } export function CreateForm({ title, fields, onSubmit, onCancel, loading = false, submitText = "Create", cancelText = "Cancel", className, initialData = {}, }: CreateFormProps) { const [formData, setFormData] = useState(() => { const initial: FormData = {}; fields.forEach((field) => { initial[field.key] = initialData[field.key] || (field.type === "number" ? 0 : field.type === "checkbox" ? false : ""); }); return initial; }); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const validateField = ( field: FormField, value: string | number | boolean ): string | null => { if ( field.required && (!value || (typeof value === "string" && !value.trim())) ) { return `${field.label} is required`; } if (field.validation) { const { min, max, pattern, minLength, maxLength } = field.validation; if (field.type === "number" && typeof value === "number") { if (min !== undefined && value < min) { return `${field.label} must be at least ${min}`; } if (max !== undefined && value > max) { return `${field.label} must be at most ${max}`; } } if (typeof value === "string") { if (minLength !== undefined && value.length < minLength) { return `${field.label} must be at least ${minLength} characters`; } if (maxLength !== undefined && value.length > maxLength) { return `${field.label} must be at most ${maxLength} characters`; } if (pattern && !new RegExp(pattern).test(value)) { return `${field.label} format is invalid`; } } } return null; }; const validateForm = (): boolean => { const newErrors: Record = {}; fields.forEach((field) => { const error = validateField(field, formData[field.key]); if (error) { newErrors[field.key] = error; } }); setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleInputChange = ( key: string, value: string | number | boolean ) => { setFormData((prev) => ({ ...prev, [key]: value })); // Clear error when user starts typing if (errors[key]) { setErrors((prev) => ({ ...prev, [key]: "" })); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } setIsSubmitting(true); try { await onSubmit(formData); // Reset form on success const resetData: FormData = {}; fields.forEach((field) => { resetData[field.key] = field.type === "number" ? 0 : field.type === "checkbox" ? false : ""; }); setFormData(resetData); setErrors({}); } catch (error: unknown) { setErrors({ submit: error instanceof Error ? error.message : "An error occurred", }); } finally { setIsSubmitting(false); } }; const renderField = (field: FormField) => { const isFieldDisabled = loading || isSubmitting || field.disabled; const fieldError = errors[field.key]; const baseInputClasses = cn( "w-full mt-1 p-2 border rounded-md transition-colors", "focus:ring-2 focus:ring-blue-500 focus:border-transparent", fieldError ? "border-red-500" : "border-gray-300", isFieldDisabled && "opacity-50 cursor-not-allowed", field.className ); switch (field.type) { case "textarea": return (