Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 15 additions & 81 deletions src/components/logged-in/dashboard/dashboard-filters.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,28 @@
'use client'

import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { format } from 'date-fns'
import { CalendarIcon } from 'lucide-react'
import { useMemo, useState } from 'react'
import {
DATE_PRESETS,
type DatePreset,
getPresetLabel,
useDashboardFilters,
} from './search-params'
DateRangeFilter,
} from '@/components/logged-in/shared/date-range-filter'
import { useDashboardFilters } from './search-params'

export function DashboardFilters() {
const { filters, setFilters } = useDashboardFilters()
const [isCalendarOpen, setIsCalendarOpen] = useState(false)

const selectedRange = useMemo(
() => ({
from: filters.from ? new Date(filters.from) : undefined,
to: filters.to ? new Date(filters.to) : undefined,
}),
[filters.from, filters.to],
)

const handlePresetClick = (preset: DatePreset) => {
if (preset === 'custom') {
setIsCalendarOpen(true)
return
}
setFilters({ preset, from: null, to: null })
}

const handleDateSelect = (range: { from?: Date; to?: Date } | undefined) => {
if (range?.from) {
setFilters({ from: range.from, preset: 'custom' })
}
if (range?.to) {
setFilters({ to: range.to, preset: 'custom' })
setIsCalendarOpen(false)
}
const handleFilterChange = (updates: {
preset?: DatePreset
from?: Date | null
to?: Date | null
}) => {
setFilters(updates)
}

return (
<div className="flex flex-wrap items-center gap-2">
{DATE_PRESETS.filter((p) => p !== 'custom').map((preset) => (
<Button
key={preset}
variant={filters.preset === preset ? 'default' : 'outline'}
size="sm"
onClick={() => handlePresetClick(preset)}
>
{getPresetLabel(preset)}
</Button>
))}

<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant={filters.preset === 'custom' ? 'default' : 'outline'}
size="sm"
className={cn(
'min-w-[140px] justify-start text-left font-normal',
filters.preset !== 'custom' && 'text-muted-foreground',
)}
>
<CalendarIcon className="mr-2 size-4" />
{filters.preset === 'custom' && filters.from && filters.to ? (
`${format(filters.from, 'MMM d')} - ${format(filters.to, 'MMM d')}`
) : (
<span>Pick a date range</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
selected={selectedRange}
onSelect={handleDateSelect}
captionLayout="dropdown"
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
<DateRangeFilter
preset={filters.preset}
from={filters.from}
to={filters.to}
onFilterChange={handleFilterChange}
/>
)
}
25 changes: 6 additions & 19 deletions src/components/logged-in/dashboard/search-params.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {
DATE_PRESETS,
type DatePreset,
} from '@/components/logged-in/shared/date-range-filter'
import { parseAsLocalDate } from '@/lib/utils'
import { startOfYear, subDays, subMonths } from 'date-fns'
import { parseAsStringLiteral, useQueryStates } from 'nuqs'
import { useCallback, useEffect, useRef } from 'react'

export const DATE_PRESETS = ['7d', '30d', '3m', '6m', 'ytd', 'custom'] as const
export type DatePreset = (typeof DATE_PRESETS)[number]
export { DATE_PRESETS, type DatePreset }
export { getPresetLabel } from '@/components/logged-in/shared/date-range-filter'

const STORAGE_KEY = 'dashboard-date-preset'
const DEFAULT_PRESET: DatePreset = '30d'
Expand Down Expand Up @@ -97,23 +101,6 @@ export function getDateRangeFromPreset(
}
}

export function getPresetLabel(preset: DatePreset): string {
switch (preset) {
case '7d':
return 'Last 7 days'
case '30d':
return 'Last 30 days'
case '3m':
return 'Last 3 months'
case '6m':
return 'Last 6 months'
case 'ytd':
return 'Year to date'
case 'custom':
return 'Custom'
}
}

export function getGroupByFromPreset(
preset: DatePreset,
): 'day' | 'week' | 'month' {
Expand Down
123 changes: 123 additions & 0 deletions src/components/logged-in/shared/date-range-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use client'

import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { format } from 'date-fns'
import { CalendarIcon } from 'lucide-react'
import { useMemo, useState } from 'react'

export const DATE_PRESETS = ['7d', '30d', '3m', '6m', 'ytd', 'custom'] as const
export type DatePreset = (typeof DATE_PRESETS)[number]

export function getPresetLabel(preset: DatePreset): string {
switch (preset) {
case '7d':
return 'Last 7 days'
case '30d':
return 'Last 30 days'
case '3m':
return 'Last 3 months'
case '6m':
return 'Last 6 months'
case 'ytd':
return 'Year to date'
case 'custom':
return 'Custom'
}
}

interface DateRangeFilterProps {
preset: DatePreset
from: Date | null
to: Date | null
onFilterChange: (updates: {
preset?: DatePreset
from?: Date | null
to?: Date | null
}) => void
}

export function DateRangeFilter({
preset,
from,
to,
onFilterChange,
}: DateRangeFilterProps) {
const [isCalendarOpen, setIsCalendarOpen] = useState(false)

const selectedRange = useMemo(
() => ({
from: from ? new Date(from) : undefined,
to: to ? new Date(to) : undefined,
}),
[from, to],
)

const handlePresetClick = (selectedPreset: DatePreset) => {
if (selectedPreset === 'custom') {
setIsCalendarOpen(true)
return
}
onFilterChange({ preset: selectedPreset, from: null, to: null })
}

const handleDateSelect = (range: { from?: Date; to?: Date } | undefined) => {
if (range?.from) {
onFilterChange({ from: range.from, preset: 'custom' })
}
if (range?.to) {
onFilterChange({ to: range.to, preset: 'custom' })
setIsCalendarOpen(false)
}

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When selecting a custom date range, only the 'to' date closes the calendar (line 76). If a user selects a 'from' date but doesn't select a 'to' date, the calendar remains open and the preset is set to 'custom' but with incomplete date information. Consider either:

  1. Automatically closing the calendar when 'from' is selected and 'to' already exists
  2. Setting preset to 'custom' only when both dates are selected
  3. Adding logic to keep the calendar open until both dates are selected

This could lead to a confusing UX where the preset shows 'custom' but only one date is set.

Suggested change
if (range?.from) {
onFilterChange({ from: range.from, preset: 'custom' })
}
if (range?.to) {
onFilterChange({ to: range.to, preset: 'custom' })
setIsCalendarOpen(false)
}
if (!range) {
return
}
const updates: { from?: Date; to?: Date; preset?: DatePreset } = {}
if (range.from) {
updates.from = range.from
}
if (range.to) {
updates.to = range.to
}
const bothSelected = !!(range.from && range.to)
if (bothSelected) {
updates.preset = 'custom'
setIsCalendarOpen(false)
}
if (updates.from || updates.to) {
onFilterChange(updates)
}

Copilot uses AI. Check for mistakes.
}

return (
<div className="flex flex-wrap items-center gap-2">
{DATE_PRESETS.filter((p) => p !== 'custom').map((presetOption) => (
<Button
key={presetOption}
variant={preset === presetOption ? 'default' : 'outline'}
size="sm"
onClick={() => handlePresetClick(presetOption)}
>
{getPresetLabel(presetOption)}
</Button>
))}

<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant={preset === 'custom' ? 'default' : 'outline'}
size="sm"
className={cn(
'min-w-[140px] justify-start text-left font-normal',
preset !== 'custom' && 'text-muted-foreground',
)}
>
<CalendarIcon className="mr-2 size-4" />
{preset === 'custom' && from && to ? (
`${format(from, 'MMM d')} - ${format(to, 'MMM d')}`
) : (
<span>Pick a date range</span>

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CalendarIcon in the custom date button doesn't have a margin-right class like the icon in the old dashboard-filters.tsx implementation (line 72 of the old code had 'mr-2'). This inconsistency in icon spacing may affect the visual appearance of the button.

Suggested change
<span>Pick a date range</span>
<CalendarIcon className="size-4 mr-2" />

Copilot uses AI. Check for mistakes.
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
selected={selectedRange}
onSelect={handleDateSelect}
captionLayout="dropdown"
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
)
}
Loading
Loading