import { useState, useEffect } from 'react'; import styles from './EditForm.module.css'; // Field configuration interface (moved from EditPopup) export interface EditFieldConfig { key: string; label: string; type: 'string' | 'email' | 'date' | 'enum' | 'boolean' | 'readonly' | 'textarea'; editable: boolean; required?: boolean; options?: string[]; // For enum types formatter?: (value: any) => string; // For display formatting validator?: (value: any) => string | null; // Returns error message or null placeholder?: string; minRows?: number; // For textarea types maxRows?: number; // For textarea types } // EditForm props export interface EditFormProps { data: T; fields: EditFieldConfig[]; onSave: (updatedData: T) => void; onCancel?: () => void; saveButtonText?: string; cancelButtonText?: string; showButtons?: boolean; className?: string; } // EditForm component - handles form logic export function EditForm>({ data, fields, onSave, onCancel, saveButtonText = 'Save', cancelButtonText = 'Cancel', showButtons = true, className = '' }: EditFormProps) { const [editedData, setEditedData] = useState(data); const [errors, setErrors] = useState>({}); const [fieldFocused, setFieldFocused] = useState>({}); // Reset data when data changes useEffect(() => { setEditedData({ ...data }); setErrors({}); setFieldFocused({}); // Initialize textarea heights for textarea fields setTimeout(() => { fields.forEach(field => { if (field.type === 'textarea') { const textarea = document.querySelector(`textarea[name="${field.key}"]`) as HTMLTextAreaElement; if (textarea) { const minRows = field.minRows || 4; const maxRows = field.maxRows || 8; textarea.style.height = 'auto'; const newHeight = Math.max( minRows * 1.5 * 16, Math.min( textarea.scrollHeight, maxRows * 1.5 * 16 ) ); textarea.style.height = `${newHeight}px`; } } }); }, 0); }, [data, fields]); // Handle field focus const handleFieldFocus = (fieldKey: string, focused: boolean) => { setFieldFocused(prev => ({ ...prev, [fieldKey]: focused })); }; // Handle field value changes const handleFieldChange = (fieldKey: string, value: any) => { setEditedData(prev => ({ ...prev, [fieldKey]: value })); // Clear error for this field when user starts typing if (errors[fieldKey]) { setErrors(prev => { const newErrors = { ...prev }; delete newErrors[fieldKey]; return newErrors; }); } }; // Validate all fields const validateFields = (): boolean => { const newErrors: Record = {}; fields.forEach(field => { const value = editedData[field.key]; // Check required fields if (field.required && (!value || value.toString().trim() === '')) { newErrors[field.key] = `${field.label} is required`; return; } // Run custom validator if (field.validator && value) { const error = field.validator(value); if (error) { newErrors[field.key] = error; } } // Basic email validation if (field.type === 'email' && value && !/\S+@\S+\.\S+/.test(value)) { newErrors[field.key] = 'Invalid email format'; } }); setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Handle save const handleSave = () => { if (validateFields()) { onSave(editedData); } }; // Handle cancel const handleCancel = () => { setEditedData({ ...data }); setErrors({}); onCancel?.(); }; // Helper function to get label class const getLabelClass = (fieldKey: string, value: any) => { const isFocused = fieldFocused[fieldKey]; const hasValue = value && value.toString().trim() !== ''; if (isFocused) { return styles.activeFocusedLabel; // Secondary color when actively focused } else if (hasValue) { return styles.focusedLabel; // Primary color when has value but not focused } else { return styles.label; // Regular position when empty and not focused } }; // Render field based on its type const renderField = (field: EditFieldConfig) => { const value = editedData[field.key]; const hasError = errors[field.key]; if (field.type === 'readonly' || !field.editable) { return (
{field.formatter ? field.formatter(value) : (value || 'N/A')}
); } if (field.type === 'enum') { return (
{hasError && {hasError}}
); } if (field.type === 'boolean') { return (
{hasError && {hasError}}
); } // Handle textarea type if (field.type === 'textarea') { const minRows = field.minRows || 4; const maxRows = field.maxRows || 8; return (