Refactor Experiment and Phase components for improved state management and UI consistency

- Resolved merge conflicts and cleaned up code in ExperimentForm, enhancing initial state handling and validation logic.
- Updated PhaseForm to reflect correct placeholder text for descriptions.
- Simplified Sidebar component by removing unused props and ensuring consistent behavior across expanded and collapsed states.
- Adjusted Scheduling component to streamline repetition management and improve user experience.
This commit is contained in:
salirezav
2026-03-09 13:53:03 -04:00
parent 97ded48d54
commit 9bcb5c203e
5 changed files with 0 additions and 388 deletions

View File

@@ -3,11 +3,7 @@ import type { CreateExperimentRequest, UpdateExperimentRequest, ScheduleStatus,
import { experimentPhaseManagement, machineTypeManagement } from '../lib/supabase'
interface ExperimentFormProps {
<<<<<<< HEAD
initialData?: Partial<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus; completion_status: boolean }>
=======
initialData?: Partial<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus; completion_status: boolean }> & { phase_id?: string | null }
>>>>>>> old-github/main
onSubmit: (data: CreateExperimentRequest | UpdateExperimentRequest) => Promise<void>
onCancel: () => void
isEditing?: boolean
@@ -16,28 +12,6 @@ interface ExperimentFormProps {
}
export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = false, loading = false, phaseId }: ExperimentFormProps) {
<<<<<<< HEAD
const [formData, setFormData] = useState<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus; completion_status: boolean }>({
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 getInitialFormState = (d: any) => ({
experiment_number: d?.experiment_number ?? 0,
reps_required: d?.reps_required ?? 1,
@@ -61,14 +35,11 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
const [formData, setFormData] = useState<CreateExperimentRequest & { schedule_status: ScheduleStatus; results_status: ResultsStatus; completion_status: boolean }>(() => getInitialFormState(initialData))
>>>>>>> old-github/main
const [errors, setErrors] = useState<Record<string, string>>({})
const [phase, setPhase] = useState<ExperimentPhase | null>(null)
const [crackingMachine, setCrackingMachine] = useState<MachineType | null>(null)
const [metaLoading, setMetaLoading] = useState<boolean>(false)
<<<<<<< HEAD
=======
// When initialData loads with phase config (edit mode), sync form state
useEffect(() => {
if ((initialData as any)?.id) {
@@ -76,7 +47,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
}, [initialData])
>>>>>>> old-github/main
useEffect(() => {
const loadMeta = async () => {
if (!phaseId) return
@@ -116,19 +86,11 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
<<<<<<< HEAD
if (formData.soaking_duration_hr < 0) {
newErrors.soaking_duration_hr = 'Soaking duration cannot be negative'
}
if (formData.air_drying_time_min < 0) {
=======
if ((formData.soaking_duration_hr ?? 0) < 0) {
newErrors.soaking_duration_hr = 'Soaking duration cannot be negative'
}
if ((formData.air_drying_time_min ?? 0) < 0) {
>>>>>>> old-github/main
newErrors.air_drying_time_min = 'Air drying time cannot be negative'
}
@@ -141,11 +103,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
if (!formData.throughput_rate_pecans_sec || formData.throughput_rate_pecans_sec <= 0) {
newErrors.throughput_rate_pecans_sec = 'Throughput rate must be positive'
}
<<<<<<< HEAD
if (formData.crush_amount_in < 0) {
=======
if ((formData.crush_amount_in ?? 0) < 0) {
>>>>>>> old-github/main
newErrors.crush_amount_in = 'Crush amount cannot be negative'
}
}
@@ -162,8 +120,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
}
<<<<<<< HEAD
=======
// Shelling: if provided, must be positive
if (phase?.has_shelling) {
if (formData.ring_gap_inches != null && (typeof formData.ring_gap_inches !== 'number' || formData.ring_gap_inches <= 0)) {
@@ -174,7 +130,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
}
>>>>>>> old-github/main
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
@@ -187,20 +142,13 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
try {
<<<<<<< HEAD
// Prepare data for submission
=======
// Prepare data: include all phase params so they are stored in experiment_soaking, experiment_airdrying, experiment_cracking, experiment_shelling
>>>>>>> old-github/main
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,
<<<<<<< HEAD
phase_id: formData.phase_id
=======
phase_id: formData.phase_id,
soaking_duration_hr: formData.soaking_duration_hr,
air_drying_time_min: formData.air_drying_time_min,
@@ -213,7 +161,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
spring_stiffness_nm: (formData as any).spring_stiffness_nm,
ring_gap_inches: formData.ring_gap_inches ?? undefined,
drum_rpm: formData.drum_rpm ?? undefined
>>>>>>> old-github/main
}
await onSubmit(submitData)
@@ -222,11 +169,7 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
}
}
<<<<<<< HEAD
const handleInputChange = (field: keyof typeof formData, value: string | number | boolean) => {
=======
const handleInputChange = (field: keyof typeof formData, value: string | number | boolean | null | undefined) => {
>>>>>>> old-github/main
setFormData(prev => ({
...prev,
[field]: value
@@ -529,20 +472,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
<h3 className="text-lg font-medium text-gray-900 mb-4">Shelling</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<<<<<<< HEAD
<label className="block text-sm font-medium text-gray-700 mb-2">
Shelling Start Offset (minutes)
</label>
<input
type="number"
value={(formData as any).shelling_start_offset_min || 0}
onChange={(e) => 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"
/>
=======
<label htmlFor="ring_gap_inches" className="block text-sm font-medium text-gray-700 mb-2">
Ring gap (inches)
</label>
@@ -577,7 +506,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
{errors.drum_rpm && (
<p className="mt-1 text-sm text-red-600">{errors.drum_rpm}</p>
)}
>>>>>>> old-github/main
</div>
</div>
</div>

View File

@@ -147,11 +147,7 @@ export function PhaseForm({ onSubmit, onCancel, loading = false }: PhaseFormProp
onChange={(e) => handleInputChange('description', e.target.value)}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
<<<<<<< HEAD
placeholder="Optional description of this experiment phase"
=======
placeholder="Optional description of this experiment book"
>>>>>>> old-github/main
disabled={loading}
/>
</div>

View File

@@ -7,11 +7,6 @@ interface SidebarProps {
onViewChange: (view: string) => void
isExpanded?: boolean
isMobileOpen?: boolean
<<<<<<< HEAD
isHovered?: boolean
setIsHovered?: (hovered: boolean) => void
=======
>>>>>>> old-github/main
}
interface MenuItem {
@@ -26,15 +21,8 @@ export function Sidebar({
user,
currentView,
onViewChange,
<<<<<<< HEAD
isExpanded = true,
isMobileOpen = false,
isHovered = false,
setIsHovered
=======
isExpanded = false,
isMobileOpen = false
>>>>>>> old-github/main
}: SidebarProps) {
const [openSubmenu, setOpenSubmenu] = useState<number | null>(null)
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>({})
@@ -178,11 +166,7 @@ export function Sidebar({
className={`menu-item group ${openSubmenu === index
? "menu-item-active"
: "menu-item-inactive"
<<<<<<< HEAD
} cursor-pointer ${!isExpanded && !isHovered
=======
} cursor-pointer ${!isExpanded
>>>>>>> old-github/main
? "lg:justify-center"
: "lg:justify-start"
}`}
@@ -195,17 +179,10 @@ export function Sidebar({
>
{nav.icon}
</span>
<<<<<<< HEAD
{(isExpanded || isHovered || isMobileOpen) && (
<span className="menu-item-text">{nav.name}</span>
)}
{(isExpanded || isHovered || isMobileOpen) && (
=======
{(isExpanded || isMobileOpen) && (
<span className="menu-item-text">{nav.name}</span>
)}
{(isExpanded || isMobileOpen) && (
>>>>>>> old-github/main
<svg
className={`ml-auto w-5 h-5 transition-transform duration-200 ${openSubmenu === index
? "rotate-180 text-brand-500"
@@ -233,20 +210,12 @@ export function Sidebar({
>
{nav.icon}
</span>
<<<<<<< HEAD
{(isExpanded || isHovered || isMobileOpen) && (
=======
{(isExpanded || isMobileOpen) && (
>>>>>>> old-github/main
<span className="menu-item-text">{nav.name}</span>
)}
</button>
)}
<<<<<<< HEAD
{nav.subItems && (isExpanded || isHovered || isMobileOpen) && (
=======
{nav.subItems && (isExpanded || isMobileOpen) && (
>>>>>>> old-github/main
<div
ref={(el) => {
subMenuRefs.current[`submenu-${index}`] = el
@@ -292,23 +261,6 @@ export function Sidebar({
className={`fixed mt-16 flex flex-col lg:mt-0 top-0 px-5 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 h-screen transition-all duration-300 ease-in-out z-50 border-r border-gray-200
${isExpanded || isMobileOpen
? "w-[290px]"
<<<<<<< HEAD
: isHovered
? "w-[290px]"
: "w-[90px]"
}
${isMobileOpen ? "translate-x-0" : "-translate-x-full"}
lg:translate-x-0`}
onMouseEnter={() => !isExpanded && setIsHovered && setIsHovered(true)}
onMouseLeave={() => setIsHovered && setIsHovered(false)}
>
<div
className={`py-8 flex ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"
}`}
>
<div>
{isExpanded || isHovered || isMobileOpen ? (
=======
: "w-[90px]"
}
${isMobileOpen ? "translate-x-0" : "-translate-x-full"}
@@ -320,7 +272,6 @@ export function Sidebar({
>
<div>
{isExpanded || isMobileOpen ? (
>>>>>>> old-github/main
<>
<h1 className="text-xl font-bold text-gray-800 dark:text-white/90">Pecan Experiments</h1>
<p className="text-sm text-gray-500 dark:text-gray-400">Research Dashboard</p>
@@ -338,20 +289,12 @@ export function Sidebar({
<div className="flex flex-col gap-4">
<div>
<h2
<<<<<<< HEAD
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded && !isHovered
=======
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded
>>>>>>> old-github/main
? "lg:justify-center"
: "justify-start"
}`}
>
<<<<<<< HEAD
{isExpanded || isHovered || isMobileOpen ? (
=======
{isExpanded || isMobileOpen ? (
>>>>>>> old-github/main
"Menu"
) : (
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">