frigate/web/src/components/TimePicker.jsx

171 lines
5.8 KiB
React
Raw Normal View History

2023-01-15 21:26:04 +03:00
import { h } from 'preact';
2023-01-22 01:11:59 +03:00
import { useEffect, useState } from 'preact/hooks';
import { ArrowDropdown } from '../icons/ArrowDropdown';
import { ArrowDropup } from '../icons/ArrowDropup';
2023-01-15 21:26:04 +03:00
2023-01-22 01:11:59 +03:00
const TimePicker = ({ dateRange, onChange }) => {
const [error, setError] = useState(null);
const [timeRange, setTimeRange] = useState(new Set());
2023-01-15 21:26:04 +03:00
2023-01-22 01:11:59 +03:00
useEffect(() => {
if (!dateRange.after) return setTimeRange(new Set());
}, [dateRange.after]);
2023-01-15 21:26:04 +03:00
2023-01-22 01:11:59 +03:00
// 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));
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
/**
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
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.
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
*/
const numberOfDaysSelected = new Set(
[...Array(before.getDate() - after.getDate() + 1)].map((_, i) => after.getDate() + i)
);
if (before.getHours() === 0) numberOfDaysSelected.delete(before.getDate());
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
// 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);
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
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,
});
2023-01-17 02:04:52 +03:00
}
2023-01-22 01:11:59 +03:00
//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);
}
2023-01-17 02:04:52 +03:00
onChange({
2023-01-22 01:11:59 +03:00
after: dayOffsetAfter / 1000,
before: dayOffsetBefore / 1000,
2023-01-17 02:04:52 +03:00
});
}
2023-01-22 01:11:59 +03:00
//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);
2023-01-17 02:04:52 +03:00
onChange({
2023-01-22 01:11:59 +03:00
after: dateRange.after / 1000,
// we add one hour to get full 60min of last selected hour
before: dayOffsetBefore.setHours(Math.max(..._timeRange) + 1) / 1000,
2023-01-17 02:04:52 +03:00
});
2023-01-22 01:11:59 +03:00
}
2023-01-17 02:04:52 +03:00
2023-01-22 01:11:59 +03:00
for (let i = 0; i < _timeRange.size; i++) {
setTimeRange((timeRange) => timeRange.add(Array.from(_timeRange)[i]));
2023-01-17 02:04:52 +03:00
}
};
2023-01-22 01:11:59 +03:00
const isSelected = (after, before, idx) => {
return !!timeRange.has(idx);
2023-01-17 02:04:52 +03:00
};
2023-01-22 01:11:59 +03:00
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];
}
2023-01-17 02:04:52 +03:00
return (
2023-01-22 01:11:59 +03:00
<>
{error ? <span className="text-red-400 text-center text-xs absolute top-1 right-0 pr-2">{error}</span> : null}
<div className="mt-2 pr-5 hidden xs:block" aria-label="Calendar timepicker, select a time range">
<div className="flex items-center justify-center">
<ArrowDropup className="w-10 text-center" />
</div>
<div className="w-24 px-1">
<div
className="border border-gray-400/50 cursor-pointer hide-scroll shadow-md rounded-md"
style={{ maxHeight: '17rem', overflowY: 'scroll' }}
>
{hoursInDays.map((_, idx) => (
<div
key={idx}
className={`${isSelected(dateRange.after, dateRange.before, idx) ? isSelectedCss : ''}
${Math.min(...timeRange) === idx ? 'rounded-t-lg' : ''}
${Math.max(...timeRange) === idx ? 'rounded-b-lg' : ''}`}
>
<div
className={`
text-gray-300 w-full font-light border border-transparent hover:border hover:rounded-md hover:border-gray-600 text-center text-sm
${randomGrayTone([Math.floor(idx / 24)])}`}
onClick={() => handleTime(idx)}
>
<span aria-label={`${idx}:00`}>{hoursInDays[idx]}:00</span>
</div>
</div>
))}
</div>
<div className="flex items-center justify-center">
<ArrowDropdown className="w-10 text-center" />
</div>
2023-01-15 21:26:04 +03:00
</div>
</div>
2023-01-22 01:11:59 +03:00
</>
2023-01-15 21:26:04 +03:00
);
};
2023-01-22 01:11:59 +03:00
export default TimePicker;