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:
committed by
Benoit TELLIER
parent
a541111f88
commit
1347583e9a
@@ -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({
|
||||
startDate: "2025-01-01",
|
||||
startTime: "10:00",
|
||||
@@ -114,9 +114,9 @@ describe("DateTimeFields", () => {
|
||||
expect(mockHandlers.onEndTimeChange).toHaveBeenCalledWith("08:00")
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockHandlers.onStartTimeChange).toHaveBeenCalledWith("07:00")
|
||||
);
|
||||
// Start time should NOT be automatically adjusted when end time changes
|
||||
expect(mockHandlers.onStartTimeChange).not.toHaveBeenCalled();
|
||||
expect(mockHandlers.onStartDateChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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 endTimeInput = screen.getByTestId("end-time-input");
|
||||
|
||||
expect(startDateInput).toHaveAttribute("aria-label", "dateTimeFields.startDate");
|
||||
expect(startTimeInput).toHaveAttribute("aria-label", "dateTimeFields.startTime");
|
||||
expect(endDateInput).toHaveAttribute("aria-label", "dateTimeFields.endDate");
|
||||
expect(endTimeInput).toHaveAttribute("aria-label", "dateTimeFields.endTime");
|
||||
expect(startDateInput).toHaveAttribute(
|
||||
"aria-label",
|
||||
"dateTimeFields.startDate"
|
||||
);
|
||||
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 shouldShowEndDateNormal = allday || showEndDate;
|
||||
const shouldShowFullFieldsInNormal =
|
||||
(!allday && hasEndDateChanged) || spansMultipleDays;
|
||||
const shouldShowFullFieldsInNormal = isExpanded;
|
||||
const showSingleDateField =
|
||||
!isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal;
|
||||
|
||||
@@ -205,22 +204,8 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
const newEnd = toDateTime(endDate, newTimeStr);
|
||||
const currentStart = toDateTime(startDate, startTime);
|
||||
|
||||
// If end is before start, adjust start to maintain duration
|
||||
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
|
||||
// Update duration when user changes end (if valid)
|
||||
if (!newEnd.isBefore(currentStart)) {
|
||||
initialDurationRef.current = newEnd.diff(currentStart, "minute");
|
||||
}
|
||||
|
||||
@@ -245,7 +230,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
[endTime]
|
||||
);
|
||||
|
||||
const getSlotProps = (testId: string, hasError = false, testLabel?: string) => ({
|
||||
const getSlotProps = (
|
||||
testId: string,
|
||||
hasError = false,
|
||||
testLabel?: string
|
||||
) => ({
|
||||
textField: {
|
||||
size: "small" as const,
|
||||
margin: "dense" as const,
|
||||
@@ -253,21 +242,25 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
InputLabelProps: { shrink: true },
|
||||
error: hasError,
|
||||
sx: { width: "100%" },
|
||||
inputProps: {
|
||||
inputProps: {
|
||||
"data-testid": testId,
|
||||
...(testLabel ? { "aria-label": testLabel } : {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const getFieldSlotProps = (testId: string, hasError = false, testLabel?: string) => ({
|
||||
const getFieldSlotProps = (
|
||||
testId: string,
|
||||
hasError = false,
|
||||
testLabel?: string
|
||||
) => ({
|
||||
size: "small" as const,
|
||||
margin: "dense" as const,
|
||||
fullWidth: true,
|
||||
InputLabelProps: { shrink: true },
|
||||
error: hasError,
|
||||
sx: { width: "100%" },
|
||||
inputProps: {
|
||||
inputProps: {
|
||||
"data-testid": testId,
|
||||
...(testLabel ? { "aria-label": testLabel } : {}),
|
||||
},
|
||||
@@ -316,7 +309,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
flexDirection="column"
|
||||
sx={{ maxWidth: showMore ? "calc(100% - 145px)" : "100%" }}
|
||||
>
|
||||
{isExpanded || shouldShowFullFieldsInNormal ? (
|
||||
{shouldShowFullFieldsInNormal ? (
|
||||
<>
|
||||
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
|
||||
<Box sx={{ maxWidth: "300px", width: "48%" }}>
|
||||
@@ -326,8 +319,16 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
onChange={handleStartDateChange}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-date-input", false, t("dateTimeFields.startDate")),
|
||||
field: getFieldSlotProps("start-date-input", false, t("dateTimeFields.startDate")) as any,
|
||||
...getSlotProps(
|
||||
"start-date-input",
|
||||
false,
|
||||
t("dateTimeFields.startDate")
|
||||
),
|
||||
field: getFieldSlotProps(
|
||||
"start-date-input",
|
||||
false,
|
||||
t("dateTimeFields.startDate")
|
||||
) as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -343,7 +344,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
timeSteps={{ minutes: 30 }}
|
||||
slots={{ actionBar: () => null }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")),
|
||||
...getSlotProps(
|
||||
"start-time-input",
|
||||
false,
|
||||
t("dateTimeFields.startTime")
|
||||
),
|
||||
openPickerButton: { sx: { display: "none" } },
|
||||
popper: {
|
||||
sx: {
|
||||
@@ -355,7 +360,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")).textField,
|
||||
...getSlotProps(
|
||||
"start-time-input",
|
||||
false,
|
||||
t("dateTimeFields.startTime")
|
||||
).textField,
|
||||
onClick: (e) =>
|
||||
handleTimeInputClick(e, setIsStartTimeOpen),
|
||||
sx: {
|
||||
@@ -446,8 +455,16 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
onChange={handleStartDateChange}
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-date-input", false, t("dateTimeFields.startDate")),
|
||||
field: getFieldSlotProps("start-date-input", false, t("dateTimeFields.startDate")) as any,
|
||||
...getSlotProps(
|
||||
"start-date-input",
|
||||
false,
|
||||
t("dateTimeFields.startDate")
|
||||
),
|
||||
field: getFieldSlotProps(
|
||||
"start-date-input",
|
||||
false,
|
||||
t("dateTimeFields.startDate")
|
||||
) as any,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@@ -482,7 +499,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
slots={{ field: ReadOnlyDateField }}
|
||||
slotProps={{
|
||||
...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>
|
||||
@@ -498,7 +519,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
timeSteps={{ minutes: 30 }}
|
||||
slots={{ actionBar: () => null }}
|
||||
slotProps={{
|
||||
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")),
|
||||
...getSlotProps(
|
||||
"start-time-input",
|
||||
false,
|
||||
t("dateTimeFields.startTime")
|
||||
),
|
||||
openPickerButton: { sx: { display: "none" } },
|
||||
popper: {
|
||||
sx: {
|
||||
@@ -510,7 +535,11 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
...getSlotProps("start-time-input", false, t("dateTimeFields.startTime")).textField,
|
||||
...getSlotProps(
|
||||
"start-time-input",
|
||||
false,
|
||||
t("dateTimeFields.startTime")
|
||||
).textField,
|
||||
onClick: (e) => handleTimeInputClick(e, setIsStartTimeOpen),
|
||||
sx: {
|
||||
"& .MuiPickersSectionList-section": {
|
||||
@@ -521,6 +550,17 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!allday && (
|
||||
<Typography
|
||||
sx={{
|
||||
alignSelf: "center",
|
||||
mx: 0.5,
|
||||
mt: 0.5,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ maxWidth: "110px" }}>
|
||||
<TimePicker
|
||||
ampm={false}
|
||||
@@ -539,7 +579,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
|
||||
fullWidth: true,
|
||||
InputLabelProps: { shrink: true },
|
||||
error: !!validation.errors.dateTime,
|
||||
inputProps: {
|
||||
inputProps: {
|
||||
"data-testid": "end-time-input",
|
||||
"aria-label": t("dateTimeFields.endTime"),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user