Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,60 @@ const Schedule = <T extends {
}) => {
const { t } = useTranslation();
const currentLanguage = getCurrentLanguageInformation();
const getEndDateForSchedulingTime = () => {
const {
scheduleStartDate,
scheduleStartHour,
scheduleStartMinute,
scheduleEndDate,
scheduleEndHour,
scheduleEndMinute,
scheduleDurationHours,
sourceMode,
} = formik.values;

const durationHours = Number(scheduleDurationHours) || 0;

if (durationHours === 0) {
return undefined;
}

const startDateTime = new Date(scheduleStartDate);
startDateTime.setHours(
parseInt(scheduleStartHour, 10),
parseInt(scheduleStartMinute, 10),
0,
0,
);

let endDateTime: Date;

if (sourceMode === "SCHEDULE_MULTIPLE") {
endDateTime = new Date(startDateTime);
endDateTime.setHours(endDateTime.getHours() + durationHours);
} else {
if (!scheduleEndDate) {
return undefined;
}
endDateTime = new Date(scheduleEndDate);
endDateTime.setHours(
parseInt(scheduleEndHour, 10),
parseInt(scheduleEndMinute, 10),
0,
0,
);
}

if (
endDateTime.getDate() !== startDateTime.getDate() ||
endDateTime.getMonth() !== startDateTime.getMonth() ||
endDateTime.getFullYear() !== startDateTime.getFullYear()
) {
return "+1 day";
}
return undefined;
};


const renderInputDeviceOptions = () => {
if (formik.values.location) {
Expand Down Expand Up @@ -606,13 +660,7 @@ const Schedule = <T extends {
);
}
}}
date={
formik.values.sourceMode === "SCHEDULE_SINGLE" &&
(new Date(formik.values.scheduleEndDate).getDate() !==
new Date(formik.values.scheduleStartDate).getDate())
? formik.values.scheduleEndDate
: undefined
}
date={getEndDateForSchedulingTime()}
/>

<SchedulingLocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,20 @@ const SchedulingTime = ({
disabled={disabled}
customCSS={{ width: 70 }}
/>

{/* Displays given date. Can be used to signify which date the
scheduling time belong to*/}
{date &&
{date && (
<span style={{ marginLeft: "10px" }}>
{new Date(date).toLocaleDateString(
currentLanguage ? currentLanguage.dateLocale.code : undefined,
)}
{typeof date === "string"
? date // show the string as it is
: date instanceof Date && !isNaN(date.getDate())
? new Date(date).toLocaleDateString(
currentLanguage ? currentLanguage.dateLocale.code : undefined,
)
: null
}
</span>
}
)}
</td>
</tr>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
"CONFLICT_ALREADY_ENDED": "Scheduling error: The event has already ended.",
"CONFLICT_END_BEFORE_START": "Scheduling error: Schedule end has to be later than the start.",
"CONFLICT_IN_THE_PAST": "The schedule could not be updated: You cannot schedule an event to be in the past.",
"CONFLICT_RANGE_DAYS":"At least one repeat day must be within the scheduled date range.",
"INVALID_ACL_RULES": "Rules have to contain a valid role and read or/and write right(s).",
"MISSING_ACL_RULES": "At least one role with Read and Write permissions is required!",
"SAVED_ACL_RULES": "The access rules have been saved.",
Expand Down
52 changes: 51 additions & 1 deletion src/slices/eventSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,20 @@ export const checkConflicts = (values: {
values.sourceMode === "SCHEDULE_SINGLE" ||
values.sourceMode === "SCHEDULE_MULTIPLE"
) {
if (values.sourceMode === "SCHEDULE_MULTIPLE") {
const isRepeatOnValid = validateRepeatOnInRange(values);
if (!isRepeatOnValid) {
dispatch(
addNotification({
type: "error",
key: "CONFLICT_RANGE_DAYS",
duration: -1,
context: NOTIFICATION_CONTEXT,
}),
);
return false; // Exit early if repeatOn is invalid
}
}
// Get timezone offset; Checks should be performed on UTC times
// let offset = getTimezoneOffset();

Expand All @@ -924,7 +938,7 @@ export const checkConflicts = (values: {
);

// If start date of event is smaller than today --> Event is in past
if (values.sourceMode === "SCHEDULE_SINGLE" && startDate < new Date()) {
if ((values.sourceMode === "SCHEDULE_SINGLE" && startDate < new Date()) || (values.sourceMode === "SCHEDULE_MULTIPLE" && startDate < new Date())) {
dispatch(
addNotification({
type: "error",
Expand Down Expand Up @@ -1107,6 +1121,42 @@ export const checkForSchedulingConflicts = (events: EditedEvents[]) => (dispatch
return data;
};

export const validateRepeatOnInRange = (values: {
repeatOn: string[], // e.g. ["MO", "TU"]
scheduleStartDate: string, // e.g. "2025-08-11"
scheduleEndDate: string // e.g. "2025-08-13"
}) => {
if (!values.repeatOn || values.repeatOn.length === 0) {
return true;
}

const start = new Date(values.scheduleStartDate);
const end = new Date(values.scheduleEndDate);

// Map day codes to numbers: Sunday=0, Monday=1, ..., Saturday=6
const dayMap: Record<string, number> = {
SU: 0,
MO: 1,
TU: 2,
WE: 3,
TH: 4,
FR: 5,
SA: 6,
};
const repeatDays = values.repeatOn.map(day => dayMap[day]);
// Check if **at least one** date in the [start..end] range matches repeatOn days
const current = new Date(start);
while (current <= end) {
if (repeatDays.includes(current.getDay())) {
return true; // Valid because this repeat day is in range
}
current.setDate(current.getDate() + 1); // next day
}

// If no repeat days fall within the range, return false
return false;
};

const eventSlice = createSlice({
name: "events",
initialState,
Expand Down