fix: update day picker to handle utc display

This commit is contained in:
Scott Wilson
2025-07-17 20:16:57 -07:00
parent bfda3776ee
commit 277b92ddec
6 changed files with 71 additions and 29 deletions
@@ -1,11 +1,13 @@
import { ChangeEventHandler, useState } from "react"; import { ChangeEventHandler, useState } from "react";
import { DayPicker, DayPickerProps, getDefaultClassNames, UI } from "react-day-picker"; import { DayPicker, DayPickerProps, getDefaultClassNames, TZDate, UI } from "react-day-picker";
import { faCalendar } from "@fortawesome/free-solid-svg-icons"; import { faCalendar } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverContentProps, PopoverProps } from "@radix-ui/react-popover"; import { PopoverContentProps, PopoverProps } from "@radix-ui/react-popover";
import { format, setHours, setMinutes } from "date-fns"; import { format, setHours, setMinutes } from "date-fns";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { formatDateTime, Timezone } from "@app/helpers/datetime";
import { Button } from "../Button"; import { Button } from "../Button";
import { Input } from "../Input"; import { Input } from "../Input";
import { Popover, PopoverContent, PopoverTrigger } from "../Popoverv2"; import { Popover, PopoverContent, PopoverTrigger } from "../Popoverv2";
@@ -19,6 +21,31 @@ export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
popUpContentProps: PopoverContentProps; popUpContentProps: PopoverContentProps;
dateFormat?: "PPP" | "PP" | "P"; // extend as needed dateFormat?: "PPP" | "PP" | "P"; // extend as needed
buttonClassName?: string; buttonClassName?: string;
timezone?: Timezone;
};
const localTimeToUTC = (timeString: string) => {
const today = new Date();
const [hours, minutes] = timeString.split(":").map(Number);
today.setHours(hours, minutes, 0, 0);
const utcHours = today.getUTCHours().toString().padStart(2, "0");
const utcMinutes = today.getUTCMinutes().toString().padStart(2, "0");
return `${utcHours}:${utcMinutes}`;
};
const utcTimeToLocal = (utcTimeString: string) => {
const today = new Date();
const [hours, minutes] = utcTimeString.split(":").map(Number);
today.setUTCHours(hours, minutes, 0, 0);
const localHours = today.getHours().toString().padStart(2, "0");
const localMinutes = today.getMinutes().toString().padStart(2, "0");
return `${localHours}:${localMinutes}`;
}; };
// Doc: https://react-day-picker.js.org/ // Doc: https://react-day-picker.js.org/
@@ -29,12 +56,15 @@ export const DatePicker = ({
popUpContentProps, popUpContentProps,
dateFormat = "PPP", dateFormat = "PPP",
buttonClassName, buttonClassName,
timezone,
...props ...props
}: DatePickerProps) => { }: DatePickerProps) => {
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00"); const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
const displayUtc = timezone === Timezone.UTC;
const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => { const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const time = e.target.value; const time = displayUtc ? utcTimeToLocal(e.target.value) : e.target.value;
if (time) { if (time) {
setTimeValue(time); setTimeValue(time);
if (value) { if (value) {
@@ -64,7 +94,9 @@ export const DatePicker = ({
variant="outline_bg" variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faCalendar} />} leftIcon={<FontAwesomeIcon icon={faCalendar} />}
> >
{value ? format(value, dateFormat) : "Pick a date and time"} {value
? formatDateTime({ timestamp: value, timezone, dateFormat })
: "Pick a date and time"}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
@@ -78,9 +110,10 @@ export const DatePicker = ({
<DayPicker <DayPicker
{...props} {...props}
mode="single" mode="single"
selected={value} selected={value ? new TZDate(value, displayUtc ? "UTC" : undefined) : undefined}
onSelect={handleDaySelect} onSelect={(date) => handleDaySelect(date ? new TZDate(date, undefined) : undefined)}
className="font-inter text-mineshaft-200" className="font-inter text-mineshaft-200"
timeZone={displayUtc ? "UTC" : undefined}
classNames={{ classNames={{
today: "text-primary border-primary", today: "text-primary border-primary",
selected: " text-mineshaft-100 bg-mineshaft-500", selected: " text-mineshaft-100 bg-mineshaft-500",
@@ -94,7 +127,7 @@ export const DatePicker = ({
<div className="mx-4 my-4"> <div className="mx-4 my-4">
<Input <Input
type="time" type="time"
value={timeValue} value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
onChange={handleTimeChange} onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]" className="bg-mineshaft-700 text-white [color-scheme:dark]"
/> />
+24
View File
@@ -0,0 +1,24 @@
import { format } from "date-fns";
export enum Timezone {
Local = "local",
UTC = "UTC"
}
export const formatDateTime = ({
timezone,
timestamp,
dateFormat = "MMM do yyyy, hh:mm a"
}: {
timestamp: string | Date;
timezone?: Timezone;
dateFormat?: string;
}) => {
const date = new Date(timestamp);
if (timezone === Timezone.UTC) {
const utcDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
return `${format(utcDate, dateFormat)} UTC`;
}
return format(date, dateFormat);
};
@@ -18,12 +18,12 @@ import {
Select, Select,
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
import { Timezone } from "@app/helpers/datetime";
import { import {
auditLogDateFilterFormSchema, auditLogDateFilterFormSchema,
AuditLogDateFilterType, AuditLogDateFilterType,
TAuditLogDateFilterFormData, TAuditLogDateFilterFormData
Timezone
} from "./types"; } from "./types";
type Props = { type Props = {
@@ -233,6 +233,7 @@ export const LogsDateFilter = ({ setFilter, filter, timezone, setTimezone }: Pro
<DatePicker <DatePicker
value={field.value || undefined} value={field.value || undefined}
onChange={onChange} onChange={onChange}
timezone={timezone}
dateFormat="P" dateFormat="P"
buttonClassName="w-44 h-8 font-normal" buttonClassName="w-44 h-8 font-normal"
popUpProps={{ popUpProps={{
@@ -266,6 +267,7 @@ export const LogsDateFilter = ({ setFilter, filter, timezone, setTimezone }: Pro
onChange={onChange} onChange={onChange}
dateFormat="P" dateFormat="P"
buttonClassName="w-44 h-8 font-normal" buttonClassName="w-44 h-8 font-normal"
timezone={timezone}
popUpProps={{ popUpProps={{
open: isEndDatePickerOpen, open: isEndDatePickerOpen,
onOpenChange: setIsEndDatePickerOpen onOpenChange: setIsEndDatePickerOpen
@@ -5,6 +5,7 @@ import ms from "ms";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { Timezone } from "@app/helpers/datetime";
import { withPermission } from "@app/hoc"; import { withPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
@@ -15,8 +16,7 @@ import {
AuditLogDateFilterType, AuditLogDateFilterType,
Presets, Presets,
TAuditLogDateFilterFormData, TAuditLogDateFilterFormData,
TAuditLogFilterFormData, TAuditLogFilterFormData
Timezone
} from "./types"; } from "./types";
type Props = { type Props = {
@@ -1,14 +1,12 @@
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { Td, Tr } from "@app/components/v2"; import { Td, Tr } from "@app/components/v2";
import { formatDateTime, Timezone } from "@app/helpers/datetime";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { ActorType } from "@app/hooks/api/auditLogs/enums"; import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { AuditLog } from "@app/hooks/api/auditLogs/types"; import { AuditLog } from "@app/hooks/api/auditLogs/types";
import { Timezone } from "./types";
type Props = { type Props = {
auditLog: AuditLog; auditLog: AuditLog;
rowNumber: number; rowNumber: number;
@@ -30,16 +28,6 @@ const Tag = ({ label, value }: TagProps) => {
); );
}; };
const formatDateTime = (timestamp: string | Date, timezone: Timezone) => {
const date = new Date(timestamp);
if (timezone === Timezone.UTC) {
const utcDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
return `${format(utcDate, "MMM do yyyy, hh:mm a")} UTC`;
}
return format(date, "MMM do yyyy, hh:mm a");
};
export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => { export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => {
const [isOpen, setIsOpen] = useToggle(); const [isOpen, setIsOpen] = useToggle();
@@ -59,7 +47,7 @@ export const LogsTableRow = ({ auditLog, rowNumber, timezone }: Props) => {
<FontAwesomeIcon icon={isOpen ? faCaretDown : faCaretRight} /> <FontAwesomeIcon icon={isOpen ? faCaretDown : faCaretRight} />
{rowNumber} {rowNumber}
</Td> </Td>
<Td className="align-top">{formatDateTime(auditLog.createdAt, timezone)}</Td> <Td className="align-top">{formatDateTime({ timestamp: auditLog.createdAt, timezone })}</Td>
<Td> <Td>
<div className="flex flex-wrap gap-4 text-sm"> <div className="flex flex-wrap gap-4 text-sm">
<Tag label="event" value={auditLog.event.type} /> <Tag label="event" value={auditLog.event.type} />
@@ -8,11 +8,6 @@ export enum AuditLogDateFilterType {
Absolute = "absolute" Absolute = "absolute"
} }
export enum Timezone {
Local = "local",
UTC = "UTC"
}
export const auditLogFilterFormSchema = z.object({ export const auditLogFilterFormSchema = z.object({
eventMetadata: z.object({}).optional(), eventMetadata: z.object({}).optional(),
project: z project: z