diff --git a/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx b/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx index 3b78615fb8..a7e42f3607 100644 --- a/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx +++ b/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx @@ -24,7 +24,8 @@ import { OutOfOfficeTab } from "./OutOfOfficeToggleGroup"; export type BookingRedirectForm = { dateRange: { startDate: Date; endDate: Date }; - offset: number; + startDateOffset: number; + endDateOffset: number; toTeamUserId: number | null; reasonId: number; notes?: string; @@ -149,7 +150,8 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ startDate: dayjs().startOf("d").toDate(), endDate: dayjs().startOf("d").add(2, "d").toDate(), }, - offset: dayjs().utcOffset(), + startDateOffset: dayjs().utcOffset(), + endDateOffset: dayjs().utcOffset(), toTeamUserId: null, reasonId: 1, forUserId: null, @@ -194,7 +196,11 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ if (!data.dateRange.endDate) { showToast(t("end_date_not_selected"), "error"); } else { - createOrEditOutOfOfficeEntry.mutate(data); + createOrEditOutOfOfficeEntry.mutate({ + ...data, + startDateOffset: -1 * data.dateRange.startDate.getTimezoneOffset(), + endDateOffset: -1 * data.dateRange.endDate.getTimezoneOffset(), + }); } })}>
diff --git a/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx b/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx index f49c5d7274..261becf825 100644 --- a/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx +++ b/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx @@ -245,14 +245,16 @@ function OutOfOfficeEntriesListContent() { data-testid={`ooo-edit-${item.toUser?.username || "n-a"}`} StartIcon="pencil" onClick={() => { - const offset = dayjs().utcOffset(); + const startDateOffset = -1 * item.start.getTimezoneOffset(); + const endDateOffset = -1 * item.end.getTimezoneOffset(); const outOfOfficeEntryData: BookingRedirectForm = { uuid: item.uuid, dateRange: { - startDate: dayjs(item.start).subtract(offset, "minute").toDate(), - endDate: dayjs(item.end).subtract(offset, "minute").startOf("d").toDate(), + startDate: dayjs(item.start).subtract(startDateOffset, "minute").toDate(), + endDate: dayjs(item.end).subtract(endDateOffset, "minute").startOf("d").toDate(), }, - offset, + startDateOffset, + endDateOffset, toTeamUserId: item.toUserId, reasonId: item.reason?.id ?? 1, notes: item.notes ?? undefined, diff --git a/packages/trpc/server/routers/viewer/__tests__/outOfOffice.handler.test.ts b/packages/trpc/server/routers/viewer/__tests__/outOfOffice.handler.test.ts index 0f735118bd..597cf54d49 100644 --- a/packages/trpc/server/routers/viewer/__tests__/outOfOffice.handler.test.ts +++ b/packages/trpc/server/routers/viewer/__tests__/outOfOffice.handler.test.ts @@ -56,7 +56,8 @@ describe("outOfOfficeCreateOrUpdate", () => { startDate: new Date("2024-11-23T23:00:00.000Z"), endDate: new Date("2024-11-22T23:00:00.000Z"), }, - offset: 60, + startDateOffset: 60, + endDateOffset: 60, reasonId: 1, notes: "", toTeamUserId: null, @@ -70,16 +71,17 @@ describe("outOfOfficeCreateOrUpdate", () => { it("should handle timezone offset correctly", async () => { const input = { dateRange: { - startDate: new Date("2024-11-23T23:00:00.000Z"), - endDate: new Date("2024-11-23T23:00:00.000Z"), + startDate: new Date("2025-03-28T23:00:00.000Z"), + endDate: new Date("2025-04-01T22:00:00.000Z"), }, - offset: 60, // Paris timezone offset in minutes + startDateOffset: 60, // Paris timezone (CET) + endDateOffset: 120, // Paris timezone (CEST) <- After summer time begins reasonId: 1, notes: "", toTeamUserId: null, }; - const startTimeUtc = "2024-11-24T00:00:00.000Z"; - const endTimeUtc = "2024-11-24T23:59:59.999Z"; + const startTimeUtc = "2025-03-29T00:00:00.000Z"; + const endTimeUtc = "2025-04-02T23:59:59.999Z"; await outOfOfficeCreateOrUpdate({ ctx: { user: mockUser }, diff --git a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts index 5eb44ce20a..28ec7ede35 100644 --- a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts +++ b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.handler.ts @@ -31,8 +31,8 @@ export const outOfOfficeCreateOrUpdate = async ({ ctx, input }: TBookingRedirect throw new TRPCError({ code: "BAD_REQUEST", message: "start_date_and_end_date_required" }); } - const startTimeUtc = dayjs.utc(startDate).add(input.offset, "minute").startOf("day"); - const endTimeUtc = dayjs.utc(endDate).add(input.offset, "minute").endOf("day"); + const startTimeUtc = dayjs.utc(startDate).add(input.startDateOffset, "minute").startOf("day"); + const endTimeUtc = dayjs.utc(endDate).add(input.endDateOffset, "minute").endOf("day"); // If start date is after end date throw error if (startTimeUtc.isAfter(endTimeUtc)) { diff --git a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.schema.ts b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.schema.ts index 4ac0781c12..e2eaa099b8 100644 --- a/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.schema.ts +++ b/packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateOrUpdate.schema.ts @@ -7,7 +7,8 @@ export const ZOutOfOfficeInputSchema = z.object({ startDate: z.date(), endDate: z.date(), }), - offset: z.number(), + startDateOffset: z.number(), + endDateOffset: z.number(), toTeamUserId: z.number().nullable(), reasonId: z.number(), notes: z.string().nullable().optional(), diff --git a/packages/ui/components/form/date-range-picker/Calendar.tsx b/packages/ui/components/form/date-range-picker/Calendar.tsx index fc71b535f7..58e0273b3d 100644 --- a/packages/ui/components/form/date-range-picker/Calendar.tsx +++ b/packages/ui/components/form/date-range-picker/Calendar.tsx @@ -33,11 +33,11 @@ function Calendar({ nav: "flex items-center", head: "", head_row: "flex w-full items-center justify-between", - head_cell: "w-8 md:w-11 h-8 text-sm font-medium text-default", + head_cell: "w-8 md:w-11 h-8 text-sm font-medium text-default text-center", nav_button: cn(buttonClasses({ color: "minimal", variant: "icon" })), table: "w-full border-collapse space-y-1", - row: "flex w-full mt-2 gap-0.5", - cell: "h-8 w-8 md:h-11 md:w-11 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", + row: "flex w-full mt-0.5 gap-0.5", + cell: "w-8 h-8 md:h-11 md:w-11 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", day: cn( buttonClasses({ color: "minimal" }), "w-8 h-8 md:h-11 md:w-11 p-0 text-sm font-medium aria-selected:opacity-100 inline-flex items-center justify-center"