Update:
* Serientermine werden nun angezeigt
* Serientermine können nun angenommen/abgelehnt bzw. geändert werden.
* Serientermin Serienzeiten ändern ist noch offen
* Alle Js nun local im Calendar Ordner
This commit is contained in:
Daniel Spitzer
2024-11-18 13:08:20 +01:00
parent 413b730397
commit 962a5db94f
10 changed files with 3615 additions and 30 deletions

View File

@@ -34,25 +34,26 @@ endforeach;
type="text/css"/>
<link href="<?= self::getResourcePath() ?>css/pages/Calendar/View.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<style>
</style>
<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"-->
<!-- src="--><?php //= self::getResourcePath() ?><!--assets/js/calendar/index.global.min.js?--><?php //= $git_merge_ts ?><!--"</script>-->
<script src='https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@6.1.15/index.global.min.js'></script>
<script src="https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-sortablejs@latest/jquery-sortable.js"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/index.global.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/Sortable.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/jquery-sortable.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/moment/index.global.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/rrule/index.global.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/locales-all.global.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/tooltip.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
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 -->
@@ -479,7 +480,10 @@ endforeach;
<div class="row justify-content-center mt-2">
<div class="col-2">
<label for="name" class="col-form-label fw-medium ">Teilnehmer <span class="ml-1 calendar-users-all" style="display: none"><i title="Alle von Planungsansicht" class="fa-solid fa-arrow-right-from-bracket"></i></span></label>
<label for="name" class="col-form-label fw-medium ">Teilnehmer <span
class="ml-1 calendar-users-all" style="display: none"><i
title="Alle von Planungsansicht"
class="fa-solid fa-arrow-right-from-bracket"></i></span></label>
</div>
<div class="col-8">
<select class="form-control form-select select2-multiple-tag" id="calendar-attendees"

View File

@@ -177,6 +177,8 @@ class CalendarApicontroller extends mfBaseApicontroller
$message[0]['attachments'] = $Calendarevent['data'][0]['attachments']['attachments'];
$message[0]['calendar_name'] = $Calendarevent['data'][0]['calendar_name']['calendar_name'];
$message[0]['isorganizer'] = $Calendarevent['data'][0]['isorganizer']['isorganizer'];
$message[0]['rrule'] = $Calendarevent['data'][0]['rrule']['rrule'];
$message[0]['duration'] = $Calendarevent['data'][0]['duration']['duration'];
$message[0]['mtime'] = $Calendarevent['data'][0]['mtime']['mtime'];
$message[0]['mname'] = $Calendarevent['data'][0]['mname']['mname'];
$message[0]['ctime'] = $Calendarevent['data'][0]['ctime']['ctime'];

View File

