import { h } from 'preact'; import { useEffect, useState } from 'preact/hooks'; import { ArrowDropdown } from '../icons/ArrowDropdown'; import { ArrowDropup } from '../icons/ArrowDropup'; const TimePicker = ({ dateRange, onChange }) => { const [error, setError] = useState(null); const [timeRange, setTimeRange] = useState(new Set()); 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 isSelected = (after, before, 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 ( <> {error ? {error} : null}