From a465133c9a962930084f3a2a69aca35edb6afca7 Mon Sep 17 00:00:00 2001 From: Daniel Spitzer Date: Sun, 2 Mar 2025 16:44:44 +0100 Subject: [PATCH] Kalender Update * Feature Serientermine Integration --- Layout/default/Calendar/View.php | 95 ++++++++++++++++++- application/Calendar/CalendarController.php | 85 ++++++++++++++++- application/Calendar/CalendarModel.php | 100 ++++++++++++++++++-- public/js/pages/Calendar/View.js | 99 +++++++++++++++++-- 4 files changed, 361 insertions(+), 18 deletions(-) diff --git a/Layout/default/Calendar/View.php b/Layout/default/Calendar/View.php index 7a855e669..3bc3eef7d 100644 --- a/Layout/default/Calendar/View.php +++ b/Layout/default/Calendar/View.php @@ -37,6 +37,7 @@ endforeach; + +
@@ -427,7 +429,7 @@ endforeach; aria-label="Uhrzeit" aria-describedby="Uhrzeit">
-
+
- +
+
+ + +
+
@@ -456,7 +465,7 @@ endforeach; aria-label="Uhrzeit" aria-describedby="Uhrzeit">
-
+
@@ -474,6 +483,85 @@ endforeach;
+ +
@@ -728,7 +816,6 @@ endforeach; endforeach; ?> - diff --git a/application/Calendar/CalendarController.php b/application/Calendar/CalendarController.php index cade4b5d5..8ec0c586d 100644 --- a/application/Calendar/CalendarController.php +++ b/application/Calendar/CalendarController.php @@ -109,7 +109,10 @@ class CalendarController extends mfBaseController die(); case "insertCalendarEvent": $r = $this->request; - $calendarEvents = CalendarModel::insertCalendarEvent($r, $this->me); + if ($r->rruleData) { + $rrules=json_encode($this->generateGraphRecurrence($r->rruleData, $r->start)); + } + $calendarEvents = CalendarModel::insertCalendarEvent($r, $this->me,$rrules); if ($r->customer_info_check) { if ($r->customer_info_type == 1) { $body = $r->customer_info_text; @@ -389,6 +392,86 @@ class CalendarController extends mfBaseController die(); } + private function generateGraphRecurrence($data, $startDate) { + $graph = [ + 'pattern' => [], + 'range' => [] + ]; + $freq = strtoupper($data['rrule_frequency']); + switch ($freq) { + case 'DAILY': + $graph['pattern']['type'] = 'daily'; + break; + case 'WEEKLY': + $graph['pattern']['type'] = 'weekly'; + break; + case 'MONTHLY': + if (!empty($data['monthly_type']) && $data['monthly_type'] === 'BYMONTHDAY') { + $graph['pattern']['type'] = 'absoluteMonthly'; + $graph['pattern']['dayOfMonth'] = intval($data['rrule_bymonthday']); + } elseif (!empty($data['monthly_type']) && $data['monthly_type'] === 'BYSETPOS') { + $graph['pattern']['type'] = 'relativeMonthly'; + // Mapping: 1 => first, 2 => second, 3 => third, 4 => fourth, -1 => last + $setpos = intval($data['rrule_setpos']); + $indexMapping = [ + 1 => 'first', + 2 => 'second', + 3 => 'third', + 4 => 'fourth', + -1 => 'last' + ]; + $graph['pattern']['index'] = isset($indexMapping[$setpos]) ? $indexMapping[$setpos] : 'first'; + if (!empty($data['rrule_bynweekday'])) { + $graph['pattern']['daysOfWeek'] = [$this->convertDayToGraph(strtoupper($data['rrule_bynweekday']))]; + } + } + break; + case 'YEARLY': + $graph['pattern']['type'] = 'absoluteYearly'; + break; + default: + $graph['pattern']['type'] = 'daily'; + } + + $graph['pattern']['interval'] = 1; + + if ($freq === 'WEEKLY' && !empty($data['rrule-byweekday'])) { + $days = is_array($data['rrule-byweekday']) ? $data['rrule-byweekday'] : [$data['rrule-byweekday']]; + $graphDays = []; + foreach($days as $day) { + $graphDays[] = $this->convertDayToGraph(strtoupper($day)); + } + $graph['pattern']['daysOfWeek'] = $graphDays; + } + + + $graph['range']['startDate'] = $startDate; + if (!empty($data['rrule_until'])) { + $graph['range']['endDate'] = $data['rrule_until']; + $graph['range']['type'] = 'endDate'; + } elseif (!empty($data['rrule_count'])) { + $graph['range']['numberOfOccurrences'] = intval($data['rrule_count']); + $graph['range']['type'] = 'numbered'; + } else { + $graph['range']['type'] = 'noEnd'; + } + return $graph; + } + + + private function convertDayToGraph($dayAbbrev) { + $mapping = [ + 'MO' => 'monday', + 'TU' => 'tuesday', + 'WE' => 'wednesday', + 'TH' => 'thursday', + 'FR' => 'friday', + 'SA' => 'saturday', + 'SU' => 'sunday' + ]; + return isset($mapping[$dayAbbrev]) ? $mapping[$dayAbbrev] : $dayAbbrev; + } + protected function indexAction() { $this->layout()->setTemplate("Calendar/Index"); diff --git a/application/Calendar/CalendarModel.php b/application/Calendar/CalendarModel.php index 32bf0e89a..3e247584e 100644 --- a/application/Calendar/CalendarModel.php +++ b/application/Calendar/CalendarModel.php @@ -445,13 +445,15 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda } $Allcalendar['Daniel Whoknows'] = 2; $Allcalendar['Stefan Plaschg'] = 26; - $res = $dbcal->select("cal_events", "id, uuid, calendar_id, user_id, start_time, end_time, timezone, all_day_event, name, description, location, repeat_end_time, reminder, ctime, mtime, muser_id, busy, status, resource_event_id, private, rrule, background, files_folder_id, read_only, category_id, exception_for_event_id, recurrence_id, is_organizer,event_type,customer,customer_info,customer_info_send,customer_info_reminder,busy,attendees,organizer,is_organizer,accepted", "1=1 AND id='" . $id . "' ORDER BY id"); + $res = $dbcal->select("cal_events", "id, uuid, calendar_id, user_id, start_time, end_time, timezone, all_day_event, name, description, location, repeat_end_time, reminder, ctime, mtime, muser_id, busy, status, resource_event_id, private, rrule, background, files_folder_id, read_only, category_id, exception_for_event_id, recurrence_id, recurrence, is_organizer,event_type,customer,customer_info,customer_info_send,customer_info_reminder,busy,attendees,organizer,is_organizer,accepted", "1=1 AND id='" . $id . "' ORDER BY id"); if ($dbcal->num_rows($res)) { $data = $dbcal->fetch_array($res); - - + if ($data['recurrence']) { + $recurrence = self::generateDataRecurrence(json_decode($data['recurrence'], true)); + } else { + $recurrence = false; + } $attachment = 0; - if ($data['all_day_event'] == 1) { $starttime = date("Y-m-d", $data['start_time']); $endtime = date("Y-m-d", $data['end_time']); @@ -517,6 +519,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda } } } + if (!$data['accepted'] && $data['busy'] == 1) { $data['accepted']['ok'] = 1; $data['accepted'] = json_encode($data['accepted']); @@ -543,6 +546,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda 'attendees' => array('attendees' => json_encode($AttendeeArray)), 'organizer' => array('organizer' => $data['organizer']), 'accepted' => array('accepted' => $data['accepted']), + 'recurrence' => array('recurrence' => $recurrence), 'attachment' => array('attachment' => $attachment, 'order' => $attachment), 'attachments' => array('attachments' => $attachments, 'order' => $attachments) ); @@ -814,7 +818,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda $db->insert("TheTool_CalendarQueue", $data); } - public static function insertCalendarEvent($r, $me) + public static function insertCalendarEvent($r, $me, $rrules = "") { $description = ($r->description); $attachments = ($r->attachments); @@ -877,7 +881,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda $customer_info_send = NULL; } - $dataarray = array("start_time" => $start, 'end_time' => $end, 'name' => $title, 'description' => $description, 'location' => $location, 'calendar_id' => $user_id, 'uuid' => "a5eb79b3-fca7-5378-a09e-" . rand(100000000000, 999999999999), 'user_id' => 1, 'timezone' => 'Europe/Amsterdam', 'all_day_event' => 0, 'repeat_end_time' => 0, 'reminder' => $reminder, 'ctime' => time(), 'cname' => $me->name, 'mtime' => time(), 'mname' => $me->name, 'user_id' => 1, 'busy' => $busy, 'status' => 'CONFIRMED', 'resource_event_id' => 0, 'private' => $privateflag, 'rrule' => '', 'background' => 'EBF1E2', 'files_folder_id' => 0, 'read_only' => 0,'categories'=>$categories, 'exception_for_event_id' => 0, 'recurrence_id' => 0, 'is_organizer' => 1, 'event_type' => $type, 'customer' => $customer, 'customer_info' => $customer_info, 'customer_info_send' => $customer_info_send, 'customer_info_reminder' => $customer_info_reminder_check); + $dataarray = array("start_time" => $start, 'end_time' => $end, 'name' => $title, 'description' => $description, 'location' => $location, 'calendar_id' => $user_id, 'uuid' => "a5eb79b3-fca7-5378-a09e-" . rand(100000000000, 999999999999), 'user_id' => 1, 'timezone' => 'Europe/Amsterdam', 'all_day_event' => 0, 'repeat_end_time' => 0, 'reminder' => $reminder, 'ctime' => time(), 'cname' => $me->name, 'mtime' => time(), 'mname' => $me->name, 'user_id' => 1, 'busy' => $busy, 'status' => 'CONFIRMED', 'resource_event_id' => 0, 'private' => $privateflag, 'rrule' => '', 'background' => 'EBF1E2', 'files_folder_id' => 0, 'read_only' => 0, 'categories' => $categories, 'exception_for_event_id' => 0, 'recurrence_id' => 0, 'is_organizer' => 1, 'event_type' => $type, 'customer' => $customer, 'customer_info' => $customer_info, 'customer_info_send' => $customer_info_send, 'customer_info_reminder' => $customer_info_reminder_check); $db->insert("cal_events", $dataarray); $event_id = $dataarray['uuid']; @@ -898,6 +902,9 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda if ($attendees) $dataarray['attendees'] = $attendees; $dataarray['attachments'] = $attachments; + if ($rrules) { + $dataarray['rrule'] = $rrules; + } $json_data = json_encode($dataarray); $data = []; @@ -972,6 +979,87 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda die(); } + public static function generateDataRecurrence($graph) + { + $data = []; + switch ($graph['pattern']['type']) { + case 'daily': + $data['rrule_frequency'] = 'DAILY'; + break; + case 'weekly': + $data['rrule_frequency'] = 'WEEKLY'; + if (!empty($graph['pattern']['daysOfWeek'])) { + $data['rrule_byweekday'] = array_map(function ($day) { + return self::convertGraphToDay($day); + }, $graph['pattern']['daysOfWeek']); + } + break; + case 'absoluteMonthly': + $data['rrule_frequency'] = 'MONTHLY'; + $data['monthly_type'] = 'BYMONTHDAY'; + $data['rrule_bymonthday'] = intval($graph['pattern']['dayOfMonth']); + break; + case 'relativeMonthly': + $data['rrule_frequency'] = 'MONTHLY'; + $data['monthly_type'] = 'BYSETPOS'; + $indexMapping = [ + 'first' => 1, + 'second' => 2, + 'third' => 3, + 'fourth' => 4, + 'last' => -1 + ]; + $index = isset($graph['pattern']['index']) ? $graph['pattern']['index'] : 'first'; + $data['rrule_setpos'] = isset($indexMapping[$index]) ? $indexMapping[$index] : 1; + if (!empty($graph['pattern']['daysOfWeek'])) { + $data['rrule_bynweekday'] = self::convertGraphToDay($graph['pattern']['daysOfWeek'][0]); + } + break; + case 'absoluteYearly': + $data['rrule_frequency'] = 'YEARLY'; + break; + default: + $data['rrule_frequency'] = 'DAILY'; + } + + // Falls benötigt, kann der Start-Datum-Wert übernommen werden. + if (!empty($graph['range']['startDate'])) { + $data['startDate'] = $graph['range']['startDate']; + } + + // Wiederherstellen des Endbereichs + if (!empty($graph['range']['type'])) { + switch ($graph['range']['type']) { + case 'endDate': + $data['rrule_until'] = $graph['range']['endDate']; + break; + case 'numbered': + $data['rrule_count'] = intval($graph['range']['numberOfOccurrences']); + break; + case 'noEnd': + break; + } + } + + return $data; + } + + public static function convertGraphToDay($graphDay) + { + $mapping = [ + 'MONDAY' => 'MO', + 'TUESDAY' => 'TU', + 'WEDNESDAY' => 'WE', + 'THURSDAY' => 'TH', + 'FRIDAY' => 'FR', + 'SATURDAY' => 'SA', + 'SUNDAY' => 'SU' + ]; + + $graphDay = strtoupper($graphDay); + return isset($mapping[$graphDay]) ? $mapping[$graphDay] : $graphDay; + } + public static function create(array $data) { $model = new Calendar(); diff --git a/public/js/pages/Calendar/View.js b/public/js/pages/Calendar/View.js index f943cbed1..cecdd60dd 100644 --- a/public/js/pages/Calendar/View.js +++ b/public/js/pages/Calendar/View.js @@ -554,6 +554,19 @@ document.addEventListener('DOMContentLoaded', function () { $('#delete-event').hide(); $('#update-event').hide(); $('.show-attendee').hide(); + $('#recurringCheck').prop('checked', true); + $('#recurringCheck').change(); + if (data.data.recurrence) + { + $('#rrule-frequency').val(data.data.recurrence.recurrence.rrule_frequency); + $('#rrule-frequency').trigger('change'); + $('#rrule-interval').val(data.data.recurrence.recurrence.rrule_interval); + $('#rrule-byweekday').val(data.data.recurrence.recurrence.rrule_byweekday); + $('#rrule-until').val(data.data.recurrence.recurrence.rrule_until); + $('#monthly-type').val(data.data.recurrence.recurrence.monthly_type); + $('#monthly-type').trigger('change'); + $('#rrule-bymonthday').val(data.data.recurrence.recurrence.rrule_bymonthday); + } } @@ -914,9 +927,11 @@ if (typeof (EventSource) !== 'undefined') { let rruleflag = false; let cursorclass = ''; if (event.rrule) { + $('.calendar-check').eq(0).trigger('change'); rrule = event.rrule; duration = event.duration; rruleflag = true; + return; } if (event.calendar_id in calendarRights) { if (calendarRights[event.calendar_id] == 'all') { @@ -1190,7 +1205,15 @@ $(document).ready(function () { $('#EventModal .is-require').each(function (index, value) { $(this).removeClass('required'); }); - + $('#recurring-settings').hide(); + $('#weekly-options').hide(); + $('#monthly-type').val('BYMONTHDAY'); + $('#rrule-frequency').val(''); + $('#rrule-count').val(''); + $('#rrule-until').val(''); + $('#rrule-byweekday').val(''); + $('#rrule-bymonthday').val('1'); + $('#monthly-options').hide(); // $('.select2-multiple').select2(); $('#calendar-users').prop("disabled", false); $("#calendar-users option").each(function () { @@ -1298,6 +1321,27 @@ $(document).ready(function () { users.push($(this).val()); }); + if ($('#recurringCheck').is(':checked') && $('#rrule-frequency').val() !== '') { + var rruleData = {}; + rruleData.rrule_frequency = $('#rrule-frequency').val(); + + if (rruleData.rrule_frequency === 'WEEKLY') { + // Mehrere Wochentage als Array + rruleData['rrule-byweekday'] = $('#rrule-byweekday').val(); + } else if (rruleData.rrule_frequency === 'MONTHLY') { + rruleData.monthly_type = $('#monthly-type').val(); + if (rruleData.monthly_type === 'BYMONTHDAY') { + rruleData.rrule_bymonthday = $('#rrule-bymonthday').val(); + } else if (rruleData.monthly_type === 'BYSETPOS') { + rruleData.rrule_setpos = $('#rrule-setpos').val(); + rruleData.rrule_bynweekday = $('#rrule-bynweekday').val(); + } + } + // Optionale Felder + rruleData.rrule_count = $('#rrule-count').val(); + rruleData.rrule_until = $('#rrule-until').val(); + } + $.post(requestInsertUrl, { start: start, @@ -1312,6 +1356,7 @@ $(document).ready(function () { attachments: attachments, users: users, privateflag: privateflag, + rruleData: rruleData, attendees: $('#calendar-attendees').val(), customer: customer, customer_info_check: customer_info_check, @@ -2416,15 +2461,55 @@ $(document).ready(function () { ; }); } - -// $('.select-2').select2({ -// containerCssClass : 'meine-custom-dropdown', // Klasse für das Dropdown-Menü -// containerCss : "wrap", -// selectionCssClass: 'meine-custom-selection' -// }); + $(document).on('focusin', function (e) { if ($(e.target).closest(".tox-tinymce, .tox-tinymce-aux, .moxman-window, .tam-assetmanager-root").length) { e.stopImmediatePropagation(); } }); + $(document).ready(function () { + // Checkbox toggelt das RRule-Panel + $('#recurringCheck').on('change', function () { + if ($(this).is(':checked')) { + $('#recurring-settings').show(); + $('#rrule-until').attr('min' , $('#start-date').val()); + } else { + $('#recurring-settings').hide(); + $('#weekly-options').hide(); + $('#monthly-options').hide(); + } + }); + + // Falls eine Frequenz gewählt wird, passende Felder (weekly/monthly) einblenden + $('#rrule-frequency').on('change', function () { + var freq = $(this).val(); + + // Alles erstmal verstecken + $('#weekly-options').hide(); + $('#monthly-options').hide(); + + if (freq === 'WEEKLY') { + $('#weekly-options').show(); + } else if (freq === 'MONTHLY') { + $('#monthly-options').show(); + } + }); + + // Zeige/Verstecke im monatlichen Bereich "Tag des Monats" oder "X. Wochentag" + $('#monthly-type').on('change', function () { + var monthlyType = $(this).val(); + if (monthlyType === 'BYMONTHDAY') { + $('#monthly-day-select').show(); + $('#monthly-setpos-select').hide(); + } else { + $('#monthly-day-select').hide(); + $('#monthly-setpos-select').show(); + } + }); + + // Du kannst bei Klick auf "Hinzufügen" oder "Speichern" dann aus den ausgewählten Werten + // dein RRule-String generieren (z.B. mit rrule.js oder manuell). + + }); + });