langfuse
207 строк · 6.1 Кб
1"use client";
2
3import * as React from "react";
4import { Calendar as CalendarIcon, X } from "lucide-react";
5import { Button } from "@/src/components/ui/button";
6import { Calendar } from "@/src/components/ui/calendar";
7import {
8Popover,
9PopoverContent,
10PopoverTrigger,
11} from "@/src/components/ui/popover";
12import { cn } from "@/src/utils/tailwind";
13import { type DateRange } from "react-day-picker";
14import { addMinutes, format } from "date-fns";
15import {
16Select,
17SelectContent,
18SelectItem,
19SelectTrigger,
20SelectValue,
21} from "@/src/components/ui/select";
22import { useEffect, useState } from "react";
23import {
24type DateTimeAggregationOption,
25dateTimeAggregationSettings,
26dateTimeAggregationOptions,
27} from "@/src/features/dashboard/lib/timeseries-aggregation";
28import { useMediaQuery } from "react-responsive";
29import { type DashboardDateRange } from "@/src/pages/project/[projectId]";
30import { isValidOption } from "@/src/utils/types";
31import { setBeginningOfDay, setEndOfDay } from "@/src/utils/dates";
32
33export const DEFAULT_DATE_RANGE_SELECTION = "Date range" as const;
34export type AvailableDateRangeSelections =
35| typeof DEFAULT_DATE_RANGE_SELECTION
36| DateTimeAggregationOption;
37
38export function DatePicker({
39date,
40onChange,
41clearable = false,
42className,
43}: {
44date?: Date | undefined;
45onChange: (date: Date | undefined) => void;
46clearable?: boolean;
47className?: string;
48}) {
49return (
50<div className="flex flex-row gap-2 align-middle">
51<Popover>
52<PopoverTrigger asChild>
53<Button
54variant={"outline"}
55className={cn(
56"justify-start text-left font-normal",
57!date && "text-muted-foreground",
58className,
59)}
60>
61<CalendarIcon className="mr-2 h-4 w-4" />
62{date ? format(date, "PPP") : <span>Pick a date</span>}
63</Button>
64</PopoverTrigger>
65<PopoverContent className="w-auto p-0">
66<Calendar
67mode="single"
68selected={date}
69onSelect={(d) => onChange(d)}
70initialFocus
71/>
72</PopoverContent>
73</Popover>
74{date && clearable && (
75<Button
76variant="ghost"
77size="icon"
78onClick={() => onChange(undefined)}
79title="reset date"
80>
81<X size={14} />
82</Button>
83)}
84</div>
85);
86}
87
88export type DatePickerWithRangeProps = {
89dateRange?: DashboardDateRange;
90className?: string;
91selectedOption: AvailableDateRangeSelections;
92setDateRangeAndOption: (
93option: AvailableDateRangeSelections,
94date?: DashboardDateRange,
95) => void;
96};
97
98export function DatePickerWithRange({
99className,
100dateRange,
101selectedOption,
102setDateRangeAndOption,
103}: DatePickerWithRangeProps) {
104const [internalDateRange, setInternalDateRange] = useState<
105DateRange | undefined
106>(dateRange);
107
108useEffect(() => {
109setInternalDateRange(dateRange);
110}, [dateRange]);
111
112const onDropDownSelection = (value: string) => {
113if (isValidOption(value)) {
114const setting = dateTimeAggregationSettings[value];
115const fromDate = addMinutes(new Date(), -1 * setting.minutes);
116
117setDateRangeAndOption(value, {
118from: fromDate,
119to: new Date(),
120});
121setInternalDateRange({ from: fromDate, to: new Date() });
122} else {
123setDateRangeAndOption(DEFAULT_DATE_RANGE_SELECTION, undefined);
124}
125};
126
127const onCalendarSelection = (range?: DateRange) => {
128const newRange = range
129? {
130from: range.from ? setBeginningOfDay(range.from) : undefined,
131to: range.to ? setEndOfDay(range.to) : undefined,
132}
133: undefined;
134
135setInternalDateRange(newRange);
136if (newRange && newRange.from && newRange.to) {
137const dashboardDateRange: DashboardDateRange = {
138from: newRange.from,
139to: newRange.to,
140};
141setDateRangeAndOption(DEFAULT_DATE_RANGE_SELECTION, dashboardDateRange);
142}
143};
144
145const isSmallScreen = useMediaQuery({ query: "(max-width: 640px)" });
146
147return (
148<div
149className={cn("my-3 flex flex-col-reverse gap-2 md:flex-row", className)}
150>
151<Popover>
152<PopoverTrigger asChild>
153<Button
154id="date"
155variant={"outline"}
156className={cn(
157"w-[330px] justify-start text-left font-normal",
158!internalDateRange && "text-muted-foreground",
159)}
160>
161<CalendarIcon className="mr-2 h-4 w-4" />
162{internalDateRange?.from ? (
163internalDateRange.to ? (
164<>
165{format(internalDateRange.from, "LLL dd, yy : HH:mm")} -{" "}
166{format(internalDateRange.to, "LLL dd, yy : HH:mm")}
167</>
168) : (
169format(internalDateRange.from, "LLL dd, y")
170)
171) : (
172<span>Pick a date</span>
173)}
174</Button>
175</PopoverTrigger>
176<PopoverContent className="w-auto p-0" align="start">
177<Calendar
178initialFocus={true}
179mode="range"
180defaultMonth={internalDateRange?.from}
181selected={internalDateRange}
182onSelect={onCalendarSelection}
183numberOfMonths={isSmallScreen ? 1 : 2} // TODO: make this configurable to screen size
184/>
185</PopoverContent>
186</Popover>
187<Select value={selectedOption} onValueChange={onDropDownSelection}>
188<SelectTrigger className="w-[120px] hover:bg-accent hover:text-accent-foreground focus:ring-0 focus:ring-offset-0">
189<SelectValue placeholder="Select" />
190</SelectTrigger>
191<SelectContent position="popper" defaultValue={60}>
192<SelectItem
193key={DEFAULT_DATE_RANGE_SELECTION}
194value={DEFAULT_DATE_RANGE_SELECTION}
195>
196{DEFAULT_DATE_RANGE_SELECTION}
197</SelectItem>
198{dateTimeAggregationOptions.toReversed().map((item) => (
199<SelectItem key={item} value={item}>
200{item}
201</SelectItem>
202))}
203</SelectContent>
204</Select>
205</div>
206);
207}
208