import { useEffect, useState } from 'react' import type { CreateExperimentRequest, UpdateExperimentRequest, ScheduleStatus, ResultsStatus, ExperimentPhase, MachineType } from '../lib/supabase' import { experimentPhaseManagement, machineTypeManagement } from '../lib/supabase' interface ExperimentFormProps { initialData?: Partial onSubmit: (data: CreateExperimentRequest | UpdateExperimentRequest) => Promise onCancel: () => void isEditing?: boolean loading?: boolean phaseId?: string } export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = false, loading = false, phaseId }: ExperimentFormProps) { const [formData, setFormData] = useState({ experiment_number: initialData?.experiment_number || 0, reps_required: initialData?.reps_required || 1, weight_per_repetition_lbs: (initialData as any)?.weight_per_repetition_lbs || 1, soaking_duration_hr: initialData?.soaking_duration_hr || 0, air_drying_time_min: initialData?.air_drying_time_min || 0, plate_contact_frequency_hz: initialData?.plate_contact_frequency_hz || 1, throughput_rate_pecans_sec: initialData?.throughput_rate_pecans_sec || 1, crush_amount_in: initialData?.crush_amount_in || 0, entry_exit_height_diff_in: initialData?.entry_exit_height_diff_in || 0, // Meyer-specific (UI only) motor_speed_hz: (initialData as any)?.motor_speed_hz || 1, jig_displacement_inches: (initialData as any)?.jig_displacement_inches || 0, spring_stiffness_nm: (initialData as any)?.spring_stiffness_nm || 1, schedule_status: initialData?.schedule_status || 'pending schedule', results_status: initialData?.results_status || 'valid', completion_status: initialData?.completion_status || false, phase_id: initialData?.phase_id || phaseId }) const [errors, setErrors] = useState>({}) const [phase, setPhase] = useState(null) const [crackingMachine, setCrackingMachine] = useState(null) const [metaLoading, setMetaLoading] = useState(false) useEffect(() => { const loadMeta = async () => { if (!phaseId) return try { setMetaLoading(true) const p = await experimentPhaseManagement.getExperimentPhaseById(phaseId) setPhase(p) if (p?.has_cracking && p.cracking_machine_type_id) { const mt = await machineTypeManagement.getMachineTypeById(p.cracking_machine_type_id) setCrackingMachine(mt) } else { setCrackingMachine(null) } } catch (e) { console.warn('Failed to load phase/machine metadata', e) } finally { setMetaLoading(false) } } loadMeta() }, [phaseId]) const validateForm = (): boolean => { const newErrors: Record = {} // Required field validation if (!formData.experiment_number || formData.experiment_number <= 0) { newErrors.experiment_number = 'Experiment number must be a positive integer' } if (!formData.reps_required || formData.reps_required <= 0) { newErrors.reps_required = 'Repetitions required must be a positive integer' } if (!formData.weight_per_repetition_lbs || formData.weight_per_repetition_lbs <= 0) { newErrors.weight_per_repetition_lbs = 'Weight per repetition must be positive' } if (formData.soaking_duration_hr < 0) { newErrors.soaking_duration_hr = 'Soaking duration cannot be negative' } if (formData.air_drying_time_min < 0) { newErrors.air_drying_time_min = 'Air drying time cannot be negative' } // Validate cracking fields depending on machine if (phase?.has_cracking) { if (crackingMachine?.name === 'JC Cracker') { if (!formData.plate_contact_frequency_hz || formData.plate_contact_frequency_hz <= 0) { newErrors.plate_contact_frequency_hz = 'Plate contact frequency must be positive' } if (!formData.throughput_rate_pecans_sec || formData.throughput_rate_pecans_sec <= 0) { newErrors.throughput_rate_pecans_sec = 'Throughput rate must be positive' } if (formData.crush_amount_in < 0) { newErrors.crush_amount_in = 'Crush amount cannot be negative' } } if (crackingMachine?.name === 'Meyer Cracker') { if (!(formData as any).motor_speed_hz || (formData as any).motor_speed_hz <= 0) { newErrors.motor_speed_hz = 'Motor speed must be positive' } if ((formData as any).jig_displacement_inches === undefined) { newErrors.jig_displacement_inches = 'Jig displacement is required' } if (!(formData as any).spring_stiffness_nm || (formData as any).spring_stiffness_nm <= 0) { newErrors.spring_stiffness_nm = 'Spring stiffness must be positive' } } } setErrors(newErrors) return Object.keys(newErrors).length === 0 } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!validateForm()) { return } try { // Prepare data for submission const submitData = isEditing ? formData : { experiment_number: formData.experiment_number, reps_required: formData.reps_required, weight_per_repetition_lbs: formData.weight_per_repetition_lbs, results_status: formData.results_status, completion_status: formData.completion_status, phase_id: formData.phase_id } await onSubmit(submitData) } catch (error) { console.error('Form submission error:', error) } } const handleInputChange = (field: keyof typeof formData, value: string | number | boolean) => { setFormData(prev => ({ ...prev, [field]: value })) // Clear error for this field when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: '' })) } } return (
{/* Basic Information */}
handleInputChange('experiment_number', parseInt(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.experiment_number ? 'border-red-300' : 'border-gray-300' }`} placeholder="Enter unique experiment number" min="1" step="1" required /> {errors.experiment_number && (

{errors.experiment_number}

)}
handleInputChange('reps_required', parseInt(e.target.value) || 1)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.reps_required ? 'border-red-300' : 'border-gray-300' }`} placeholder="Total repetitions needed" min="1" step="1" required /> {errors.reps_required && (

{errors.reps_required}

)}
handleInputChange('weight_per_repetition_lbs' as any, parseFloat(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.weight_per_repetition_lbs ? 'border-red-300' : 'border-gray-300'}`} placeholder="e.g. 10.0" min="0.1" step="0.1" required /> {errors.weight_per_repetition_lbs && (

{errors.weight_per_repetition_lbs}

)}
{/* Dynamic Sections by Phase */}
{/* Soaking */} {phase?.has_soaking && (

Soaking

handleInputChange('soaking_duration_hr', parseFloat(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.soaking_duration_hr ? 'border-red-300' : 'border-gray-300'}`} placeholder="0.0" min="0" step="0.1" required /> {errors.soaking_duration_hr && (

{errors.soaking_duration_hr}

)}
)} {/* Air-Drying */} {phase?.has_airdrying && (

Air-Drying

handleInputChange('air_drying_time_min', parseInt(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.air_drying_time_min ? 'border-red-300' : 'border-gray-300'}`} placeholder="0" min="0" step="1" required /> {errors.air_drying_time_min && (

{errors.air_drying_time_min}

)}
)} {/* Cracking - machine specific */} {phase?.has_cracking && (

Cracking {crackingMachine ? `(${crackingMachine.name})` : ''}

{crackingMachine?.name === 'JC Cracker' && ( <>
handleInputChange('plate_contact_frequency_hz', parseFloat(e.target.value) || 1)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.plate_contact_frequency_hz ? 'border-red-300' : 'border-gray-300'}`} placeholder="1.0" min="0.1" step="0.1" required /> {errors.plate_contact_frequency_hz && (

{errors.plate_contact_frequency_hz}

)}
handleInputChange('throughput_rate_pecans_sec', parseFloat(e.target.value) || 1)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.throughput_rate_pecans_sec ? 'border-red-300' : 'border-gray-300'}`} placeholder="1.0" min="0.1" step="0.1" required /> {errors.throughput_rate_pecans_sec && (

{errors.throughput_rate_pecans_sec}

)}
handleInputChange('crush_amount_in', parseFloat(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.crush_amount_in ? 'border-red-300' : 'border-gray-300'}`} placeholder="0.0" min="0" step="0.001" required /> {errors.crush_amount_in && (

{errors.crush_amount_in}

)}
handleInputChange('entry_exit_height_diff_in', parseFloat(e.target.value) || 0)} className={`max-w-sm px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.entry_exit_height_diff_in ? 'border-red-300' : 'border-gray-300'}`} placeholder="0.0 (can be negative)" step="0.1" required /> {errors.entry_exit_height_diff_in && (

{errors.entry_exit_height_diff_in}

)}

