diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx index 2e175298..d5bdc0b5 100644 --- a/src/components/input/DateFilter.tsx +++ b/src/components/input/DateFilter.tsx @@ -4,6 +4,7 @@ import { Fragment, type Key, useState } from 'react'; import { DateDisplay } from '@/components/common/DateDisplay'; import { useMessages, useMobile } from '@/components/hooks'; import { DatePickerForm } from '@/components/metrics/DatePickerForm'; +import { TimeRangePickerForm } from '@/components/metrics/TimeRangePickerForm'; import { parseDateRange } from '@/lib/date'; export interface DateFilterProps extends SelectProps { @@ -24,6 +25,7 @@ export function DateFilter({ }: DateFilterProps) { const { formatMessage, labels } = useMessages(); const [showPicker, setShowPicker] = useState(false); + const [showTimePicker, setShowTimePicker] = useState(false); const { startDate, endDate } = parseDateRange(value) || {}; const { isMobile } = useMobile(); @@ -33,6 +35,27 @@ export function DateFilter({ label: formatMessage(labels.lastHours, { x: '24' }), value: '24hour', }, + { + label: formatMessage(labels.lastHours, { x: '12' }), + value: '12hour', + }, + { + label: formatMessage(labels.lastHours, { x: '6' }), + value: '6hour', + }, + { + label: formatMessage(labels.lastHours, { x: '4' }), + value: '4hour', + }, + { + label: formatMessage(labels.lastHours, { x: '2' }), + value: '2hour', + }, + { + label: formatMessage(labels.lastHours, { x: '1' }), + value: '1hour', + divider: true, + }, { label: formatMessage(labels.thisWeek), value: '0week', @@ -75,6 +98,10 @@ export function DateFilter({ value: 'custom', divider: true, }, + { + label: formatMessage(labels.timeRange), + value: 'timeRange', + }, ] .filter(n => n) .map((a, id) => ({ ...a, id })); @@ -84,6 +111,10 @@ export function DateFilter({ setShowPicker(true); return; } + if (value === 'timeRange') { + setShowTimePicker(true); + return; + } onChange(value.toString()); }; @@ -92,6 +123,11 @@ export function DateFilter({ onChange(value.toString()); }; + const handleTimePickerChange = (value: string) => { + setShowTimePicker(false); + onChange(value.toString()); + }; + const renderValue = ({ defaultChildren }) => { return value?.startsWith('range') || renderDate ? ( @@ -136,6 +172,20 @@ export function DateFilter({ )} + {showTimePicker && ( + + + setShowTimePicker(false)} + /> + + + )} ); } diff --git a/src/components/messages.ts b/src/components/messages.ts index cebb8c1d..8ccc1192 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -133,6 +133,9 @@ export const labels = defineMessages({ thisYear: { id: 'label.this-year', defaultMessage: 'This year' }, allTime: { id: 'label.all-time', defaultMessage: 'All time' }, customRange: { id: 'label.custom-range', defaultMessage: 'Custom range' }, + timeRange: { id: 'label.time-range', defaultMessage: 'Time range' }, + startTime: { id: 'label.start-time', defaultMessage: 'Start time' }, + endTime: { id: 'label.end-time', defaultMessage: 'End time' }, selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' }, selectRole: { id: 'label.select-role', defaultMessage: 'Select role' }, selectDate: { id: 'label.select-date', defaultMessage: 'Select date' }, diff --git a/src/components/metrics/TimeRangePickerForm.tsx b/src/components/metrics/TimeRangePickerForm.tsx new file mode 100644 index 00000000..5c6cb71b --- /dev/null +++ b/src/components/metrics/TimeRangePickerForm.tsx @@ -0,0 +1,99 @@ +import { Button, Calendar, Column, ListItem, Row, Select } from '@umami/react-zen'; +import { setHours, setMinutes, startOfDay } from 'date-fns'; +import { type Key, useState } from 'react'; +import { useMessages } from '@/components/hooks'; + +// Generate hour options (00:00 to 23:00) +const HOUR_OPTIONS = Array.from({ length: 24 }, (_, i) => ({ + id: i.toString(), + label: `${i.toString().padStart(2, '0')}:00`, + value: i, +})); + +interface TimeRangePickerFormProps { + startDate?: Date; + endDate?: Date; + minDate?: Date; + maxDate?: Date; + onChange: (value: string) => void; + onClose: () => void; +} + +export function TimeRangePickerForm({ + startDate: defaultStartDate, + endDate: defaultEndDate, + minDate, + maxDate, + onChange, + onClose, +}: TimeRangePickerFormProps) { + const [date, setDate] = useState(defaultStartDate || new Date()); + const [startHour, setStartHour] = useState(defaultStartDate?.getHours() || 0); + const [endHour, setEndHour] = useState(defaultEndDate?.getHours() || 23); + const { formatMessage, labels } = useMessages(); + + const disabled = startHour > endHour; + + const handleSave = () => { + const start = setMinutes(setHours(startOfDay(date), startHour), 0); + const end = setMinutes(setHours(startOfDay(date), endHour + 1), 0); + onChange(`range:${start.getTime()}:${end.getTime()}`); + }; + + const handleStartHourChange = (value: Key) => { + setStartHour(Number(value)); + }; + + const handleEndHourChange = (value: Key) => { + setEndHour(Number(value)); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + ); +}