Skip to content

Commit

Permalink
javascript and api to stop and display active records (#772)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpapst committed May 10, 2019
1 parent e619b5f commit 09da7cd
Show file tree
Hide file tree
Showing 86 changed files with 918 additions and 449 deletions.
7 changes: 3 additions & 4 deletions assets/app.js
@@ -1,15 +1,14 @@
// ------------------- INLINED ADMIN-LTE DEFINITIONS -------------------
// this was done to save around 300kb by:
// require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte');
// this was replaced to save around 300kb by:
// - removing moment locales which are not used
// - removing fullcalendar locales which are not used
// - removing icheck which is not used

//require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte');
// - removing jquery-ui which is not used?

const $ = require('jquery');
global.$ = global.jQuery = $;

require('jquery-ui');
require('bootstrap-sass');
require('jquery-slimscroll');
require('bootstrap-select');
Expand Down
10 changes: 8 additions & 2 deletions assets/js/KimaiLoader.js
Expand Up @@ -25,8 +25,11 @@ import KimaiSelectDataAPI from "./plugins/KimaiSelectDataAPI";
import KimaiDateTimePicker from "./plugins/KimaiDateTimePicker";
import KimaiAlternativeLinks from "./plugins/KimaiAlternativeLinks";
import KimaiAjaxModalForm from "./plugins/KimaiAjaxModalForm";
import KimaiActiveRecords from "./plugins/KimaiActiveRecords";
import KimaiRecentActivities from "./plugins/KimaiRecentActivities";
import KimaiEvent from "./plugins/KimaiEvent";
import KimaiAPILink from "./plugins/KimaiAPILink";
import KimaiAlert from "./plugins/KimaiAlert";

export default class KimaiLoader {

Expand Down Expand Up @@ -64,19 +67,22 @@ export default class KimaiLoader {

kimai.registerPlugin(new KimaiEvent());
kimai.registerPlugin(new KimaiAPI());
kimai.registerPlugin(new KimaiAlert());
kimai.registerPlugin(new KimaiActiveRecordsDuration('[data-since]'));
kimai.registerPlugin(new KimaiDatatableColumnView('data-column-visibility'));
kimai.registerPlugin(new KimaiThemeInitializer());
kimai.registerPlugin(new KimaiJqueryPluginInitializer());
kimai.registerPlugin(new KimaiDateRangePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDateTimePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDatatable());
kimai.registerPlugin(new KimaiDatatable('table.dataTable'));
kimai.registerPlugin(new KimaiToolbar());
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
//kimai.registerPlugin(new KimaiPauseRecord('li.messages-menu ul.menu li'));
kimai.registerPlugin(new KimaiRecentActivities('li.notifications-menu'));
kimai.registerPlugin(new KimaiActiveRecords('li.messages-menu', 'li.messages-menu-empty'));
kimai.registerPlugin(new KimaiAPILink('a.api-link'));
//kimai.registerPlugin(new KimaiPauseRecord('li.messages-menu ul.menu li'));

// notify all listeners that Kimai plugins can now be registered
kimai.getPlugin('event').trigger('kimai.pluginRegister');
Expand Down
16 changes: 15 additions & 1 deletion assets/js/plugins/KimaiAPI.js
Expand Up @@ -6,7 +6,7 @@
*/

/*!
* [KIMAI] KimaiToolbar: some event listener to handle the toolbar/data-table filter, toolbar and navigation
* [KIMAI] KimaiAPI: easy access to API methods
*/

import jQuery from 'jquery';
Expand All @@ -31,4 +31,18 @@ export default class KimaiAPI extends KimaiPlugin {
});
}

patch(url, callbackSuccess, callbackError) {
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'PATCH',
dataType: 'json',
success: callbackSuccess,
error: callbackError
});
}

}
70 changes: 70 additions & 0 deletions assets/js/plugins/KimaiAPILink.js
@@ -0,0 +1,70 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/*!
* [KIMAI] KimaiAPILink
*
* allows to assign the given selector to any element, which then is used as click-handler
* calling an API method and trigger the event from data-event attribute afterwards
*/

import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";

export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {

constructor(selector) {
super();
this.selector = selector;
}

init() {
const self = this;
document.addEventListener('click', function(event) {
let target = event.target;
while (!target.matches('body')) {
if (target.matches(self.selector)) {
const attributes = target.dataset;

let url = attributes['href'];
if (!url) {
url = target.getAttribute('href');
}

const method = attributes['method'];
const eventName = attributes['event'];
const api = self.getContainer().getPlugin('api');
const eventing = self.getContainer().getPlugin('event');
const alert = self.getContainer().getPlugin('alert');

if (method === 'PATCH') {
api.patch(url, function(result) {
eventing.trigger(eventName);
if (attributes.msgSuccess) {
alert.success(attributes.msgSuccess);
}
}, function(xhr, err) {
let message = 'action.update.error';
if (attributes.msgError) {
message = attributes.msgError;
}
if (xhr.responseJSON && xhr.responseJSON.message) {
err = xhr.responseJSON.message;
}
alert.error(message, err);
});
}

event.preventDefault();
event.stopPropagation();
}

target = target.parentNode;
}
});
}

}
113 changes: 113 additions & 0 deletions assets/js/plugins/KimaiActiveRecords.js
@@ -0,0 +1,113 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/*!
* [KIMAI] KimaiActiveRecords: responsible to display the users active records
*/

