Merge branch 'spidev' into 'master'

Kalender Update

See merge request fronk/thetool!1058
This commit is contained in:
Daniel Spitzer
2025-03-02 15:46:22 +00:00
4 changed files with 361 additions and 18 deletions

View File

@@ -37,6 +37,7 @@ endforeach;
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/rrule/rrule.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/moment/moment.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
@@ -57,6 +58,7 @@ endforeach;
src="<?= self::getResourcePath() ?>assets/js/calendar/eventsource.min.js?<?= $git_merge_ts ?>"></script>
<link href="<?= self::getResourcePath() ?>assets/css/datatables-std.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<!-- start page title -->
<div class="row">
<div class="col-12">
@@ -427,7 +429,7 @@ endforeach;
aria-label="Uhrzeit"
aria-describedby="Uhrzeit">
</div>
<div class="col-3 text-center">
<div class="col-2 text-center">
<div class="form-check" style="margin-top: 7px;">
<input class="form-check-input eventmodal-checkbox" type="checkbox" value=""
@@ -437,7 +439,14 @@ endforeach;
</div>
</div>
<div class="col-2 text-right">
<div class="form-check" style="margin-top: 7px;">
<input class="form-check-input eventmodal-checkbox" type="checkbox" value="" id="recurringCheck">
<label class="form-check-label fw-medium checkbox-label" for="recurringCheck">
Serientermin
</label>
</div>
</div>
</div>
<div class="row justify-content-center">
@@ -456,7 +465,7 @@ endforeach;
aria-label="Uhrzeit"
aria-describedby="Uhrzeit">
</div>
<div class="col-3">
<div class="col-4">
<div class="input-group mb-2">
<span title="Erinnerung" class="input-group-text spanwidht"><i
class="fa-regular fa-bell"></i></span>
@@ -474,6 +483,85 @@ endforeach;
</div>
</div>
</div>
<!-- Serientermin-Checkbox und zugehörige RRule-Optionen -->
<div class="row" id="recurring-settings" style="display:none;">
<div class="col-1"></div>
<div class="col-3"">
<!-- Frequenz: Täglich / Wöchentlich / Monatlich / Jährlich -->
<div class="mb-2">
<label for="rrule-frequency" class="col-form-label fw-medium">Wiederholungstyp</label>
<select class="form-control" id="rrule-frequency">
<option value="">Bitte wählen</option>
<option value="DAILY">Täglich</option>
<option value="WEEKLY">Wöchentlich</option>
<option value="MONTHLY">Monatlich</option>
<option value="YEARLY">Jährlich</option>
</select>
</div>
<div class="mb-2">
<label for="rrule-count" class="col-form-label fw-medium">Wiederhol. (optional)</label>
<input type="number" min="1" class="form-control" id="rrule-count" placeholder="z.B. 10">
</div>
<!-- Enddatum der Serie (optional) -->
<div class="mb-2">
<label for="rrule-until" class="col-form-label fw-medium">Ende (optional)</label>
<input type="date" class="form-control" id="rrule-until">
</div>
</div>
<!-- Wöchentliche Optionen (z.B. jeden Dienstag, jeden Freitag + Montag) -->
<div class="col-7">
<div class="mb-2" id="weekly-options" style="display:none;">
<label for="rrule-byweekday" class="col-form-label fw-medium">Wochentag(e)</label>
<select class="form-control" id="rrule-byweekday" multiple>
<option value="MO">Montag</option>
<option value="TU">Dienstag</option>
<option value="WE">Mittwoch</option>
<option value="TH">Donnerstag</option>
<option value="FR">Freitag</option>
<option value="SA">Samstag</option>
<option value="SU">Sonntag</option>
</select>
</div>
<div class="mb-2" id="monthly-options" style="display:none;">
<label class="col-form-label fw-medium">Monatliches Muster</label>
<select class="form-control" id="monthly-type">
<option value="BYMONTHDAY">Jeden X. Tag des Monats</option>
<option value="BYSETPOS">Jeden X. [Wochentag] im Monat</option>
</select>
<div class=" mt-2 row" id="monthly-day-select">
<label class="mr-2 col-form-label fw-medium col-2">Tag:</label>
<select class="form-control w-auto col-3" id="rrule-bymonthday">
<!-- Auswahl 131 -->
<?php for($i=1; $i<=31; $i++): ?>
<option value="<?= $i ?>"><?= $i ?></option>
<?php endfor; ?>
</select>
</div>
<div class="mt-2 row" id="monthly-setpos-select" style="display:none;">
<label class="mr-2 col-2 col-form-label fw-medium">Position:</label>
<select class="form-control col-3 w-auto" id="rrule-setpos">
<option value="1">Erster</option>
<option value="2">Zweiter</option>
<option value="3">Dritter</option>
<option value="4">Vierter</option>
<option value="-1">Letzter</option>
</select>
<label class="mr-2 col-2 ml-3 col-form-label fw-medium text-center">Tag:</label>
<select class="form-control col-3 w-auto" id="rrule-bynweekday">
<option value="MO">Montag</option>
<option value="TU">Dienstag</option>
<option value="WE">Mittwoch</option>
<option value="TH">Donnerstag</option>
<option value="FR">Freitag</option>
<option value="SA">Samstag</option>
<option value="SU">Sonntag</option>
</select>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mt-2">
<div class="col-2">
@@ -728,7 +816,6 @@ endforeach;
endforeach; ?>
</script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>js/pages/Calendar/View.js?<?= $git_merge_ts ?>"></script>

View File

@@ -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");

View File

@@ -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();

View File

@@ -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).
});
});