Merge remote-tracking branch 'old-github/main' into integrate-old-refactors-of-github
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"build:watch": "vite build --watch",
|
||||
"serve:dist": "serve -s dist -l 3003",
|
||||
"preview": "vite preview --port 3003",
|
||||
"dev:watch": "npm run build && (npm run build:watch &) && sleep 1 && npx http-server dist -p 3003 --cors -c-1"
|
||||
"dev:watch": "./wait-and-serve.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.52.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,8 +70,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
|
||||
// Track repetitions that have been dropped/moved and should show time points
|
||||
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
|
||||
const [schedulingRepetitions, setSchedulingRepetitions] = useState<Set<string>>(new Set())
|
||||
// Track conductor assignments for each phase marker (markerId -> conductorIds[])
|
||||
@@ -253,6 +256,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
}
|
||||
|
||||
const toggleRepetition = (repId: string) => {
|
||||
<<<<<<< HEAD
|
||||
setSelectedRepetitionIds(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(repId)) {
|
||||
@@ -291,6 +295,24 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
// 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.
|
||||
// It must NOT clear scheduling info or conductor assignments.
|
||||
setSelectedRepetitionIds(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(repId)) {
|
||||
// Hide this repetition from the timeline
|
||||
next.delete(repId)
|
||||
// Keep scheduledRepetitions and repetitionsWithTimes intact so that
|
||||
// re-checking the box restores the repetition in the correct spot.
|
||||
} else {
|
||||
// Show this repetition on the timeline
|
||||
next.add(repId)
|
||||
// Auto-spawn when checked - pass the updated set to ensure correct stagger calculation
|
||||
// spawnSingleRepetition will position the new repetition relative to existing ones
|
||||
// without resetting existing positions
|
||||
spawnSingleRepetition(repId, next)
|
||||
>>>>>>> old-github/main
|
||||
}
|
||||
return next
|
||||
})
|
||||
@@ -305,6 +327,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
const allSelected = allRepetitions.every(rep => prev.has(rep.id))
|
||||
|
||||
if (allSelected) {
|
||||
<<<<<<< HEAD
|
||||
// Deselect all repetitions in this phase
|
||||
const next = new Set(prev)
|
||||
allRepetitions.forEach(rep => {
|
||||
@@ -319,6 +342,16 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
return next
|
||||
} else {
|
||||
// Select all repetitions in this phase
|
||||
=======
|
||||
// Deselect all repetitions in this phase (hide from timeline only)
|
||||
const next = new Set(prev)
|
||||
allRepetitions.forEach(rep => {
|
||||
next.delete(rep.id)
|
||||
})
|
||||
return next
|
||||
} else {
|
||||
// Select all repetitions in this phase (show on timeline)
|
||||
>>>>>>> old-github/main
|
||||
const next = new Set(prev)
|
||||
allRepetitions.forEach(rep => {
|
||||
next.add(rep.id)
|
||||
@@ -356,7 +389,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
|
||||
// Re-stagger all repetitions to prevent overlap
|
||||
// IMPORTANT: Skip locked repetitions to prevent them from moving
|
||||
<<<<<<< HEAD
|
||||
const reStaggerRepetitions = useCallback((repIds: string[]) => {
|
||||
=======
|
||||
const reStaggerRepetitions = useCallback((repIds: string[], onlyResetWithoutCustomTimes: boolean = false) => {
|
||||
>>>>>>> old-github/main
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
tomorrow.setHours(9, 0, 0, 0)
|
||||
@@ -364,8 +401,16 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
setScheduledRepetitions(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
|
||||
let unlockedRepIds = repIds
|
||||
if (onlyResetWithoutCustomTimes) {
|
||||
unlockedRepIds = unlockedRepIds.filter(repId => !repetitionsWithTimes.has(repId))
|
||||
}
|
||||
>>>>>>> old-github/main
|
||||
|
||||
// Calculate stagger index only for unlocked repetitions
|
||||
let staggerIndex = 0
|
||||
@@ -407,7 +452,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
|
||||
return newScheduled
|
||||
})
|
||||
<<<<<<< HEAD
|
||||
}, [lockedSchedules, repetitionsByExperiment, experimentsByPhase, soakingByExperiment, airdryingByExperiment])
|
||||
=======
|
||||
}, [repetitionsByExperiment, experimentsByPhase, soakingByExperiment, airdryingByExperiment, repetitionsWithTimes])
|
||||
>>>>>>> old-github/main
|
||||
|
||||
// Spawn a single repetition in calendar
|
||||
const spawnSingleRepetition = (repId: string, updatedSelectedIds?: Set<string>) => {
|
||||
@@ -477,10 +526,18 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
let newScheduled = { ...prev }
|
||||
|
||||
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)
|
||||
const min = new Date(d)
|
||||
min.setHours(0, 0, 0, 0)
|
||||
const max = new Date(d)
|
||||
max.setHours(23, 59, 59, 999)
|
||||
>>>>>>> old-github/main
|
||||
const t = d.getTime()
|
||||
return new Date(Math.min(Math.max(t, min.getTime()), max.getTime()))
|
||||
}
|
||||
@@ -536,6 +593,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
const repetition = repetitionsByExperiment[scheduled.experimentId]?.find(r => r.id === scheduled.repetitionId)
|
||||
|
||||
if (experiment && repetition && scheduled.soakingStart) {
|
||||
<<<<<<< HEAD
|
||||
const isLocked = lockedSchedules.has(scheduled.repetitionId)
|
||||
const lockIcon = isLocked ? '🔒' : '🔓'
|
||||
|
||||
@@ -543,6 +601,12 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
events.push({
|
||||
id: `${scheduled.repetitionId}-soaking`,
|
||||
title: `${lockIcon} 💧 Soaking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
||||
=======
|
||||
// Soaking marker
|
||||
events.push({
|
||||
id: `${scheduled.repetitionId}-soaking`,
|
||||
title: `💧 Soaking - Exp ${experiment.experiment_number} Rep ${repetition.repetition_number}`,
|
||||
>>>>>>> old-github/main
|
||||
start: scheduled.soakingStart,
|
||||
end: new Date(scheduled.soakingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||
resource: 'soaking'
|
||||
@@ -552,7 +616,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
if (scheduled.airdryingStart) {
|
||||
events.push({
|
||||
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}`,
|
||||
>>>>>>> old-github/main
|
||||
start: scheduled.airdryingStart,
|
||||
end: new Date(scheduled.airdryingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||
resource: 'airdrying'
|
||||
@@ -563,7 +631,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
if (scheduled.crackingStart) {
|
||||
events.push({
|
||||
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}`,
|
||||
>>>>>>> old-github/main
|
||||
start: scheduled.crackingStart,
|
||||
end: new Date(scheduled.crackingStart.getTime() + 15 * 60000), // 15 minute duration for better visibility
|
||||
resource: 'cracking'
|
||||
@@ -573,7 +645,11 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
})
|
||||
|
||||
return events
|
||||
<<<<<<< HEAD
|
||||
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment, lockedSchedules])
|
||||
=======
|
||||
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment])
|
||||
>>>>>>> old-github/main
|
||||
|
||||
// Memoize the calendar events
|
||||
const calendarEvents = useMemo(() => {
|
||||
@@ -609,6 +685,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
return moment(date).format('MMM D, h:mm A')
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
const toggleScheduleLock = (repId: string) => {
|
||||
setLockedSchedules(prev => {
|
||||
const next = new Set(prev)
|
||||
@@ -618,6 +695,18 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
next.add(repId)
|
||||
}
|
||||
return next
|
||||
=======
|
||||
// Remove all conductor assignments from a repetition
|
||||
const removeRepetitionAssignments = (repId: string) => {
|
||||
const markerIdPrefix = repId
|
||||
setConductorAssignments(prev => {
|
||||
const newAssignments = { ...prev }
|
||||
// Remove assignments for all three phases
|
||||
delete newAssignments[`${markerIdPrefix}-soaking`]
|
||||
delete newAssignments[`${markerIdPrefix}-airdrying`]
|
||||
delete newAssignments[`${markerIdPrefix}-cracking`]
|
||||
return newAssignments
|
||||
>>>>>>> old-github/main
|
||||
})
|
||||
}
|
||||
|
||||
@@ -625,6 +714,7 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
// Only make repetition markers draggable, not availability events
|
||||
const resource = event.resource as string
|
||||
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]
|
||||
@@ -633,16 +723,25 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
}
|
||||
return false
|
||||
}, [lockedSchedules])
|
||||
=======
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, [])
|
||||
>>>>>>> old-github/main
|
||||
|
||||
const eventPropGetter = useCallback((event: any) => {
|
||||
const resource = event.resource as string
|
||||
|
||||
// Styling for repetition markers (foreground events)
|
||||
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 = {
|
||||
soaking: '#3b82f6', // blue
|
||||
airdrying: '#10b981', // green
|
||||
@@ -652,8 +751,13 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
|
||||
return {
|
||||
style: {
|
||||
<<<<<<< HEAD
|
||||
backgroundColor: isLocked ? '#9ca3af' : color, // gray if locked
|
||||
borderColor: isLocked ? color : color, // border takes original color when locked
|
||||
=======
|
||||
backgroundColor: color,
|
||||
borderColor: color,
|
||||
>>>>>>> old-github/main
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
@@ -674,17 +778,28 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
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',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
|
||||
transition: 'all 0.2s ease',
|
||||
opacity: 1
|
||||
>>>>>>> old-github/main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default styling for other events
|
||||
return {}
|
||||
<<<<<<< HEAD
|
||||
}, [lockedSchedules])
|
||||
=======
|
||||
}, [])
|
||||
>>>>>>> old-github/main
|
||||
|
||||
const scheduleRepetition = async (repId: string, experimentId: string) => {
|
||||
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
||||
@@ -756,6 +871,54 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
// Unschedule a repetition: clear its scheduling info and unassign all conductors.
|
||||
const unscheduleRepetition = async (repId: string, experimentId: string) => {
|
||||
setSchedulingRepetitions(prev => new Set(prev).add(repId))
|
||||
|
||||
try {
|
||||
// Remove all conductor assignments for this repetition
|
||||
removeRepetitionAssignments(repId)
|
||||
|
||||
// Clear scheduled_date on the repetition in local state
|
||||
setRepetitionsByExperiment(prev => ({
|
||||
...prev,
|
||||
[experimentId]: prev[experimentId]?.map(r =>
|
||||
r.id === repId ? { ...r, scheduled_date: null } : r
|
||||
) || []
|
||||
}))
|
||||
|
||||
// Clear scheduled times for this repetition so it disappears from the timeline
|
||||
setScheduledRepetitions(prev => {
|
||||
const next = { ...prev }
|
||||
delete next[repId]
|
||||
return next
|
||||
})
|
||||
|
||||
// This repetition no longer has active times
|
||||
setRepetitionsWithTimes(prev => {
|
||||
const next = new Set(prev)
|
||||
next.delete(repId)
|
||||
return next
|
||||
})
|
||||
|
||||
// Also clear scheduled_date in the database for this repetition
|
||||
await repetitionManagement.updateRepetition(repId, {
|
||||
scheduled_date: null
|
||||
})
|
||||
} catch (error: any) {
|
||||
setError(error?.message || 'Failed to unschedule repetition')
|
||||
} finally {
|
||||
setSchedulingRepetitions(prev => {
|
||||
const next = new Set(prev)
|
||||
next.delete(repId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
>>>>>>> old-github/main
|
||||
// Restore scroll position after scheduledRepetitions changes
|
||||
useEffect(() => {
|
||||
if (scrollPositionRef.current) {
|
||||
@@ -806,11 +969,22 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
phase: 'soaking' | 'airdrying' | 'cracking'
|
||||
startTime: Date
|
||||
assignedConductors: string[]
|
||||
<<<<<<< HEAD
|
||||
locked: boolean
|
||||
=======
|
||||
>>>>>>> old-github/main
|
||||
}> = []
|
||||
|
||||
Object.values(scheduledRepetitions).forEach(scheduled => {
|
||||
const repId = scheduled.repetitionId
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
// Only include markers for repetitions that are checked (selected)
|
||||
if (!selectedRepetitionIds.has(repId)) {
|
||||
return
|
||||
}
|
||||
|
||||
>>>>>>> old-github/main
|
||||
const markerIdPrefix = repId
|
||||
|
||||
if (scheduled.soakingStart) {
|
||||
@@ -820,8 +994,12 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
experimentId: scheduled.experimentId,
|
||||
phase: 'soaking',
|
||||
startTime: scheduled.soakingStart,
|
||||
<<<<<<< HEAD
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-soaking`] || [],
|
||||
locked: lockedSchedules.has(repId)
|
||||
=======
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-soaking`] || []
|
||||
>>>>>>> old-github/main
|
||||
})
|
||||
}
|
||||
|
||||
@@ -832,8 +1010,12 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
experimentId: scheduled.experimentId,
|
||||
phase: 'airdrying',
|
||||
startTime: scheduled.airdryingStart,
|
||||
<<<<<<< HEAD
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-airdrying`] || [],
|
||||
locked: lockedSchedules.has(repId)
|
||||
=======
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-airdrying`] || []
|
||||
>>>>>>> old-github/main
|
||||
})
|
||||
}
|
||||
|
||||
@@ -844,8 +1026,12 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
experimentId: scheduled.experimentId,
|
||||
phase: 'cracking',
|
||||
startTime: scheduled.crackingStart,
|
||||
<<<<<<< HEAD
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-cracking`] || [],
|
||||
locked: lockedSchedules.has(repId)
|
||||
=======
|
||||
assignedConductors: conductorAssignments[`${markerIdPrefix}-cracking`] || []
|
||||
>>>>>>> old-github/main
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -856,7 +1042,70 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
conductorAvailabilities,
|
||||
phaseMarkers
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
}, [selectedConductorIds, conductors, conductorColorMap, colorPalette, availabilityEvents, scheduledRepetitions, conductorAssignments, lockedSchedules, calendarStartDate, calendarZoom])
|
||||
=======
|
||||
}, [selectedConductorIds, conductors, conductorColorMap, colorPalette, availabilityEvents, scheduledRepetitions, conductorAssignments, calendarStartDate, calendarZoom, selectedRepetitionIds])
|
||||
|
||||
// Build repetition metadata mapping for timeline display
|
||||
const repetitionMetadata = useMemo(() => {
|
||||
const metadata: Record<string, { phaseName: string; experimentNumber: number; repetitionNumber: number; experimentId: string; isScheduledInDb: boolean }> = {}
|
||||
|
||||
Object.values(scheduledRepetitions).forEach(scheduled => {
|
||||
const repId = scheduled.repetitionId
|
||||
// Only include metadata for repetitions that are checked (selected)
|
||||
if (!selectedRepetitionIds.has(repId)) {
|
||||
return
|
||||
}
|
||||
|
||||
const experiment = Object.values(experimentsByPhase).flat().find(e => e.id === scheduled.experimentId)
|
||||
const repetition = Object.values(repetitionsByExperiment).flat().find(r => r.id === repId)
|
||||
const phase = phases.find(p =>
|
||||
Object.values(experimentsByPhase[p.id] || []).some(e => e.id === scheduled.experimentId)
|
||||
)
|
||||
|
||||
if (experiment && repetition && phase) {
|
||||
metadata[repId] = {
|
||||
phaseName: phase.name,
|
||||
experimentNumber: experiment.experiment_number,
|
||||
repetitionNumber: repetition.repetition_number,
|
||||
experimentId: scheduled.experimentId,
|
||||
// Consider a repetition \"scheduled\" in DB if it has a non-null scheduled_date
|
||||
isScheduledInDb: Boolean(repetition.scheduled_date)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return metadata
|
||||
}, [scheduledRepetitions, experimentsByPhase, repetitionsByExperiment, phases, selectedRepetitionIds])
|
||||
|
||||
// Scroll to repetition in accordion
|
||||
const handleScrollToRepetition = useCallback(async (repetitionId: string) => {
|
||||
// First, expand the phase if it's collapsed
|
||||
const repetition = Object.values(repetitionsByExperiment).flat().find(r => r.id === repetitionId)
|
||||
if (repetition) {
|
||||
const experiment = Object.values(experimentsByPhase).flat().find(e =>
|
||||
(repetitionsByExperiment[e.id] || []).some(r => r.id === repetitionId)
|
||||
)
|
||||
if (experiment) {
|
||||
const phase = phases.find(p =>
|
||||
(experimentsByPhase[p.id] || []).some(e => e.id === experiment.id)
|
||||
)
|
||||
if (phase && !expandedPhaseIds.has(phase.id)) {
|
||||
await togglePhaseExpand(phase.id)
|
||||
// Wait a bit for the accordion to expand
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then scroll to the element
|
||||
const element = document.getElementById(`repetition-${repetitionId}`)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
}, [repetitionsByExperiment, experimentsByPhase, phases, expandedPhaseIds, togglePhaseExpand])
|
||||
>>>>>>> old-github/main
|
||||
|
||||
// Handlers for horizontal calendar
|
||||
const handleHorizontalMarkerDrag = useCallback((markerId: string, newTime: Date) => {
|
||||
@@ -878,6 +1127,7 @@ 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
|
||||
@@ -893,6 +1143,8 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
=======
|
||||
>>>>>>> old-github/main
|
||||
|
||||
|
||||
return (
|
||||
@@ -1027,7 +1279,13 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
phaseMarkers={horizontalCalendarData.phaseMarkers}
|
||||
onMarkerDrag={handleHorizontalMarkerDrag}
|
||||
onMarkerAssignConductors={handleHorizontalMarkerAssignConductors}
|
||||
<<<<<<< HEAD
|
||||
onMarkerLockToggle={handleHorizontalMarkerLockToggle}
|
||||
=======
|
||||
repetitionMetadata={repetitionMetadata}
|
||||
onScrollToRepetition={handleScrollToRepetition}
|
||||
onScheduleRepetition={scheduleRepetition}
|
||||
>>>>>>> old-github/main
|
||||
timeStep={15}
|
||||
minHour={6}
|
||||
maxHour={22}
|
||||
@@ -1196,11 +1454,29 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
const checked = selectedRepetitionIds.has(rep.id)
|
||||
const hasTimes = repetitionsWithTimes.has(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)
|
||||
|
||||
// Check if there are any conductor assignments
|
||||
const markerIdPrefix = rep.id
|
||||
const soakingConductors = conductorAssignments[`${markerIdPrefix}-soaking`] || []
|
||||
const airdryingConductors = conductorAssignments[`${markerIdPrefix}-airdrying`] || []
|
||||
const crackingConductors = conductorAssignments[`${markerIdPrefix}-cracking`] || []
|
||||
const hasAssignments = soakingConductors.length > 0 || airdryingConductors.length > 0 || crackingConductors.length > 0
|
||||
|
||||
return (
|
||||
<div
|
||||
key={rep.id}
|
||||
id={`repetition-${rep.id}`}
|
||||
className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded p-3"
|
||||
>
|
||||
>>>>>>> old-github/main
|
||||
{/* Checkbox row */}
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
@@ -1212,6 +1488,7 @@ 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>
|
||||
</label>
|
||||
|
||||
<<<<<<< HEAD
|
||||
{/* Time points (shown only if has been dropped/moved) */}
|
||||
{hasTimes && scheduled && (
|
||||
<div className="mt-2 ml-6 text-xs space-y-1">
|
||||
@@ -1250,6 +1527,91 @@ export function ScheduleExperiment({ user, onBack }: { user: User; onBack: () =>
|
||||
>
|
||||
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
||||
</button>
|
||||
=======
|
||||
{/* Time points (shown whenever the repetition has scheduled times) */}
|
||||
{scheduled && (
|
||||
<div className="mt-2 ml-6 text-xs space-y-1">
|
||||
{(() => {
|
||||
const repId = rep.id
|
||||
const markerIdPrefix = repId
|
||||
|
||||
// Get assigned conductors for each phase
|
||||
const soakingConductors = conductorAssignments[`${markerIdPrefix}-soaking`] || []
|
||||
const airdryingConductors = conductorAssignments[`${markerIdPrefix}-airdrying`] || []
|
||||
const crackingConductors = conductorAssignments[`${markerIdPrefix}-cracking`] || []
|
||||
|
||||
// Helper to get conductor names
|
||||
const getConductorNames = (conductorIds: string[]) => {
|
||||
return conductorIds.map(id => {
|
||||
const conductor = conductors.find(c => c.id === id)
|
||||
if (!conductor) return null
|
||||
return [conductor.first_name, conductor.last_name].filter(Boolean).join(' ') || conductor.email
|
||||
}).filter(Boolean).join(', ')
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span>💧</span>
|
||||
<span>Soaking: {formatTime(scheduled.soakingStart)}</span>
|
||||
{soakingConductors.length > 0 && (
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
({getConductorNames(soakingConductors)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span>🌬️</span>
|
||||
<span>Airdrying: {formatTime(scheduled.airdryingStart)}</span>
|
||||
{airdryingConductors.length > 0 && (
|
||||
<span className="text-green-600 dark:text-green-400">
|
||||
({getConductorNames(airdryingConductors)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span>⚡</span>
|
||||
<span>Cracking: {formatTime(scheduled.crackingStart)}</span>
|
||||
{crackingConductors.length > 0 && (
|
||||
<span className="text-orange-600 dark:text-orange-400">
|
||||
({getConductorNames(crackingConductors)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
|
||||
{/* Remove Assignments button and Schedule/Unschedule button */}
|
||||
<div className="flex items-center gap-3 mt-3 pt-2 border-t border-gray-200 dark:border-gray-600">
|
||||
{hasAssignments && (
|
||||
<button
|
||||
onClick={() => removeRepetitionAssignments(rep.id)}
|
||||
disabled={Boolean(rep.scheduled_date)}
|
||||
className="px-3 py-1 bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white rounded text-xs transition-colors"
|
||||
title={rep.scheduled_date ? "Unschedule the repetition first before removing assignments" : "Remove all conductor assignments from this repetition"}
|
||||
>
|
||||
Remove Assignments
|
||||
</button>
|
||||
)}
|
||||
{rep.scheduled_date ? (
|
||||
<button
|
||||
onClick={() => unscheduleRepetition(rep.id, exp.id)}
|
||||
disabled={isScheduling}
|
||||
className="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-400 text-white rounded text-xs transition-colors"
|
||||
>
|
||||
{isScheduling ? 'Unscheduling...' : 'Unschedule'}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => scheduleRepetition(rep.id, exp.id)}
|
||||
disabled={isScheduling}
|
||||
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>
|
||||
)}
|
||||
>>>>>>> old-github/main
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
57
scheduling-remote/wait-and-serve.sh
Normal file
57
scheduling-remote/wait-and-serve.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Build the project first
|
||||
echo "Building scheduling-remote..."
|
||||
npm run build
|
||||
|
||||
# Verify the initial build created remoteEntry.js
|
||||
REMOTE_ENTRY_PATH="dist/assets/remoteEntry.js"
|
||||
if [ ! -f "$REMOTE_ENTRY_PATH" ]; then
|
||||
echo "ERROR: Initial build did not create remoteEntry.js!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Initial build complete. remoteEntry.js exists."
|
||||
|
||||
# Start build:watch in the background
|
||||
echo "Starting build:watch in background..."
|
||||
npm run build:watch &
|
||||
BUILD_WATCH_PID=$!
|
||||
|
||||
# Wait a moment for build:watch to start and potentially rebuild
|
||||
echo "Waiting for build:watch to stabilize..."
|
||||
sleep 3
|
||||
|
||||
# Verify remoteEntry.js still exists (build:watch might have rebuilt it)
|
||||
MAX_WAIT=30
|
||||
WAIT_COUNT=0
|
||||
while [ ! -f "$REMOTE_ENTRY_PATH" ] && [ $WAIT_COUNT -lt $MAX_WAIT ]; do
|
||||
sleep 1
|
||||
WAIT_COUNT=$((WAIT_COUNT + 1))
|
||||
if [ $((WAIT_COUNT % 5)) -eq 0 ]; then
|
||||
echo "Waiting for remoteEntry.js after build:watch... (${WAIT_COUNT}s)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -f "$REMOTE_ENTRY_PATH" ]; then
|
||||
echo "ERROR: remoteEntry.js was not available after ${MAX_WAIT} seconds!"
|
||||
kill $BUILD_WATCH_PID 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait a bit more to ensure build:watch has finished any initial rebuild
|
||||
echo "Ensuring build:watch has completed initial build..."
|
||||
sleep 2
|
||||
|
||||
# Check file size to ensure it's not empty or being written
|
||||
FILE_SIZE=$(stat -f%z "$REMOTE_ENTRY_PATH" 2>/dev/null || stat -c%s "$REMOTE_ENTRY_PATH" 2>/dev/null || echo "0")
|
||||
if [ "$FILE_SIZE" -lt 100 ]; then
|
||||
echo "WARNING: remoteEntry.js seems too small (${FILE_SIZE} bytes), waiting a bit more..."
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo "remoteEntry.js is ready (${FILE_SIZE} bytes). Starting http-server..."
|
||||
|
||||
# Start http-server and give it time to fully initialize
|
||||
# Use a simple approach: start server and wait a moment for it to be ready
|
||||
exec npx http-server dist -p 3003 --cors -c-1
|
||||
Reference in New Issue
Block a user