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({
|
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}
|
||||||
|
|||||||
Reference in New Issue
Block a user