feat(events): reliable holiday skipping for recurrences + UI badge; clean logs

Backend: generate EventException on create/update when skip_holidays or recurrence changes; emit RecurrenceException (EXDATE) with exact occurrence start time (UTC)
API: return master events with RecurrenceRule + RecurrenceException
Frontend: map RecurrenceException → recurrenceException; ensure SkipHolidays instances never render on holidays; place TentTree icon (black) next to main event icon via template
Docs: update README and Copilot instructions for recurrence/holiday behavior
Cleanup: remove dataSource and debug console logs
This commit is contained in:
RobbStarkAustria
2025-10-12 12:00:43 +00:00
parent 7ab4ea14c4
commit 773628c324
15 changed files with 698 additions and 122 deletions

View File

@@ -6,6 +6,7 @@ import { DropDownListComponent, MultiSelectComponent } from '@syncfusion/ej2-rea
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
import CustomSelectUploadEventModal from './CustomSelectUploadEventModal';
import { updateEvent } from '../apiEvents';
// Holiday exceptions are now created in the backend
type CustomEventData = {
title: string;
@@ -76,7 +77,10 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
const [repeat, setRepeat] = React.useState(initialData.repeat || false);
const [weekdays, setWeekdays] = React.useState<number[]>(initialData.weekdays || []);
const [repeatUntil, setRepeatUntil] = React.useState(initialData.repeatUntil || null);
const [skipHolidays, setSkipHolidays] = React.useState(initialData.skipHolidays || false);
// Default to true so recurrences skip holidays by default
const [skipHolidays, setSkipHolidays] = React.useState(
initialData.skipHolidays !== undefined ? initialData.skipHolidays : true
);
const [errors, setErrors] = React.useState<{ [key: string]: string }>({});
// --- KORREKTUR: Media, SlideshowInterval, WebsiteUrl aus initialData übernehmen ---
const [media, setMedia] = React.useState<{ id: string; path: string; name: string } | null>(
@@ -104,7 +108,7 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
setRepeat(initialData.repeat || false);
setWeekdays(initialData.weekdays || []);
setRepeatUntil(initialData.repeatUntil || null);
setSkipHolidays(initialData.skipHolidays || false);
setSkipHolidays(initialData.skipHolidays !== undefined ? initialData.skipHolidays : true);
// --- KORREKTUR: Media, SlideshowInterval, WebsiteUrl aus initialData übernehmen ---
setMedia(initialData.media ?? null);
setSlideshowInterval(initialData.slideshowInterval ?? 10);
@@ -190,6 +194,27 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
const group_id = typeof groupName === 'object' && groupName !== null ? groupName.id : groupName;
// Build recurrence rule if repeat is enabled
let recurrenceRule = null;
let recurrenceEnd = null;
if (repeat && weekdays.length > 0) {
// Convert weekdays to RRULE format (0=Monday -> MO)
const rruleDays = weekdays.map(day => {
const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'];
return dayNames[day];
}).join(',');
recurrenceRule = `FREQ=WEEKLY;BYDAY=${rruleDays}`;
if (repeatUntil) {
const untilDate = new Date(repeatUntil);
untilDate.setHours(23, 59, 59);
recurrenceEnd = untilDate.toISOString();
// Note: RRULE UNTIL should be in UTC format for consistency
const untilUTC = untilDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
recurrenceRule += `;UNTIL=${untilUTC}`;
}
}
const payload: CustomEventData & { [key: string]: unknown } = {
group_id,
title,
@@ -225,6 +250,8 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
event_type: type,
is_active: 1,
created_by: 1,
recurrence_rule: recurrenceRule,
recurrence_end: recurrenceEnd,
};
if (type === 'presentation') {
@@ -250,6 +277,7 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
});
res = await res.json();
}
if (res.success) {
onSave(payload);
onClose(); // <--- Box nach erfolgreichem Speichern schließen