From c1f37e3ebdd6abfc1687effc6cf999dabc4333c7 Mon Sep 17 00:00:00 2001 From: lenhanphung Date: Wed, 5 Nov 2025 13:34:21 +0700 Subject: [PATCH] feat: improve date/time fields UI and logic - Change 'Start Date' label to 'Date' when only single date field is visible in normal mode - Fix all-day slot display: show correct start/end dates for single click and multi-day drag - Update API save logic: add +1 day to end date for all-day events (API requirement) - Migrate from moment to dayjs for date handling with AdapterDayjs - Remove manual date clamping logic - MUI/dayjs handles invalid dates automatically - Add validation guard to prevent invalid dates from clearing field values - Update test case to match new all-day event API behavior --- __test__/features/Events/EventModal.test.tsx | 6 +- src/components/Event/EventFormFields.tsx | 31 +++- .../Event/components/DateTimeFields.tsx | 135 +++++++++++----- src/components/Event/utils/formValidation.ts | 105 ++++++++++--- src/features/Events/EventModal.tsx | 148 ++++++++++++++++-- src/features/Events/EventUpdateModal.tsx | 28 +++- 6 files changed, 363 insertions(+), 90 deletions(-) diff --git a/__test__/features/Events/EventModal.test.tsx b/__test__/features/Events/EventModal.test.tsx index 930c0a0..3f0f042 100644 --- a/__test__/features/Events/EventModal.test.tsx +++ b/__test__/features/Events/EventModal.test.tsx @@ -312,11 +312,15 @@ describe("EventPopover", () => { new Date(receivedPayload.newEvent.start) ).split("T")[0] ).toBe(formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)).split("T")[0]); + + const expectedAllDayEnd = new Date(newEvent.end); + expectedAllDayEnd.setUTCDate(expectedAllDayEnd.getUTCDate() + 1); + expect( formatDateToYYYYMMDDTHHMMSS( new Date(receivedPayload.newEvent.end || new Date()) ).split("T")[0] - ).toBe(formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.end)).split("T")[0]); + ).toBe(formatDateToYYYYMMDDTHHMMSS(expectedAllDayEnd).split("T")[0]); expect(receivedPayload.newEvent.location).toBe(newEvent.location); expect(receivedPayload.newEvent.organizer).toEqual(newEvent.organizer); expect(receivedPayload.newEvent.color).toEqual( diff --git a/src/components/Event/EventFormFields.tsx b/src/components/Event/EventFormFields.tsx index 20f6753..415caa4 100644 --- a/src/components/Event/EventFormFields.tsx +++ b/src/components/Event/EventFormFields.tsx @@ -104,6 +104,7 @@ interface EventFormFieldsProps { // Validation onValidationChange?: (isValid: boolean) => void; showValidationErrors?: boolean; + onHasEndDateChangedChange?: (has: boolean) => void; } export default function EventFormFields({ @@ -152,6 +153,7 @@ export default function EventFormFields({ onCalendarChange, onValidationChange, showValidationErrors = false, + onHasEndDateChangedChange, }: EventFormFieldsProps) { // Internal state for 4 separate fields const [startDate, setStartDate] = React.useState(""); @@ -159,8 +161,20 @@ export default function EventFormFields({ const [endDate, setEndDate] = React.useState(""); const [endTime, setEndTime] = React.useState(""); - // UI state for showing/hiding end date field - // keep local flag if needed for future UI toggles (not used now) + // Track if user has manually changed end date in extended mode + const [hasEndDateChanged, setHasEndDateChanged] = React.useState(false); + + // Reset hasEndDateChanged when modal closes + React.useEffect(() => { + if (!isOpen) { + setHasEndDateChanged(false); + } + }, [isOpen]); + + // Notify parent about end date change visibility state + React.useEffect(() => { + onHasEndDateChangedChange?.(hasEndDateChanged); + }, [hasEndDateChanged, onHasEndDateChangedChange]); // Use all-day toggle hook const { handleAllDayToggle } = useAllDayToggle({ @@ -275,6 +289,10 @@ export default function EventFormFields({ const handleEndDateChange = React.useCallback( (newDate: string) => { setEndDate(newDate); + // Track if user changed end date in extended mode + if (showMore) { + setHasEndDateChanged(true); + } const newEnd = combineDateTime(newDate, endTime); if (onEndChange) { onEndChange(newEnd); @@ -282,7 +300,7 @@ export default function EventFormFields({ setEnd(newEnd); } }, - [endTime, onEndChange, setEnd] + [endTime, onEndChange, setEnd, showMore] ); const handleEndTimeChange = React.useCallback( @@ -308,6 +326,8 @@ export default function EventFormFields({ endTime, allday, showValidationErrors, + hasEndDateChanged, + showMore, }); }, [ title, @@ -317,6 +337,8 @@ export default function EventFormFields({ endTime, allday, showValidationErrors, + hasEndDateChanged, + showMore, ]); // Notify parent about validation changes @@ -457,6 +479,7 @@ export default function EventFormFields({ endTime={endTime} allday={allday} showMore={showMore} + hasEndDateChanged={hasEndDateChanged} validation={validation} onStartDateChange={handleStartDateChange} onStartTimeChange={handleStartTimeChange} @@ -606,7 +629,7 @@ export default function EventFormFields({ )}