@@ -118,6 +118,7 @@ class CalendarModel
public static function getCalendarEvents($me, $id = 0, $r = 0)
{
$rrulefreq = array('daily' => 'DAILY', 'weekly' => 'WEEKLY', 'relativeMonthly' => 'MONTHLY', 'yearly' => 'YEARLY');
$calendar = self::search(array("user_id" => $me));
$standardCalendarColors = CalendarModel::$standardCalendarColors;
$calendarColors = json_decode($calendar[0]->colors, true);
@@ -155,12 +156,14 @@ class CalendarModel
if ($visibleCalendars) {
$where .= " AND calendar_id IN (" . implode(",", $visibleCalendars) . ")";
}
$sql = "SELECT `cal_events`.id, uuid, calendar_id, `cal_events`.user_id, start_time, end_time, timezone, all_day_event, `cal_events`.name,`cal_calendars`.name calendar_name, description, location, repeat_end_time, reminder, ctime,cname, mtime,mname, muser_id, busy, status, resource_event_id, private, rrule, `cal_events`.background, `cal_events`.files_folder_id, read_only, category_id, exception_for_event_id, recurrence_id, is_organizer,event_type,busy FROM cal_events INNER JOIN `cal_calendars` ON (`cal_calendars`.`id`=`cal_events`.`calendar_id`) WHERE 1=1 $where ";
$sql = "SELECT `cal_events`.id, uuid, calendar_id, `cal_events`.user_id, start_time, end_time, timezone, all_day_event, `cal_events`.name,`cal_calendars`.name calendar_name, description, location, repeat_end_time, reminder, ctime,cname, mtime,mname, muser_id, busy, status, resource_event_id, private, rrule, `cal_events`.background, `cal_events`.files_folder_id, read_only, category_id, exception_for_event_id, recurrence_id, is_organizer,event_type,busy,recurrence FROM cal_events INNER JOIN `cal_calendars` ON (`cal_calendars`.`id`=`cal_events`.`calendar_id`) WHERE 1=1 $where ";
$res = $dbcal->query($sql);
if ($dbcal->num_rows($res)) {
while ($data = $dbcal->fetch_array($res)) {
unset($byweekday);
$rrule = false;
if ($attachments[$data['uuid']]) {
$attachment = 1;
$attachmentLinks = json_encode($attachments[$data['uuid']]['attachments']);
@@ -184,6 +187,40 @@ class CalendarModel
$name = $data['name'];
}
if ($data['recurrence']) {
$recurrence = json_decode($data['recurrence'], true);
if ($rrulefreq[$recurrence['pattern']['type']]) {
unset ($byweekday);
$freq = $rrulefreq[$recurrence['pattern']['type']];
foreach ($recurrence['pattern']['daysOfWeek'] as $value) {
$byweekday[] = strtolower(substr($value, 0, 2));
}
$duration = ($data['end_time'] - $data['start_time']) * 1000;
$until = $recurrence['range']['endDate'];
if ($until == "0001-01-01") {
$until = strtotime("+3 year", $data['start_time']);
$until = date("Y-m-d", $until);
} else {
$until = date("Y-m-d", strtotime($recurrence['range']['endDate']) + 86400);
}
$rrule = [
'freq' => $freq,
'interval' => $recurrence['pattern']['interval'],
'byweekday' => $byweekday,
'dtstart' => date("Y-m-d\TH:i", $data['start_time']),
'until' => $until
];
if ($freq == "MONTHLY") {
$bystpos = array("first" => 1, "second" => 2, "third" => 3, "fourth" => 4, "last" => -1);
$rrule['bysetpos'] = $bystpos[$recurrence['pattern']['index']];
}
}
} else {
$rrule = false;
}
if ($calendarColors[$data['calendar_id']]['bgcolor']) {
$bgcolor = $calendarColors[$data['calendar_id']]['bgcolor'];
$txtcolor = $calendarColors[$data['calendar_id']]['txtcolor'];
@@ -207,6 +244,8 @@ class CalendarModel
'rights' => array('rights' => $rights, 'order' => $rights),
'location' => array('location' => $data['location']),
'busy' => array('busy' => $data['busy']),
'rrule' => array('rrule' => $rrule),
'duration' => array('duration' => $duration),
'event_type' => array('event_type' => $data['event_type']),
'description' => array('description' => ($data['description'])),
'attachment' => array('attachment' => $attachment),
@@ -347,8 +386,8 @@ class CalendarModel
}
}
if (!$data['accepted'] && $data['busy'] == 1) {
$data['accepted']['ok']=1;
$data['accepted']=json_encode($data['accepted']);
$data['accepted']['ok'] = 1;
$data['accepted'] = json_encode($data['accepted']);
}
$rows = array(
@@ -718,7 +757,7 @@ class CalendarModel
$data['create_timestamp'] = time();
$data['edit_timestamp'] = time();
$db->insert("tmp_cal_events_attachments", $data);
echo "insert_id: " . $db->insert_id;
// echo "insert_id: " . $db->insert_id;
return $db->insert_id;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,76 @@
(function (factory) {
"use strict";
var sortable,
jq,
_this = this
;
if (typeof define === "function" && define.amd) {
try {
define(["sortablejs", "jquery"], function(Sortable, $) {
sortable = Sortable;
jq = $;
checkErrors();
factory(Sortable, $);
});
} catch(err) {
checkErrors();
}
return;
} else if (typeof exports === 'object') {
try {
sortable = require('sortablejs');
jq = require('jquery');
} catch(err) { }
}
if (typeof jQuery === 'function' || typeof $ === 'function') {
jq = jQuery || $;
}
if (typeof Sortable !== 'undefined') {
sortable = Sortable;
}
function checkErrors() {
if (!jq) {
throw new Error('jQuery is required for jquery-sortablejs');
}
if (!sortable) {
throw new Error('SortableJS is required for jquery-sortablejs (https://github.com/SortableJS/Sortable)');
}
}
checkErrors();
factory(sortable, jq);
})(function (Sortable, $) {
"use strict";
$.fn.sortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
} else if (sortable) {
if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
} else if (options === 'widget') {
retVal = sortable;
} else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
} else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
});

View File

@@ -0,0 +1,6 @@
/*!
FullCalendar RRule Plugin v6.1.15
Docs & License: https://fullcalendar.io/docs/rrule-plugin
(c) 2024 Adam Shaw
*/
FullCalendar.RRule=function(e,r,t,i){"use strict";function n(e){if(e&&e.__esModule)return e;var r=Object.create(null);return e&&Object.keys(e).forEach((function(t){if("default"!==t){var i=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,i.get?i:{enumerable:!0,get:function(){return e[t]}})}})),r.default=e,r}var l=n(t);const u={parse(e,r){if(null!=e.rrule){let t=function(e,r){let t,n=!1,u=!1;if("string"==typeof e.rrule){let r=function(e){let r=l.rrulestr(e,{forceset:!0}),t=function(e){let r=!1,t=!1;function n(e,n,l){let u=i.parseMarker(l);r=r||!u.isTimeUnspecified,t=t||null!==u.timeZoneOffset}return e.replace(/\b(DTSTART:)([^\n]*)/,n),e.replace(/\b(EXDATE:)([^\n]*)/,n),e.replace(/\b(UNTIL=)([^;\n]*)/,n),{isTimeSpecified:r,isTimeZoneSpecified:t}}(e);return Object.assign({rruleSet:r},t)}(e.rrule);t=r.rruleSet,n=r.isTimeSpecified,u=r.isTimeZoneSpecified}if("object"==typeof e.rrule&&e.rrule){let i=a(e.rrule,r);t=new l.RRuleSet,t.rrule(i.rrule),n=i.isTimeSpecified,u=i.isTimeZoneSpecified}let f=[].concat(e.exdate||[]),s=[].concat(e.exrule||[]);for(let e of f){let r=i.parseMarker(e);n=n||!r.isTimeUnspecified,u=u||null!==r.timeZoneOffset,t.exdate(new Date(r.marker.valueOf()-60*(r.timeZoneOffset||0)*1e3))}for(let e of s){let i=a(e,r);n=n||i.isTimeSpecified,u=u||i.isTimeZoneSpecified,t.exrule(i.rrule)}return{rruleSet:t,isTimeSpecified:n,isTimeZoneSpecified:u}}(e,r);if(t)return{typeData:{rruleSet:t.rruleSet,isTimeZoneSpecified:t.isTimeZoneSpecified},allDayGuess:!t.isTimeSpecified,duration:e.duration}}return null},expand(e,r,t){let i;return i=e.isTimeZoneSpecified?e.rruleSet.between(t.toDate(r.start),t.toDate(r.end),!0).map(e=>t.createMarker(e)):e.rruleSet.between(r.start,r.end,!0),i}};function a(e,r){let t=!1,n=!1;function u(e){if("string"==typeof e){let r=i.parseMarker(e);return r?(t=t||!r.isTimeUnspecified,n=n||null!==r.timeZoneOffset,new Date(r.marker.valueOf()-60*(r.timeZoneOffset||0)*1e3)):null}return e}let a=Object.assign(Object.assign({},e),{dtstart:u(e.dtstart),until:u(e.until),freq:s(e.freq),wkst:null==e.wkst?(r.weekDow-1+7)%7:s(e.wkst),byweekday:f(e.byweekday)});return{rrule:new l.RRule(a),isTimeSpecified:t,isTimeZoneSpecified:n}}function f(e){return Array.isArray(e)?e.map(s):s(e)}function s(e){return"string"==typeof e?l.RRule[e.toUpperCase()]:e}const c={rrule:i.identity,exrule:i.identity,exdate:i.identity,duration:i.createDuration};var o=r.createPlugin({name:"@fullcalendar/rrule",recurringTypes:[u],eventRefiners:c});return r.globalPlugins.push(o),e.default=o,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,rrule,FullCalendar.Internal);

