From def4f93eb4832a3d7139c0e1b7a4dbda75febc58 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Tue, 24 Mar 2026 17:01:35 +0000 Subject: [PATCH] refactor(site): replace react-date-range with shadcn Calendar + DateRangePicker (#23495) --- site/package.json | 3 +- site/pnpm-lock.yaml | 81 +++--- .../AgentsPage/AgentSettingsPageView.tsx | 13 +- .../components/Calendar/Calendar.stories.tsx | 59 +++++ .../components/Calendar/Calendar.tsx | 206 +++++++++++++++ .../DateRangePicker.stories.tsx | 180 +++++++++++++ .../DateRangePicker/DateRangePicker.tsx | 243 ++++++++++++++++++ .../TemplateInsightsPage/DateRange.tsx | 238 ----------------- .../TemplateInsightsPage.tsx | 5 +- .../TemplateInsightsPage/WeekPicker.tsx | 2 +- 10 files changed, 733 insertions(+), 297 deletions(-) create mode 100644 site/src/pages/AgentsPage/components/Calendar/Calendar.stories.tsx create mode 100644 site/src/pages/AgentsPage/components/Calendar/Calendar.tsx create mode 100644 site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.stories.tsx create mode 100644 site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.tsx delete mode 100644 site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx diff --git a/site/package.json b/site/package.json index 41344acd46..f13e7f45bf 100644 --- a/site/package.json +++ b/site/package.json @@ -105,7 +105,7 @@ "react": "19.2.2", "react-color": "2.19.3", "react-confetti": "6.4.0", - "react-date-range": "1.4.0", + "react-day-picker": "9.14.0", "react-dom": "19.2.2", "react-markdown": "9.1.0", "react-query": "npm:@tanstack/react-query@5.77.0", @@ -162,7 +162,6 @@ "@types/novnc__novnc": "1.5.0", "@types/react": "19.2.7", "@types/react-color": "3.0.13", - "@types/react-date-range": "1.4.4", "@types/react-dom": "19.2.3", "@types/react-syntax-highlighter": "15.5.13", "@types/react-virtualized-auto-sizer": "1.0.8", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 1b864d12f2..22fb47ab5c 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -223,9 +223,9 @@ importers: react-confetti: specifier: 6.4.0 version: 6.4.0(react@19.2.2) - react-date-range: - specifier: 1.4.0 - version: 1.4.0(date-fns@2.30.0)(react@19.2.2) + react-day-picker: + specifier: 9.14.0 + version: 9.14.0(react@19.2.2) react-dom: specifier: 19.2.2 version: 19.2.2(react@19.2.2) @@ -389,9 +389,6 @@ importers: '@types/react-color': specifier: 3.0.13 version: 3.0.13(@types/react@19.2.7) - '@types/react-date-range': - specifier: 1.4.4 - version: 1.4.4 '@types/react-dom': specifier: 19.2.3 version: 19.2.3(@types/react@19.2.7) @@ -878,6 +875,9 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, tarball: https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz} engines: {node: '>=18'} + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==, tarball: https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==, tarball: https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz} @@ -2579,6 +2579,10 @@ packages: peerDependencies: '@swc/core': '*' + '@tabby_ai/hijri-converter@1.0.5': + resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==, tarball: https://registry.npmjs.org/@tabby_ai/hijri-converter/-/hijri-converter-1.0.5.tgz} + engines: {node: '>=16.0.0'} + '@tailwindcss/typography@0.5.19': resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==, tarball: https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz} peerDependencies: @@ -2901,9 +2905,6 @@ packages: peerDependencies: '@types/react': '*' - '@types/react-date-range@1.4.4': - resolution: {integrity: sha512-9Y9NyNgaCsEVN/+O4HKuxzPbVjRVBGdOKRxMDcsTRWVG62lpYgnxefNckTXDWup8FvczoqPW0+ESZR6R1yymDg==, tarball: https://registry.npmjs.org/@types/react-date-range/-/react-date-range-1.4.4.tgz} - '@types/react-dom@18.3.7': resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==, tarball: https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz} peerDependencies: @@ -3504,9 +3505,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, tarball: https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz} - classnames@2.3.2: - resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==, tarball: https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, tarball: https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz} engines: {node: '>=8'} @@ -3851,9 +3849,11 @@ packages: resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==, tarball: https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz} engines: {node: '>=20'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==, tarball: https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz} - engines: {node: '>=0.11'} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==, tarball: https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==, tarball: https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz} dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==, tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz} @@ -5898,11 +5898,11 @@ packages: peerDependencies: react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 - react-date-range@1.4.0: - resolution: {integrity: sha512-+9t0HyClbCqw1IhYbpWecjsiaftCeRN5cdhsi9v06YdimwyMR2yYHWcgVn3URwtN/txhqKpEZB6UX1fHpvK76w==, tarball: https://registry.npmjs.org/react-date-range/-/react-date-range-1.4.0.tgz} + react-day-picker@9.14.0: + resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==, tarball: https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.14.0.tgz} + engines: {node: '>=18'} peerDependencies: - date-fns: 2.0.0-alpha.7 || >=2.0.0 - react: ^0.14 || ^15.0.0-rc || >=15.0 + react: '>=16.8.0' react-docgen-typescript@2.4.0: resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==, tarball: https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.4.0.tgz} @@ -5943,11 +5943,6 @@ packages: react-is@19.1.1: resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==, tarball: https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz} - react-list@0.8.17: - resolution: {integrity: sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==, tarball: https://registry.npmjs.org/react-list/-/react-list-0.8.17.tgz} - peerDependencies: - react: 0.14 || 15 - 18 - react-markdown@9.1.0: resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==, tarball: https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz} peerDependencies: @@ -6264,9 +6259,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, tarball: https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz} - shallow-equal@1.2.1: - resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==, tarball: https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, tarball: https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz} engines: {node: '>=8'} @@ -7605,6 +7597,8 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@date-fns/tz@1.4.1': {} + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -9442,6 +9436,8 @@ snapshots: '@swc/counter': 0.1.3 jsonc-parser: 3.2.0 + '@tabby_ai/hijri-converter@1.0.5': {} + '@tailwindcss/typography@0.5.19(tailwindcss@3.4.18(yaml@2.7.0))': dependencies: postcss-selector-parser: 6.0.10 @@ -9825,11 +9821,6 @@ snapshots: '@types/react': 19.2.7 '@types/reactcss': 1.2.13(@types/react@19.2.7) - '@types/react-date-range@1.4.4': - dependencies: - '@types/react': 19.2.7 - date-fns: 2.30.0 - '@types/react-dom@18.3.7(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 @@ -10501,8 +10492,6 @@ snapshots: dependencies: clsx: 2.1.1 - classnames@2.3.2: {} - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -10861,9 +10850,9 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.26.10 + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} dayjs@1.11.19: {} @@ -13527,14 +13516,13 @@ snapshots: react: 19.2.2 tween-functions: 1.2.0 - react-date-range@1.4.0(date-fns@2.30.0)(react@19.2.2): + react-day-picker@9.14.0(react@19.2.2): dependencies: - classnames: 2.3.2 - date-fns: 2.30.0 - prop-types: 15.8.1 + '@date-fns/tz': 1.4.1 + '@tabby_ai/hijri-converter': 1.0.5 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 react: 19.2.2 - react-list: 0.8.17(react@19.2.2) - shallow-equal: 1.2.1 react-docgen-typescript@2.4.0(typescript@5.6.3): dependencies: @@ -13578,11 +13566,6 @@ snapshots: react-is@19.1.1: {} - react-list@0.8.17(react@19.2.2): - dependencies: - prop-types: 15.8.1 - react: 19.2.2 - react-markdown@9.1.0(@types/react@19.2.7)(react@19.2.2): dependencies: '@types/hast': 3.0.4 @@ -13991,8 +13974,6 @@ snapshots: setprototypeof@1.2.0: {} - shallow-equal@1.2.1: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 diff --git a/site/src/pages/AgentsPage/AgentSettingsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPageView.tsx index fe493b6467..24677a51a9 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPageView.tsx @@ -52,12 +52,12 @@ import { useSearchParams } from "react-router"; import TextareaAutosize from "react-textarea-autosize"; import { formatTokenCount } from "utils/analytics"; import { formatCostMicros } from "utils/currency"; -import { - DateRange, - type DateRangeValue, -} from "../TemplatePage/TemplateInsightsPage/DateRange"; import { ChatCostSummaryView } from "./components/ChatCostSummaryView"; import { ChatModelAdminPanel } from "./components/ChatModelAdminPanel/ChatModelAdminPanel"; +import { + DateRangePicker, + type DateRangeValue, +} from "./components/DateRangePicker/DateRangePicker"; import { InsightsContent } from "./components/InsightsContent"; import { LimitsTab } from "./components/LimitsTab"; import { MCPServerAdminPanel } from "./components/MCPServerAdminPanel"; @@ -260,7 +260,10 @@ const UsageContent: FC = ({ now }) => { } badge={} action={ - + } /> ); diff --git a/site/src/pages/AgentsPage/components/Calendar/Calendar.stories.tsx b/site/src/pages/AgentsPage/components/Calendar/Calendar.stories.tsx new file mode 100644 index 0000000000..7609d20b11 --- /dev/null +++ b/site/src/pages/AgentsPage/components/Calendar/Calendar.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import type { DateRange } from "react-day-picker"; +import { Calendar } from "./Calendar"; + +const meta: Meta = { + title: "components/Calendar", + component: Calendar, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Single: Story = { + args: { + mode: "single", + selected: new Date("2025-03-15"), + }, +}; + +export const Range: Story = { + render: function RangeStory() { + const [range, setRange] = useState({ + from: new Date("2025-03-10"), + to: new Date("2025-03-18"), + }); + return ( + r && setRange(r)} + numberOfMonths={2} + /> + ); + }, +}; + +export const TwoMonths: Story = { + args: { + mode: "single", + numberOfMonths: 2, + selected: new Date("2025-03-15"), + }, +}; + +export const DisabledFutureDates: Story = { + args: { + mode: "single", + selected: new Date("2025-03-15"), + disabled: { after: new Date("2025-03-20") }, + }, +}; diff --git a/site/src/pages/AgentsPage/components/Calendar/Calendar.tsx b/site/src/pages/AgentsPage/components/Calendar/Calendar.tsx new file mode 100644 index 0000000000..d7437ad07d --- /dev/null +++ b/site/src/pages/AgentsPage/components/Calendar/Calendar.tsx @@ -0,0 +1,206 @@ +/** + * Adapted from shadcn/ui on 2025-03-24. + * @see {@link https://ui.shadcn.com/docs/components/calendar} + * + * Built on top of React DayPicker v9. Styled with Tailwind using the + * project's existing design tokens so it matches every other primitive + * in the component library. + */ + +import { Button, type ButtonProps } from "components/Button/Button"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import type { ComponentProps } from "react"; +import { + type DayButton, + DayPicker, + getDefaultClassNames, +} from "react-day-picker"; +import { cn } from "utils/cn"; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "subtle", + formatters, + components, + ...props +}: ComponentProps & { + buttonVariant?: ButtonProps["variant"]; +}) { + const defaultClassNames = getDefaultClassNames(); + + return ( + + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "relative flex flex-col gap-4 md:flex-row", + defaultClassNames.months, + ), + month: cn("flex w-full flex-col gap-4", defaultClassNames.month), + nav: cn( + "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", + defaultClassNames.nav, + ), + button_previous: cn( + "h-[--cell-size] w-[--cell-size] select-none p-0", + "inline-flex items-center justify-center rounded-md", + "bg-transparent border-0 cursor-pointer", + "text-content-secondary hover:text-content-primary hover:bg-surface-secondary", + "aria-disabled:opacity-50", + defaultClassNames.button_previous, + ), + button_next: cn( + "h-[--cell-size] w-[--cell-size] select-none p-0", + "inline-flex items-center justify-center rounded-md", + "bg-transparent border-0 cursor-pointer", + "text-content-secondary hover:text-content-primary hover:bg-surface-secondary", + "aria-disabled:opacity-50", + defaultClassNames.button_next, + ), + month_caption: cn( + "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]", + defaultClassNames.month_caption, + ), + dropdowns: cn( + "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium", + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + "has-focus:border-content-link border-border-default relative rounded-md border", + defaultClassNames.dropdown_root, + ), + dropdown: cn( + "bg-surface-primary absolute inset-0 opacity-0", + defaultClassNames.dropdown, + ), + caption_label: cn( + "select-none font-medium text-sm text-content-primary", + defaultClassNames.caption_label, + ), + table: + "w-full border-collapse border-0 [&_td]:border-0 [&_th]:border-0", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-content-secondary flex-1 select-none rounded-md text-[0.8rem] font-normal", + defaultClassNames.weekday, + ), + week: cn("mt-2 flex w-full", defaultClassNames.week), + week_number_header: cn( + "w-[--cell-size] select-none", + defaultClassNames.week_number_header, + ), + week_number: cn( + "text-content-secondary select-none text-[0.8rem]", + defaultClassNames.week_number, + ), + day: cn( + "group/day relative aspect-square h-full w-full select-none p-0 text-center", + "[&:first-child[data-selected=true]_button]:rounded-l-md", + "[&:last-child[data-selected=true]_button]:rounded-r-md", + defaultClassNames.day, + ), + range_start: cn( + "bg-surface-tertiary rounded-l-md", + defaultClassNames.range_start, + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn( + "bg-surface-tertiary rounded-r-md", + defaultClassNames.range_end, + ), + today: cn( + "bg-surface-tertiary text-content-primary rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today, + ), + outside: cn( + "text-content-disabled aria-selected:text-content-disabled", + defaultClassNames.outside, + ), + disabled: cn( + "text-content-disabled opacity-50", + defaultClassNames.disabled, + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Chevron: ({ + className, + orientation, + size: _size, + disabled: _disabled, + ...rest + }) => { + const Icon = + orientation === "left" ? ChevronLeftIcon : ChevronRightIcon; + return ; + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...weekProps }) => ( + +
+ {children} +
+ + ), + ...components, + }} + {...props} + /> + ); +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + return ( + + + e.preventDefault()} + > +
+ {/* Presets sidebar */} +
+ {presets.map((preset) => ( + + ))} +
+ + {/* Calendar + footer */} +
+ {/* Selected range display */} +
+ + {selection?.from + ? dayjs(selection.from).format("MMM D, YYYY") + : "Start date"} + + + + {selection?.to + ? dayjs(selection.to).format("MMM D, YYYY") + : "End date"} + +
+ + {/* Two-month calendar */} +
+ +
+ + {/* Apply footer */} +
+ + +
+
+
+
+ + ); +}; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx deleted file mode 100644 index df6e75866b..0000000000 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import "react-date-range/dist/styles.css"; -import "react-date-range/dist/theme/default.css"; -import type { Interpolation, Theme } from "@emotion/react"; -import { Button } from "components/Button/Button"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/Popover/Popover"; -import dayjs from "dayjs"; -import { MoveRightIcon } from "lucide-react"; -import { type ComponentProps, type FC, useRef, useState } from "react"; -import { createStaticRanges, DateRangePicker } from "react-date-range"; - -// The type definition from @types is wrong -declare module "react-date-range" { - export function createStaticRanges( - ranges: Omit[], - ): StaticRange[]; -} - -export type DateRangeValue = { - startDate: Date; - endDate: Date; -}; - -type RangesState = NonNullable< - ComponentProps["ranges"] ->; - -interface DateRangeProps { - value: DateRangeValue; - onChange: (value: DateRangeValue) => void; -} - -export const DateRange: FC = ({ value, onChange }) => { - const selectionStatusRef = useRef<"idle" | "selecting">("idle"); - const selectionSourceRef = useRef<"preset" | null>(null); - const [ranges, setRanges] = useState([ - { - ...value, - key: "selection", - }, - ]); - const [open, setOpen] = useState(false); - - const applyRangeSelection = (range: RangesState[number]) => { - selectionStatusRef.current = "idle"; - const startDate = range.startDate as Date; - const endDate = range.endDate as Date; - const now = new Date(); - onChange({ - startDate: dayjs(startDate).startOf("day").toDate(), - endDate: dayjs(endDate).isSame(dayjs(), "day") - ? dayjs(now).startOf("hour").add(1, "hour").toDate() - : dayjs(endDate).startOf("day").add(1, "day").toDate(), - }); - setOpen(false); - }; - - return ( - - - - - -
{ - selectionSourceRef.current = - event.target instanceof HTMLElement && - event.target.closest(".rdrStaticRange") - ? "preset" - : null; - }} - > - { - const range = item.selection; - const isPresetSelection = selectionSourceRef.current === "preset"; - selectionSourceRef.current = null; - setRanges([range]); - - // When it is the first calendar selection, we don't want to - // close the popover. Presets already provide a complete range, - // so apply them immediately. - if (selectionStatusRef.current === "idle" && !isPresetSelection) { - selectionStatusRef.current = "selecting"; - return; - } - - applyRangeSelection(range); - }} - moveRangeOnFirstSelection={false} - months={2} - ranges={ranges} - maxDate={new Date()} - direction="horizontal" - staticRanges={createStaticRanges([ - { - label: "Today", - range: () => ({ - startDate: new Date(), - endDate: new Date(), - }), - }, - { - label: "Yesterday", - range: () => ({ - startDate: dayjs().subtract(1, "day").toDate(), - endDate: dayjs().subtract(1, "day").toDate(), - }), - }, - { - label: "Last 7 days", - range: () => ({ - startDate: dayjs().subtract(6, "day").toDate(), - endDate: new Date(), - }), - }, - { - label: "Last 14 days", - range: () => ({ - startDate: dayjs().subtract(13, "day").toDate(), - endDate: new Date(), - }), - }, - { - label: "Last 30 days", - range: () => ({ - startDate: dayjs().subtract(29, "day").toDate(), - endDate: new Date(), - }), - }, - ])} - /> -
-
-
- ); -}; - -const styles = { - wrapper: (theme) => ({ - "& .rdrDefinedRangesWrapper": { - background: theme.palette.background.paper, - borderColor: theme.palette.divider, - }, - - "& .rdrStaticRange": { - background: theme.palette.background.paper, - border: 0, - fontSize: 14, - color: theme.palette.text.secondary, - - "&:is(:hover, :focus) .rdrStaticRangeLabel": { - background: theme.palette.background.paper, - color: theme.palette.text.primary, - }, - - "&.rdrStaticRangeSelected": { - color: `${theme.palette.text.primary} !important`, - }, - }, - - "& .rdrInputRanges": { - display: "none", - }, - - "& .rdrDateDisplayWrapper": { - backgroundColor: theme.palette.background.paper, - }, - - "& .rdrCalendarWrapper": { - backgroundColor: theme.palette.background.paper, - }, - - "& .rdrDateDisplayItem": { - background: "transparent", - borderColor: theme.palette.divider, - - "& input": { - color: theme.palette.text.secondary, - }, - - "&.rdrDateDisplayItemActive": { - borderColor: theme.palette.text.primary, - backgroundColor: theme.palette.background.paper, - - "& input": { - color: theme.palette.text.primary, - }, - }, - }, - - "& .rdrMonthPicker select, & .rdrYearPicker select": { - color: theme.palette.text.primary, - appearance: "auto", - background: "transparent", - }, - - "& .rdrMonthName, & .rdrWeekDay": { - color: theme.palette.text.secondary, - }, - - "& .rdrDayPassive .rdrDayNumber span": { - color: theme.palette.text.disabled, - }, - - "& .rdrDayNumber span": { - color: theme.palette.text.primary, - }, - - "& .rdrDayToday .rdrDayNumber span": { - fontWeight: 900, - - "&:after": { - display: "none", - }, - }, - - "& .rdrInRange, & .rdrEndEdge, & .rdrStartEdge": { - color: theme.palette.primary.main, - }, - - "& .rdrDayDisabled": { - backgroundColor: "transparent", - - "& .rdrDayNumber span": { - color: theme.palette.text.disabled, - }, - }, - }), -} satisfies Record>; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index c32da8e268..f599dfdc4f 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -41,6 +41,10 @@ import { SquareArrowOutUpRightIcon, } from "lucide-react"; import { RequirePermission } from "modules/permissions/RequirePermission"; +import { + DateRangePicker as DailyPicker, + type DateRangeValue, +} from "pages/AgentsPage/components/DateRangePicker/DateRangePicker"; import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { type FC, @@ -62,7 +66,6 @@ import { subtractTime, } from "utils/time"; import { getTemplatePageTitle } from "../utils"; -import { DateRange as DailyPicker, type DateRangeValue } from "./DateRange"; import { type InsightsInterval, IntervalMenu } from "./IntervalMenu"; import { lastWeeks } from "./utils"; import { numberOfWeeksOptions, WeekPicker } from "./WeekPicker"; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx index 80dbd8e9ca..f52d85c76c 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx @@ -8,8 +8,8 @@ import { DropdownMenuTrigger, } from "components/DropdownMenu/DropdownMenu"; import dayjs from "dayjs"; +import type { DateRangeValue } from "pages/AgentsPage/components/DateRangePicker/DateRangePicker"; import type { FC } from "react"; -import type { DateRangeValue } from "./DateRange"; import { lastWeeks } from "./utils"; // There is no point in showing the period > 6 months. We prune stats older than