Kalender Update

* Feature Implementation Excel Export
This commit is contained in:
Daniel Spitzer
2025-03-03 09:21:24 +01:00
parent 007a04f332
commit cb67e821ec
5 changed files with 391 additions and 171 deletions
+22 -6
View File
@@ -104,6 +104,7 @@ endforeach;
class="btn btn-light btn-light-search top-search-filter"><i class="btn btn-light btn-light-search top-search-filter"><i
class="fa-duotone fa-solid fa-phone-volume fa-calendar-call fa-calendar-call-search"></i> class="fa-duotone fa-solid fa-phone-volume fa-calendar-call fa-calendar-call-search"></i>
</button> </button>
</div> </div>
</div> </div>
<?php if (!$rights) : ?> <?php if (!$rights) : ?>
@@ -111,7 +112,14 @@ endforeach;
<span class="font-18 font-weight-500">Du wurdest nicht für den Kalender frei geschalten.</span> <span class="font-18 font-weight-500">Du wurdest nicht für den Kalender frei geschalten.</span>
</div> </div>
<?php die(); endif; ?> <?php die(); endif; ?>
<div class="d-inline-block mr-1">
<button title="Excel Export"
class="btn btn-light btn-xls-calendar">
<i class="fa-duotone fa-solid fa-file-xls fa-xls-calendar"></i>
</div>
</button>
<div class="d-inline-block w-50 search-div" style="margin-top: -7px;"> <div class="d-inline-block w-50 search-div" style="margin-top: -7px;">
<select id="jumpevent" class="jumpevent"></select> <select id="jumpevent" class="jumpevent"></select>
</div> </div>
@@ -433,7 +441,8 @@ endforeach;
<div class="form-check" style="margin-top: 7px;"> <div class="form-check" style="margin-top: 7px;">
<input class="form-check-input eventmodal-checkbox" type="checkbox" value="" <input class="form-check-input eventmodal-checkbox" type="checkbox" value=""
id="allday"> <label class="form-check-label fw-medium checkbox-label" for="allday"> id="allday"> <label class="form-check-label fw-medium checkbox-label"
for="allday">
Ganztägig Ganztägig
</label> </label>
@@ -441,7 +450,8 @@ endforeach;
</div> </div>
<div class="col-2 text-right"> <div class="col-2 text-right">
<div class="form-check" style="margin-top: 7px;"> <div class="form-check" style="margin-top: 7px;">
<input class="form-check-input eventmodal-checkbox" type="checkbox" value="" id="recurringCheck"> <input class="form-check-input eventmodal-checkbox" type="checkbox" value=""
id="recurringCheck">
<label class="form-check-label fw-medium checkbox-label" for="recurringCheck"> <label class="form-check-label fw-medium checkbox-label" for="recurringCheck">
Serientermin Serientermin
</label> </label>
@@ -469,7 +479,8 @@ endforeach;
<div class="input-group mb-2"> <div class="input-group mb-2">
<span title="Erinnerung" class="input-group-text spanwidht"><i <span title="Erinnerung" class="input-group-text spanwidht"><i
class="fa-regular fa-bell"></i></span> class="fa-regular fa-bell"></i></span>
<select class="form-control form-select select2" aria-label="Default select" id="reminder"> <select class="form-control form-select select2" aria-label="Default select"
id="reminder">
<option value="NULL">Keine</option> <option value="NULL">Keine</option>
<option value="0">Zum Termin</option> <option value="0">Zum Termin</option>
<option value="300">5 Minuten</option> <option value="300">5 Minuten</option>
@@ -486,7 +497,8 @@ endforeach;
<!-- Serientermin-Checkbox und zugehörige RRule-Optionen --> <!-- Serientermin-Checkbox und zugehörige RRule-Optionen -->
<div class="row" id="recurring-settings" style="display:none;"> <div class="row" id="recurring-settings" style="display:none;">
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-3""> <div class="col-3"
">
<!-- Frequenz: Täglich / Wöchentlich / Monatlich / Jährlich --> <!-- Frequenz: Täglich / Wöchentlich / Monatlich / Jährlich -->
<div class="mb-2"> <div class="mb-2">
<label for="rrule-frequency" class="col-form-label fw-medium">Wiederholungstyp</label> <label for="rrule-frequency" class="col-form-label fw-medium">Wiederholungstyp</label>
@@ -658,6 +670,7 @@ endforeach;
</div> </div>
<div class="d-inline-block" id="customer-info-check-info"></div> <div class="d-inline-block" id="customer-info-check-info"></div>
</div> </div>
<div id="relContainer2" style="position:relative"> <div id="relContainer2" style="position:relative">
<select id="customer" class="jumpevent"></select> <select id="customer" class="jumpevent"></select>
</div> </div>
@@ -739,13 +752,16 @@ endforeach;
</div> </div>
<div id="output"></div> <div id="output"></div>
<script type="text/javascript" src="<?= self::getResourcePath() ?>plugins/select2/js/i18n/de.js?<?= $git_merge_ts ?>"></script> <script type="text/javascript"
<script type="text/javascript" src="<?=self::getResourcePath()?>plugins/tinymce/tinymce.min.js?<?= $git_merge_ts ?>"></script> src="<?= self::getResourcePath() ?>plugins/select2/js/i18n/de.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
src="<?= self::getResourcePath() ?>plugins/tinymce/tinymce.min.js?<?= $git_merge_ts ?>"></script>
<script> <script>
let requestUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvents']) ?>"; let requestUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvents']) ?>";
let requestEventUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvent']) ?>"; let requestEventUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvent']) ?>";
let requestEventSearchURL = "<?= self::getUrl("Calendar", "api", ['do' => 'searchCalendarEvents']) ?>"; let requestEventSearchURL = "<?= self::getUrl("Calendar", "api", ['do' => 'searchCalendarEvents']) ?>";
let requestgenerateEventsxlsx = "<?= self::getUrl("Calendar", "api", ['do' => 'generateCalendarEventsXlsx']) ?>";
let requestEventAttachmentUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEventAttachment']) ?>"; let requestEventAttachmentUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEventAttachment']) ?>";
let requestEventAttachmentTmpUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEventAttachmentTmp']) ?>"; let requestEventAttachmentTmpUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEventAttachmentTmp']) ?>";
let requestEventAttachmentTmpDeleteUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'deleteCalendarEventAttachmentTmp']) ?>"; let requestEventAttachmentTmpDeleteUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'deleteCalendarEventAttachmentTmp']) ?>";
+114 -2
View File
@@ -1,5 +1,12 @@
<?php <?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Helper\Html;
class CalendarController extends mfBaseController class CalendarController extends mfBaseController
{ {
@@ -45,6 +52,11 @@ class CalendarController extends mfBaseController
$calendarEvents = CalendarModel::searchCalendarEvents($r); $calendarEvents = CalendarModel::searchCalendarEvents($r);
return $calendarEvents; return $calendarEvents;
die(); die();
case "generateCalendarEventsXlsx" :
$r = $this->request;
$this->generateXlsx($r);
return $calendarEvents;
die();
case "getCalendarEventAttachment" : case "getCalendarEventAttachment" :
$r = $this->request; $r = $this->request;
$id = ($r->id); $id = ($r->id);
@@ -392,7 +404,8 @@ class CalendarController extends mfBaseController
die(); die();
} }
private function generateGraphRecurrence($data, $startDate) { private function generateGraphRecurrence($data, $startDate)
{
$graph = [ $graph = [
'pattern' => [], 'pattern' => [],
'range' => [] 'range' => []
@@ -459,7 +472,8 @@ class CalendarController extends mfBaseController
} }
private function convertDayToGraph($dayAbbrev) { private function convertDayToGraph($dayAbbrev)
{
$mapping = [ $mapping = [
'MO' => 'monday', 'MO' => 'monday',
'TU' => 'tuesday', 'TU' => 'tuesday',
@@ -472,6 +486,104 @@ class CalendarController extends mfBaseController
return isset($mapping[$dayAbbrev]) ? $mapping[$dayAbbrev] : $dayAbbrev; return isset($mapping[$dayAbbrev]) ? $mapping[$dayAbbrev] : $dayAbbrev;
} }
private function generateXlsx($r)
{
$calendarEvents = json_decode(CalendarModel::getCalendarEvents($this->me->id, 0, $r, true), true);
$eventTypes = CalendarModel::$eventTypes;
$xls = new Spreadsheet();
$sheet = $xls->getActiveSheet();
$writer = new Xlsx($xls);
$sheet->getStyle('A1:AN1')->getFont()->setBold(true);
$sheet->getStyle('A:E')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('I:L')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
$sheet->getColumnDimension('A')->setWidth(25);
$sheet->getColumnDimension('B')->setWidth(15);
$sheet->getColumnDimension('C')->setWidth(10);
$sheet->getColumnDimension('D')->setWidth(15);
$sheet->getColumnDimension('E')->setWidth(10);
$sheet->getColumnDimension('F')->setWidth(40);
$sheet->getColumnDimension('G')->setWidth(40);
$sheet->getColumnDimension('H')->setWidth(20);
$sheet->getColumnDimension('I')->setWidth(10);
$sheet->getColumnDimension('J')->setWidth(10);
$sheet->getColumnDimension('K')->setWidth(15);
$sheet->getColumnDimension('L')->setWidth(15);
$sheet->setCellValue([1, 1], 'Kalender')
->setCellValue([2, 1], 'Startdatum')
->setCellValue([3, 1], 'Startzeit')
->setCellValue([4, 1], 'Enddatum')
->setCellValue([5, 1], 'Endzeit')
->setCellValue([6, 1], 'Betreff')
->setCellValue([7, 1], 'Ort')
->setCellValue([8, 1], 'Beschreibung')
->setCellValue([9, 1], 'Termintyp')
->setCellValue([10, 1], 'Privat')
->setCellValue([11, 1], 'Sichtbarkeit')
->setCellValue([12, 1], 'Organisator')
;
$counter = 2;
foreach ($calendarEvents['data'] as $event) {
if ($event['privateflag']['privateflag']) {
$event['privateflag']['privateflag'] = "Ja";
} else {
$event['privateflag']['privateflag'] = "Nein";
}
if ($event['isorganizer']['isorganizer']) {
$event['isorganizer']['isorganizer'] = "Ja";
} else {
$event['isorganizer']['isorganizer'] = "Nein";
}
if ($event['busy']['busy'] == 1) {
$event['busy']['busy'] = "gebucht";
} else if ($event['busy']['busy'] == 2) {
$event['busy']['busy'] = "mit Vorbehalt";
} else {
$event['busy']['busy'] = "frei";
}
if ($event['allDay']['allDay'] == 0) {
$starttime = date("H:i", strtotime($event['cstart']['cstart']));
$endtime = date("H:i", strtotime($event['cend']['cend']));
$startdate = date("d.m.Y", strtotime($event['cstart']['cstart']));
$enddate = date("d.m.Y", strtotime($event['cend']['cend']));
} else {
$starttime = "";
$startdate = date("d.m.Y", strtotime($event['cstart']['cstart']));
$endtime = "";
$enddate = date("d.m.Y", strtotime($event['cend']['cend']) - 7500);
}
if ($event['event_type']['event_type'] != "") {
$helper = new Html();
$richText = $helper->toRichTextObject($event['description']['description']);
$calendarType = $eventTypes[$event['event_type']['event_type']];
$sheet->setCellValue([1, $counter], $event['calendar_name']['calendar_name'])
->setCellValue([2, $counter], $startdate)
->setCellValue([3, $counter], $starttime)
->setCellValue([4, $counter], $enddate)
->setCellValue([5, $counter], $endtime)
->setCellValue([6, $counter], $event['ccategory']['ccategory'])
->setCellValue([7, $counter], $event['location']['location'])
->setCellValue([8, $counter], $richText)
->setCellValue([9, $counter], $calendarType)
->setCellValue([10, $counter], $event['privateflag']['privateflag'])
->setCellValue([11, $counter], $event['busy']['busy'])
->setCellValue([12, $counter], $event['isorganizer']['isorganizer'])
;
$counter++;
}
}
header("Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
header('Content-disposition: attachment; filename="Kalender-' . date('Y-m-d_H-i-s') . '-' . uniqid() . '.xlsx"');
$writer->save('php://output');
exit;
}
protected function indexAction() protected function indexAction()
{ {
$this->layout()->setTemplate("Calendar/Index"); $this->layout()->setTemplate("Calendar/Index");
+12 -3
View File
@@ -32,6 +32,15 @@ class CalendarModel
'690', // Spusu '690', // Spusu
'699' // 3 (Drei) '699' // 3 (Drei)
); );
public static $eventTypes = array(
1 => 'Termin',
2 => 'IBN Xinon',
3 => 'IBN ESTMK',
4 => 'IBN Snopp',
5 => 'Störung',
6 => 'Support Gespräch'
);
public static $standardCalendarColors = [ public static $standardCalendarColors = [
'#b3d5f3', '#d0e2c0', '#bac2d4', '#c6d4c0', '#e3e3e3', '#b3d5f3', '#d0e2c0', '#bac2d4', '#c6d4c0', '#e3e3e3',
'#acdce5', '#cdb6c0', '#aae0e1', '#d9d9d9', '#d9e5d5', '#acdce5', '#cdb6c0', '#aae0e1', '#d9d9d9', '#d9e5d5',
@@ -149,7 +158,7 @@ class CalendarModel
die(); die();
} }
public static function getCalendarEvents($me, $id = 0, $r = 0) public static function getCalendarEvents($me, $id = 0, $r = 0, $returnvalues = false)
{ {
$rrulefreq = array('daily' => 'DAILY', 'weekly' => 'WEEKLY', 'relativeMonthly' => 'MONTHLY', 'yearly' => 'YEARLY', 'absoluteMonthly' => 'absoluteMonthly'); $rrulefreq = array('daily' => 'DAILY', 'weekly' => 'WEEKLY', 'relativeMonthly' => 'MONTHLY', 'yearly' => 'YEARLY', 'absoluteMonthly' => 'absoluteMonthly');
$calendar = self::search(array("user_id" => $me)); $calendar = self::search(array("user_id" => $me));
@@ -196,7 +205,7 @@ class CalendarModel
} }
$sql = "SELECT `cal_events`.id,`cal_events`.categories, 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,rrule_events FROM cal_events INNER JOIN `cal_calendars` ON (`cal_calendars`.`id`=`cal_events`.`calendar_id`) WHERE 1=1 $where "; $sql = "SELECT `cal_events`.id,`cal_events`.categories, 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,rrule_events FROM cal_events INNER JOIN `cal_calendars` ON (`cal_calendars`.`id`=`cal_events`.`calendar_id`) WHERE 1=1 $where ORDER BY start_time DESC";
$res = $dbcal->query($sql); $res = $dbcal->query($sql);
if ($dbcal->num_rows($res)) { if ($dbcal->num_rows($res)) {
@@ -384,7 +393,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda
$json = json_encode($json); $json = json_encode($json);
if ($id == 0) { if ($id == 0 && $returnvalues == false) {
echo trim($json); echo trim($json);
die(); die();
} else { } else {
+39 -12
View File
@@ -673,11 +673,12 @@ thead .fc-day-today .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion {
margin-left: -28px; margin-left: -28px;
margin-top: 4px; margin-top: 4px;
} }
.cursor-alias { .cursor-alias {
cursor: alias; cursor: alias;
} }
.fa-calendar-alert
{ .fa-calendar-alert {
font-size: 15px; font-size: 15px;
vertical-align: top; vertical-align: top;
margin-top: -1px; margin-top: -1px;
@@ -688,57 +689,65 @@ thead .fc-day-today .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion {
color: #ff0000; color: #ff0000;
opacity: 0.7; opacity: 0.7;
} }
.fa-calendar-alert:before { .fa-calendar-alert:before {
color: #fff; color: #fff;
} }
.fa-calendar-alert-search
{ .fa-calendar-alert-search {
font-size: 22px; font-size: 22px;
} }
.logo-top-search
{ .logo-top-search {
height: 20px; height: 20px;
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: pointer;
} }
.btn-light-search
{ .btn-light-search {
border-color: #e5e5e5; border-color: #e5e5e5;
padding: 3px 6px; padding: 3px 6px;
} }
.top-search-filter
{ .top-search-filter {
filter: grayscale(100%); filter: grayscale(100%);
} }
.fc-event-time { .fc-event-time {
z-index: 100; z-index: 100;
} }
.note-editor .dropdown-item { .note-editor .dropdown-item {
padding: .15rem 1.2rem; padding: .15rem 1.2rem;
} }
.note-editable p { .note-editable p {
line-height: 1.2; line-height: 1.2;
margin-bottom: 0; margin-bottom: 0;
} }
.tooltip-description p { .tooltip-description p {
margin-bottom: 0; margin-bottom: 0;
line-height: 1.2; line-height: 1.2;
} }
.note-dropdown-menu { .note-dropdown-menu {
max-height: 250px !important; max-height: 250px !important;
overflow-y: auto !important; overflow-y: auto !important;
z-index: 99999 !important; z-index: 99999 !important;
} }
.tox-dialog { .tox-dialog {
z-index: 1060 !important; /* oder ein höherer Wert, je nach Bedarf */ z-index: 1060 !important; /* oder ein höherer Wert, je nach Bedarf */
} }
.tox .tox-tbtn--select { .tox .tox-tbtn--select {
max-width: 95px; max-width: 95px;
} }
.fa-calendar-call
{ .fa-calendar-call {
font-size: 13px; font-size: 13px;
vertical-align: top; vertical-align: top;
margin-top: 0px; margin-top: 0px;
@@ -748,12 +757,30 @@ thead .fc-day-today .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion {
color: #ff0000; color: #ff0000;
opacity: 1; opacity: 1;
} }
.fa-calendar-call:before { .fa-calendar-call:before {
content: "\f2a0"; content: "\f2a0";
color: #000000; color: #000000;
opacity: 1; opacity: 1;
} }
.fa-calendar-call-search { .fa-calendar-call-search {
font-size: 21px; font-size: 21px;
margin-top: 0px; margin-top: 0px;
} }
.fa-xls-calendar {
font-size: 24px;
padding-top: 2px;
padding-bottom: 2px;
}
.btn-xls-calendar {
padding: 3px 6px;
background-color: #ffffff;
}
.btn-xls-calendar:hover, .btn-xls-calendar:focus, .btn-xls-calendar:active {
background-color: #ffffff !important;
border-color: #ffffff !important;
}
+58 -2
View File
@@ -556,8 +556,7 @@ document.addEventListener('DOMContentLoaded', function () {
$('.show-attendee').hide(); $('.show-attendee').hide();
$('#recurringCheck').prop('checked', true); $('#recurringCheck').prop('checked', true);
$('#recurringCheck').change(); $('#recurringCheck').change();
if (data.data.recurrence) if (data.data.recurrence) {
{
$('#rrule-frequency').val(data.data.recurrence.recurrence.rrule_frequency); $('#rrule-frequency').val(data.data.recurrence.recurrence.rrule_frequency);
$('#rrule-frequency').trigger('change'); $('#rrule-frequency').trigger('change');
$('#rrule-interval').val(data.data.recurrence.recurrence.rrule_interval); $('#rrule-interval').val(data.data.recurrence.recurrence.rrule_interval);
@@ -2094,6 +2093,63 @@ $(document).ready(function () {
} }
}); });
$("body").on("click", ".fa-xls-calendar", function (e) {
let checkbox = $(".calendar-check");
let calendarFilter = $('.btn-light-search');
let visibleCalendars = [];
let visibleCalendarTypes = [];
let mycalendar_id = $('#calendar-id').data('calendarid');
visibleCalendars.push(0);
checkbox.each(function () {
if ($(this).prop('checked')) {
visibleCalendars.push($(this).data('calendar_id'));
}
});
calendarFilter.each(function () {
if (!$(this).hasClass('top-search-filter')) {
visibleCalendarTypes.push($(this).data('ctype'));
}
});
$.ajax({
url: requestgenerateEventsxlsx,
method: 'POST',
data: {
visibleCalendars: visibleCalendars,
visibleCalendarTypes: visibleCalendarTypes
},
xhrFields: {
responseType: 'blob' // Erwartet binäre Daten als Blob
},
success: function (data, status, xhr) {
// Header auslesen, um den Dateinamen zu erhalten
var disposition = xhr.getResponseHeader('Content-Disposition');
var filename = "download.xlsx";
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
// Blob in eine URL umwandeln und Download auslösen
var url = window.URL.createObjectURL(data);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
},
error: function (jqXHR, status, error) {
console.error("Download error:", error);
}
});
});
$("body").on("blur", ".cal-group-name-input", function (e) { $("body").on("blur", ".cal-group-name-input", function (e) {
let groupname = $.trim($(this).val()); let groupname = $.trim($(this).val());
if (groupname.length > 3) { if (groupname.length > 3) {