File diff suppressed because one or more lines are too long

View File

@@ -4,10 +4,14 @@
cursor: move;
font-size: 13px;
margin: 3px 7px;
padding: 3px 5px;
padding: 1px 5px;
text-align: center;
}
.fc-timegrid-event-short .fc-event-main-frame {
flex-direction: row;
overflow: hidden;
margin-left: 10px;
}
.fc-toolbar {
@media (max-width: 767px) {
flex-direction: column;
@@ -310,7 +314,16 @@ thead .fc-day-today .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion {
.fc-event-type {
position: absolute;
top: 0;
left: 1px;
left: -4px;
}
.fc-event-recurrence {
position: absolute;
bottom: -4px;
left: -4px;
}
.fc-timegrid-event-short .fc-event-time::after {
content: "";
}
.fa-duotone {

View File

@@ -82,6 +82,9 @@ document.addEventListener('DOMContentLoaded', function () {
var rights = false;
var movable = false;
var resourceCounter = 0;
var rrule = null;
var duration = null;
var rruleflag = false;
$.each($('.calendar-check'), function (index, value) {
if ($(this).prop('checked')) {
rights = true;
@@ -98,7 +101,16 @@ document.addEventListener('DOMContentLoaded', function () {
$.each(json.data, function (index, value) {
rrule = null;
duration = null;
rruleflag = false;
category = value.ccategory.ccategory;
if (value.rrule.rrule) {
rrule = value.rrule.rrule;
duration = value.duration.duration;
rruleflag = true;
}
if (value.calendar_id.calendar_id in calendarRights) {
if (calendarRights[value.calendar_id.calendar_id] == 'all') {
@@ -111,8 +123,7 @@ document.addEventListener('DOMContentLoaded', function () {
} else {
movable = false;
}
userevents.push({
let event = {
id: value.id.id,
start: value.cstart.cstart,
end: value.cend.cend,
@@ -127,6 +138,9 @@ document.addEventListener('DOMContentLoaded', function () {
textColor: value.txtColor.txtColor,
backgroundColor: value.bgColor.bgColor,
editable: rights,
rruleflag: rruleflag,
rrule: rrule,
duration: duration,
droppable: movable,
startEditable: movable,
durationEditable: movable,
@@ -139,7 +153,10 @@ document.addEventListener('DOMContentLoaded', function () {
ctime: value.ctime.ctime,
cname: value.cname.cname,
busy: value.busy.busy
});
};
userevents.push(event);
if (value.rrule.rrule) {
}
} else {
otherevents.push({
id: value.id.id,
@@ -149,6 +166,8 @@ document.addEventListener('DOMContentLoaded', function () {
description: category,
color: 'red',
editable: false,
rruleflag: rruleflag,
rrule: rrule,
calendar_id: value.calendar_id,
event_type: value.event_type.event_type,
classNames: ['cal-class-group-' + value.calendar_id.calendar_id, 'cal-class-id-' + value.id.id],
@@ -220,7 +239,6 @@ document.addEventListener('DOMContentLoaded', function () {
select: function (info) {
let resourceId = "";
if (info.resource) {
console.log(info.resource.id);
resourceId = info.resource.id;
}
let cestDate = new Date(info.startStr);
@@ -265,15 +283,11 @@ document.addEventListener('DOMContentLoaded', function () {
$('#end-time').val(EndformattedTime);
}, eventClick: function (info, element) {
let isOrganizer;
$.getJSON(requestEventUrl, {
id: info.event.id
}, function (data) {
}).done(function (data) {
// console.log(data);
if (data.data.attachment.attachment) {
let docs = "";
$.each(data.data.attachments.attachments, function (index, value) {
@@ -480,7 +494,6 @@ document.addEventListener('DOMContentLoaded', function () {
}
},
eventDidMount: function (info) {
// console.log(info);
var eventstart = new Date(info.event.startStr);
var eventend = new Date(info.event.endStr);
@@ -495,7 +508,6 @@ document.addEventListener('DOMContentLoaded', function () {
if (eventend == "NaN.NaN.NaN") {
eventend = eventstart;
}
// console.log(eventend);
title = '<div class="tooltip-description text-center">Ganztägig von ' + eventstart + ' bis ' + eventend + ' </div>';
}
@@ -528,7 +540,6 @@ document.addEventListener('DOMContentLoaded', function () {
title += '<div class="text-left font-13"><span class="font-weight-500">geändert am </span> ' + info.event.extendedProps['mtime'] + '</div>';
title += '<div class="text-left font-13"><span class="font-weight-500">geändert von </span> ' + info.event.extendedProps['mname'] + '</div>';
}
// console.log(info);
if ($('.fc-button-active').hasClass('fc-timeGridWeek-button') || $('.fc-button-active').hasClass('fc-timeGridDay-button')) {
if (info.event.extendedProps['attachment']) {
info.el.querySelector(".fc-event-title").insertAdjacentHTML("afterend", "<div class=\"fc-event-attachment\"><i class=\"fa-light fa-paperclip-vertical\"></i></div>");
@@ -537,6 +548,9 @@ document.addEventListener('DOMContentLoaded', function () {
info.el.querySelector(".fc-event-title").insertAdjacentHTML("afterend", "<div class=\"fc-event-type\"><i class=\"fa-light fa-user-helmet-safety\"></i></div>");
}
if (info.event.extendedProps['rruleflag']) {
info.el.querySelector(".fc-event-title").insertAdjacentHTML("afterend", "<div class=\"fc-event-recurrence\"><i class=\"fa-regular fa-arrows-rotate\"></i></div>");
}
}
var tooltip = new Tooltip(info.el, {
@@ -714,6 +728,14 @@ if (typeof (EventSource) !== 'undefined') {
var cevent = calendar.getEventById(event.cal_events_id);
var rights = false;
var movable = false;
var rrule = null;
var duration = null;
var rruleflag = false;
if (event.rrule) {
rrule = event.rrule;
duration = event.duration;
rruleflag = true;
}
if (event.calendar_id in calendarRights) {
if (calendarRights[event.calendar_id] == 'all') {
rights = true;
@@ -746,6 +768,9 @@ if (typeof (EventSource) !== 'undefined') {
attachment: event.attachment,
attachments: event.attachments,
editable: movable,
rruleflag: rruleflag,
rrule: rrule,
duration: duration,
resourceId: event.calendar_id,
calendar_name: event.calendar_name,
clickable: rights,
@@ -1718,6 +1743,9 @@ Xinon GMbH`;
var rights = false;
var movable = false;
var resourceCounter = 0;
var rrule = null;
var duration = null;
var rruleflag = false;
$.each($('.calendar-check'), function (index, value) {
if ($(this).prop('checked')) {
rights = true;
@@ -1734,8 +1762,16 @@ Xinon GMbH`;
$.each(json.data, function (index, value) {
rrule = null;
duration = null;
rruleflag = false;
category = value.ccategory.ccategory;
if (value.rrule.rrule) {
rrule = value.rrule.rrule;
duration = value.duration.duration;
rruleflag = true;
}
if (value.calendar_id.calendar_id in calendarRights) {
if (calendarRights[value.calendar_id.calendar_id] == 'all') {
rights = true;
@@ -1762,6 +1798,9 @@ Xinon GMbH`;
textColor: value.txtColor.txtColor,
backgroundColor: value.bgColor.bgColor,
editable: movable,
rruleflag: rruleflag,
rrule: rrule,
duration: duration,
resourceId: value.calendar_id.calendar_id,
calendar_name: value.calendar_name.calendar_name,
clickable: rights,
@@ -1780,6 +1819,9 @@ Xinon GMbH`;
description: category,
color: 'red',
editable: false,
rruleflag: rruleflag,
rrule: rrule,
duration: duration,
calendar_id: value.calendar_id,
event_type: value.event_type.event_type,
classNames: ['cal-class-group-' + value.calendar_id.calendar_id, 'cal-class-id-' + value.id.id],