import KimaiPlugin from '../KimaiPlugin';

export default class KimaiActiveRecords extends KimaiPlugin {

constructor(selector, selectorEmpty) {
super();
this.selector = selector;
this.selectorEmpty = selectorEmpty;
}

getId() {
return 'active-records';
}

init() {
const menu = document.querySelector(this.selector);

// the menu can be hidden if user has no permissions to see it
if (menu === null) {
return;
}

const dropdown = menu.querySelector('ul.dropdown-menu');

this.attributes = dropdown.dataset;
this.itemList = dropdown.querySelector('li > ul.menu');
this.label = menu.querySelector('a > span.label');

const self = this;
const handle = function() { self.reloadActiveRecords(); };

document.addEventListener('kimai.timesheetUpdate', handle);
document.addEventListener('kimai.activityUpdate', handle);
document.addEventListener('kimai.projectUpdate', handle);
document.addEventListener('kimai.customerUpdate', handle);
}

emptyList() {
this.itemList.innerHTML = '';
}

_toggleMenu(hasEntries) {
const menu = document.querySelector(this.selector);
const menuEmpty = document.querySelector(this.selectorEmpty);

menu.style.display = hasEntries ? 'inline-block' : 'none';
if (menuEmpty !== null) {
menuEmpty.style.display = !hasEntries ? 'inline-block' : 'none';
}
}

setEntries(entries) {
this._toggleMenu(entries.length > 0);

if (entries.length === 0) {
this.label.innerText = '';
this.emptyList();
return;
}

let htmlToInsert = '';
const durations = this.getContainer().getPlugin('timesheet-duration');

for (let timesheet of entries) {
htmlToInsert +=
`<li>` +
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStop kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">` +
`<div class="pull-left">` +
`<i class="${ this.attributes['icon'] } fa-2x"></i>` +
`</div>` +
`<h4>` +
`<span>${ timesheet.activity.name }</span>` +
`<small>` +
`<span data-title="true" data-since="${ timesheet.begin }" data-format="${ this.attributes['format'] }">${ durations.formatDuration(timesheet.duration, this.attributes['format']) }</span>` +
`</small>` +
`</h4>` +
`<p>${ timesheet.project.name } (${ timesheet.project.customer.name })</p>` +
`</a>` +
`</li>`;
}

if (this.label.dataset.warning < entries.length) {
this.label.classList = 'label label-danger';
} else {
this.label.classList = 'label label-warning';
}
this.label.innerText = entries.length;
this.itemList.innerHTML = htmlToInsert;

durations.updateRecords();
}

reloadActiveRecords() {
const self = this;
const apiService = this.getContainer().getPlugin('api');

apiService.get(this.attributes['api'], function(result) {
self.setEntries(result);
});
}

}
37 changes: 20 additions & 17 deletions assets/js/plugins/KimaiActiveRecordsDuration.js
Expand Up @@ -19,19 +19,17 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {
this.selector = selector;
}

init() {
this.updateRecords();
this.registerUpdates(10000);
getId() {
return 'timesheet-duration';
}

registerUpdates(interval) {
let self = this;
this._updatesHandler = setInterval(
function() {
self.updateRecords();
},
interval
);
init() {
this.updateRecords();
const self = this;
const handle = function() { self.updateRecords(); };
this._updatesHandler = setInterval(handle, 10000);
// this will probably not work as expected, as other event-handler might need longer to update the DOM
document.addEventListener('kimai.timesheetUpdate', handle);
}

unregisterUpdates() {
Expand All @@ -40,18 +38,25 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {

updateRecords() {
let durations = [];
for(let record of document.querySelectorAll(this.selector)) {
const activeRecords = document.querySelectorAll(this.selector);

if (activeRecords.length === 0) {
document.title = document.querySelector('body').dataset['title'];
return;
}

for(let record of activeRecords) {
const since = record.getAttribute('data-since');
const format = record.getAttribute('data-format');
const duration = KimaiActiveRecordsDuration._getDuration(since, format);
const duration = this.formatDuration(since, format);
if (record.getAttribute('data-title') !== null) {
durations.push(duration);
}
record.textContent = duration;
}

if (durations.length === 0) {
return this;
return;
}

let title = durations.shift();
Expand All @@ -61,11 +66,9 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {
title += prefix + duration;
}
document.title = title;

return this;
}

static _getDuration(since, format) {
formatDuration(since, format) {
const duration = moment.duration(moment(new Date()).diff(moment(since)));

let hours = parseInt(duration.asHours()).toString();
Expand Down

0 comments on commit 09da7cd

Please sign in to comment.