372 drag and drop multiple days event to use normal mode instead of extended mode (#407)
- When dragging from allday slot: open normal mode with allday checked, show start date and end date - When dragging from week/month view grid: open normal mode with allday checked, show 4 fields (start date, start time, end date, end time) - Update DateTimeFields logic to support displaying time fields when allday=true and hasEndDateChanged=true for multiple days events Add setHasEndDateChanged(false) to resetAllStateToDefault function to prevent stale state when: - User creates multi-day event from grid (sets hasEndDateChanged = true) - Closes the modal - Opens modal again for a single-day event This ensures hasEndDateChanged is properly reset to false when modal is closed or reset. Fix drag and drop logic: keep allday=false when dragging from week view - Remove setAllDay(true) when dragging multiple days from week view grid - Keep allday=false to show time fields for week view drag scenarios - Add comprehensive test cases for drag and drop scenarios - Test cases cover allday slot and week view drag scenarios - Prevent regressions with test coverage for display logic
This commit is contained in:
@@ -279,4 +279,65 @@ describe("DateTimeFields", () => {
|
|||||||
"dateTimeFields.endTime"
|
"dateTimeFields.endTime"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("DateTimeFields - Drag and Drop Display Logic", () => {
|
||||||
|
// Test 1.1: Display 2 fields when drag from allday slot (multiple days)
|
||||||
|
it("displays only 2 date fields when allday=true and multiple days", async () => {
|
||||||
|
await renderField({
|
||||||
|
allday: true,
|
||||||
|
hasEndDateChanged: false,
|
||||||
|
startDate: "2025-07-18",
|
||||||
|
endDate: "2025-07-20",
|
||||||
|
showEndDate: true,
|
||||||
|
showMore: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("start-time-input")).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("end-time-input")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 1.2: Display 4 fields when drag from week view (multiple days)
|
||||||
|
it("displays 4 fields when allday=false, hasEndDateChanged=true, and multiple days", async () => {
|
||||||
|
await renderField({
|
||||||
|
allday: false,
|
||||||
|
hasEndDateChanged: true,
|
||||||
|
startDate: "2025-07-18",
|
||||||
|
endDate: "2025-07-20",
|
||||||
|
startTime: "09:00",
|
||||||
|
endTime: "10:00",
|
||||||
|
showEndDate: true,
|
||||||
|
showMore: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 1.3: Display single date + time fields
|
||||||
|
it("displays single date field with time fields for single day event", async () => {
|
||||||
|
await renderField({
|
||||||
|
allday: false,
|
||||||
|
hasEndDateChanged: false,
|
||||||
|
startDate: "2025-07-18",
|
||||||
|
endDate: "2025-07-18",
|
||||||
|
startTime: "09:00",
|
||||||
|
endTime: "10:00",
|
||||||
|
showEndDate: false,
|
||||||
|
showMore: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const startDateInput = screen.getByTestId("start-date-input");
|
||||||
|
expect(startDateInput).toHaveAttribute(
|
||||||
|
"aria-label",
|
||||||
|
"dateTimeFields.date"
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("end-date-input")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -344,4 +344,126 @@ describe("EventPopover", () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("EventModal - Drag and Drop Scenarios", () => {
|
||||||
|
// Test 2.1: Drag from allday slot (single day)
|
||||||
|
it("sets allday=true when drag from allday slot (single day)", async () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2025-07-18",
|
||||||
|
endStr: "2025-07-19",
|
||||||
|
start: new Date("2025-07-18"),
|
||||||
|
end: new Date("2025-07-19"),
|
||||||
|
allDay: true,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
renderPopover(selectedRange);
|
||||||
|
|
||||||
|
const allDayCheckbox = screen.getByLabelText("event.form.allDay");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(allDayCheckbox).toBeChecked();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2.2: Drag from allday slot (multiple days)
|
||||||
|
it("sets allday=true and shows 2 date fields when drag from allday slot (multiple days)", async () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2025-07-18",
|
||||||
|
endStr: "2025-07-21",
|
||||||
|
start: new Date("2025-07-18"),
|
||||||
|
end: new Date("2025-07-21"),
|
||||||
|
allDay: true,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
renderPopover(selectedRange);
|
||||||
|
|
||||||
|
const allDayCheckbox = screen.getByLabelText("event.form.allDay");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(allDayCheckbox).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify only date fields are shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("start-time-input")
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("end-time-input")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2.3: Drag from week view (single day)
|
||||||
|
it("keeps allday=false when drag from week view (single day)", async () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2025-07-18T09:00",
|
||||||
|
endStr: "2025-07-18T10:00",
|
||||||
|
start: new Date("2025-07-18T09:00"),
|
||||||
|
end: new Date("2025-07-18T10:00"),
|
||||||
|
allDay: false,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
renderPopover(selectedRange);
|
||||||
|
|
||||||
|
const allDayCheckbox = screen.getByLabelText("event.form.allDay");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(allDayCheckbox).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify time fields are shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2.4: Drag from week view (multiple days) - CRITICAL TEST
|
||||||
|
it("keeps allday=false and shows 4 fields when drag from week view (multiple days)", async () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2025-07-18T09:00",
|
||||||
|
endStr: "2025-07-20T10:00",
|
||||||
|
start: new Date("2025-07-18T09:00"),
|
||||||
|
end: new Date("2025-07-20T10:00"),
|
||||||
|
allDay: false,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
renderPopover(selectedRange);
|
||||||
|
|
||||||
|
const allDayCheckbox = screen.getByLabelText("event.form.allDay");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(allDayCheckbox).not.toBeChecked(); // CRITICAL: should NOT be checked
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify all 4 fields are shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2.5: Drag from week view (multiple days) - fallback path
|
||||||
|
it("handles drag from week view (multiple days) with Date objects only", async () => {
|
||||||
|
const selectedRange = {
|
||||||
|
start: new Date("2025-07-18T09:00"),
|
||||||
|
end: new Date("2025-07-20T10:00"),
|
||||||
|
allDay: false,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
renderPopover(selectedRange);
|
||||||
|
|
||||||
|
const allDayCheckbox = screen.getByLabelText("event.form.allDay");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(allDayCheckbox).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify all 4 fields are shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("start-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("start-time-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-date-input")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("end-time-input")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,10 +104,26 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
|||||||
});
|
});
|
||||||
}, [startDate, endDate, startTime, endTime, getCurrentDuration]);
|
}, [startDate, endDate, startTime, endTime, getCurrentDuration]);
|
||||||
|
|
||||||
|
const spansMultipleDays = React.useMemo(() => {
|
||||||
|
return startDate !== endDate;
|
||||||
|
}, [startDate, endDate]);
|
||||||
|
|
||||||
const isExpanded = showMore;
|
const isExpanded = showMore;
|
||||||
const shouldShowEndDateNormal = allday || showEndDate;
|
const shouldShowEndDateNormal = allday || showEndDate;
|
||||||
const showSingleDateField = !isExpanded && !shouldShowEndDateNormal;
|
// Show full 4 fields when:
|
||||||
const showFourFieldsNormal = !isExpanded && showEndDate && !allday;
|
// 1. Non-allday with hasEndDateChanged
|
||||||
|
// 2. Multiple days with hasEndDateChanged (supports drag from week/month view grid with allday checked)
|
||||||
|
// 3. Multiple days without allday (original behavior)
|
||||||
|
const shouldShowFullFieldsInNormal =
|
||||||
|
(!allday && hasEndDateChanged) ||
|
||||||
|
(hasEndDateChanged && spansMultipleDays) ||
|
||||||
|
(spansMultipleDays && !allday);
|
||||||
|
const showSingleDateField =
|
||||||
|
!isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal;
|
||||||
|
|
||||||
|
// When shouldShowFullFieldsInNormal is true, show time fields even if allday is true
|
||||||
|
// This supports the case: drag from week/month view grid with multiple days
|
||||||
|
const shouldShowTimeFields = !allday || shouldShowFullFieldsInNormal;
|
||||||
|
|
||||||
const startDateLabel = showSingleDateField
|
const startDateLabel = showSingleDateField
|
||||||
? t("dateTimeFields.date")
|
? t("dateTimeFields.date")
|
||||||
@@ -328,7 +344,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
|||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
sx={{ maxWidth: showMore ? "calc(100% - 145px)" : "100%" }}
|
sx={{ maxWidth: showMore ? "calc(100% - 145px)" : "100%" }}
|
||||||
>
|
>
|
||||||
{isExpanded || showFourFieldsNormal ? (
|
{isExpanded || shouldShowFullFieldsInNormal ? (
|
||||||
<>
|
<>
|
||||||
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
||||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||||
@@ -351,7 +367,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{!allday && (
|
{shouldShowTimeFields && (
|
||||||
<Box sx={{ width: "110px" }}>
|
<Box sx={{ width: "110px" }}>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
ampm={false}
|
ampm={false}
|
||||||
@@ -397,7 +413,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{!allday && (
|
{shouldShowTimeFields && (
|
||||||
<Box sx={{ width: "110px" }}>
|
<Box sx={{ width: "110px" }}>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
ampm={false}
|
ampm={false}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ function EventPopover({
|
|||||||
setTimezone(resolvedCalendarTimezone);
|
setTimezone(resolvedCalendarTimezone);
|
||||||
setHasVideoConference(false);
|
setHasVideoConference(false);
|
||||||
setMeetingLink(null);
|
setMeetingLink(null);
|
||||||
|
setHasEndDateChanged(false);
|
||||||
}, [resolvedCalendarTimezone, defaultCalendarId]);
|
}, [resolvedCalendarTimezone, defaultCalendarId]);
|
||||||
|
|
||||||
// Track if we should sync from selectedRange (only on initial selection, not on toggle)
|
// Track if we should sync from selectedRange (only on initial selection, not on toggle)
|
||||||
@@ -286,11 +287,22 @@ function EventPopover({
|
|||||||
setStart(startValue);
|
setStart(startValue);
|
||||||
setEnd(endValue);
|
setEnd(endValue);
|
||||||
|
|
||||||
// If start date != end date, open extended mode
|
// Check if multiple days event
|
||||||
const startDateOnly = startValue.slice(0, 10);
|
const startDateOnly = startValue.slice(0, 10);
|
||||||
const endDateOnly = endValue.slice(0, 10);
|
const endDateOnly = endValue.slice(0, 10);
|
||||||
if (startDateOnly !== endDateOnly) {
|
const isMultipleDays = startDateOnly !== endDateOnly;
|
||||||
setShowMore(true);
|
|
||||||
|
if (isMultipleDays) {
|
||||||
|
// Keep normal mode (showMore = false) for multiple days events
|
||||||
|
setShowMore(false);
|
||||||
|
|
||||||
|
if (selectedRange.allDay) {
|
||||||
|
// Dragged from allday slot: allday already set to true at line 234
|
||||||
|
// Will show start date and end date (handled by showEndDate logic)
|
||||||
|
} else {
|
||||||
|
// Dragged from week/month view grid: keep allday=false to show time fields, trigger 4 fields display
|
||||||
|
setHasEndDateChanged(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: format Date objects using local time components
|
// Fallback: format Date objects using local time components
|
||||||
@@ -337,8 +349,19 @@ function EventPopover({
|
|||||||
if (formattedStart && formattedEnd) {
|
if (formattedStart && formattedEnd) {
|
||||||
const startDateOnly = formattedStart.slice(0, 10);
|
const startDateOnly = formattedStart.slice(0, 10);
|
||||||
const endDateOnly = formattedEnd.slice(0, 10);
|
const endDateOnly = formattedEnd.slice(0, 10);
|
||||||
if (startDateOnly !== endDateOnly) {
|
const isMultipleDays = startDateOnly !== endDateOnly;
|
||||||
setShowMore(true);
|
|
||||||
|
if (isMultipleDays) {
|
||||||
|
// Keep normal mode (showMore = false) for multiple days events
|
||||||
|
setShowMore(false);
|
||||||
|
|
||||||
|
if (selectedRange.allDay) {
|
||||||
|
// Dragged from allday slot: allday already set to true at line 234
|
||||||
|
// Will show start date and end date (handled by showEndDate logic)
|
||||||
|
} else {
|
||||||
|
// Dragged from week/month view grid: keep allday=false to show time fields, trigger 4 fields display
|
||||||
|
setHasEndDateChanged(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,6 +516,7 @@ function EventPopover({
|
|||||||
setBusy("OPAQUE");
|
setBusy("OPAQUE");
|
||||||
setHasVideoConference(false);
|
setHasVideoConference(false);
|
||||||
setMeetingLink(null);
|
setMeetingLink(null);
|
||||||
|
setHasEndDateChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCreatingNew) {
|
if (!isCreatingNew) {
|
||||||
|
|||||||
Reference in New Issue
Block a user