diff --git a/ui/src/components/analytics/date-range-filter.tsx b/ui/src/components/analytics/date-range-filter.tsx index e2dca7f1..fd0730e0 100644 --- a/ui/src/components/analytics/date-range-filter.tsx +++ b/ui/src/components/analytics/date-range-filter.tsx @@ -2,16 +2,18 @@ * Date Range Filter Component * * Provides date range selection with preset options for analytics. - * Uses react-day-picker for date selection UI. + * Uses react-day-picker for date selection UI within a Popover. */ import React from 'react'; -import { format } from 'date-fns'; -import type { DateRange } from 'react-day-picker'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { cn } from '@/lib/utils'; +import { format, subDays } from 'date-fns'; import { CalendarIcon } from 'lucide-react'; +import type { DateRange } from 'react-day-picker'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; interface DateRangeFilterProps { value?: DateRange; @@ -26,70 +28,94 @@ interface DateRangeFilterProps { export function DateRangeFilter({ value, onChange, - presets = [], + presets = [ + { + label: 'Last 7 days', + range: { + from: subDays(new Date(), 7), + to: new Date(), + }, + }, + { + label: 'Last 30 days', + range: { + from: subDays(new Date(), 30), + to: new Date(), + }, + }, + { + label: 'Last 90 days', + range: { + from: subDays(new Date(), 90), + to: new Date(), + }, + }, + ], className, }: DateRangeFilterProps) { - const handlePresetClick = (range: DateRange) => { - onChange(range); - }; + const [isOpen, setIsOpen] = React.useState(false); - const handleFromChange = (e: React.ChangeEvent) => { - const from = e.target.value ? new Date(e.target.value) : undefined; - onChange({ from, to: value?.to }); - }; + // Helper to check if a preset is currently selected + const isPresetSelected = (presetRange: DateRange) => { + if (!value || !value.from || !value.to || !presetRange.from || !presetRange.to) { + return false; + } - const handleToChange = (e: React.ChangeEvent) => { - const to = e.target.value ? new Date(e.target.value) : undefined; - onChange({ from: value?.from, to }); + // Compare dates (ignoring time components if needed, but day-picker usually returns start of day) + // Simple comparison using formatted strings or timestamps + return ( + format(value.from, 'yyyy-MM-dd') === format(presetRange.from, 'yyyy-MM-dd') && + format(value.to, 'yyyy-MM-dd') === format(presetRange.to, 'yyyy-MM-dd') + ); }; return ( -
- {/* Preset Buttons */} - {presets.map((preset, index) => ( +
+ {presets.map((preset) => ( ))} - - {/* Custom Date Range Inputs */} -
-
- - + + + + + -
- to - -
+ +
); } - -// Helper to compare date ranges -function isSameRange(a?: DateRange, b?: DateRange): boolean { - if (!a || !b) return a === b; - - const fromA = a.from?.getTime() ?? 0; - const fromB = b.from?.getTime() ?? 0; - const toA = a.to?.getTime() ?? 0; - const toB = b.to?.getTime() ?? 0; - - return fromA === fromB && toA === toB; -} diff --git a/ui/src/components/ui/calendar.tsx b/ui/src/components/ui/calendar.tsx new file mode 100644 index 00000000..0c8b98c8 --- /dev/null +++ b/ui/src/components/ui/calendar.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; +import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react'; +import { DayPicker } from 'react-day-picker'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button-variants'; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { + return ( + { + const Icon = + orientation === 'left' + ? ChevronLeft + : orientation === 'right' + ? ChevronRight + : orientation === 'up' + ? ChevronUp + : ChevronDown; + return ; + }, + }} + {...props} + /> + ); +} +Calendar.displayName = 'Calendar'; + +export { Calendar }; diff --git a/ui/vite.config.ts b/ui/vite.config.ts index dad4049f..d905eb63 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -23,16 +23,31 @@ export default defineConfig({ 'react-vendor': ['react', 'react-dom', 'react-router-dom'], 'radix-ui': [ '@radix-ui/react-alert-dialog', + '@radix-ui/react-checkbox', + '@radix-ui/react-collapsible', '@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-label', + '@radix-ui/react-popover', + '@radix-ui/react-scroll-area', + '@radix-ui/react-select', '@radix-ui/react-separator', '@radix-ui/react-slot', + '@radix-ui/react-switch', + '@radix-ui/react-tabs', '@radix-ui/react-tooltip', ], 'tanstack': ['@tanstack/react-query', '@tanstack/react-table'], 'form-utils': ['react-hook-form', '@hookform/resolvers', 'zod'], 'icons': ['lucide-react'], + // Charts - large library, separate chunk + 'charts': ['recharts'], + // Code editor / syntax highlighting + 'code-highlight': ['prism-react-renderer'], + // Notifications + 'notifications': ['sonner'], + // Utilities + 'utils': ['date-fns', 'clsx', 'class-variance-authority', 'tailwind-merge', 'yaml'], }, }, },