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:
@@ -51,11 +51,7 @@ VITE_SUPABASE_ANON_KEY=<your-anon-key>
|
|||||||
The default anon key for local development is:
|
The default anon key for local development is:
|
||||||
|
|
||||||
```
|
```
|
||||||
<<<<<<< HEAD
|
|
||||||
[REDACTED]
|
[REDACTED]
|
||||||
=======
|
|
||||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Migrations
|
### Migrations
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import type { CreateExperimentRequest, UpdateExperimentRequest, ScheduleStatus,
|
|||||||
import { experimentPhaseManagement, machineTypeManagement } from '../lib/supabase'
|
import { experimentPhaseManagement, machineTypeManagement } from '../lib/supabase'
|
||||||
|
|
||||||
interface ExperimentFormProps {
|
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 }
|
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>
|
onSubmit: (data: CreateExperimentRequest | UpdateExperimentRequest) => Promise<void>
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
isEditing?: boolean
|
isEditing?: boolean
|
||||||
@@ -16,28 +12,6 @@ interface ExperimentFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = false, loading = false, phaseId }: 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) => ({
|
const getInitialFormState = (d: any) => ({
|
||||||
experiment_number: d?.experiment_number ?? 0,
|
experiment_number: d?.experiment_number ?? 0,
|
||||||
reps_required: d?.reps_required ?? 1,
|
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))
|
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 [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
const [phase, setPhase] = useState<ExperimentPhase | null>(null)
|
const [phase, setPhase] = useState<ExperimentPhase | null>(null)
|
||||||
const [crackingMachine, setCrackingMachine] = useState<MachineType | null>(null)
|
const [crackingMachine, setCrackingMachine] = useState<MachineType | null>(null)
|
||||||
const [metaLoading, setMetaLoading] = useState<boolean>(false)
|
const [metaLoading, setMetaLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
// When initialData loads with phase config (edit mode), sync form state
|
// When initialData loads with phase config (edit mode), sync form state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((initialData as any)?.id) {
|
if ((initialData as any)?.id) {
|
||||||
@@ -76,7 +47,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
|
|||||||
}
|
}
|
||||||
}, [initialData])
|
}, [initialData])
|
||||||
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadMeta = async () => {
|
const loadMeta = async () => {
|
||||||
if (!phaseId) return
|
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) {
|
if ((formData.soaking_duration_hr ?? 0) < 0) {
|
||||||
newErrors.soaking_duration_hr = 'Soaking duration cannot be negative'
|
newErrors.soaking_duration_hr = 'Soaking duration cannot be negative'
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((formData.air_drying_time_min ?? 0) < 0) {
|
if ((formData.air_drying_time_min ?? 0) < 0) {
|
||||||
>>>>>>> old-github/main
|
|
||||||
newErrors.air_drying_time_min = 'Air drying time cannot be negative'
|
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) {
|
if (!formData.throughput_rate_pecans_sec || formData.throughput_rate_pecans_sec <= 0) {
|
||||||
newErrors.throughput_rate_pecans_sec = 'Throughput rate must be positive'
|
newErrors.throughput_rate_pecans_sec = 'Throughput rate must be positive'
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
|
||||||
if (formData.crush_amount_in < 0) {
|
|
||||||
=======
|
|
||||||
if ((formData.crush_amount_in ?? 0) < 0) {
|
if ((formData.crush_amount_in ?? 0) < 0) {
|
||||||
>>>>>>> old-github/main
|
|
||||||
newErrors.crush_amount_in = 'Crush amount cannot be negative'
|
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
|
// Shelling: if provided, must be positive
|
||||||
if (phase?.has_shelling) {
|
if (phase?.has_shelling) {
|
||||||
if (formData.ring_gap_inches != null && (typeof formData.ring_gap_inches !== 'number' || formData.ring_gap_inches <= 0)) {
|
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)
|
setErrors(newErrors)
|
||||||
return Object.keys(newErrors).length === 0
|
return Object.keys(newErrors).length === 0
|
||||||
}
|
}
|
||||||
@@ -187,20 +142,13 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// 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 : {
|
const submitData = isEditing ? formData : {
|
||||||
experiment_number: formData.experiment_number,
|
experiment_number: formData.experiment_number,
|
||||||
reps_required: formData.reps_required,
|
reps_required: formData.reps_required,
|
||||||
weight_per_repetition_lbs: formData.weight_per_repetition_lbs,
|
weight_per_repetition_lbs: formData.weight_per_repetition_lbs,
|
||||||
results_status: formData.results_status,
|
results_status: formData.results_status,
|
||||||
completion_status: formData.completion_status,
|
completion_status: formData.completion_status,
|
||||||
<<<<<<< HEAD
|
|
||||||
phase_id: formData.phase_id
|
|
||||||
=======
|
|
||||||
phase_id: formData.phase_id,
|
phase_id: formData.phase_id,
|
||||||
soaking_duration_hr: formData.soaking_duration_hr,
|
soaking_duration_hr: formData.soaking_duration_hr,
|
||||||
air_drying_time_min: formData.air_drying_time_min,
|
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,
|
spring_stiffness_nm: (formData as any).spring_stiffness_nm,
|
||||||
ring_gap_inches: formData.ring_gap_inches ?? undefined,
|
ring_gap_inches: formData.ring_gap_inches ?? undefined,
|
||||||
drum_rpm: formData.drum_rpm ?? undefined
|
drum_rpm: formData.drum_rpm ?? undefined
|
||||||
>>>>>>> old-github/main
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await onSubmit(submitData)
|
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) => {
|
const handleInputChange = (field: keyof typeof formData, value: string | number | boolean | null | undefined) => {
|
||||||
>>>>>>> old-github/main
|
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value
|
[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>
|
<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 className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<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">
|
<label htmlFor="ring_gap_inches" className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Ring gap (inches)
|
Ring gap (inches)
|
||||||
</label>
|
</label>
|
||||||
@@ -577,7 +506,6 @@ export function ExperimentForm({ initialData, onSubmit, onCancel, isEditing = fa
|
|||||||
{errors.drum_rpm && (
|
{errors.drum_rpm && (
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.drum_rpm}</p>
|
<p className="mt-1 text-sm text-red-600">{errors.drum_rpm}</p>
|
||||||
)}
|
)}
|
||||||
>>>>>>> old-github/main
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -147,11 +147,7 @@ export function PhaseForm({ onSubmit, onCancel, loading = false }: PhaseFormProp
|
|||||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||||
rows={3}
|
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"
|
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"
|
placeholder="Optional description of this experiment book"
|
||||||
>>>>>>> old-github/main
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ interface SidebarProps {
|
|||||||
onViewChange: (view: string) => void
|
onViewChange: (view: string) => void
|
||||||
isExpanded?: boolean
|
isExpanded?: boolean
|
||||||
isMobileOpen?: boolean
|
isMobileOpen?: boolean
|
||||||
<<<<<<< HEAD
|
|
||||||
isHovered?: boolean
|
|
||||||
setIsHovered?: (hovered: boolean) => void
|
|
||||||
=======
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
@@ -26,15 +21,8 @@ export function Sidebar({
|
|||||||
user,
|
user,
|
||||||
currentView,
|
currentView,
|
||||||
onViewChange,
|
onViewChange,
|
||||||
<<<<<<< HEAD
|
|
||||||
isExpanded = true,
|
|
||||||
isMobileOpen = false,
|
|
||||||
isHovered = false,
|
|
||||||
setIsHovered
|
|
||||||
=======
|
|
||||||
isExpanded = false,
|
isExpanded = false,
|
||||||
isMobileOpen = false
|
isMobileOpen = false
|
||||||
>>>>>>> old-github/main
|
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
const [openSubmenu, setOpenSubmenu] = useState<number | null>(null)
|
const [openSubmenu, setOpenSubmenu] = useState<number | null>(null)
|
||||||
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>({})
|
const [subMenuHeight, setSubMenuHeight] = useState<Record<string, number>>({})
|
||||||
@@ -178,11 +166,7 @@ export function Sidebar({
|
|||||||
className={`menu-item group ${openSubmenu === index
|
className={`menu-item group ${openSubmenu === index
|
||||||
? "menu-item-active"
|
? "menu-item-active"
|
||||||
: "menu-item-inactive"
|
: "menu-item-inactive"
|
||||||
<<<<<<< HEAD
|
|
||||||
} cursor-pointer ${!isExpanded && !isHovered
|
|
||||||
=======
|
|
||||||
} cursor-pointer ${!isExpanded
|
} cursor-pointer ${!isExpanded
|
||||||
>>>>>>> old-github/main
|
|
||||||
? "lg:justify-center"
|
? "lg:justify-center"
|
||||||
: "lg:justify-start"
|
: "lg:justify-start"
|
||||||
}`}
|
}`}
|
||||||
@@ -195,17 +179,10 @@ export function Sidebar({
|
|||||||
>
|
>
|
||||||
{nav.icon}
|
{nav.icon}
|
||||||
</span>
|
</span>
|
||||||
<<<<<<< HEAD
|
|
||||||
{(isExpanded || isHovered || isMobileOpen) && (
|
|
||||||
<span className="menu-item-text">{nav.name}</span>
|
|
||||||
)}
|
|
||||||
{(isExpanded || isHovered || isMobileOpen) && (
|
|
||||||
=======
|
|
||||||
{(isExpanded || isMobileOpen) && (
|
{(isExpanded || isMobileOpen) && (
|
||||||
<span className="menu-item-text">{nav.name}</span>
|
<span className="menu-item-text">{nav.name}</span>
|
||||||
)}
|
)}
|
||||||
{(isExpanded || isMobileOpen) && (
|
{(isExpanded || isMobileOpen) && (
|
||||||
>>>>>>> old-github/main
|
|
||||||
<svg
|
<svg
|
||||||
className={`ml-auto w-5 h-5 transition-transform duration-200 ${openSubmenu === index
|
className={`ml-auto w-5 h-5 transition-transform duration-200 ${openSubmenu === index
|
||||||
? "rotate-180 text-brand-500"
|
? "rotate-180 text-brand-500"
|
||||||
@@ -233,20 +210,12 @@ export function Sidebar({
|
|||||||
>
|
>
|
||||||
{nav.icon}
|
{nav.icon}
|
||||||
</span>
|
</span>
|
||||||
<<<<<<< HEAD
|
|
||||||
{(isExpanded || isHovered || isMobileOpen) && (
|
|
||||||
=======
|
|
||||||
{(isExpanded || isMobileOpen) && (
|
{(isExpanded || isMobileOpen) && (
|
||||||
>>>>>>> old-github/main
|
|
||||||
<span className="menu-item-text">{nav.name}</span>
|
<span className="menu-item-text">{nav.name}</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<<<<<<< HEAD
|
|
||||||
{nav.subItems && (isExpanded || isHovered || isMobileOpen) && (
|
|
||||||
=======
|
|
||||||
{nav.subItems && (isExpanded || isMobileOpen) && (
|
{nav.subItems && (isExpanded || isMobileOpen) && (
|
||||||
>>>>>>> old-github/main
|
|
||||||
<div
|
<div
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
subMenuRefs.current[`submenu-${index}`] = 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
|
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
|
${isExpanded || isMobileOpen
|
||||||
? "w-[290px]"
|
? "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]"
|
: "w-[90px]"
|
||||||
}
|
}
|
||||||
${isMobileOpen ? "translate-x-0" : "-translate-x-full"}
|
${isMobileOpen ? "translate-x-0" : "-translate-x-full"}
|
||||||
@@ -320,7 +272,6 @@ export function Sidebar({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{isExpanded || isMobileOpen ? (
|
{isExpanded || isMobileOpen ? (
|
||||||
>>>>>>> old-github/main
|
|
||||||
<>
|
<>
|
||||||
<h1 className="text-xl font-bold text-gray-800 dark:text-white/90">Pecan Experiments</h1>
|
<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>
|
<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 className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<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
|
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded
|
||||||
>>>>>>> old-github/main
|
|
||||||
? "lg:justify-center"
|
? "lg:justify-center"
|
||||||
: "justify-start"
|
: "justify-start"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<<<<<<< HEAD
|
|
||||||
{isExpanded || isHovered || isMobileOpen ? (
|
|
||||||
=======
|
|
||||||
{isExpanded || isMobileOpen ? (
|
{isExpanded || isMobileOpen ? (
|
||||||
>>>>>>> old-github/main
|
|
||||||
"Menu"
|
"Menu"
|
||||||
) : (
|
) : (
|
||||||
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
|
|
||||||
// Track repetitions that have been dropped/moved and should show time points
|
// Track repetitions that have been dropped/moved and should show time points
|
||||||
const [repetitionsWithTimes, setRepetitionsWithTimes] = useState<Set<string>>(new Set())
|
const [repetitionsWithTimes, setRepetitionsWithTimes] = useState<Set<string>>(new Set())
|
||||||
<<<<<<< HEAD
|
|
||||||
// Track which repetitions are locked (prevent dragging)
|
|
||||||
const [lockedSchedules, setLockedSchedules] = useState<Set<string>>(new Set())
|
|
||||||
=======
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
// Track which repetitions are currently being scheduled
|
// Track which repetitions are currently being scheduled
|
||||||
const [schedulingRepetitions, setSchedulingRepetitions] = useState<Set<string>>(new Set())
|
const [schedulingRepetitions, setSchedulingRepetitions] = useState<Set<string>>(new Set())
|
||||||
// Track conductor assignments for each phase marker (markerId -> conductorIds[])
|
// Track conductor assignments for each phase marker (markerId -> conductorIds[])
|
||||||
@@ -256,46 +251,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toggleRepetition = (repId: string) => {
|
const toggleRepetition = (repId: string) => {
|
||||||
<<<<<<< HEAD
|
|
||||||
setSelectedRepetitionIds(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
if (next.has(repId)) {
|
|
||||||
next.delete(repId)
|
|
||||||
// Remove from scheduled repetitions when unchecked
|
|
||||||
setScheduledRepetitions(prevScheduled => {
|
|
||||||
const newScheduled = { ...prevScheduled }
|
|
||||||
delete newScheduled[repId]
|
|
||||||
return newScheduled
|
|
||||||
})
|
|
||||||
// Clear all related state when unchecked
|
|
||||||
setRepetitionsWithTimes(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
next.delete(repId)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
setLockedSchedules(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
next.delete(repId)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
setSchedulingRepetitions(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
next.delete(repId)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
// Re-stagger remaining repetitions
|
|
||||||
const remainingIds = Array.from(next).filter(id => id !== repId)
|
|
||||||
if (remainingIds.length > 0) {
|
|
||||||
reStaggerRepetitions(remainingIds)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next.add(repId)
|
|
||||||
// Auto-spawn when checked - pass the updated set to ensure correct stagger calculation
|
|
||||||
spawnSingleRepetition(repId, next)
|
|
||||||
// Re-stagger all existing repetitions to prevent overlap
|
|
||||||
// Note: reStaggerRepetitions will automatically skip locked repetitions
|
|
||||||
reStaggerRepetitions([...next, repId])
|
|
||||||
=======
|
|
||||||
// Checking/unchecking should only control visibility on the timeline.
|
// Checking/unchecking should only control visibility on the timeline.
|
||||||
// It must NOT clear scheduling info or conductor assignments.
|
// It must NOT clear scheduling info or conductor assignments.
|
||||||
setSelectedRepetitionIds(prev => {
|
setSelectedRepetitionIds(prev => {
|
||||||
@@ -312,7 +267,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
// spawnSingleRepetition will position the new repetition relative to existing ones
|
// spawnSingleRepetition will position the new repetition relative to existing ones
|
||||||
// without resetting existing positions
|
// without resetting existing positions
|
||||||
spawnSingleRepetition(repId, next)
|
spawnSingleRepetition(repId, next)
|
||||||
>>>>>>> old-github/main
|
|
||||||
}
|
}
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
@@ -327,22 +281,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
const allSelected = allRepetitions.every(rep => prev.has(rep.id))
|
const allSelected = allRepetitions.every(rep => prev.has(rep.id))
|
||||||
|
|
||||||
if (allSelected) {
|
if (allSelected) {
|
||||||
<<<<<<< HEAD
|
|
||||||
// Deselect all repetitions in this phase
|
|
||||||
const next = new Set(prev)
|
|
||||||
allRepetitions.forEach(rep => {
|
|
||||||
next.delete(rep.id)
|
|
||||||
// Remove from scheduled repetitions
|
|
||||||
setScheduledRepetitions(prevScheduled => {
|
|
||||||
const newScheduled = { ...prevScheduled }
|
|
||||||
delete newScheduled[rep.id]
|
|
||||||
return newScheduled
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return next
|
|
||||||
} else {
|
|
||||||
// Select all repetitions in this phase
|
|
||||||
=======
|
|
||||||
// Deselect all repetitions in this phase (hide from timeline only)
|
// Deselect all repetitions in this phase (hide from timeline only)
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
allRepetitions.forEach(rep => {
|
allRepetitions.forEach(rep => {
|
||||||
@@ -351,7 +289,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
return next
|
return next
|
||||||
} else {
|
} else {
|
||||||
// Select all repetitions in this phase (show on timeline)
|
// Select all repetitions in this phase (show on timeline)
|
||||||
>>>>>>> old-github/main
|
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
allRepetitions.forEach(rep => {
|
allRepetitions.forEach(rep => {
|
||||||
next.add(rep.id)
|
next.add(rep.id)
|
||||||
@@ -389,11 +326,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
|
|
||||||
// Re-stagger all repetitions to prevent overlap
|
// Re-stagger all repetitions to prevent overlap
|
||||||
// IMPORTANT: Skip locked repetitions to prevent them from moving
|
// IMPORTANT: Skip locked repetitions to prevent them from moving
|
||||||
<<<<<<< HEAD
|
|
||||||
const reStaggerRepetitions = useCallback((repIds: string[]) => {
|
|
||||||
=======
|
|
||||||
const reStaggerRepetitions = useCallback((repIds: string[], onlyResetWithoutCustomTimes: boolean = false) => {
|
const reStaggerRepetitions = useCallback((repIds: string[], onlyResetWithoutCustomTimes: boolean = false) => {
|
||||||
>>>>>>> old-github/main
|
|
||||||
const tomorrow = new Date()
|
const tomorrow = new Date()
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||||
tomorrow.setHours(9, 0, 0, 0)
|
tomorrow.setHours(9, 0, 0, 0)
|
||||||
@@ -401,16 +334,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
setScheduledRepetitions(prev => {
|
setScheduledRepetitions(prev => {
|
||||||
const newScheduled = { ...prev }
|
const newScheduled = { ...prev }
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
// Filter out locked repetitions - they should not be moved
|
|
||||||
const unlockedRepIds = repIds.filter(repId => !lockedSchedules.has(repId))
|
|
||||||
=======
|
|
||||||
// If onlyResetWithoutCustomTimes is true, filter out repetitions that have custom times set
|
// If onlyResetWithoutCustomTimes is true, filter out repetitions that have custom times set
|
||||||
let unlockedRepIds = repIds
|
let unlockedRepIds = repIds
|
||||||
if (onlyResetWithoutCustomTimes) {
|
if (onlyResetWithoutCustomTimes) {
|
||||||
unlockedRepIds = unlockedRepIds.filter(repId => !repetitionsWithTimes.has(repId))
|
unlockedRepIds = unlockedRepIds.filter(repId => !repetitionsWithTimes.has(repId))
|
||||||
}
|
}
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
// Calculate stagger index only for unlocked repetitions
|
// Calculate stagger index only for unlocked repetitions
|
||||||
let staggerIndex = 0
|
let staggerIndex = 0
|
||||||
@@ -452,11 +380,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
|
|
||||||
return newScheduled
|
return newScheduled
|
||||||
})
|
})
|
||||||
<<<<<<< HEAD
|
|
||||||
}, [lockedSchedules, repetitionsByExperiment, experimentsByPhase, soakingByExperiment, airdryingByExperiment])
|
|
||||||
=======
|
|
||||||
}, [repetitionsByExperiment, experimentsByPhase, soakingByExperiment, airdryingByExperiment, repetitionsWithTimes])
|
}, [repetitionsByExperiment, experimentsByPhase, soakingByExperiment, airdryingByExperiment, repetitionsWithTimes])
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
// Spawn a single repetition in calendar
|
// Spawn a single repetition in calendar
|
||||||
const spawnSingleRepetition = (repId: string, updatedSelectedIds?: Set<string>) => {
|
const spawnSingleRepetition = (repId: string, updatedSelectedIds?: Set<string>) => {
|
||||||
@@ -526,18 +450,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
let newScheduled = { ...prev }
|
let newScheduled = { ...prev }
|
||||||
|
|
||||||
const clampToReasonableHours = (d: Date) => {
|
const clampToReasonableHours = (d: Date) => {
|
||||||
<<<<<<< HEAD
|
|
||||||
const min = new Date(d)
|
|
||||||
min.setHours(5, 0, 0, 0)
|
|
||||||
const max = new Date(d)
|
|
||||||
max.setHours(23, 0, 0, 0)
|
|
||||||
=======
|
|
||||||
// Allow full 24 hours (midnight to midnight)
|
// Allow full 24 hours (midnight to midnight)
|
||||||
const min = new Date(d)
|
const min = new Date(d)
|
||||||
min.setHours(0, 0, 0, 0)
|
min.setHours(0, 0, 0, 0)
|
||||||
const max = new Date(d)
|
const max = new Date(d)
|
||||||
max.setHours(23, 59, 59, 999)
|
max.setHours(23, 59, 59, 999)
|
||||||
>>>>>>> old-github/main
|
|
||||||
const t = d.getTime()
|
const t = d.getTime()
|
||||||
return new Date(Math.min(Math.max(t, min.getTime()), max.getTime()))
|
return new Date(Math.min(Math.max(t, min.getTime()), max.getTime()))
|
||||||
}
|
}
|
||||||
@@ -593,20 +510,10 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
const repetition = repetitionsByExperiment[scheduled.experimentId]?.find(r => r.id === scheduled.repetitionId)
|
const repetition = repetitionsByExperiment[scheduled.experimentId]?.find(r => r.id === scheduled.repetitionId)
|
||||||
|
|
||||||
if (experiment && repetition && scheduled.soakingStart) {
|
if (experiment && repetition && scheduled.soakingStart) {
|
||||||
<<<<<<< HEAD
|
|
||||||
const isLocked = lockedSchedules.has(scheduled.repetitionId)
|
|
||||||
const lockIcon = isLocked ? '🔒' : '🔓'
|
|
||||||
|
|
||||||
// Soaking marker
|
|
||||||
events.push({
|
|
||||||
id: `${scheduled.repetitionId}-soaking`,
|
|
||||||
title: `${lockIcon} 💧 Soaking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
|
||||||
=======
|
|
||||||
// Soaking marker
|
// Soaking marker
|
||||||
events.push({
|
events.push({
|
||||||
id: `${scheduled.repetitionId}-soaking`,
|
id: `${scheduled.repetitionId}-soaking`,
|
||||||
title: `💧 Soaking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
title: `💧 Soaking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
||||||
>>>>>>> old-github/main
|
|
||||||
start: scheduled.soakingStart,
|
start: scheduled.soakingStart,
|
||||||
end: new Date(scheduled.soakingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
end: new Date(scheduled.soakingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||||
resource: 'soaking'
|
resource: 'soaking'
|
||||||
@@ -616,11 +523,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
if (scheduled.airdryingStart) {
|
if (scheduled.airdryingStart) {
|
||||||
events.push({
|
events.push({
|
||||||
id: `${scheduled.repetitionId}-airdrying`,
|
id: `${scheduled.repetitionId}-airdrying`,
|
||||||
<<<<<<< HEAD
|
|
||||||
title: `${lockIcon} 🌬️ Airdrying - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
|
||||||
=======
|
|
||||||
title: `🌬️ Airdrying - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
title: `🌬️ Airdrying - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
||||||
>>>>>>> old-github/main
|
|
||||||
start: scheduled.airdryingStart,
|
start: scheduled.airdryingStart,
|
||||||
end: new Date(scheduled.airdryingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
end: new Date(scheduled.airdryingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||||
resource: 'airdrying'
|
resource: 'airdrying'
|
||||||
@@ -631,11 +534,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
if (scheduled.crackingStart) {
|
if (scheduled.crackingStart) {
|
||||||
events.push({
|
events.push({
|
||||||
id: `${scheduled.repetitionId}-cracking`,
|
id: `${scheduled.repetitionId}-cracking`,
|
||||||
<<<<<<< HEAD
|
|
||||||
title: `${lockIcon} ⚡ Cracking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
|
||||||
=======
|
|
||||||
title: `⚡ Cracking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
title: `⚡ Cracking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
||||||
>>>>>>> old-github/main
|
|
||||||
start: scheduled.crackingStart,
|
start: scheduled.crackingStart,
|
||||||
end: new Date(scheduled.crackingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
end: new Date(scheduled.crackingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||||
resource: 'cracking'
|
resource: 'cracking'
|
||||||
@@ -645,11 +544,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
return events
|
return events
|
||||||
<<<<<<< HEAD
|
|
||||||
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment, lockedSchedules])
|
|
||||||
=======
|
|
||||||
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment])
|
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment])
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
// Memoize the calendar events
|
// Memoize the calendar events
|
||||||
const calendarEvents = useMemo(() => {
|
const calendarEvents = useMemo(() => {
|
||||||
@@ -685,17 +580,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
return moment(date).format('MMM D, h:mm A')
|
return moment(date).format('MMM D, h:mm A')
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const toggleScheduleLock = (repId: string) => {
|
|
||||||
setLockedSchedules(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
if (next.has(repId)) {
|
|
||||||
next.delete(repId)
|
|
||||||
} else {
|
|
||||||
next.add(repId)
|
|
||||||
}
|
|
||||||
return next
|
|
||||||
=======
|
|
||||||
// Remove all conductor assignments from a repetition
|
// Remove all conductor assignments from a repetition
|
||||||
const removeRepetitionAssignments = (repId: string) => {
|
const removeRepetitionAssignments = (repId: string) => {
|
||||||
const markerIdPrefix = repId
|
const markerIdPrefix = repId
|
||||||
@@ -706,7 +590,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
delete newAssignments[`${markerIdPrefix}-airdrying`]
|
delete newAssignments[`${markerIdPrefix}-airdrying`]
|
||||||
delete newAssignments[`${markerIdPrefix}-cracking`]
|
delete newAssignments[`${markerIdPrefix}-cracking`]
|
||||||
return newAssignments
|
return newAssignments
|
||||||
>>>>>>> old-github/main
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,34 +597,16 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
// Only make repetition markers draggable, not availability events
|
// Only make repetition markers draggable, not availability events
|
||||||
const resource = event.resource as string
|
const resource = event.resource as string
|
||||||
if (resource === 'soaking' || resource === 'airdrying' || resource === 'cracking') {
|
if (resource === 'soaking' || resource === 'airdrying' || resource === 'cracking') {
|
||||||
<<<<<<< HEAD
|
|
||||||
// Check if the repetition is locked
|
|
||||||
const eventId = event.id as string
|
|
||||||
const repId = eventId.split('-')[0]
|
|
||||||
const isLocked = lockedSchedules.has(repId)
|
|
||||||
return !isLocked
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}, [lockedSchedules])
|
|
||||||
=======
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, [])
|
}, [])
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
const eventPropGetter = useCallback((event: any) => {
|
const eventPropGetter = useCallback((event: any) => {
|
||||||
const resource = event.resource as string
|
const resource = event.resource as string
|
||||||
|
|
||||||
// Styling for repetition markers (foreground events)
|
// Styling for repetition markers (foreground events)
|
||||||
if (resource === 'soaking' || resource === 'airdrying' || resource === 'cracking') {
|
if (resource === 'soaking' || resource === 'airdrying' || resource === 'cracking') {
|
||||||
<<<<<<< HEAD
|
|
||||||
const eventId = event.id as string
|
|
||||||
const repId = eventId.split('-')[0]
|
|
||||||
const isLocked = lockedSchedules.has(repId)
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
const colors = {
|
const colors = {
|
||||||
soaking: '#3b82f6', // blue
|
soaking: '#3b82f6', // blue
|
||||||
airdrying: '#10b981', // green
|
airdrying: '#10b981', // green
|
||||||
@@ -751,13 +616,8 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
style: {
|
style: {
|
||||||
<<<<<<< HEAD
|
|
||||||
backgroundColor: isLocked ? '#9ca3af' : color, // gray if locked
|
|
||||||
borderColor: isLocked ? color : color, // border takes original color when locked
|
|
||||||
=======
|
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
>>>>>>> old-github/main
|
|
||||||
color: 'white',
|
color: 'white',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '2px solid',
|
border: '2px solid',
|
||||||
@@ -778,28 +638,17 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
<<<<<<< HEAD
|
|
||||||
cursor: isLocked ? 'not-allowed' : 'grab',
|
|
||||||
boxShadow: isLocked ? '0 1px 2px rgba(0,0,0,0.1)' : '0 2px 4px rgba(0,0,0,0.2)',
|
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
opacity: isLocked ? 0.7 : 1
|
|
||||||
=======
|
|
||||||
cursor: 'grab',
|
cursor: 'grab',
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
|
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
opacity: 1
|
opacity: 1
|
||||||
>>>>>>> old-github/main
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default styling for other events
|
// Default styling for other events
|
||||||
return {}
|
return {}
|
||||||
<<<<<<< HEAD
|
|
||||||
}, [lockedSchedules])
|
|
||||||
=======
|
|
||||||
}, [])
|
}, [])
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
const scheduleRepetition = async (repId: string, experimentId: string) => {
|
const scheduleRepetition = async (repId: string, experimentId: string) => {
|
||||||
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
||||||
@@ -871,8 +720,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
// Unschedule a repetition: clear its scheduling info and unassign all conductors.
|
// Unschedule a repetition: clear its scheduling info and unassign all conductors.
|
||||||
const unscheduleRepetition = async (repId: string, experimentId: string) => {
|
const unscheduleRepetition = async (repId: string, experimentId: string) => {
|
||||||
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
||||||
@@ -918,7 +765,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
// Restore scroll position after scheduledRepetitions changes
|
// Restore scroll position after scheduledRepetitions changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollPositionRef.current) {
|
if (scrollPositionRef.current) {
|
||||||
@@ -969,22 +815,15 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
phase: 'soaking' | 'airdrying' | 'cracking'
|
phase: 'soaking' | 'airdrying' | 'cracking'
|
||||||
startTime: Date
|
startTime: Date
|
||||||
assignedConductors: string[]
|
assignedConductors: string[]
|
||||||
<<<<<<< HEAD
|
|
||||||
locked: boolean
|
|
||||||
=======
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
Object.values(scheduledRepetitions).forEach(scheduled => {
|
Object.values(scheduledRepetitions).forEach(scheduled => {
|
||||||
const repId = scheduled.repetitionId
|
const repId = scheduled.repetitionId
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
// Only include markers for repetitions that are checked (selected)
|
// Only include markers for repetitions that are checked (selected)
|
||||||
if (!selectedRepetitionIds.has(repId)) {
|
if (!selectedRepetitionIds.has(repId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
const markerIdPrefix = repId
|
const markerIdPrefix = repId
|
||||||
|
|
||||||
if (scheduled.soakingStart) {
|
if (scheduled.soakingStart) {
|
||||||
@@ -994,12 +833,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
experimentId: scheduled.experimentId,
|
experimentId: scheduled.experimentId,
|
||||||
phase: 'soaking',
|
phase: 'soaking',
|
||||||
startTime: scheduled.soakingStart,
|
startTime: scheduled.soakingStart,
|
||||||
<<<<<<< HEAD
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-soaking`] || [],
|
|
||||||
locked: lockedSchedules.has(repId)
|
|
||||||
=======
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-soaking`] || []
|
assignedConductors: conductorAssignments[`${markerIdPrefix}-soaking`] || []
|
||||||
>>>>>>> old-github/main
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,12 +844,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
experimentId: scheduled.experimentId,
|
experimentId: scheduled.experimentId,
|
||||||
phase: 'airdrying',
|
phase: 'airdrying',
|
||||||
startTime: scheduled.airdryingStart,
|
startTime: scheduled.airdryingStart,
|
||||||
<<<<<<< HEAD
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-airdrying`] || [],
|
|
||||||
locked: lockedSchedules.has(repId)
|
|
||||||
=======
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-airdrying`] || []
|
assignedConductors: conductorAssignments[`${markerIdPrefix}-airdrying`] || []
|
||||||
>>>>>>> old-github/main
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,12 +855,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
experimentId: scheduled.experimentId,
|
experimentId: scheduled.experimentId,
|
||||||
phase: 'cracking',
|
phase: 'cracking',
|
||||||
startTime: scheduled.crackingStart,
|
startTime: scheduled.crackingStart,
|
||||||
<<<<<<< HEAD
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-cracking`] || [],
|
|
||||||
locked: lockedSchedules.has(repId)
|
|
||||||
=======
|
|
||||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-cracking`] || []
|
assignedConductors: conductorAssignments[`${markerIdPrefix}-cracking`] || []
|
||||||
>>>>>>> old-github/main
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1042,9 +866,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
conductorAvailabilities,
|
conductorAvailabilities,
|
||||||
phaseMarkers
|
phaseMarkers
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
|
||||||
}, [selectedConductorIds, conductors, conductorColorMap, colorPalette, availabilityEvents, scheduledRepetitions, conductorAssignments, lockedSchedules, calendarStartDate, calendarZoom])
|
|
||||||
=======
|
|
||||||
}, [selectedConductorIds, conductors, conductorColorMap, colorPalette, availabilityEvents, scheduledRepetitions, conductorAssignments, calendarStartDate, calendarZoom, selectedRepetitionIds])
|
}, [selectedConductorIds, conductors, conductorColorMap, colorPalette, availabilityEvents, scheduledRepetitions, conductorAssignments, calendarStartDate, calendarZoom, selectedRepetitionIds])
|
||||||
|
|
||||||
// Build repetition metadata mapping for timeline display
|
// Build repetition metadata mapping for timeline display
|
||||||
@@ -1105,7 +926,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
}
|
}
|
||||||
}, [repetitionsByExperiment, experimentsByPhase, phases, expandedPhaseIds, togglePhaseExpand])
|
}, [repetitionsByExperiment, experimentsByPhase, phases, expandedPhaseIds, togglePhaseExpand])
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
// Handlers for horizontal calendar
|
// Handlers for horizontal calendar
|
||||||
const handleHorizontalMarkerDrag = useCallback((markerId: string, newTime: Date) => {
|
const handleHorizontalMarkerDrag = useCallback((markerId: string, newTime: Date) => {
|
||||||
@@ -1127,24 +947,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const handleHorizontalMarkerLockToggle = useCallback((markerId: string) => {
|
|
||||||
// Marker ID format: ${repId}-${phase} where repId is a UUID with hyphens
|
|
||||||
// Split by '-' and take all but the last segment as repId
|
|
||||||
const parts = markerId.split('-')
|
|
||||||
const repId = parts.slice(0, -1).join('-')
|
|
||||||
setLockedSchedules(prev => {
|
|
||||||
const next = new Set(prev)
|
|
||||||
if (next.has(repId)) {
|
|
||||||
next.delete(repId)
|
|
||||||
} else {
|
|
||||||
next.add(repId)
|
|
||||||
}
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
=======
|
|
||||||
>>>>>>> old-github/main
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1279,13 +1081,9 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
phaseMarkers={horizontalCalendarData.phaseMarkers}
|
phaseMarkers={horizontalCalendarData.phaseMarkers}
|
||||||
onMarkerDrag={handleHorizontalMarkerDrag}
|
onMarkerDrag={handleHorizontalMarkerDrag}
|
||||||
onMarkerAssignConductors={handleHorizontalMarkerAssignConductors}
|
onMarkerAssignConductors={handleHorizontalMarkerAssignConductors}
|
||||||
<<<<<<< HEAD
|
|
||||||
onMarkerLockToggle={handleHorizontalMarkerLockToggle}
|
|
||||||
=======
|
|
||||||
repetitionMetadata={repetitionMetadata}
|
repetitionMetadata={repetitionMetadata}
|
||||||
onScrollToRepetition={handleScrollToRepetition}
|
onScrollToRepetition={handleScrollToRepetition}
|
||||||
onScheduleRepetition={scheduleRepetition}
|
onScheduleRepetition={scheduleRepetition}
|
||||||
>>>>>>> old-github/main
|
|
||||||
timeStep={15}
|
timeStep={15}
|
||||||
minHour={6}
|
minHour={6}
|
||||||
maxHour={22}
|
maxHour={22}
|
||||||
@@ -1454,13 +1252,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
const checked = selectedRepetitionIds.has(rep.id)
|
const checked = selectedRepetitionIds.has(rep.id)
|
||||||
const hasTimes = repetitionsWithTimes.has(rep.id)
|
const hasTimes = repetitionsWithTimes.has(rep.id)
|
||||||
const scheduled = scheduledRepetitions[rep.id]
|
const scheduled = scheduledRepetitions[rep.id]
|
||||||
<<<<<<< HEAD
|
|
||||||
const isLocked = lockedSchedules.has(rep.id)
|
|
||||||
const isScheduling = schedulingRepetitions.has(rep.id)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={rep.id} className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded p-3">
|
|
||||||
=======
|
|
||||||
const isScheduling = schedulingRepetitions.has(rep.id)
|
const isScheduling = schedulingRepetitions.has(rep.id)
|
||||||
|
|
||||||
// Check if there are any conductor assignments
|
// Check if there are any conductor assignments
|
||||||
@@ -1476,7 +1267,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
id={`repetition-${rep.id}`}
|
id={`repetition-${rep.id}`}
|
||||||
className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded p-3"
|
className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded p-3"
|
||||||
>
|
>
|
||||||
>>>>>>> old-github/main
|
|
||||||
{/* Checkbox row */}
|
{/* Checkbox row */}
|
||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
@@ -1488,46 +1278,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Rep {rep.repetition_number}</span>
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Rep {rep.repetition_number}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
{/* Time points (shown only if has been dropped/moved) */}
|
|
||||||
{hasTimes && scheduled && (
|
|
||||||
<div className="mt-2 ml-6 text-xs space-y-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>💧</span>
|
|
||||||
<span>Soaking: {formatTime(scheduled.soakingStart)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>🌬️</span>
|
|
||||||
<span>Airdrying: {formatTime(scheduled.airdryingStart)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>⚡</span>
|
|
||||||
<span>Cracking: {formatTime(scheduled.crackingStart)}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Lock checkbox and Schedule button */}
|
|
||||||
<div className="flex items-center gap-3 mt-3 pt-2 border-t border-gray-200 dark:border-gray-600">
|
|
||||||
<label className="flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="h-3 w-3 text-blue-600 border-gray-300 rounded"
|
|
||||||
checked={isLocked}
|
|
||||||
onChange={() => {
|
|
||||||
toggleScheduleLock(rep.id)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
{isLocked ? '🔒 Locked' : '🔓 Unlocked'}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
onClick={() => scheduleRepetition(rep.id, exp.id)}
|
|
||||||
disabled={isScheduling || !isLocked}
|
|
||||||
className="px-3 py-1 bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white rounded text-xs transition-colors"
|
|
||||||
>
|
|
||||||
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
|
||||||
</button>
|
|
||||||
=======
|
|
||||||
{/* Time points (shown whenever the repetition has scheduled times) */}
|
{/* Time points (shown whenever the repetition has scheduled times) */}
|
||||||
{scheduled && (
|
{scheduled && (
|
||||||
<div className="mt-2 ml-6 text-xs space-y-1">
|
<div className="mt-2 ml-6 text-xs space-y-1">
|
||||||
@@ -1611,7 +1361,6 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
|||||||
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
>>>>>>> old-github/main
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user