improved calendar (#784)

This commit is contained in:
Kevin Papst
2019-05-19 16:16:20 +02:00
committed by GitHub
parent 1903d6fb77
commit d2ad87d09c
66 changed files with 1030 additions and 1190 deletions

View File

@@ -8,7 +8,7 @@
<div class="row">
<div class="col-md-3">
<div class="box box-primary">
<div class="box box-{{ admin_lte_context.widget.type }}">
<div class="box-body">
<img src="{{ asset('apple-touch-icon.png') }}" class="profile-user-img img-responsive img-circle" alt="Kimai logo" style="border: 0;">
<h3 class="profile-username text-center">{{ constant('App\\Constants::SOFTWARE') }}</h3>

View File

@@ -56,11 +56,19 @@
{% block navbar_start %}
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %}
{% if is_granted('view_own_timesheet') %}
<li class="visible-xs-inline-block">
<a href="{{ path('timesheet') }}" class="ddt-large">
<i class="{{ 'timesheet'|icon }} fa-2x"></i>
</a>
</li>
{% if app.user.preferenceValue('login.initial_view') == 'calendar' %}
<li class="visible-xs-inline-block">
<a href="{{ path('calendar') }}" class="ddt-large">
<i class="{{ 'calendar'|icon }} fa-2x"></i>
</a>
</li>
{% else %}
<li class="visible-xs-inline-block">
<a href="{{ path('timesheet') }}" class="ddt-large">
<i class="{{ 'timesheet'|icon }} fa-2x"></i>
</a>
</li>
{% endif %}
{% endif %}
{% if is_granted('view_invoice') %}
<li class="visible-xs-inline-block">
@@ -176,7 +184,7 @@
{# no call to parent(), as we use a custom built for the frontend assets and don't want the default <script> #}
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
window.kimai = new KimaiWebLoader(
var loader = new KimaiWebLoader(
{
locale: '{{ app.request.locale }}',
twentyFourHours: {{ 'true'|hour24('false') }}
@@ -206,6 +214,7 @@
'delete': '{{ 'action.delete' |trans }}'
}
);
window.kimai = loader.getKimai();
});
</script>
{% set event = trigger(constant('App\\Event\\ThemeEvent::JAVASCRIPT')) %}

View File