Positive values indicate entry is higher than exit

)} {crackingMachine?.name === 'Meyer Cracker' && ( <>
handleInputChange('motor_speed_hz' as any, parseFloat(e.target.value) || 1)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.motor_speed_hz ? 'border-red-300' : 'border-gray-300'}`} placeholder="1.0" min="0.1" step="0.1" required /> {errors.motor_speed_hz && (

{errors.motor_speed_hz}

)}
handleInputChange('jig_displacement_inches' as any, parseFloat(e.target.value) || 0)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.jig_displacement_inches ? 'border-red-300' : 'border-gray-300'}`} placeholder="0.0" min="0" step="0.01" required /> {errors.jig_displacement_inches && (

{errors.jig_displacement_inches}

)}
handleInputChange('spring_stiffness_nm' as any, parseFloat(e.target.value) || 1)} className={`max-w-xs px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.spring_stiffness_nm ? 'border-red-300' : 'border-gray-300'}`} placeholder="1.0" min="0.1" step="0.1" required /> {errors.spring_stiffness_nm && (

{errors.spring_stiffness_nm}

)}
)}
)} {/* Shelling */} {phase?.has_shelling && (

Shelling

handleInputChange('shelling_start_offset_min' as any, parseInt(e.target.value) || 0)} className="max-w-xs px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm" placeholder="0" min="0" step="1" />
)}
{/* Status Fields (only show when editing) */} {isEditing && (

Status

handleInputChange('completion_status', e.target.checked)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
)} {/* Form Actions */}
) }