create new experiment works

This commit is contained in:
Alireza Vaezi
2025-07-20 19:59:28 -04:00
parent bd0ae321de
commit 6797519b0a
6 changed files with 54 additions and 69 deletions

View File

@@ -106,7 +106,7 @@ export function CreateUserModal({ roles, onClose, onUserCreated }: CreateUserMod
} }
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4"> <div className="fixed inset-0 bg-black bg-opacity-25 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4">
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-md mx-auto"> <div className="relative bg-white rounded-xl shadow-2xl w-full max-w-md mx-auto">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200"> <div className="flex items-center justify-between p-6 border-b border-gray-200">

View File

@@ -13,7 +13,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
const [formData, setFormData] = useState<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus }>({ const [formData, setFormData] = useState<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus }>({
experiment_number: initialData?.experiment_number || 0, experiment_number: initialData?.experiment_number || 0,
reps_required: initialData?.reps_required || 1, reps_required: initialData?.reps_required || 1,
rep_number: initialData?.rep_number || 1,
soaking_duration_hr: initialData?.soaking_duration_hr || 0, soaking_duration_hr: initialData?.soaking_duration_hr || 0,
air_drying_time_min: initialData?.air_drying_time_min || 0, air_drying_time_min: initialData?.air_drying_time_min || 0,
plate_contact_frequency_hz: initialData?.plate_contact_frequency_hz || 1, plate_contact_frequency_hz: initialData?.plate_contact_frequency_hz || 1,
@@ -38,13 +37,9 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
newErrors.reps_required = 'Repetitions required must be a positive integer' newErrors.reps_required = 'Repetitions required must be a positive integer'
} }
if (!formData.rep_number || formData.rep_number <= 0) {
newErrors.rep_number = 'Repetition number must be a positive integer'
}
if (formData.rep_number > formData.reps_required) {
newErrors.rep_number = 'Repetition number cannot exceed repetitions required'
}
if (formData.soaking_duration_hr < 0) { if (formData.soaking_duration_hr < 0) {
newErrors.soaking_duration_hr = 'Soaking duration cannot be negative' newErrors.soaking_duration_hr = 'Soaking duration cannot be negative'
@@ -78,7 +73,21 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
} }
try { try {
await onSubmit(formData) // Prepare data for submission
const submitData = isEditing ? formData : {
experiment_number: formData.experiment_number,
reps_required: formData.reps_required,
soaking_duration_hr: formData.soaking_duration_hr,
air_drying_time_min: formData.air_drying_time_min,
plate_contact_frequency_hz: formData.plate_contact_frequency_hz,
throughput_rate_pecans_sec: formData.throughput_rate_pecans_sec,
crush_amount_in: formData.crush_amount_in,
entry_exit_height_diff_in: formData.entry_exit_height_diff_in,
schedule_status: formData.schedule_status,
results_status: formData.results_status
}
await onSubmit(submitData)
} catch (error) { } catch (error) {
console.error('Form submission error:', error) console.error('Form submission error:', error)
} }
@@ -112,7 +121,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="experiment_number" id="experiment_number"
value={formData.experiment_number} value={formData.experiment_number}
onChange={(e) => handleInputChange('experiment_number', parseInt(e.target.value) || 0)} onChange={(e) => handleInputChange('experiment_number', parseInt(e.target.value) || 0)}
className={`w-full px-4 py-3 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' 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" placeholder="Enter unique experiment number"
min="1" min="1"
@@ -133,7 +142,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="reps_required" id="reps_required"
value={formData.reps_required} value={formData.reps_required}
onChange={(e) => handleInputChange('reps_required', parseInt(e.target.value) || 1)} onChange={(e) => handleInputChange('reps_required', parseInt(e.target.value) || 1)}
className={`w-full px-4 py-3 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' 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" placeholder="Total repetitions needed"
min="1" min="1"
@@ -145,27 +154,13 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
)} )}
</div> </div>
<div>
<label htmlFor="rep_number" className="block text-sm font-medium text-gray-700 mb-2">
Current Repetition Number *
</label>
<input
type="number"
id="rep_number"
value={formData.rep_number}
onChange={(e) => handleInputChange('rep_number', parseInt(e.target.value) || 1)}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm ${errors.rep_number ? 'border-red-300' : 'border-gray-300'
}`}
placeholder="Current repetition"
min="1"
step="1"
required
/>
{errors.rep_number && (
<p className="mt-1 text-sm text-red-600">{errors.rep_number}</p>
)}
</div> </div>
{/* Experiment Parameters */}
<div className="border-t pt-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">Experiment Parameters</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label htmlFor="soaking_duration_hr" className="block text-sm font-medium text-gray-700 mb-2"> <label htmlFor="soaking_duration_hr" className="block text-sm font-medium text-gray-700 mb-2">
Soaking Duration (hours) * Soaking Duration (hours) *
@@ -175,7 +170,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="soaking_duration_hr" id="soaking_duration_hr"
value={formData.soaking_duration_hr} value={formData.soaking_duration_hr}
onChange={(e) => handleInputChange('soaking_duration_hr', parseFloat(e.target.value) || 0)} onChange={(e) => handleInputChange('soaking_duration_hr', parseFloat(e.target.value) || 0)}
className={`w-full px-4 py-3 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' 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" placeholder="0.0"
min="0" min="0"
@@ -186,12 +181,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
<p className="mt-1 text-sm text-red-600">{errors.soaking_duration_hr}</p> <p className="mt-1 text-sm text-red-600">{errors.soaking_duration_hr}</p>
)} )}
</div> </div>
</div>
{/* Process Parameters */}
<div className="border-t pt-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">Process Parameters</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label htmlFor="air_drying_time_min" className="block text-sm font-medium text-gray-700 mb-2"> <label htmlFor="air_drying_time_min" className="block text-sm font-medium text-gray-700 mb-2">
Air Drying Time (minutes) * Air Drying Time (minutes) *
@@ -201,7 +191,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="air_drying_time_min" id="air_drying_time_min"
value={formData.air_drying_time_min} value={formData.air_drying_time_min}
onChange={(e) => handleInputChange('air_drying_time_min', parseInt(e.target.value) || 0)} onChange={(e) => handleInputChange('air_drying_time_min', parseInt(e.target.value) || 0)}
className={`w-full px-4 py-3 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' 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" placeholder="0"
min="0" min="0"
@@ -222,7 +212,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="plate_contact_frequency_hz" id="plate_contact_frequency_hz"
value={formData.plate_contact_frequency_hz} value={formData.plate_contact_frequency_hz}
onChange={(e) => handleInputChange('plate_contact_frequency_hz', parseFloat(e.target.value) || 1)} onChange={(e) => handleInputChange('plate_contact_frequency_hz', parseFloat(e.target.value) || 1)}
className={`w-full px-4 py-3 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' 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" placeholder="1.0"
min="0.1" min="0.1"
@@ -243,7 +233,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="throughput_rate_pecans_sec" id="throughput_rate_pecans_sec"
value={formData.throughput_rate_pecans_sec} value={formData.throughput_rate_pecans_sec}
onChange={(e) => handleInputChange('throughput_rate_pecans_sec', parseFloat(e.target.value) || 1)} onChange={(e) => handleInputChange('throughput_rate_pecans_sec', parseFloat(e.target.value) || 1)}
className={`w-full px-4 py-3 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' 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" placeholder="1.0"
min="0.1" min="0.1"
@@ -264,7 +254,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="crush_amount_in" id="crush_amount_in"
value={formData.crush_amount_in} value={formData.crush_amount_in}
onChange={(e) => handleInputChange('crush_amount_in', parseFloat(e.target.value) || 0)} onChange={(e) => handleInputChange('crush_amount_in', parseFloat(e.target.value) || 0)}
className={`w-full px-4 py-3 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' 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" placeholder="0.0"
min="0" min="0"
@@ -285,7 +275,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="entry_exit_height_diff_in" id="entry_exit_height_diff_in"
value={formData.entry_exit_height_diff_in} value={formData.entry_exit_height_diff_in}
onChange={(e) => handleInputChange('entry_exit_height_diff_in', parseFloat(e.target.value) || 0)} onChange={(e) => handleInputChange('entry_exit_height_diff_in', parseFloat(e.target.value) || 0)}
className={`w-full px-4 py-3 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' 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)" placeholder="0.0 (can be negative)"
step="0.1" step="0.1"
@@ -312,7 +302,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="schedule_status" id="schedule_status"
value={formData.schedule_status} value={formData.schedule_status}
onChange={(e) => handleInputChange('schedule_status', e.target.value as ScheduleStatus)} onChange={(e) => handleInputChange('schedule_status', e.target.value as ScheduleStatus)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm" 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"
> >
<option value="pending schedule">Pending Schedule</option> <option value="pending schedule">Pending Schedule</option>
<option value="scheduled">Scheduled</option> <option value="scheduled">Scheduled</option>
@@ -329,7 +319,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
id="results_status" id="results_status"
value={formData.results_status} value={formData.results_status}
onChange={(e) => handleInputChange('results_status', e.target.value as ResultsStatus)} onChange={(e) => handleInputChange('results_status', e.target.value as ResultsStatus)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm" 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"
> >
<option value="valid">Valid</option> <option value="valid">Valid</option>
<option value="invalid">Invalid</option> <option value="invalid">Invalid</option>

View File

@@ -60,7 +60,7 @@ export function ExperimentModal({ experiment, onClose, onExperimentSaved }: Expe
} }
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4"> <div className="fixed inset-0 bg-black bg-opacity-25 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4">
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-4xl mx-auto max-h-[90vh] overflow-y-auto"> <div className="relative bg-white rounded-xl shadow-2xl w-full max-w-4xl mx-auto max-h-[90vh] overflow-y-auto">
{/* Header */} {/* Header */}
<div className="sticky top-0 bg-white flex items-center justify-between p-6 border-b border-gray-200 rounded-t-xl"> <div className="sticky top-0 bg-white flex items-center justify-between p-6 border-b border-gray-200 rounded-t-xl">

View File

@@ -190,10 +190,10 @@ export function Experiments() {
Experiment # Experiment #
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Repetitions Reps Required
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Process Parameters Experiment Parameters
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Schedule Status Schedule Status
@@ -222,7 +222,7 @@ export function Experiments() {
#{experiment.experiment_number} #{experiment.experiment_number}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{experiment.rep_number} / {experiment.reps_required} {experiment.reps_required}
</td> </td>
<td className="px-6 py-4 text-sm text-gray-500"> <td className="px-6 py-4 text-sm text-gray-500">
<div className="space-y-1"> <div className="space-y-1">

View File

@@ -32,7 +32,6 @@ export interface Experiment {
id: string id: string
experiment_number: number experiment_number: number
reps_required: number reps_required: number
rep_number: number
soaking_duration_hr: number soaking_duration_hr: number
air_drying_time_min: number air_drying_time_min: number
plate_contact_frequency_hz: number plate_contact_frequency_hz: number
@@ -49,7 +48,6 @@ export interface Experiment {
export interface CreateExperimentRequest { export interface CreateExperimentRequest {
experiment_number: number experiment_number: number
reps_required: number reps_required: number
rep_number: number
soaking_duration_hr: number soaking_duration_hr: number
air_drying_time_min: number air_drying_time_min: number
plate_contact_frequency_hz: number plate_contact_frequency_hz: number
@@ -63,7 +61,6 @@ export interface CreateExperimentRequest {
export interface UpdateExperimentRequest { export interface UpdateExperimentRequest {
experiment_number?: number experiment_number?: number
reps_required?: number reps_required?: number
rep_number?: number
soaking_duration_hr?: number soaking_duration_hr?: number
air_drying_time_min?: number air_drying_time_min?: number
plate_contact_frequency_hz?: number plate_contact_frequency_hz?: number

View File

@@ -6,7 +6,6 @@ CREATE TABLE IF NOT EXISTS public.experiments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
experiment_number INTEGER UNIQUE NOT NULL, experiment_number INTEGER UNIQUE NOT NULL,
reps_required INTEGER NOT NULL CHECK (reps_required > 0), reps_required INTEGER NOT NULL CHECK (reps_required > 0),
rep_number INTEGER NOT NULL CHECK (rep_number > 0),
soaking_duration_hr FLOAT NOT NULL CHECK (soaking_duration_hr >= 0), soaking_duration_hr FLOAT NOT NULL CHECK (soaking_duration_hr >= 0),
air_drying_time_min INTEGER NOT NULL CHECK (air_drying_time_min >= 0), air_drying_time_min INTEGER NOT NULL CHECK (air_drying_time_min >= 0),
plate_contact_frequency_hz FLOAT NOT NULL CHECK (plate_contact_frequency_hz > 0), plate_contact_frequency_hz FLOAT NOT NULL CHECK (plate_contact_frequency_hz > 0),
@@ -91,7 +90,6 @@ CREATE POLICY "experiments_delete_policy" ON public.experiments
COMMENT ON TABLE public.experiments IS 'Stores experiment definitions for pecan processing with parameters and status tracking'; COMMENT ON TABLE public.experiments IS 'Stores experiment definitions for pecan processing with parameters and status tracking';
COMMENT ON COLUMN public.experiments.experiment_number IS 'User-defined unique experiment identifier'; COMMENT ON COLUMN public.experiments.experiment_number IS 'User-defined unique experiment identifier';
COMMENT ON COLUMN public.experiments.reps_required IS 'Total number of repetitions needed for this experiment'; COMMENT ON COLUMN public.experiments.reps_required IS 'Total number of repetitions needed for this experiment';
COMMENT ON COLUMN public.experiments.rep_number IS 'Current repetition number for this entry';
COMMENT ON COLUMN public.experiments.soaking_duration_hr IS 'Soaking process duration in hours'; COMMENT ON COLUMN public.experiments.soaking_duration_hr IS 'Soaking process duration in hours';
COMMENT ON COLUMN public.experiments.air_drying_time_min IS 'Air drying duration in minutes'; COMMENT ON COLUMN public.experiments.air_drying_time_min IS 'Air drying duration in minutes';
COMMENT ON COLUMN public.experiments.plate_contact_frequency_hz IS 'JC Cracker machine plate contact frequency in Hz'; COMMENT ON COLUMN public.experiments.plate_contact_frequency_hz IS 'JC Cracker machine plate contact frequency in Hz';