@@ -1,43 +1,91 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/actions.html.twig" as actions %}
{% block page_title %}{{ 'calendar.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'calendar.subtitle'|trans }}{% endblock %}
{% block page_actions %}
{% set actions = {'list': path('timesheet')} %}
{% if is_granted('create_own_timesheet') %}
{% set actions = actions|merge({'create': {'url': path('timesheet_create', {'origin': 'calendar'})}}) %}
{% endif %}
{{ widgets.page_actions(actions) }}
{% endblock %}
{% block page_actions %}{{ actions.calendar('index') }}{% endblock %}
{% block main %}
<div class="row">
<div class="col-md-12">
<div class="box box-success">
<div class="box-body no-padding">
<div id="calendar"></div>
</div>
</div>
</div>
</div>
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_body_class %}no-padding{% endblock %}
{% block box_body %}
<div id="timesheet_calendar"></div>
{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
$(function () {
$('#calendar').fullCalendar({
document.addEventListener('kimai.timesheetUpdate', function() {
$('#timesheet_calendar').fullCalendar('refetchEvents');
});
var changeHandler = function(event, delta, revertFunc) {
var updateUrl = '{{ path('patch_timesheet', {id: '-XX-'}) }}'.replace('-XX-', event.id);
var API = kimai.getPlugin('api');
var ALERT = kimai.getPlugin('alert');
var payload = {'begin': event.start.format()};
if (event.end !== null && event.end !== undefined) {
payload.end = event.end.format();
} else {
payload.end = null;
}
API.patch(updateUrl, JSON.stringify(payload), function(result) {
ALERT.success('action.update.success');
}, function(xhr, err) {
if (xhr.responseJSON && xhr.responseJSON.message) {
err = xhr.responseJSON.message;
}
ALERT.error('action.update.error', err);
revertFunc();
});
};
document.addEventListener('kimai.initialized', function(event) {
var API = event.detail.kimai.getPlugin('api');
$('#timesheet_calendar').fullCalendar({
defaultView: '{{ app.user.getPreferenceValue('calendar.initial_view') }}',
{% if not google.apiKey is null %}
googleCalendarApiKey: '{{ google.apiKey }}',
{% endif %}
eventSources: [
{
url: '{{ path('calendar_entries') }}',
color: '#00a65a',
borderColor: '#00a65a',
events: function(start, end, timezone, callback) {
var from = moment(start.format()).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
var to = moment(end.format()).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
API.get('{{ path('get_timesheets') }}?size=1000&full=true&begin='+from+'&end='+to, {}, function(result) {
var apiEvents = [];
for (var record of result) {
var color = record.activity.color;
if (color === null) {
color = record.project.color;
if (color === null) {
color = record.project.customer.color;
}
}
apiEvents.push({
id: record.id,
title: record.activity.name,
description: record.description,
start: record.begin,
end: record.end,
activity: record.activity.name,
project: record.project.name,
customer: record.project.customer.name,
tags: record.tags,
color: color,
});
}
callback(apiEvents);
});
},
color: '#d2d6de', // see class "dot"
name: 'kimaiUserTimeSource'
}
{% for source in google.sources %}
@@ -46,7 +94,7 @@
googleCalendarId: '{{ source.uri }}',
name: '{{ source.id }}',
color: '{{ source.color }}',
borderColor: '{{ source.color }}'
editable: false
}
{% endfor %}
],
@@ -57,38 +105,46 @@
},
eventLimit: {{ config.dayLimit }},
weekNumbers: {% if config.showWeekNumbers %}true{% else %}false{% endif %},
allDaySlot: false,
navLinks: true,
locale: '{{ app.request.locale }}',
editable : false,
droppable : false,
/*
// TODO calculate height properly instead of relying on the default aspectRatio
height: function() {
return 'parent';
},
*/
height: 'auto',
nowIndicator: true,
now: '{{ now|date_format('c') }}',
//timezone: '{{ app.user.preferenceValue("timezone") }}',
timezone: '{{ app.user.preferenceValue("timezone") }}',
weekends: {% if config.showWeekends %}true{% else %}false{% endif %},
businessHours: {
dow: [{{ config.businessDays|join(',') }}],
start: '{{ config.businessTimeBegin }}',
end: '{{ config.businessTimeEnd }}'
},
eventRender: function(eventObj, $el) {
if (eventObj.source.ajaxSettings.name !== 'kimaiUserTimeSource') {
// don't show popover for google calendar
if (eventObj.source.ajaxSettings !== undefined) {
return;
}
// or when an event is dragged or resized
if(window.hidePopover) {
return;
}
$el.popover({
title: eventObj.title,
title: eventObj.start.format('L') + ' | ' + eventObj.start.format('LT') + ' - ' + (eventObj.end ? eventObj.end.format('LT') : ''),
content:
'<div class="calendar-entry">' +
'<ul>' +
'<li>' + '{{ 'label.date'|trans }}: ' + eventObj.start.format('L') + '</li>' +
'<li>' + '{{ 'label.begin'|trans }}: ' + eventObj.start.format('LT') + '</li>' +
'<li>' + '{{ 'label.end'|trans }}: ' + (eventObj.end ? eventObj.end.format('LT') : '<i>-</i>') + '</li>' +
'<hr>' +
'<li>' + '{{ 'label.customer'|trans }}: ' + eventObj.customer + '</li>' +
'<li>' + '{{ 'label.project'|trans }}: ' + eventObj.project + '</li>' +
'<li>' + '{{ 'label.activity'|trans }}: ' + eventObj.activity + '</li>' +
(eventObj.tags !== null && eventObj.tags.length > 0 ? '<li>' + '{{ 'label.tags'|trans }}: ' + eventObj.tags + '</li>' : '') +
'</ul>' +
(eventObj.description ? '<hr><p>' + eventObj.description + '</p>' : '') +
(eventObj.description || eventObj.tags ? '<hr>' : '') +
(eventObj.description ? '<p>' + eventObj.description + '</p>' : '') +
(eventObj.tags !== null && eventObj.tags.length > 0 ? '<span class="badge bg-green">' + eventObj.tags.join('</span> <span class="badge bg-green">') + '</span>' : '') +
'</div>'
,
trigger: 'hover',
@@ -97,26 +153,63 @@
html: true
});
},
eventClick: function(calEvent, jsEvent, view) {
{% if is_granted('edit_own_timesheet') %}
location.href = '{{ path('timesheet_edit', {id: '-XX-'}) }}?origin=calendar'.replace('-XX-', calEvent.id);
{% endif %}
},
{% if is_granted('create_own_timesheet') %}
// day-clicks are always triggered, unless a selection was created
// so clicking in a day (month view) or any slot (week and day view) will trigger a dayClick
// BEFORE triggering a select - make sure not two create dialogs are requested
dayClick: function(date, jsEvent, view) {
{% if is_granted('create_own_timesheet') %}
location.href = '{{ path('timesheet_create') }}' + '?origin=calendar&begin=' + date.format();
{% endif %}
},
selectable: true,
select: function( start, end, jsEvent, view) {
if(view.type === 'month') {
if(view.type !== 'month') {
return;
}
{% if is_granted('create_own_timesheet') %}
location.href = '{{ path('timesheet_create') }}' + '?from=' + start.format() + '&to=' + end.format();
{% endif %}
}
var param = 'begin';
var begin = date.format();
var createUrl = '{{ path('timesheet_create') }}' + '?' + param + '=' + begin;
kimai.getPlugin('modal').openUrlInModal(createUrl);
},
selectable: true,
select: function(start, end, jsEvent, view) {
if(view.type === 'month') {
// multi-day clicks are NOT allowed in the month view, as simple day clicks would also trigger
// a select - there is no way to distinguish a simple click and a two day selection
return;
}
var createUrl = '{{ path('timesheet_create') }}' + '?from=' + start.format() + '&to=' + end.format();
kimai.getPlugin('modal').openUrlInModal(createUrl);
},
{% endif %}
{% if is_granted('edit_own_timesheet') %}
eventClick: function(eventObj, jsEvent, view) {
if (eventObj.source.ajaxSettings !== undefined) {
jsEvent.preventDefault();
return;
}
var editUrl = '{{ path('timesheet_edit', {id: '-XX-'}) }}'.replace('-XX-', eventObj.id);
kimai.getPlugin('modal').openUrlInModal(editUrl);
},
editable: true,
eventDragStart: function(event, jsEvent, ui, view) {
window.hidePopover = true;
},
eventDragStop: function( event, jsEvent, ui, view ) {
window.hidePopover = false;
},
eventDrop: changeHandler,
eventResizeStart: function(event, jsEvent, ui, view) {
window.hidePopover = true;
},
eventResizeStop: function( event, jsEvent, ui, view ) {
window.hidePopover = false;
},
eventResize: changeHandler,
{% endif %}
/*
slotDuration: '00:30:00', // TODO make me configurable
*/
minTime: '{{ config.timeframeBegin }}',
maxTime: '{{ config.timeframeEnd }}',
defaultTimedEventDuration: '00:30'
})
})
});
</script>
{% endblock %}

View File

@@ -145,6 +145,7 @@
function updateTimesheetExportState(button, id, exported)
{
// FIXME use kimai API and ALERT
$.ajax({
url: '{{ path('patch_timesheet', {id: '-s-'}) }}'.replace('-s-', id),
headers: {
@@ -171,7 +172,6 @@
}, error: function(jqXHR, textStatus, errorThrown) {
var message = 'An error occured';
if (jqXHR.responseJSON !== undefined) {
console.log(jqXHR);
message = jqXHR.responseJSON.message;
if (jqXHR.responseJSON.errors !== undefined)
{

View File

@@ -191,6 +191,18 @@
{% endif %}
{% endmacro %}
{% macro calendar(view) %}
{% import "macros/widgets.html.twig" as widgets %}
{% set actions = {} %}
{% if is_granted('create_own_timesheet') %}
{% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %}
{% endif %}
{% set event = trigger('actions.calendar', {'actions': actions, 'view': view}) %}
{{ widgets.page_actions(event.payload.actions) }}
{% endmacro %}
{% macro timesheets(view) %}
{% import "macros/widgets.html.twig" as widgets %}
@@ -199,7 +211,6 @@
{% set actions = actions|merge({'download': {'onclick': 'return exportTimesheet()'}}) %}
{% endif %}
{% set actions = actions|merge({'visibility': '#modal_timesheet'}) %}
{% set actions = actions|merge({'calendar': path('calendar')}) %}
{% if is_granted('create_own_timesheet') %}
{% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %}
{% endif %}

View File

@@ -72,7 +72,7 @@
{% macro data_table_header(name, columns, skipStripped, reloadEvent) %}
{% import _self as macro %}
<div class="box data_table" id="datatable_{{ name }}">
<div class="box box-{{ admin_lte_context.widget.type }} data_table" id="datatable_{{ name }}">
<div class="box-body no-padding">
<div class="dataTables_wrapper form-inline dt-bootstrap">
<div class="row">

View File

@@ -150,6 +150,7 @@
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}"></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}"></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}"></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'tags') }}"></td>
<td class="actions"></td>
</tr>
{% endmacro %}

View File

@@ -24,7 +24,7 @@
{% endblock %}
{% macro profile_infos(user, stats) %}
<div class="box box-primary">
<div class="box box-{{ admin_lte_context.widget.type }}">
<div class="box-header with-border">
<h3 class="box-title">{{ 'profile.about_me'|trans }}</h3>
</div>
@@ -46,7 +46,7 @@
{% import "@AdminLTE/Macros/default.html.twig" as macro %}
{% import "macros/widgets.html.twig" as widgets %}
<div class="box box-primary">
<div class="box box-{{ admin_lte_context.widget.type }}">
<div class="box-body box-profile">
{{ macro.avatar(user.avatar, user.username, 'profile-user-img img-responsive img-circle') }}
<h3 class="profile-username text-center">{{ widgets.username(user) }}</h3>