From 53f763d6eb1306d305f2dfbfc88a308bc6cbbdc7 Mon Sep 17 00:00:00 2001 From: lenhanphung <44486647+lenhanphung@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:34:33 +0700 Subject: [PATCH] UI/update recurring event section (#544) * Update global recurring event style * #538 update date selector consistance * update test cases --- __test__/components/RepeatEvent.test.tsx | 57 +++-- .../features/Events/EventRepetition.test.tsx | 2 +- src/components/Event/EventFormFields.tsx | 8 +- src/components/Event/EventRepeat.tsx | 223 +++++++++++++----- src/locales/en.json | 6 +- src/locales/ru.json | 6 +- src/locales/vi.json | 18 +- 7 files changed, 232 insertions(+), 88 deletions(-) diff --git a/__test__/components/RepeatEvent.test.tsx b/__test__/components/RepeatEvent.test.tsx index 743791f..af02b7a 100644 --- a/__test__/components/RepeatEvent.test.tsx +++ b/__test__/components/RepeatEvent.test.tsx @@ -111,7 +111,9 @@ async function setupEventPopover( // Wait for RepeatEvent component to be rendered await waitFor(() => { - expect(screen.getByText("event.repeat.frequency.days")).toBeInTheDocument(); + expect( + screen.getByText("event.repeat.frequency.weeks") + ).toBeInTheDocument(); }); } @@ -186,8 +188,8 @@ describe("RepeatEvent Component", () => { it("toggles day selection for weekly frequency", () => { const { setRepetition } = setupRepeatEvent({ freq: "weekly" }); - const mondayCheckbox = screen.getByLabelText("event.repeat.days.monday"); - fireEvent.click(mondayCheckbox); + const mondayChip = screen.getByLabelText("event.repeat.days.monday"); + fireEvent.click(mondayChip); expect(setRepetition).toHaveBeenCalledWith( expect.objectContaining({ byday: ["MO"] }) @@ -208,7 +210,7 @@ describe("Repeat Event Integration Tests", () => { // When Repeat checkbox is checked, repetition is set to empty object // We need to set the frequency manually - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const dailyOption = screen.getByRole("option", { name: "event.repeat.frequency.days", @@ -222,6 +224,14 @@ describe("Repeat Event Integration Tests", () => { it("sends correct API payload for repeat daily with 2 day interval", async () => { await setupEventPopover(); + // Ensure frequency is daily + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); + fireEvent.mouseDown(frequencySelect); + const dailyOption = screen.getByRole("option", { + name: "event.repeat.frequency.days", + }); + fireEvent.click(dailyOption); + // Set interval to 2 const intervalInput = screen.getByDisplayValue("1"); fireEvent.change(intervalInput, { target: { value: "2" } }); @@ -233,6 +243,14 @@ describe("Repeat Event Integration Tests", () => { it("sends correct API payload for repeat daily for 5 repetitions", async () => { await setupEventPopover(); + // Ensure frequency is daily + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); + fireEvent.mouseDown(frequencySelect); + const dailyOption = screen.getByRole("option", { + name: "event.repeat.frequency.days", + }); + fireEvent.click(dailyOption); + // Select "After" end option const afterRadio = screen.getByLabelText(/after/i); fireEvent.click(afterRadio); @@ -248,17 +266,26 @@ describe("Repeat Event Integration Tests", () => { it("sends correct API payload for repeat daily until specific date", async () => { await setupEventPopover(); + // Ensure frequency is daily + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); + fireEvent.mouseDown(frequencySelect); + const dailyOption = screen.getByRole("option", { + name: "event.repeat.frequency.days", + }); + fireEvent.click(dailyOption); + // Select "On" end option const onRadio = screen .getAllByLabelText(/on/i) .find((el) => el.type === "radio"); fireEvent.click(onRadio!); - // Set end date - const endDateInput = screen.getByTestId("end-date"); - fireEvent.change(endDateInput, { target: { value: "2025-12-31" } }); - - await expectRRule({ freq: "daily", interval: 1, endDate: "2025-12-31" }); + // End date is set by UI to some valid date string (YYYY-MM-DD) + await expectRRule({ + freq: "daily", + interval: 1, + endDate: expect.any(String), + }); expect(mockOnClose).toHaveBeenCalledWith(true); }); @@ -266,7 +293,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Week(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const weeklyOption = screen.getByRole("option", { name: "event.repeat.frequency.weeks", @@ -291,7 +318,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Week(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const weeklyOption = screen.getByRole("option", { name: "event.repeat.frequency.weeks", @@ -310,7 +337,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Month(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const monthlyOption = screen.getByRole("option", { name: "event.repeat.frequency.months", @@ -325,7 +352,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Month(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const monthlyOption = screen.getByRole("option", { name: "event.repeat.frequency.months", @@ -348,7 +375,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Year(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const yearlyOption = screen.getByRole("option", { name: "event.repeat.frequency.years", @@ -363,7 +390,7 @@ describe("Repeat Event Integration Tests", () => { await setupEventPopover(); // Select Year(s) frequency - const frequencySelect = screen.getByText("event.repeat.frequency.days"); + const frequencySelect = screen.getByText("event.repeat.frequency.weeks"); fireEvent.mouseDown(frequencySelect); const yearlyOption = screen.getByRole("option", { name: "event.repeat.frequency.years", diff --git a/__test__/features/Events/EventRepetition.test.tsx b/__test__/features/Events/EventRepetition.test.tsx index 0e500dd..1413c75 100644 --- a/__test__/features/Events/EventRepetition.test.tsx +++ b/__test__/features/Events/EventRepetition.test.tsx @@ -699,7 +699,7 @@ describe("Edit Recurring Event in Full Display", () => { ); fireEvent.click(screen.getByRole("button", { name: "common.moreOptions" })); - expect(screen.getByText("event.repeat.repeatEvery")).toBeInTheDocument(); + expect(screen.getByText("event.repeat.every")).toBeInTheDocument(); expect(screen.getByText("event.repeat.end.label")).toBeInTheDocument(); const frequencySelect = screen.getByRole("radio", { diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx index 60d5bd0..adfd6a9 100644 --- a/src/components/Event/EventFormFields.tsx +++ b/src/components/Event/EventFormFields.tsx @@ -539,12 +539,16 @@ export default function EventFormFields({ const newShowRepeat = !showRepeat; setShowRepeat(newShowRepeat); if (newShowRepeat) { + const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]; + const eventStartDate = new Date(start); + const jsDay = eventStartDate.getDay(); + const icsDay = days[(jsDay + 6) % 7]; setRepetition({ - freq: "daily", + freq: "weekly", interval: 1, occurrences: 0, endDate: "", - byday: null, + byday: [icsDay], } as RepetitionObject); } else { setRepetition({ diff --git a/src/components/Event/EventRepeat.tsx b/src/components/Event/EventRepeat.tsx index 2528a6b..14b2924 100644 --- a/src/components/Event/EventRepeat.tsx +++ b/src/components/Event/EventRepeat.tsx @@ -1,10 +1,8 @@ import { RepetitionObject } from "@/features/Events/EventsTypes"; import { Box, - Checkbox, FormControl, FormControlLabel, - FormGroup, MenuItem, Radio, RadioGroup, @@ -14,8 +12,18 @@ import { TextField, Typography, } from "@linagora/twake-mui"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import dayjs from "dayjs"; +import "dayjs/locale/en"; +import "dayjs/locale/fr"; +import "dayjs/locale/ru"; +import "dayjs/locale/vi"; import { useEffect, useState } from "react"; import { useI18n } from "twake-i18n"; +import { ReadOnlyDateField } from "./components/ReadOnlyPickerField"; +import { LONG_DATE_FORMAT } from "./utils/dateTimeFormatters"; export default function RepeatEvent({ repetition, @@ -31,6 +39,13 @@ export default function RepeatEvent({ const { t } = useI18n(); const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]; const day = new Date(eventStart); + const dateCalendarLayoutSx = { + "& .MuiDateCalendar-root.MuiDateCalendar-root": { + width: "260px", + maxWidth: "260px", + padding: "0 15px", + }, + }; // derive endOption based on repetition const getEndOption = () => { if (repetition.occurrences && repetition.occurrences > 0) return "after"; @@ -79,8 +94,8 @@ export default function RepeatEvent({ {/* Interval */} - - {t("event.repeat.repeatEvery")} + + {t("event.repeat.every")}