diff --git a/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js b/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js index d7a4e19b..3816ec85 100644 --- a/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js +++ b/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js @@ -1,33 +1,43 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {Popup} from 'semantic-ui-react'; /** * Displays a placeholder for a candidate time slot when the Timeline is hovered. */ -export default function CandidatePlaceholder({xPosition, yPosition, height, widthPercent}) { +export default function CandidatePlaceholder({visible, left, width, time}) { + if (!visible) { + return null; + } + return ( -
+ } /> ); } CandidatePlaceholder.propTypes = { - height: PropTypes.number.isRequired, - widthPercent: PropTypes.number.isRequired, - xPosition: PropTypes.number.isRequired, - yPosition: PropTypes.number.isRequired, + visible: PropTypes.bool.isRequired, + width: PropTypes.number.isRequired, + left: PropTypes.number.isRequired, + time: PropTypes.string.isRequired, }; diff --git a/newdle/client/src/components/creation/timeslots/Timeline.js b/newdle/client/src/components/creation/timeslots/Timeline.js index f7c74680..9b7f32c4 100644 --- a/newdle/client/src/components/creation/timeslots/Timeline.js +++ b/newdle/client/src/components/creation/timeslots/Timeline.js @@ -56,6 +56,17 @@ function calculatePosition(start, minHour, maxHour) { return position < 100 ? position : 100 - OVERFLOW_WIDTH; } +function calculatePlaceholderStart(e, minHour, maxHour) { + const timelineRect = e.target.getBoundingClientRect(); + const position = (e.clientX - timelineRect.left) / timelineRect.width; + const totalMinutes = (maxHour - minHour) * 60; + + let minutes = minHour * 60 + position * totalMinutes; + minutes = Math.floor(minutes / 15) * 15; + + return moment().startOf('day').add(minutes, 'minutes'); +} + function getSlotProps(startTime, endTime, minHour, maxHour) { const start = toMoment(startTime, DEFAULT_TIME_FORMAT); const end = toMoment(endTime, DEFAULT_TIME_FORMAT); @@ -165,10 +176,7 @@ function TimelineInput({minHour, maxHour}) { const duration = useSelector(getDuration); const date = useSelector(getCreationCalendarActiveDate); const candidates = useSelector(getTimeslotsForActiveDate); - const pastCandidates = useSelector(getPreviousDayTimeslots); const availability = useSelector(getParticipantAvailability); - const [_editing, setEditing] = useState(false); - const editing = _editing || !!candidates.length; const latestStartTime = useSelector(getNewTimeslotStartTime); const [timeslotTime, setTimeslotTime] = useState(latestStartTime); const [newTimeslotPopupOpen, setTimeslotPopupOpen] = useState(false); @@ -176,41 +184,16 @@ function TimelineInput({minHour, maxHour}) { const [candidatePlaceholder, setCandidatePlaceholder] = useState({ visible: false, time: '', - x: 0, - y: 0, + left: 0, + width: 0, }); // We don't want to show the tooltip when the mouse is hovering over a slot const [isHoveringSlot, setIsHoveringSlot] = useState(false); - const placeHolderSlot = getCandidateSlotProps('00:00', duration, minHour, maxHour); - - useEffect(() => { - const handleScroll = () => { - setCandidatePlaceholder({visible: false}); - }; - - window.addEventListener('scroll', handleScroll); - - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, []); useEffect(() => { setTimeslotTime(latestStartTime); }, [latestStartTime, candidates, duration]); - const handleStartEditing = () => { - setEditing(true); - setTimeslotPopupOpen(true); - }; - - const handleCopyClick = () => { - pastCandidates.forEach(time => { - dispatch(addTimeslot(date, time)); - }); - setEditing(true); - }; - const handlePopupClose = () => { setTimeslotPopupOpen(false); }; @@ -235,30 +218,10 @@ function TimelineInput({minHour, maxHour}) { }; const handleMouseDown = e => { - const parentRect = e.target.getBoundingClientRect(); - const totalMinutes = (maxHour - minHour) * 60; - - // Get the parent rect start position - const parentRectStart = parentRect.left; - // Get the parent rect end position - const parentRectEnd = parentRect.right; - - const clickPositionRelative = (e.clientX - parentRectStart) / (parentRectEnd - parentRectStart); - - let clickTimeRelative = clickPositionRelative * totalMinutes; - - // Round clickTimeRelative to the nearest 15-minute interval - clickTimeRelative = Math.round(clickTimeRelative / 15) * 15; - - // Convert clickTimeRelative to a time format (HH:mm) - const clickTimeRelativeTime = moment() - .startOf('day') - .add(clickTimeRelative, 'minutes') - .format('HH:mm'); - - const canBeAdded = clickTimeRelativeTime && !isTimeSlotTaken(clickTimeRelativeTime); - if (canBeAdded) { - handleAddSlot(clickTimeRelativeTime); + const start = calculatePlaceholderStart(e, minHour, maxHour); + const formattedTime = start.format(DEFAULT_TIME_FORMAT); + if (!isTimeSlotTaken(formattedTime)) { + handleAddSlot(formattedTime); } }; @@ -269,216 +232,172 @@ function TimelineInput({minHour, maxHour}) { */ const handleTimelineMouseMove = e => { if (isHoveringSlot) { - setCandidatePlaceholder({visible: false}); + setCandidatePlaceholder(p => ({...p, visible: false})); return; } - const timelineRect = e.target.getBoundingClientRect(); - const relativeMouseXPosition = e.clientX - timelineRect.left; - - const totalMinutes = (maxHour - minHour) * 60; // Total minutes in the timeline - let timeInMinutes = (relativeMouseXPosition / timelineRect.width) * totalMinutes; - // Round timeInMinutes to the nearest 15-minute interval - timeInMinutes = Math.round(timeInMinutes / 15) * 15; - const slotWidth = (placeHolderSlot.width / timelineRect.width) * 100; - const hours = Math.floor(timeInMinutes / 60) + minHour; - const minutes = Math.floor(timeInMinutes % 60); - const time = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`; - - if (time === candidatePlaceholder.time) { - return; - } + const start = calculatePlaceholderStart(e, minHour, maxHour); + const end = moment(start).add(duration, 'minutes'); + const time = start.format(DEFAULT_TIME_FORMAT); // Check if the time slot is already taken if (isTimeSlotTaken(time)) { - setCandidatePlaceholder({visible: false}); + setCandidatePlaceholder(p => ({...p, visible: false})); return; } - const timelineVerticalPadding = 16; - const timelineVerticalPaddingBottom = 5; - const candidatePlaceholderPaddingLeft = 5; - const tooltipMarginLeft = -20; - const candidatePlaceholderMarginLeft = 5; - - const tempPlaceholder = { + setCandidatePlaceholder(p => ({ + ...p, visible: true, time, - candidateX: e.clientX + candidatePlaceholderMarginLeft, - candidateY: timelineRect.top + timelineRect.height - timelineVerticalPaddingBottom, - candidateHeight: timelineRect.height - timelineVerticalPadding, - tooltipMarginLeft: tooltipMarginLeft, - tooltipX: relativeMouseXPosition + candidatePlaceholderPaddingLeft, - width: slotWidth, - }; - - if (hours >= 0 && minutes >= 0) { - setCandidatePlaceholder(tempPlaceholder); - } + left: calculatePosition(start, minHour, maxHour), + width: calculateWidth(start, end, minHour, maxHour), + })); }; const handleTimelineMouseLeave = () => { - setCandidatePlaceholder({visible: false}); + setCandidatePlaceholder(p => ({...p, visible: false})); }; const groupedCandidates = splitOverlappingCandidates(candidates, duration); - return editing ? ( -