diff --git a/web/src/components/TimePicker.jsx b/web/src/components/TimePicker.jsx index 38ce2b23f..85a493f72 100644 --- a/web/src/components/TimePicker.jsx +++ b/web/src/components/TimePicker.jsx @@ -1,90 +1,170 @@ import { h } from 'preact'; -import { useState } from 'preact/hooks'; +import { useEffect, useState } from 'preact/hooks'; +import { ArrowDropdown } from '../icons/ArrowDropdown'; +import { ArrowDropup } from '../icons/ArrowDropup'; -const CalendarTimePicker = ({ dateRange, onChange }) => { - const [selected, setSelected] = useState([0]); +const TimePicker = ({ dateRange, onChange }) => { + const [error, setError] = useState(null); + const [timeRange, setTimeRange] = useState(new Set()); - const timestampHour = (timestamp) => { - if (!timestamp) return undefined; - return new Date(timestamp).getHours(); + useEffect(() => { + if (!dateRange.after) return setTimeRange(new Set()); + }, [dateRange.after]); + + // We subtract one from the before time to count the exact number of days in unix timestamp selected in the calendar + const before = dateRange.before ? new Date(dateRange.before) : new Date(new Date().setHours(24, 0, 0, 0)); + const after = dateRange.after ? new Date(dateRange.after) : new Date(new Date().setHours(0, 0, 0, 0)); + + /** + + numberOfDaysSelected is a set that holds the number of days selected in the dateRange. + The loop iterates through the days starting from the after date's day to the before date's day. + If the before date's hour is 0, it skips it. + + */ + const numberOfDaysSelected = new Set( + [...Array(before.getDate() - after.getDate() + 1)].map((_, i) => after.getDate() + i) + ); + if (before.getHours() === 0) numberOfDaysSelected.delete(before.getDate()); + + // Create repeating array with the number of hours for each day selected ...23,24,0,1,2... + const hoursInDays = Array.from({ length: numberOfDaysSelected.size * 24 }, (_, i) => i % 24); + + const handleTime = (hour) => { + if (!hour) return; + + const _timeRange = new Set([...timeRange]); + _timeRange.add(hour); + + setError(null); + if (timeRange.has(hour)) { + setTimeRange(new Set()); + const resetBefore = before.setDate(after.getDate() + numberOfDaysSelected.size - 1); + return onChange({ + after: after.setHours(0, 0, 0, 0) / 1000, + before: new Date(resetBefore).setHours(24, 0, 0, 0) / 1000, + }); + } + + //update after + if (_timeRange.size === 1) { + // check if the first selected value is within first day + const selDay = Math.ceil(Math.min(..._timeRange) / 24); + if (selDay !== 1) { + return setError('Select a time on the initial day!'); + } + + // calculate days offset + const dayOffsetAfter = new Date(after).setHours(Math.min(..._timeRange)); + + let dayOffsetBefore = before; + if (numberOfDaysSelected.size === 1) { + dayOffsetBefore = new Date(after).setHours(Math.min(..._timeRange) + 1); + } + + onChange({ + after: dayOffsetAfter / 1000, + before: dayOffsetBefore / 1000, + }); + } + + //update before + if (_timeRange.size > 1) { + let selectedDay = Math.ceil(Math.max(..._timeRange) / 24); + + // if user selects time 00:00 for the next day, add one day + if (hour === 24 && selectedDay === numberOfDaysSelected.size - 1) { + selectedDay += 1; + } + + // Check if end time is on the last day + if (selectedDay !== numberOfDaysSelected.size) { + return setError('Ending must occur on final day!'); + } + + // Check if end time is later than start time + const startHour = Math.min(..._timeRange); + if (hour <= startHour) { + return setError('Ending hour must be greater than start time!'); + } + + // Add all hours between start and end times to the set + for (let x = startHour; x <= hour; x++) { + _timeRange.add(x); + } + + // calculate days offset + const dayOffsetBefore = new Date(dateRange.after); + onChange({ + after: dateRange.after / 1000, + // we add one hour to get full 60min of last selected hour + before: dayOffsetBefore.setHours(Math.max(..._timeRange) + 1) / 1000, + }); + } + + for (let i = 0; i < _timeRange.size; i++) { + setTimeRange((timeRange) => timeRange.add(Array.from(_timeRange)[i])); + } }; - const handleTime = (number) => { - const currentHour = new Date(); - currentHour.setHours(number, 0, 0, 0); - - // Get current time if dateRange.after or dateRange.before are not set - const afterTime = dateRange.after ? dateRange.after : currentHour.getTime(); - const beforetime = afterTime + 1; - - // Update dateRange object with new "before" time and set selected to new number - if (selected.length === 1 && number > Math.max(...selected)) { - onChange({ - after: afterTime / 1000, - before: new Date(afterTime).setHours(number + 1) / 1000, - }); - - return setSelected([number]); - } - - // Updating the start of the selected range - if (selected.length > 0 && number < selected[0]) { - onChange({ - after: new Date(afterTime).setHours(number) / 1000, - before: dateRange.before / 1000, - }); - - return setSelected([number]); - } - - // Updating the end of the selected range - if (selected.length === 2) { - onChange({ - after: dateRange.after ? dateRange.after / 1000 : 0, - before: new Date(beforetime) / 1000, - }); - - return setSelected([number]); - } - - // Update dateRange object with both "after" and "before" times - if (selected.length === 1) { - onChange({ - after: new Date(afterTime).setHours(number) / 1000, - before: new Date(afterTime).setHours(number + 1) / 1000, - }); - - return setSelected([Math.min(selected[0], number), Math.max(selected[0], number)]); - } - setSelected([number]); - }; const isSelected = (after, before, idx) => { - if (timestampHour(before) === 0 && timestampHour(after) === 0) return false; - - return timestampHour(after) <= idx && timestampHour(before - 1) >= idx; + return !!timeRange.has(idx); }; + + const isSelectedCss = 'bg-blue-600 transition-all duration-50 hover:rounded-none'; + function randomGrayTone(shade) { + const grayTones = [ + 'bg-[#212529]/50', + 'bg-[#343a40]/50', + 'bg-[#495057]/50', + 'bg-[#666463]/50', + 'bg-[#817D7C]/50', + 'bg-[#73706F]/50', + 'bg-[#585655]/50', + 'bg-[#4F4D4D]/50', + 'bg-[#454343]/50', + 'bg-[#363434]/50', + ]; + return grayTones[shade % grayTones.length]; + } + return ( -
-

TimePicker

-
-
- {Array.from({ length: 24 }, (_, idx) => ( -
handleTime(idx)} - > - {idx}:00 -
- ))} + <> + {error ? {error} : null} +
+
+ +
+
+
+ {hoursInDays.map((_, idx) => ( +
+
handleTime(idx)} + > + {hoursInDays[idx]}:00 +
+
+ ))} +
+
+ +
-
+ ); }; -export default CalendarTimePicker; +export default TimePicker;