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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user