| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- import dayjs from 'dayjs'
- import { ChevronDown } from 'lucide-react'
- import { ReactNode, useEffect, useState } from 'react'
- import {
- Button,
- cn,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
- } from 'ui'
- import { DATE_FORMAT } from '@/lib/constants'
- /**
- * There's timestamp rounding that kicks in if there are more than 50 data points
- * being returned, in order to increase cache hit rates.
- * Ensure that whenever we create a new period_start under handleChange, it should return
- * at point 50 data points. (e.g 3 hours, 5 mins interval = 36 points)
- * Otherwise there won't be any data shown on the graphs
- */
- interface DateRangePickerProps {
- value: string
- loading: boolean
- className?: string
- onChange: ({
- period_start,
- period_end,
- interval,
- }: {
- period_start: { date: string; time_period: string }
- period_end: { date: string; time_period: string }
- interval?: string
- }) => void
- options: { key: string; label: string; interval?: string }[]
- currentBillingPeriodStart?: number
- currentBillingPeriodEnd?: number
- footer?: ReactNode
- }
- export const DateRangePicker = ({
- onChange,
- value,
- options,
- className,
- loading,
- currentBillingPeriodStart,
- currentBillingPeriodEnd,
- footer,
- }: DateRangePickerProps) => {
- const [timePeriod, setTimePeriod] = useState(value)
- useEffect(() => {
- handleChange(value)
- }, [loading])
- const startOfMonth = dayjs().startOf('month').format(DATE_FORMAT)
- const nextMonth = dayjs().add(1, 'month').startOf('month').format(DATE_FORMAT)
- const today = dayjs().format(DATE_FORMAT)
- function handleChange(x: any) {
- setTimePeriod(x)
- switch (x) {
- // please note: these are fixed date ranges based on subscription
- // billing cycles are always assumed to be 1 month long
- // only currentPeriodStart is used to calculate dates
- case 'currentBillingCycle':
- onChange({
- period_start: {
- date: dayjs.unix(currentBillingPeriodStart ?? 0).format(DATE_FORMAT),
- time_period: '1d',
- },
- period_end: {
- date: dayjs.unix(currentBillingPeriodEnd ?? 0).format(DATE_FORMAT),
- time_period: 'today',
- },
- interval: '1d',
- })
- break
- case 'previousBillingCycle':
- onChange({
- period_start: {
- date: dayjs
- .unix(currentBillingPeriodStart ?? 0)
- .subtract(1, 'month')
- .format(DATE_FORMAT),
- time_period: '1d',
- },
- period_end: {
- date: dayjs.unix(currentBillingPeriodStart ?? 0).format(DATE_FORMAT),
- time_period: 'today',
- },
- interval: '1d',
- })
- break
- // all other time periods below are based on current date and time
- // they will generate flexible dynamic date ranges
- case '10m':
- onChange({
- period_start: {
- date: dayjs().subtract(10, 'minutes').format(DATE_FORMAT),
- time_period: '1d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1m',
- })
- break
- case '30m':
- onChange({
- period_start: {
- date: dayjs().subtract(30, 'minutes').format(DATE_FORMAT),
- time_period: '1d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1m',
- })
- break
- case '1d':
- onChange({
- period_start: {
- date: dayjs().subtract(24, 'hour').format(DATE_FORMAT),
- time_period: '1d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1h',
- })
- break
- case '3h':
- onChange({
- period_start: {
- date: dayjs().subtract(3, 'hour').format(DATE_FORMAT),
- time_period: '3h',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '5m',
- })
- break
- case '1h':
- onChange({
- period_start: {
- date: dayjs().subtract(1, 'hour').format(DATE_FORMAT),
- time_period: '1h',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1m',
- })
- break
- case '7d':
- onChange({
- period_start: {
- date: dayjs().subtract(7, 'day').format(DATE_FORMAT),
- time_period: '7d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1d',
- })
- break
- case '30d':
- onChange({
- period_start: {
- date: dayjs().subtract(30, 'day').format(DATE_FORMAT),
- time_period: '30d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '1d',
- })
- break
- case '60d':
- onChange({
- period_start: {
- date: dayjs().subtract(60, 'day').format(DATE_FORMAT),
- time_period: '60d',
- },
- period_end: {
- date: today,
- time_period: 'today',
- },
- interval: '3d',
- })
- break
- case 'startMonth':
- onChange({
- period_start: {
- date: startOfMonth,
- time_period: 'startMonth',
- },
- period_end: {
- date: nextMonth,
- time_period: 'endMonth',
- },
- interval: '1d',
- })
- break
- default:
- console.warn(`Unknown period encountered: ${x}`)
- break
- }
- }
- return (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button type="default" iconRight={<ChevronDown />}>
- <span>{timePeriod && options.find((x) => x.key === timePeriod)?.label}</span>
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent side="bottom" align="start" className={cn(!footer && 'w-36', className)}>
- <DropdownMenuRadioGroup value={timePeriod} onValueChange={(x) => handleChange(x)}>
- {options.map((option) => {
- return (
- <DropdownMenuRadioItem value={option.key} key={option.key}>
- {option.label}
- </DropdownMenuRadioItem>
- )
- })}
- </DropdownMenuRadioGroup>
- {!!footer && (
- <>
- <DropdownMenuSeparator />
- {footer}
- </>
- )}
- </DropdownMenuContent>
- </DropdownMenu>
- )
- }
- export default DateRangePicker
|