ui: fix timepicker and timezone interactions

This commit is contained in:
nick-delirium 2025-04-22 17:37:04 +02:00 committed by Delirium
parent 05cbb831c7
commit 68ea291444

View file

@ -12,60 +12,123 @@ import {
getDateRangeFromValue, getDateRangeFromValue,
getDateRangeLabel, getDateRangeLabel,
} from 'App/dateRange'; } from 'App/dateRange';
import { DateTime, Interval } from 'luxon'; import { DateTime, Interval, Settings } from 'luxon';
import styles from './dateRangePopup.module.css'; import styles from './dateRangePopup.module.css';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function DateRangePopup(props: any) { function DateRangePopup(props: any) {
const { t } = useTranslation(); const { t } = useTranslation();
const [displayDates, setDisplayDates] = React.useState<[Date, Date]>([new Date(), new Date()]);
const [range, setRange] = React.useState( const [range, setRange] = React.useState(
props.selectedDateRange || props.selectedDateRange ||
Interval.fromDateTimes(DateTime.now(), DateTime.now()), Interval.fromDateTimes(DateTime.now(), DateTime.now()),
); );
const [value, setValue] = React.useState<string | null>(null); const [value, setValue] = React.useState<string | null>(null);
const selectCustomRange = (range) => { React.useEffect(() => {
let newRange; if (props.selectedDateRange) {
if (props.singleDay) { const start = new Date(
newRange = Interval.fromDateTimes( props.selectedDateRange.start.year,
DateTime.fromJSDate(range), props.selectedDateRange.start.month - 1, // JS months are 0-based
DateTime.fromJSDate(range), props.selectedDateRange.start.day
); );
} else { const end = new Date(
newRange = Interval.fromDateTimes( props.selectedDateRange.end.year,
DateTime.fromJSDate(range[0]), props.selectedDateRange.end.month - 1,
DateTime.fromJSDate(range[1]), props.selectedDateRange.end.day
); );
} setDisplayDates([start, end]);
setRange(newRange); }
}, [props.selectedDateRange]);
const createNaiveTime = (dateTime: DateTime) => {
if (!dateTime) return null;
return DateTime.fromObject({
hour: dateTime.hour,
minute: dateTime.minute
});
};
const selectCustomRange = (newDates: [Date, Date]) => {
if (!newDates || !newDates[0] || !newDates[1]) return;
setDisplayDates(newDates);
const selectedTzStart = DateTime.fromObject({
year: newDates[0].getFullYear(),
month: newDates[0].getMonth() + 1,
day: newDates[0].getDate(),
hour: 0,
minute: 0
}).setZone(Settings.defaultZone);
const selectedTzEnd = DateTime.fromObject({
year: newDates[1].getFullYear(),
month: newDates[1].getMonth() + 1,
day: newDates[1].getDate(),
hour: 23,
minute: 59
}).setZone(Settings.defaultZone);
const updatedRange = Interval.fromDateTimes(selectedTzStart, selectedTzEnd);
setRange(updatedRange);
setValue(CUSTOM_RANGE); setValue(CUSTOM_RANGE);
}; };
const setRangeTimeStart = (value: DateTime) => { const setRangeTimeStart = (naiveTime: DateTime) => {
if (!range.end || value > range.end) { if (!range.end || !naiveTime) return;
return;
} const newStart = range.start.set({
const newRange = range.start.set({ hour: naiveTime.hour,
hour: value.hour, minute: naiveTime.minute
minute: value.minute,
}); });
setRange(Interval.fromDateTimes(newRange, range.end));
if (newStart > range.end) return;
setRange(Interval.fromDateTimes(newStart, range.end));
setValue(CUSTOM_RANGE); setValue(CUSTOM_RANGE);
}; };
const setRangeTimeEnd = (value: DateTime) => { const setRangeTimeEnd = (naiveTime: DateTime) => {
if (!range.start || (value && value < range.start)) { if (!range.start || !naiveTime) return;
return;
} const newEnd = range.end.set({
const newRange = range.end.set({ hour: value.hour, minute: value.minute }); hour: naiveTime.hour,
setRange(Interval.fromDateTimes(range.start, newRange)); minute: naiveTime.minute
});
if (newEnd < range.start) return;
setRange(Interval.fromDateTimes(range.start, newEnd));
setValue(CUSTOM_RANGE); setValue(CUSTOM_RANGE);
}; };
const selectValue = (value: string) => { const selectValue = (value: string) => {
const range = getDateRangeFromValue(value); const newRange = getDateRangeFromValue(value);
setRange(range);
if (!newRange.start || !newRange.end) {
setRange(Interval.fromDateTimes(DateTime.now(), DateTime.now()));
setDisplayDates([new Date(), new Date()]);
setValue(null);
return;
}
const zonedStart = newRange.start.setZone(Settings.defaultZone);
const zonedEnd = newRange.end.setZone(Settings.defaultZone);
setRange(Interval.fromDateTimes(zonedStart, zonedEnd));
const start = new Date(
zonedStart.year,
zonedStart.month - 1,
zonedStart.day
);
const end = new Date(
zonedEnd.year,
zonedEnd.month - 1,
zonedEnd.day
);
setDisplayDates([start, end]);
setValue(value); setValue(value);
}; };
@ -77,9 +140,9 @@ function DateRangePopup(props: any) {
const isUSLocale = const isUSLocale =
navigator.language === 'en-US' || navigator.language.startsWith('en-US'); navigator.language === 'en-US' || navigator.language.startsWith('en-US');
const rangeForDisplay = props.singleDay const naiveStartTime = createNaiveTime(range.start);
? range.start.ts const naiveEndTime = createNaiveTime(range.end);
: [range.start!.startOf('day').ts, range.end!.startOf('day').ts];
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<div className={`${styles.body} h-fit`}> <div className={`${styles.body} h-fit`}>
@ -103,7 +166,7 @@ function DateRangePopup(props: any) {
shouldCloseCalendar={() => false} shouldCloseCalendar={() => false}
isOpen isOpen
maxDate={new Date()} maxDate={new Date()}
value={rangeForDisplay} value={displayDates}
calendarProps={{ calendarProps={{
tileDisabled: props.isTileDisabled, tileDisabled: props.isTileDisabled,
selectRange: !props.singleDay, selectRange: !props.singleDay,
@ -122,7 +185,7 @@ function DateRangePopup(props: any) {
<span>{range.start.toFormat(isUSLocale ? 'MM/dd' : 'dd/MM')} </span> <span>{range.start.toFormat(isUSLocale ? 'MM/dd' : 'dd/MM')} </span>
<TimePicker <TimePicker
format={isUSLocale ? 'hh:mm a' : 'HH:mm'} format={isUSLocale ? 'hh:mm a' : 'HH:mm'}
value={range.start} value={naiveStartTime}
onChange={setRangeTimeStart} onChange={setRangeTimeStart}
needConfirm={false} needConfirm={false}
showNow={false} showNow={false}
@ -132,7 +195,7 @@ function DateRangePopup(props: any) {
<span>{range.end.toFormat(isUSLocale ? 'MM/dd' : 'dd/MM')} </span> <span>{range.end.toFormat(isUSLocale ? 'MM/dd' : 'dd/MM')} </span>
<TimePicker <TimePicker
format={isUSLocale ? 'hh:mm a' : 'HH:mm'} format={isUSLocale ? 'hh:mm a' : 'HH:mm'}
value={range.end} value={naiveEndTime}
onChange={setRangeTimeEnd} onChange={setRangeTimeEnd}
needConfirm={false} needConfirm={false}
showNow={false} showNow={false}