Update DateTimeFields: improve UI layout and adjust time change logic

- Add separator '-' between start time and end time in normal mode
- Fix layout logic: keep single row for allday events in normal mode
- Remove auto-adjustment of start time when end time changes
- Update test case to match new behavior
This commit is contained in:
Lê Nhân Phụng
2025-12-08 10:46:11 +07:00
committed by Benoit TELLIER
parent a541111f88
commit 1347583e9a
2 changed files with 93 additions and 41 deletions
+20 -8
View File
@@ -97,7 +97,7 @@ describe("DateTimeFields", () => {
); );
}); });
it("moves START backward when END moves before START (normal mode)", async () => { it("does NOT move START backward when END moves before START (normal mode)", async () => {
await renderField({ await renderField({
startDate: "2025-01-01", startDate: "2025-01-01",
startTime: "10:00", startTime: "10:00",
@@ -114,9 +114,9 @@ describe("DateTimeFields", () => {
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("08:00") expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("08:00")
); );
await waitFor(() => // Start time should NOT be automatically adjusted when end time changes
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith("07:00") expect(mockHandlers.onStartTimeChange).not.toHaveBeenCalled();
); expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled();
}); });
it("moves START backward properly when END date jumps before START date", async () => { it("moves START backward properly when END date jumps before START date", async () => {
@@ -256,9 +256,21 @@ describe("DateTimeFields", () => {
const endDateInput = screen.getByTestId("end-date-input"); const endDateInput = screen.getByTestId("end-date-input");
const endTimeInput = screen.getByTestId("end-time-input"); const endTimeInput = screen.getByTestId("end-time-input");
expect(startDateInput).toHaveAttribute("aria-label", "dateTimeFields.startDate"); expect(startDateInput).toHaveAttribute(
expect(startTimeInput).toHaveAttribute("aria-label", "dateTimeFields.startTime"); "aria-label",
expect(endDateInput).toHaveAttribute("aria-label", "dateTimeFields.endDate"); "dateTimeFields.startDate"
expect(endTimeInput).toHaveAttribute("aria-label", "dateTimeFields.endTime"); );
expect(startTimeInput).toHaveAttribute(
"aria-label",
"dateTimeFields.startTime"
);
expect(endDateInput).toHaveAttribute(
"aria-label",
"dateTimeFields.endDate"
);
expect(endTimeInput).toHaveAttribute(
"aria-label",
"dateTimeFields.endTime"
);
}); });
}); });
@@ -94,8 +94,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
const isExpanded = showMore; const isExpanded = showMore;
const shouldShowEndDateNormal = allday || showEndDate; const shouldShowEndDateNormal = allday || showEndDate;
const shouldShowFullFieldsInNormal = const shouldShowFullFieldsInNormal = isExpanded;
(!allday && hasEndDateChanged) || spansMultipleDays;
const showSingleDateField = const showSingleDateField =
!isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal; !isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal;
@@ -205,22 +204,8 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
const newEnd = toDateTime(endDate, newTimeStr); const newEnd = toDateTime(endDate, newTimeStr);
const currentStart = toDateTime(startDate, startTime); const currentStart = toDateTime(startDate, startTime);
// If end is before start, adjust start to maintain duration // Update duration when user changes end (if valid)
if (newEnd.isBefore(currentStart)) { if (!newEnd.isBefore(currentStart)) {
const duration = initialDurationRef.current ?? getCurrentDuration();
const newStart = newEnd.subtract(duration, "minute");
const newStartDate = dtDate(newStart);
const newStartTime = dtTime(newStart);
if (newStartDate !== startDate) {
onStartDateChange(newStartDate);
}
if (newStartTime !== startTime) {
onStartTimeChange(newStartTime);
}
} else {
// Update duration when user changes end
initialDurationRef.current = newEnd.diff(currentStart, "minute"); initialDurationRef.current = newEnd.diff(currentStart, "minute");
} }
@@ -245,7 +230,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
[endTime] [endTime]
); );
const getSlotProps = (testId: string, hasError = false, testLabel?: string) => ({ const getSlotProps = (
testId: string,
hasError = false,
testLabel?: string
) => ({
textField: { textField: {
size: "small" as const, size: "small" as const,
margin: "dense" as const, margin: "dense" as const,
@@ -260,7 +249,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}, },
}); });
const getFieldSlotProps = (testId: string, hasError = false, testLabel?: string) => ({ const getFieldSlotProps = (
testId: string,
hasError = false,
testLabel?: string
) => ({
size: "small" as const, size: "small" as const,
margin: "dense" as const, margin: "dense" as const,
fullWidth: true, fullWidth: true,
@@ -316,7 +309,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 || shouldShowFullFieldsInNormal ? ( {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%" }}>
@@ -326,8 +319,16 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
onChange={handleStartDateChange} onChange={handleStartDateChange}
slots={{ field: ReadOnlyDateField }} slots={{ field: ReadOnlyDateField }}
slotProps={{ slotProps={{
...getSlotProps("start-date-input", false, t("dateTimeFields.startDate")), ...getSlotProps(
field: getFieldSlotProps("start-date-input", false, t("dateTimeFields.startDate")) as any, "start-date-input",
false,
t("dateTimeFields.startDate")
),
field: getFieldSlotProps(
"start-date-input",
false,
t("dateTimeFields.startDate")
) as any,
}} }}
/> />
</Box> </Box>
@@ -343,7 +344,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
timeSteps={{ minutes: 30 }} timeSteps={{ minutes: 30 }}
slots={{ actionBar: () => null }} slots={{ actionBar: () => null }}
slotProps={{ slotProps={{
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")), ...getSlotProps(
"start-time-input",
false,
t("dateTimeFields.startTime")
),
openPickerButton: { sx: { display: "none" } }, openPickerButton: { sx: { display: "none" } },
popper: { popper: {
sx: { sx: {
@@ -355,7 +360,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}, },
}, },
textField: { textField: {
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")).textField, ...getSlotProps(
"start-time-input",
false,
t("dateTimeFields.startTime")
).textField,
onClick: (e) => onClick: (e) =>
handleTimeInputClick(e, setIsStartTimeOpen), handleTimeInputClick(e, setIsStartTimeOpen),
sx: { sx: {
@@ -446,8 +455,16 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
onChange={handleStartDateChange} onChange={handleStartDateChange}
slots={{ field: ReadOnlyDateField }} slots={{ field: ReadOnlyDateField }}
slotProps={{ slotProps={{
...getSlotProps("start-date-input", false, t("dateTimeFields.startDate")), ...getSlotProps(
field: getFieldSlotProps("start-date-input", false, t("dateTimeFields.startDate")) as any, "start-date-input",
false,
t("dateTimeFields.startDate")
),
field: getFieldSlotProps(
"start-date-input",
false,
t("dateTimeFields.startDate")
) as any,
}} }}
/> />
</Box> </Box>
@@ -482,7 +499,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
slots={{ field: ReadOnlyDateField }} slots={{ field: ReadOnlyDateField }}
slotProps={{ slotProps={{
...getSlotProps("start-date-input", false, startDateLabel), ...getSlotProps("start-date-input", false, startDateLabel),
field: getFieldSlotProps("start-date-input", false, startDateLabel) as any, field: getFieldSlotProps(
"start-date-input",
false,
startDateLabel
) as any,
}} }}
/> />
</Box> </Box>
@@ -498,7 +519,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
timeSteps={{ minutes: 30 }} timeSteps={{ minutes: 30 }}
slots={{ actionBar: () => null }} slots={{ actionBar: () => null }}
slotProps={{ slotProps={{
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")), ...getSlotProps(
"start-time-input",
false,
t("dateTimeFields.startTime")
),
openPickerButton: { sx: { display: "none" } }, openPickerButton: { sx: { display: "none" } },
popper: { popper: {
sx: { sx: {
@@ -510,7 +535,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}, },
}, },
textField: { textField: {
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")).textField, ...getSlotProps(
"start-time-input",
false,
t("dateTimeFields.startTime")
).textField,
onClick: (e) => handleTimeInputClick(e, setIsStartTimeOpen), onClick: (e) => handleTimeInputClick(e, setIsStartTimeOpen),
sx: { sx: {
"& .MuiPickersSectionList-section": { "& .MuiPickersSectionList-section": {
@@ -521,6 +550,17 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
}} }}
/> />
</Box> </Box>
{!allday && (
<Typography
sx={{
alignSelf: "center",
mx: 0.5,
mt: 0.5,
}}
>
-
</Typography>
)}
<Box sx={{ maxWidth: "110px" }}> <Box sx={{ maxWidth: "110px" }}>
<TimePicker <TimePicker
ampm={false} ampm={false}