refactored javascript to ES6 classes (#759)
This commit is contained in:
@@ -62,9 +62,6 @@ require('fullcalendar/dist/fullcalendar.min.css');
|
||||
require('chart.js/dist/Chart.min');
|
||||
|
||||
// ------ Kimai itself ------
|
||||
require('./js/kimai.js');
|
||||
require('./js/KimaiDatatableColumnView.js');
|
||||
require('./js/KimaiActiveRecordsDuration.js');
|
||||
require('./js/toolbar.js');
|
||||
require('./js/KimaiWebLoader.js');
|
||||
require('./images/default_avatar.png');
|
||||
require('./images/signature.png');
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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] KimaiActiveRecordsDuration: updates active records on your personal timesheet view
|
||||
*/
|
||||
|
||||
// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['moment'], function (moment) {
|
||||
return (root.KimaiActiveRecordsDuration = factory(moment));
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
let moment = (typeof window != 'undefined') ? window.moment : undefined;
|
||||
if (!moment) {
|
||||
moment = require('moment');
|
||||
}
|
||||
module.exports = factory(moment);
|
||||
} else {
|
||||
root.KimaiActiveRecordsDuration = factory(root.moment);
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function (moment) {
|
||||
|
||||
class KimaiActiveRecordsDuration {
|
||||
constructor(selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
registerUpdates(timeout) {
|
||||
let self = this;
|
||||
window.setTimeout(
|
||||
function() {
|
||||
self.updateRecords().registerUpdates(timeout);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
updateRecords() {
|
||||
let durations = [];
|
||||
for(let record of document.querySelectorAll(this.selector)) {
|
||||
const since = record.getAttribute('data-since');
|
||||
const format = record.getAttribute('data-format');
|
||||
const duration = this.getDuration(since, format);
|
||||
if (record.getAttribute('data-title') !== null) {
|
||||
durations.push(duration);
|
||||
}
|
||||
record.textContent = duration;
|
||||
}
|
||||
|
||||
if (durations.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let title = durations.shift();
|
||||
let prefix = ' | ';
|
||||
|
||||
for (let duration of durations.slice(0, 2)) {
|
||||
title += prefix + duration;
|
||||
}
|
||||
document.title = title;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getDuration(since, format) {
|
||||
let duration = moment.duration(moment(new Date()).diff(moment(since)));
|
||||
|
||||
let hours = parseInt(duration.asHours());
|
||||
let minutes = duration.minutes();
|
||||
let seconds = duration.seconds();
|
||||
let formatted = '';
|
||||
|
||||
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
|
||||
if (hours.length === 1) {
|
||||
hours = '0' + hours;
|
||||
}
|
||||
|
||||
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
|
||||
}
|
||||
}
|
||||
|
||||
return KimaiActiveRecordsDuration;
|
||||
|
||||
}));
|
||||
26
assets/js/KimaiConfiguration.js
Normal file
26
assets/js/KimaiConfiguration.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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] KimaiConfiguration: handling all configuration and runtime settings
|
||||
*/
|
||||
|
||||
export default class KimaiConfiguration {
|
||||
|
||||
constructor(configurations) {
|
||||
this._configurations = configurations;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
return this._configurations[name];
|
||||
}
|
||||
|
||||
has(name) {
|
||||
return name in this._configurations;
|
||||
}
|
||||
|
||||
}
|
||||
91
assets/js/KimaiContainer.js
Normal file
91
assets/js/KimaiContainer.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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] KimaiContainer
|
||||
*
|
||||
* ServiceContainer for Kimai
|
||||
*/
|
||||
|
||||
import KimaiConfiguration from './KimaiConfiguration';
|
||||
import KimaiTranslation from './KimaiTranslation';
|
||||
import KimaiPlugin from './KimaiPlugin';
|
||||
|
||||
export default class KimaiContainer {
|
||||
|
||||
/**
|
||||
* Create a new Container with the given configurations and translations.
|
||||
*
|
||||
* @param {Object} configuration
|
||||
* @param {Object} translation
|
||||
*/
|
||||
constructor(configuration, translation) {
|
||||
if (!(configuration instanceof KimaiConfiguration)) {
|
||||
throw new Error('Configuration needs to a KimaiConfiguration instance');
|
||||
}
|
||||
this._configuration = configuration;
|
||||
|
||||
if (!(translation instanceof KimaiTranslation)) {
|
||||
throw new Error('Configuration needs to a KimaiTranslation instance');
|
||||
}
|
||||
this._translation = translation;
|
||||
this._plugins = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new Plugin.
|
||||
*
|
||||
* @param {KimaiPlugin} plugin
|
||||
* @returns {KimaiPlugin}
|
||||
*/
|
||||
registerPlugin(plugin) {
|
||||
if (!(plugin instanceof KimaiPlugin)) {
|
||||
throw new Error('Invalid plugin given, needs to be a KimaiPlugin instance');
|
||||
}
|
||||
|
||||
plugin.setContainer(this);
|
||||
|
||||
this._plugins.push(plugin);
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {KimaiPlugin}
|
||||
*/
|
||||
getPlugin(name) {
|
||||
for (let plugin of this._plugins) {
|
||||
if (plugin.getId() !== null && plugin.getId() === name) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
throw new Error('Unknown plugin: ' + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Array<KimaiPlugin>}
|
||||
*/
|
||||
getPlugins() {
|
||||
return this._plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {KimaiTranslation}
|
||||
*/
|
||||
getTranslation() {
|
||||
return this._translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {KimaiConfiguration}
|
||||
*/
|
||||
getConfiguration() {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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] KimaiDatatableColumnView: manages the visibility of data-table columns in cookies
|
||||
*/
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['jquery'], function (jquery) {
|
||||
return (root.KimaiDatatableColumnView = factory(jquery));
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
let jQuery = (typeof window != 'undefined') ? window.jQuery : undefined;
|
||||
if (!jQuery) {
|
||||
jQuery = require('jquery');
|
||||
if (!jQuery.fn) {
|
||||
jQuery.fn = {};
|
||||
}
|
||||
}
|
||||
module.exports = factory(jQuery);
|
||||
} else {
|
||||
root.KimaiDatatableColumnView = factory(root.jQuery);
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function ($) {
|
||||
|
||||
/**
|
||||
* This is my first approach on ES6, so it can be optimized.
|
||||
* Please: show your JS skills and teach a PHP backend developer how to do it properly, sent a PR!
|
||||
*
|
||||
* BTW: I tried to get rid of it, but jQuery is still required for the bootstrap modal ...
|
||||
*/
|
||||
class KimaiDatatableColumnView {
|
||||
|
||||
constructor(selector) {
|
||||
this.id = selector;
|
||||
this.modal = document.getElementById('modal_' + selector);
|
||||
this.bindButtons();
|
||||
}
|
||||
|
||||
bindButtons() {
|
||||
let self = this;
|
||||
this.modal.querySelector('button[data-type=save]').addEventListener('click', function() {
|
||||
self.saveVisibility();
|
||||
});
|
||||
this.modal.querySelector('button[data-type=reset]').addEventListener('click', function() {
|
||||
self.resetVisibility();
|
||||
});
|
||||
for (let checkbox of this.modal.querySelectorAll('form input[type=checkbox]')) {
|
||||
checkbox.addEventListener('click', function () {
|
||||
self.changeVisibility(checkbox.getAttribute('name'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveVisibility() {
|
||||
const form = this.modal.getElementsByTagName('form')[0];
|
||||
let settings = {};
|
||||
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||
settings[checkbox.getAttribute('name')] = checkbox.checked;
|
||||
}
|
||||
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
|
||||
$(this.modal).modal('toggle');
|
||||
}
|
||||
|
||||
resetVisibility() {
|
||||
const form = this.modal.getElementsByTagName('form')[0];
|
||||
Cookies.remove(form.getAttribute('name'));
|
||||
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||
if (!checkbox.checked) {
|
||||
checkbox.click();
|
||||
}
|
||||
}
|
||||
$(this.modal).modal('toggle');
|
||||
}
|
||||
|
||||
changeVisibility(columnName) {
|
||||
const table = document.getElementById('datatable_' + this.id).getElementsByClassName('dataTable')[0];
|
||||
let column = 0;
|
||||
let foundColumn = false;
|
||||
for (let columnElement of table.getElementsByTagName('th')) {
|
||||
if (columnElement.getAttribute('data-field') === columnName) {
|
||||
foundColumn = true;
|
||||
break;
|
||||
}
|
||||
column++;
|
||||
}
|
||||
|
||||
if (!foundColumn) {
|
||||
console.error('Could not find column: ' + columnName);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let rowElement of table.getElementsByTagName('tr')) {
|
||||
rowElement.children[column].classList.toggle('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return KimaiDatatableColumnView;
|
||||
|
||||
}));
|
||||
|
||||
91
assets/js/KimaiLoader.js
Normal file
91
assets/js/KimaiLoader.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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] KimaiLoader: bootstrap the application and all plugins
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import KimaiTranslation from "./KimaiTranslation";
|
||||
import KimaiConfiguration from "./KimaiConfiguration";
|
||||
import KimaiContainer from "./KimaiContainer";
|
||||
import KimaiActiveRecordsDuration from './plugins/KimaiActiveRecordsDuration.js';
|
||||
import KimaiDatatableColumnView from './plugins/KimaiDatatableColumnView.js';
|
||||
import KimaiThemeInitializer from "./plugins/KimaiThemeInitializer";
|
||||
import KimaiJqueryPluginInitializer from "./plugins/KimaiJqueryPluginInitializer";
|
||||
import KimaiDateRangePicker from "./plugins/KimaiDateRangePicker";
|
||||
import KimaiDatatable from "./plugins/KimaiDatatable";
|
||||
import KimaiToolbar from "./plugins/KimaiToolbar";
|
||||
import KimaiAPI from "./plugins/KimaiAPI";
|
||||
import KimaiSelectDataAPI from "./plugins/KimaiSelectDataAPI";
|
||||
import KimaiDateTimePicker from "./plugins/KimaiDateTimePicker";
|
||||
import KimaiAlternativeLinks from "./plugins/KimaiAlternativeLinks";
|
||||
import KimaiAjaxModalForm from "./plugins/KimaiAjaxModalForm";
|
||||
|
||||
export default class KimaiLoader {
|
||||
|
||||
constructor(configurations, translations) {
|
||||
const defaultTranslations = {
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
apply: 'Apply',
|
||||
cancel: 'Cancel',
|
||||
thisWeek: 'This week',
|
||||
lastWeek: 'Last week',
|
||||
thisMonth: 'This month',
|
||||
lastMonth: 'Last month',
|
||||
thisYear: 'This year',
|
||||
lastYear: 'Last year',
|
||||
customRange: 'Custom range',
|
||||
};
|
||||
|
||||
translations = Object.assign(defaultTranslations, translations);
|
||||
|
||||
const defaultConfigurations = {
|
||||
locale: 'en',
|
||||
twentyFourHours: true
|
||||
};
|
||||
|
||||
configurations = Object.assign(defaultConfigurations, configurations);
|
||||
|
||||
// set the current locale for all javascript components
|
||||
moment.locale(configurations['locale']);
|
||||
|
||||
const kimai = new KimaiContainer(
|
||||
new KimaiConfiguration(configurations),
|
||||
new KimaiTranslation(translations)
|
||||
);
|
||||
|
||||
kimai.registerPlugin(new KimaiAPI());
|
||||
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 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'));
|
||||
|
||||
// notify all listeners that Kimai plugins can now be registered
|
||||
this._sendEvent('kimai.pluginRegister');
|
||||
|
||||
// initialize all plugins
|
||||
kimai.getPlugins().map(plugin => { plugin.init(); });
|
||||
|
||||
// notify all listeners that Kimai is now ready to be used
|
||||
this._sendEvent('kimai.initialized');
|
||||
}
|
||||
|
||||
_sendEvent(name) {
|
||||
document.dispatchEvent(new Event(name));
|
||||
}
|
||||
|
||||
}
|
||||
53
assets/js/KimaiPlugin.js
Normal file
53
assets/js/KimaiPlugin.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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] KimaiPlugin: base class for all plugins
|
||||
*/
|
||||
|
||||
import KimaiContainer from "./KimaiContainer";
|
||||
|
||||
export default class KimaiPlugin {
|
||||
|
||||
/**
|
||||
* Overwrite this method to initialize your plugin.
|
||||
*
|
||||
* It is called AFTER setContainer() and AFTER DOMContentLoaded was fired.
|
||||
* You don't have access to the container before this method!
|
||||
*/
|
||||
init() {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you return an ID, you indicate that your plugin can be used by other plugins.
|
||||
*
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KimaiContainer} core
|
||||
*/
|
||||
setContainer(core) {
|
||||
if (!(core instanceof KimaiContainer)) {
|
||||
throw new Error('Plugin was given an invalid KimaiContainer');
|
||||
}
|
||||
this._core = core;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns null, if xou call it BEFORE init().
|
||||
*
|
||||
* @returns {KimaiContainer}
|
||||
*/
|
||||
getContainer() {
|
||||
return this._core;
|
||||
}
|
||||
|
||||
}
|
||||
26
assets/js/KimaiTranslation.js
Normal file
26
assets/js/KimaiTranslation.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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] KimaiTranslation: handling translation strings
|
||||
*/
|
||||
|
||||
export default class KimaiTranslation {
|
||||
|
||||
constructor(translations) {
|
||||
this._translations = translations;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
return this._translations[name];
|
||||
}
|
||||
|
||||
has(name) {
|
||||
return name in this._translations;
|
||||
}
|
||||
|
||||
}
|
||||
33
assets/js/KimaiWebLoader.js
Normal file
33
assets/js/KimaiWebLoader.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* Main JS application file for Kimai 2. This file should be included in all pages.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] Wrapper class for loading Kimai app in browser script scope
|
||||
*/
|
||||
|
||||
import KimaiLoader from "./KimaiLoader";
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], function () {
|
||||
return (root.KimaiWebLoader = factory());
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.KimaiWebLoader = factory();
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
|
||||
class KimaiWebLoader extends KimaiLoader {
|
||||
}
|
||||
|
||||
return KimaiWebLoader;
|
||||
|
||||
}));
|
||||
@@ -1,400 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* Main JS application file for Kimai 2. This file should be included in all pages.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* [KIMAI] Main JS application file for Kimai 2
|
||||
*/
|
||||
|
||||
/** global: jQuery */
|
||||
/** global: moment */
|
||||
|
||||
if (typeof jQuery === 'undefined') {
|
||||
throw new Error('Kimai requires jQuery');
|
||||
}
|
||||
|
||||
if (typeof moment === 'undefined') {
|
||||
throw new Error('Kimai requires moment.js');
|
||||
}
|
||||
|
||||
/* kimai
|
||||
*
|
||||
* @type Object
|
||||
* @description $.kimai is the main object for the template's app.
|
||||
* It's used for implementing functions and options related
|
||||
* to the template. Keeping everything wrapped in an object
|
||||
* prevents conflict with other plugins and is a better
|
||||
* way to organize our code.
|
||||
*/
|
||||
$.kimai = {};
|
||||
|
||||
$(function() {
|
||||
"use strict";
|
||||
|
||||
$.kimai = {
|
||||
init: function(options) {
|
||||
if (typeof options !== 'undefined') {
|
||||
$.kimai.settings = $.extend({}, $.kimai.defaults, options);
|
||||
}
|
||||
|
||||
// set the current locale for all javascript components
|
||||
moment.locale($.kimai.settings['locale']);
|
||||
|
||||
// activate the dropdown functionality
|
||||
$('.dropdown-toggle').dropdown();
|
||||
// activate the tooltip functionality
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
// auto hide success messages, as they are just meant as user feedback and not as a permanent information
|
||||
this.activateAutomaticAlertRemove('div.alert-success', 5000);
|
||||
// activate the (daterangepicker) compound field in toolbar
|
||||
this.activateDateRangePicker('.content-wrapper');
|
||||
// single select boxes in toolbars
|
||||
this.activateDatePicker('.content-wrapper');
|
||||
// edit timesheet - date with time
|
||||
this.activateDateTimePicker('.content-wrapper');
|
||||
// some actions can be performed in a modal for a better UX
|
||||
this.activateAjaxFormInModal('.modal-ajax-form');
|
||||
// activate select boxes that load dynamic data via API
|
||||
this.activateApiSelects('select[data-related-select]');
|
||||
},
|
||||
reloadDatatableWithToolbarFilter: function() {
|
||||
// TODO check if toolbar form is present, if not, reload current URL
|
||||
var $form = $('.toolbar form');
|
||||
var loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
|
||||
$('section.content').append(loading);
|
||||
|
||||
// remove the empty fields to prevent errors
|
||||
var formData = $('.toolbar form :input')
|
||||
.filter(function(index, element) {
|
||||
return $(element).val() != '';
|
||||
})
|
||||
.serialize();
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
type: $form.attr('method'),
|
||||
data: formData,
|
||||
success: function(html) {
|
||||
$('section.content').replaceWith(
|
||||
$(html).find('section.content')
|
||||
);
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
$form.submit();
|
||||
}
|
||||
});
|
||||
},
|
||||
pauseRecord: function(selector) {
|
||||
$(selector + ' .pull-left i').hover(function () {
|
||||
var link = $(this).parents('a');
|
||||
link.attr('href', link.attr('href').replace('/stop', '/pause'));
|
||||
$(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange');
|
||||
},function () {
|
||||
var link = $(this).parents('a');
|
||||
link.attr('href', link.attr('href').replace('/pause', '/stop'));
|
||||
$(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle');
|
||||
});
|
||||
},
|
||||
activateAutomaticAlertRemove(selector, mseconds) {
|
||||
setTimeout(
|
||||
function() {
|
||||
$(selector).alert('close');
|
||||
},
|
||||
mseconds
|
||||
);
|
||||
},
|
||||
activateApiSelects: function(selector) {
|
||||
const self = this;
|
||||
$('body').on('change', selector, function(event) {
|
||||
let apiUrl = $(this).attr('data-api-url').replace('-s-', $(this).val());
|
||||
const targetSelect = '#' + $(this).attr('data-related-select');
|
||||
|
||||
// if the related target select does not exist, we do not need to load the related data
|
||||
if ($(targetSelect).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(this).val() === '') {
|
||||
if ($(this).attr('data-empty-url') === undefined) {
|
||||
self.updateSelect(targetSelect, {});
|
||||
$(targetSelect).attr('disabled', 'disabled');
|
||||
return;
|
||||
}
|
||||
apiUrl = $(this).attr('data-empty-url').replace('-s-', $(this).val());
|
||||
}
|
||||
|
||||
$(targetSelect).removeAttr('disabled');
|
||||
|
||||
$.ajax({
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'X-AUTH-SESSION': true,
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data){
|
||||
self.updateSelect(targetSelect, data);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
updateSelect: function(selectName, data) {
|
||||
var $select = $(selectName);
|
||||
var $emptyOption = $(selectName + ' option[value=""]');
|
||||
|
||||
$select.find('option').remove().end().find('optgroup').remove().end();
|
||||
|
||||
if ($emptyOption.length !== 0) {
|
||||
$select.append('<option value="">' + $emptyOption.text() + '</option>');
|
||||
}
|
||||
|
||||
$.each(data, function(i, obj) {
|
||||
$select.append('<option value="' + obj.id + '">' + obj.name + '</option>');
|
||||
});
|
||||
|
||||
// if we don't trigger the change, the other selects won't be resetted
|
||||
$select.trigger('change');
|
||||
|
||||
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
|
||||
$('.selectpicker').selectpicker('refresh');
|
||||
},
|
||||
activateDatePicker: function(selector) {
|
||||
$(selector + ' input[data-datepickerenable="on"]').each(function(index) {
|
||||
var localeFormat = $(this).data('format');
|
||||
$(this).daterangepicker({
|
||||
singleDatePicker: true,
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: $.kimai.settings['apply'],
|
||||
cancelLabel: $.kimai.settings['cancel'],
|
||||
customRangeLabel: $.kimai.settings['customRange']
|
||||
}
|
||||
});
|
||||
|
||||
$(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
$(this).val(picker.startDate.format(localeFormat));
|
||||
$(this).trigger("change");
|
||||
});
|
||||
});
|
||||
},
|
||||
activateDateTimePicker: function(selector) {
|
||||
$(selector + ' input[data-datetimepicker="on"]').each(function(index) {
|
||||
var localeFormat = $(this).data('format');
|
||||
$(this).daterangepicker({
|
||||
singleDatePicker: true,
|
||||
timePicker: true,
|
||||
timePicker24Hour: $.kimai.settings['twentyFourHours'],
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: $.kimai.settings['apply'],
|
||||
cancelLabel: $.kimai.settings['cancel'],
|
||||
customRangeLabel: $.kimai.settings['customRange']
|
||||
}
|
||||
});
|
||||
|
||||
$(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
$(this).val(picker.startDate.format(localeFormat));
|
||||
$(this).trigger("change");
|
||||
});
|
||||
});
|
||||
},
|
||||
activateDateRangePicker: function(selector) {
|
||||
$(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
|
||||
var localeFormat = $(this).data('format');
|
||||
var separator = $(this).data('separator');
|
||||
var rangesList = {};
|
||||
rangesList[$.kimai.settings['today']] = [moment(), moment()];
|
||||
rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
|
||||
rangesList[$.kimai.settings['thisWeek']] = [moment().startOf('week'), moment().endOf('week')];
|
||||
rangesList[$.kimai.settings['lastWeek']] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
|
||||
rangesList[$.kimai.settings['thisMonth']] = [moment().startOf('month'), moment().endOf('month')];
|
||||
rangesList[$.kimai.settings['lastMonth']] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
|
||||
rangesList[$.kimai.settings['thisYear']] = [moment().startOf('year'), moment().endOf('year')];
|
||||
rangesList[$.kimai.settings['lastYear']] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
|
||||
|
||||
$(this).daterangepicker({
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
autoApply: false,
|
||||
linkedCalendars: false,
|
||||
locale: {
|
||||
separator: separator,
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: $.kimai.settings['apply'],
|
||||
cancelLabel: $.kimai.settings['cancel'],
|
||||
customRangeLabel: $.kimai.settings['customRange']
|
||||
},
|
||||
ranges: rangesList,
|
||||
alwaysShowCalendars: true
|
||||
});
|
||||
|
||||
$(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
$(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
|
||||
$(this).trigger("change");
|
||||
});
|
||||
});
|
||||
},
|
||||
ajaxFormInModal: function(html) {
|
||||
// the modal that we use to render the form in
|
||||
var formIdentifier = '#remote_form_modal .modal-content form';
|
||||
var flashErrorIdentifier = 'div.alert-error';
|
||||
var $form = $(formIdentifier);
|
||||
var $modal = $('#remote_form_modal');
|
||||
|
||||
// will be (re-)activated later
|
||||
$form.off('submit');
|
||||
|
||||
// load new form from given content
|
||||
if ($(html).find('#form_modal .modal-content').length > 0 ) {
|
||||
// switch classes, in case the modal type changed
|
||||
$modal.on('hidden.bs.modal', function () {
|
||||
if ($modal.hasClass('modal-danger')) {
|
||||
$modal.removeClass('modal-danger');
|
||||
}
|
||||
});
|
||||
|
||||
if ($(html).find('#form_modal').hasClass('modal-danger')) {
|
||||
$modal.addClass('modal-danger');
|
||||
}
|
||||
|
||||
// TODO cleanup widgets before replacing the content?
|
||||
$('#remote_form_modal .modal-content').replaceWith(
|
||||
$(html).find('#form_modal .modal-content')
|
||||
);
|
||||
// activate new loaded widgets
|
||||
$.kimai.activateDateTimePicker(formIdentifier);
|
||||
}
|
||||
|
||||
// show error flash messages
|
||||
if ($(html).find(flashErrorIdentifier).length > 0) {
|
||||
$('#remote_form_modal .modal-body').prepend(
|
||||
$(html).find(flashErrorIdentifier)
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// a fix for firefox focus problems with datepicker in modal
|
||||
// see https://github.com/kevinpapst/kimai2/issues/618
|
||||
var enforceModalFocusFn = $.fn.modal.Constructor.prototype.enforceFocus;
|
||||
$.fn.modal.Constructor.prototype.enforceFocus = function() {};
|
||||
$modal.on('hidden.bs.modal', function () {
|
||||
$.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn;
|
||||
});
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// workaround for autofocus attribute, as the modal "steals" it
|
||||
$modal.on('shown.bs.modal', function () {
|
||||
$(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
|
||||
});
|
||||
|
||||
$modal.modal('show');
|
||||
|
||||
// the new form that was loaded via ajax
|
||||
$form = $(formIdentifier);
|
||||
|
||||
// click handler for modal save button, to send forms via ajax
|
||||
$form.on('submit', function(event){
|
||||
var btn = $(formIdentifier + ' button[type=submit]').button('loading');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
type: $form.attr('method'),
|
||||
data: $form.serialize(),
|
||||
success: function(html) {
|
||||
btn.button('reset');
|
||||
var hasFieldError = $(html).find('#form_modal .modal-content .has-error').length > 0;
|
||||
var hasFormError = $(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0;
|
||||
var hasFlashError = $(html).find(flashErrorIdentifier).length > 0;
|
||||
|
||||
if (hasFieldError || hasFormError || hasFlashError) {
|
||||
$.kimai.ajaxFormInModal(html);
|
||||
} else {
|
||||
$.kimai.reloadDatatableWithToolbarFilter();
|
||||
$modal.modal('hide');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
// FIXME problem in google and 500 error, keeps on submitting...
|
||||
// what else could we do? submitting again at least gives us the opportunity to see errors,
|
||||
// which maybe would be hidden otherwise... this one is totally up for discussion!
|
||||
$form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
activateAjaxFormInModal: function(selector) {
|
||||
$('body').on('click', selector, function(event) {
|
||||
// just in case an inner element is editable, than this should not be triggered
|
||||
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handles the "click" on table rows to open an entry for editing: when a button within a row is clicked,
|
||||
// we don't want the table row event to be processed - so we intercept it
|
||||
var target = event.target;
|
||||
if (event.currentTarget.tagName === 'TR') {
|
||||
while (target.tagName !== 'BODY') {
|
||||
if (target.tagName === 'A' || target.tagName === 'BUTTON') {
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// any element can open the modal - for none <a> elements use "data-href" instead of "href" attribute
|
||||
var href = $(this).attr('data-href');
|
||||
if (!href) {
|
||||
href = $(this).attr('href');
|
||||
}
|
||||
$.ajax({
|
||||
url: href,
|
||||
success: function(html) {
|
||||
$.kimai.ajaxFormInModal(html);
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
window.location = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// default values
|
||||
$.kimai.defaults = {
|
||||
locale: 'en',
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
apply: 'Apply',
|
||||
cancel: 'Cancel',
|
||||
thisWeek: 'This week',
|
||||
lastWeek: 'Last week',
|
||||
thisMonth: 'This month',
|
||||
lastMonth: 'Last month',
|
||||
thisYear: 'This year',
|
||||
lastYear: 'Last year',
|
||||
customRange: 'Custom range',
|
||||
twentyFourHours: true
|
||||
};
|
||||
|
||||
// once initialized, here are all values
|
||||
$.kimai.settings = {};
|
||||
|
||||
});
|
||||
34
assets/js/plugins/KimaiAPI.js
Normal file
34
assets/js/plugins/KimaiAPI.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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] KimaiToolbar: some event listener to handle the toolbar/data-table filter, toolbar and navigation
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiAPI extends KimaiPlugin {
|
||||
|
||||
getId() {
|
||||
return 'api';
|
||||
}
|
||||
|
||||
get(url, callback) {
|
||||
jQuery.ajax({
|
||||
url: url,
|
||||
headers: {
|
||||
'X-AUTH-SESSION': true,
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: callback
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
82
assets/js/plugins/KimaiActiveRecordsDuration.js
Normal file
82
assets/js/plugins/KimaiActiveRecordsDuration.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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] KimaiActiveRecordsDuration: activate the updates for all active timesheet records on this page
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiActiveRecordsDuration extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateRecords();
|
||||
this.registerUpdates(10000);
|
||||
}
|
||||
|
||||
registerUpdates(interval) {
|
||||
let self = this;
|
||||
this._updatesHandler = setInterval(
|
||||
function() {
|
||||
self.updateRecords();
|
||||
},
|
||||
interval
|
||||
);
|
||||
}
|
||||
|
||||
unregisterUpdates() {
|
||||
clearInterval(this._updatesHandler);
|
||||
}
|
||||
|
||||
updateRecords() {
|
||||
let durations = [];
|
||||
for(let record of document.querySelectorAll(this.selector)) {
|
||||
const since = record.getAttribute('data-since');
|
||||
const format = record.getAttribute('data-format');
|
||||
const duration = KimaiActiveRecordsDuration._getDuration(since, format);
|
||||
if (record.getAttribute('data-title') !== null) {
|
||||
durations.push(duration);
|
||||
}
|
||||
record.textContent = duration;
|
||||
}
|
||||
|
||||
if (durations.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let title = durations.shift();
|
||||
let prefix = ' | ';
|
||||
|
||||
for (let duration of durations.slice(0, 2)) {
|
||||
title += prefix + duration;
|
||||
}
|
||||
document.title = title;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static _getDuration(since, format) {
|
||||
const duration = moment.duration(moment(new Date()).diff(moment(since)));
|
||||
|
||||
let hours = parseInt(duration.asHours()).toString();
|
||||
let minutes = duration.minutes();
|
||||
let seconds = duration.seconds();
|
||||
|
||||
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
|
||||
if (hours.length === 1) {
|
||||
hours = '0' + hours;
|
||||
}
|
||||
|
||||
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
|
||||
}
|
||||
}
|
||||
137
assets/js/plugins/KimaiAjaxModalForm.js
Normal file
137
assets/js/plugins/KimaiAjaxModalForm.js
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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] KimaiAjaxModalForm
|
||||
*
|
||||
* allows to assign the given selector to any element, which then is used as click-handler:
|
||||
* opening a modal with the content from the URL given in the elements 'data-href' or 'href' attribute
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
|
||||
|
||||
export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableRow {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
init() {
|
||||
const self = this;
|
||||
|
||||
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
|
||||
jQuery.ajax({
|
||||
url: href,
|
||||
success: function(html) {
|
||||
self._openFormInModal(html);
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
console.log('Failed opening modal', err);
|
||||
window.location = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_openFormInModal(html) {
|
||||
const self = this;
|
||||
|
||||
// the modal that we use to render the form in
|
||||
let formIdentifier = '#remote_form_modal .modal-content form';
|
||||
let flashErrorIdentifier = 'div.alert-error';
|
||||
let form = jQuery(formIdentifier);
|
||||
let remoteModal = jQuery('#remote_form_modal');
|
||||
|
||||
// will be (re-)activated later
|
||||
form.off('submit');
|
||||
|
||||
// load new form from given content
|
||||
if (jQuery(html).find('#form_modal .modal-content').length > 0 ) {
|
||||
// switch classes, in case the modal type changed
|
||||
remoteModal.on('hidden.bs.modal', function () {
|
||||
if (remoteModal.hasClass('modal-danger')) {
|
||||
remoteModal.removeClass('modal-danger');
|
||||
}
|
||||
});
|
||||
|
||||
if (jQuery(html).find('#form_modal').hasClass('modal-danger')) {
|
||||
remoteModal.addClass('modal-danger');
|
||||
}
|
||||
|
||||
jQuery('#remote_form_modal .modal-content').replaceWith(
|
||||
jQuery(html).find('#form_modal .modal-content')
|
||||
);
|
||||
|
||||
// activate new loaded widgets
|
||||
self.getContainer().getPlugin('date-time-picker').activateDateTimePicker(formIdentifier);
|
||||
}
|
||||
|
||||
// show error flash messages
|
||||
if (jQuery(html).find(flashErrorIdentifier).length > 0) {
|
||||
jQuery('#remote_form_modal .modal-body').prepend(
|
||||
jQuery(html).find(flashErrorIdentifier)
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// a fix for firefox focus problems with datepicker in modal
|
||||
// see https://github.com/kevinpapst/kimai2/issues/618
|
||||
let enforceModalFocusFn = jQuery.fn.modal.Constructor.prototype.enforceFocus;
|
||||
jQuery.fn.modal.Constructor.prototype.enforceFocus = function() {};
|
||||
remoteModal.on('hidden.bs.modal', function () {
|
||||
jQuery.fn.modal.Constructor.prototype.enforceFocus = enforceModalFocusFn;
|
||||
});
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// workaround for autofocus attribute, as the modal "steals" it
|
||||
remoteModal.on('shown.bs.modal', function () {
|
||||
jQuery(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
|
||||
});
|
||||
|
||||
remoteModal.modal('show');
|
||||
|
||||
// the new form that was loaded via ajax
|
||||
form = jQuery(formIdentifier);
|
||||
|
||||
// click handler for modal save button, to send forms via ajax
|
||||
form.on('submit', function(event){
|
||||
let btn = jQuery(formIdentifier + ' button[type=submit]').button('loading');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
jQuery.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(),
|
||||
success: function(html) {
|
||||
btn.button('reset');
|
||||
let hasFieldError = jQuery(html).find('#form_modal .modal-content .has-error').length > 0;
|
||||
let hasFormError = jQuery(html).find('#form_modal .modal-content ul.list-unstyled li.text-danger').length > 0;
|
||||
let hasFlashError = jQuery(html).find(flashErrorIdentifier).length > 0;
|
||||
|
||||
if (hasFieldError || hasFormError || hasFlashError) {
|
||||
self._openFormInModal(html);
|
||||
} else {
|
||||
self.getContainer().getPlugin('datatable').reload();
|
||||
remoteModal.modal('hide');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
console.log('Failed submitting modal form', err);
|
||||
|
||||
// FIXME problem in google and 500 error, keeps on submitting...
|
||||
// what else could we do? submitting again at least gives us the opportunity to see errors,
|
||||
// which maybe would be hidden otherwise... this one is totally up for discussion!
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
31
assets/js/plugins/KimaiAlternativeLinks.js
Normal file
31
assets/js/plugins/KimaiAlternativeLinks.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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] KimaiAlternativeLinks
|
||||
*
|
||||
* allows to assign the given selector to any element, which then is used as click-handler
|
||||
* redirecting to the URL given in the elements 'data-href' or 'href' attribute
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
|
||||
|
||||
export default class KimaiAlternativeLinks extends KimaiClickHandlerReducedInTableRow {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
init() {
|
||||
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
|
||||
window.location = href;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
48
assets/js/plugins/KimaiClickHandlerReducedInTableRow.js
Normal file
48
assets/js/plugins/KimaiClickHandlerReducedInTableRow.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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] KimaiClickHandlerReducedInTableRow: abstract class
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiClickHandlerReducedInTableRow extends KimaiPlugin {
|
||||
|
||||
_addClickHandlerReducedInTableRow(selector, callback) {
|
||||
jQuery('body').on('click', selector, function(event) {
|
||||
// just in case an inner element is editable, than this should not be triggered
|
||||
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handles the "click" on table rows to open an entry for editing: when a button within a row is clicked,
|
||||
// we don't want the table row event to be processed - so we intercept it
|
||||
let target = event.target;
|
||||
if (event.currentTarget.matches('tr')) {
|
||||
while (!target.matches('body')) {
|
||||
if (target.matches('a') || target.matches ('button')) {
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let href = jQuery(this).attr('data-href');
|
||||
if (!href) {
|
||||
href = jQuery(this).attr('href');
|
||||
}
|
||||
|
||||
callback(href);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
55
assets/js/plugins/KimaiDatatable.js
Normal file
55
assets/js/plugins/KimaiDatatable.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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] KimaiDatatable: handles functionality for the datatable
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiDatatable extends KimaiPlugin {
|
||||
|
||||
getId() {
|
||||
return 'datatable';
|
||||
}
|
||||
|
||||
init() {
|
||||
const self = this;
|
||||
document.addEventListener('KimaiDatatableRequestReload', function() {
|
||||
self.reload();
|
||||
});
|
||||
}
|
||||
|
||||
reload() {
|
||||
let form = jQuery('.toolbar form');
|
||||
let loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
|
||||
jQuery('section.content').append(loading);
|
||||
|
||||
// remove the empty fields to prevent errors
|
||||
let formData = jQuery('.toolbar form :input')
|
||||
.filter(function(index, element) {
|
||||
return jQuery(element).val() != '';
|
||||
})
|
||||
.serialize();
|
||||
|
||||
jQuery.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: formData,
|
||||
success: function(html) {
|
||||
jQuery('section.content').replaceWith(
|
||||
jQuery(html).find('section.content')
|
||||
);
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
95
assets/js/plugins/KimaiDatatableColumnView.js
Normal file
95
assets/js/plugins/KimaiDatatableColumnView.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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] KimaiDatatableColumnView: manages the visibility of data-table columns in cookies
|
||||
*/
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiDatatableColumnView extends KimaiPlugin {
|
||||
|
||||
constructor(dataAttribute) {
|
||||
super();
|
||||
this.dataAttribute = dataAttribute;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'datatable-column-visibility';
|
||||
}
|
||||
|
||||
init() {
|
||||
let dataTable = document.querySelector('[' + this.dataAttribute + ']');
|
||||
if (dataTable === null) {
|
||||
return;
|
||||
}
|
||||
this.id = dataTable.getAttribute(this.dataAttribute);
|
||||
this.modal = document.getElementById('modal_' + this.id);
|
||||
this.bindButtons();
|
||||
}
|
||||
|
||||
bindButtons() {
|
||||
let self = this;
|
||||
this.modal.querySelector('button[data-type=save]').addEventListener('click', function() {
|
||||
self.saveVisibility();
|
||||
});
|
||||
this.modal.querySelector('button[data-type=reset]').addEventListener('click', function() {
|
||||
self.resetVisibility();
|
||||
});
|
||||
for (let checkbox of this.modal.querySelectorAll('form input[type=checkbox]')) {
|
||||
checkbox.addEventListener('click', function () {
|
||||
self.changeVisibility(checkbox.getAttribute('name'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveVisibility() {
|
||||
const form = this.modal.getElementsByTagName('form')[0];
|
||||
let settings = {};
|
||||
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||
settings[checkbox.getAttribute('name')] = checkbox.checked;
|
||||
}
|
||||
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
|
||||
jQuery(this.modal).modal('toggle');
|
||||
}
|
||||
|
||||
resetVisibility() {
|
||||
const form = this.modal.getElementsByTagName('form')[0];
|
||||
Cookies.remove(form.getAttribute('name'));
|
||||
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||
if (!checkbox.checked) {
|
||||
checkbox.click();
|
||||
}
|
||||
}
|
||||
jQuery(this.modal).modal('toggle');
|
||||
}
|
||||
|
||||
changeVisibility(columnName) {
|
||||
const table = document.getElementById('datatable_' + this.id).getElementsByClassName('dataTable')[0];
|
||||
let column = 0;
|
||||
let foundColumn = false;
|
||||
for (let columnElement of table.getElementsByTagName('th')) {
|
||||
if (columnElement.getAttribute('data-field') === columnName) {
|
||||
foundColumn = true;
|
||||
break;
|
||||
}
|
||||
column++;
|
||||
}
|
||||
|
||||
if (!foundColumn) {
|
||||
console.error('Could not find column: ' + columnName);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let rowElement of table.getElementsByTagName('tr')) {
|
||||
rowElement.children[column].classList.toggle('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
54
assets/js/plugins/KimaiDatePicker.js
Normal file
54
assets/js/plugins/KimaiDatePicker.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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] KimaiDatePicker: single date selects (currently unused)
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiDatePicker extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'date-picker';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.activateDatePicker(this.selector);
|
||||
}
|
||||
|
||||
activateDatePicker(selector) {
|
||||
let translator = this.getContainer().getTranslation();
|
||||
jQuery(selector + ' input[data-datepickerenable="on"]').each(function(index) {
|
||||
let localeFormat = jQuery(this).data('format');
|
||||
jQuery(this).daterangepicker({
|
||||
singleDatePicker: true,
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: translator.get('apply'),
|
||||
cancelLabel: translator.get('cancel'),
|
||||
customRangeLabel: translator.get('customRange')
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
jQuery(this).val(picker.startDate.format(localeFormat));
|
||||
jQuery(this).trigger("change");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
71
assets/js/plugins/KimaiDateRangePicker.js
Normal file
71
assets/js/plugins/KimaiDateRangePicker.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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] KimaiDateRangePicker: activate the (daterange picker) compound field in toolbar
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiDateRangePicker extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'date-range-picker';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.activateDateRangePicker(this.selector);
|
||||
}
|
||||
|
||||
activateDateRangePicker(selector) {
|
||||
let translator = this.getContainer().getTranslation();
|
||||
jQuery(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
|
||||
let localeFormat = jQuery(this).data('format');
|
||||
let separator = jQuery(this).data('separator');
|
||||
let rangesList = {};
|
||||
|
||||
rangesList[translator.get('today')] = [moment(), moment()];
|
||||
rangesList[translator.get('yesterday')] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
|
||||
rangesList[translator.get('thisWeek')] = [moment().startOf('week'), moment().endOf('week')];
|
||||
rangesList[translator.get('lastWeek')] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
|
||||
rangesList[translator.get('thisMonth')] = [moment().startOf('month'), moment().endOf('month')];
|
||||
rangesList[translator.get('lastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
|
||||
rangesList[translator.get('thisYear')] = [moment().startOf('year'), moment().endOf('year')];
|
||||
rangesList[translator.get('lastYear')] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
|
||||
|
||||
jQuery(this).daterangepicker({
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
autoApply: false,
|
||||
linkedCalendars: false,
|
||||
locale: {
|
||||
separator: separator,
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: translator.get('apply'),
|
||||
cancelLabel: translator.get('cancel'),
|
||||
customRangeLabel: translator.get('customRange')
|
||||
},
|
||||
ranges: rangesList,
|
||||
alwaysShowCalendars: true
|
||||
});
|
||||
|
||||
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
jQuery(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
|
||||
jQuery(this).trigger("change");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
58
assets/js/plugins/KimaiDateTimePicker.js
Normal file
58
assets/js/plugins/KimaiDateTimePicker.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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] KimaiDateTimePicker: activate the (datetime picker) field in timesheet edit dialog
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiDateTimePicker extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'date-time-picker';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.activateDateTimePicker(this.selector);
|
||||
}
|
||||
|
||||
activateDateTimePicker(selector) {
|
||||
let translator = this.getContainer().getTranslation();
|
||||
let configuration = this.getContainer().getConfiguration();
|
||||
|
||||
jQuery(selector + ' input[data-datetimepicker="on"]').each(function(index) {
|
||||
let localeFormat = jQuery(this).data('format');
|
||||
jQuery(this).daterangepicker({
|
||||
singleDatePicker: true,
|
||||
timePicker: true,
|
||||
timePicker24Hour: configuration.get('twentyFourHours'),
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
format: localeFormat,
|
||||
firstDay: 1,
|
||||
applyLabel: translator.get('apply'),
|
||||
cancelLabel: translator.get('cancel'),
|
||||
customRangeLabel: translator.get('customRange')
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
|
||||
jQuery(this).val(picker.startDate.format(localeFormat));
|
||||
jQuery(this).trigger("change");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
24
assets/js/plugins/KimaiJqueryPluginInitializer.js
Normal file
24
assets/js/plugins/KimaiJqueryPluginInitializer.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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] KimaiJqueryPluginInitializer: initialize jQuery plugins
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiJqueryPluginInitializer extends KimaiPlugin {
|
||||
|
||||
init() {
|
||||
// activate the dropdown functionality
|
||||
jQuery('.dropdown-toggle').dropdown();
|
||||
// activate the tooltip functionality
|
||||
jQuery('[data-toggle="tooltip"]').tooltip();
|
||||
}
|
||||
|
||||
}
|
||||
41
assets/js/plugins/KimaiPauseRecord.js
Normal file
41
assets/js/plugins/KimaiPauseRecord.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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] KimaiPauseRecord
|
||||
*
|
||||
* allows to pause records
|
||||
* THIS IS JUST A DRAFT FOR THE DOM, IT IS NOT SUPPORTED IN KIMAI ITSELF!
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiPauseRecord extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.activate(this.selector);
|
||||
}
|
||||
|
||||
activate(selector) {
|
||||
jQuery(selector + ' .pull-left i').hover(function () {
|
||||
let link = jQuery(this).parents('a');
|
||||
link.attr('href', link.attr('href').replace('/stop', '/pause'));
|
||||
jQuery(this).removeClass('fa-stop-circle').addClass('fa-pause-circle').addClass('text-orange');
|
||||
},function () {
|
||||
let link = jQuery(this).parents('a');
|
||||
link.attr('href', link.attr('href').replace('/pause', '/stop'));
|
||||
jQuery(this).removeClass('fa-pause-circle').removeClass('text-orange').addClass('fa-stop-circle');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
81
assets/js/plugins/KimaiSelectDataAPI.js
Normal file
81
assets/js/plugins/KimaiSelectDataAPI.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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] KimaiSelectDataAPI: <select> boxes with dynamic data from API
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiSelectDataAPI extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'select-data-api';
|
||||
}
|
||||
|
||||
init() {
|
||||
this.activateApiSelects(this.selector);
|
||||
}
|
||||
|
||||
activateApiSelects(selector) {
|
||||
const self = this;
|
||||
const API = this.getContainer().getPlugin('api');
|
||||
|
||||
jQuery('body').on('change', selector, function(event) {
|
||||
let apiUrl = jQuery(this).attr('data-api-url').replace('-s-', jQuery(this).val());
|
||||
const targetSelect = '#' + jQuery(this).attr('data-related-select');
|
||||
|
||||
// if the related target select does not exist, we do not need to load the related data
|
||||
if (jQuery(targetSelect).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (jQuery(this).val() === '') {
|
||||
if (jQuery(this).attr('data-empty-url') === undefined) {
|
||||
self._updateSelect(targetSelect, {});
|
||||
jQuery(targetSelect).attr('disabled', 'disabled');
|
||||
return;
|
||||
}
|
||||
apiUrl = jQuery(this).attr('data-empty-url').replace('-s-', jQuery(this).val());
|
||||
}
|
||||
|
||||
jQuery(targetSelect).removeAttr('disabled');
|
||||
|
||||
API.get(apiUrl, function(data){
|
||||
self._updateSelect(targetSelect, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_updateSelect(selectName, data) {
|
||||
let select = jQuery(selectName);
|
||||
let emptyOption = jQuery(selectName + ' option[value=""]');
|
||||
|
||||
select.find('option').remove().end().find('optgroup').remove().end();
|
||||
|
||||
if (emptyOption.length !== 0) {
|
||||
select.append('<option value="">' + emptyOption.text() + '</option>');
|
||||
}
|
||||
|
||||
jQuery.each(data, function(i, obj) {
|
||||
select.append('<option value="' + obj.id + '">' + obj.name + '</option>');
|
||||
});
|
||||
|
||||
// if we don't trigger the change, the other selects won't be resetted
|
||||
select.trigger('change');
|
||||
|
||||
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
|
||||
jQuery('.selectpicker').selectpicker('refresh');
|
||||
}
|
||||
|
||||
}
|
||||
48
assets/js/plugins/KimaiThemeInitializer.js
Normal file
48
assets/js/plugins/KimaiThemeInitializer.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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] KimaiThemeInitializer: initialize theme functionality
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
|
||||
export default class KimaiThemeInitializer extends KimaiPlugin {
|
||||
|
||||
init() {
|
||||
this.registerAutomaticAlertRemove('div.alert-success', 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* auto hide success messages, as they are just meant as user feedback and not as a permanent information
|
||||
*
|
||||
* @param {string} selector
|
||||
* @param {integer} interval
|
||||
*/
|
||||
registerAutomaticAlertRemove(selector, interval) {
|
||||
const self = this;
|
||||
this._alertRemoveHandler = setInterval(
|
||||
function() {
|
||||
self.hideAlert(selector);
|
||||
},
|
||||
interval
|
||||
);
|
||||
}
|
||||
|
||||
unregisterAutomaticAlertRemove() {
|
||||
clearInterval(this._alertRemoveHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
*/
|
||||
hideAlert(selector) {
|
||||
jQuery(selector).alert('close');
|
||||
}
|
||||
|
||||
}
|
||||
73
assets/js/plugins/KimaiToolbar.js
Normal file
73
assets/js/plugins/KimaiToolbar.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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] KimaiToolbar: some event listener to handle the toolbar/data-table filter, toolbar and navigation
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiToolbar extends KimaiPlugin {
|
||||
|
||||
init() {
|
||||
const self = this;
|
||||
|
||||
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
|
||||
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
|
||||
let pager = jQuery(".toolbar form input[name='page']");
|
||||
if (pager.length === 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let urlParts = jQuery(this).attr('href').split('/');
|
||||
let page = urlParts[urlParts.length-1];
|
||||
pager.val(page);
|
||||
pager.trigger('change');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Reset the page if any other value is changed, otherwise we might end up with a limited set
|
||||
// of data which does not support the given page - and it would be just wrong to stay in the same page
|
||||
jQuery('.toolbar form input').change(function (event) {
|
||||
switch (event.target.id) {
|
||||
case 'page':
|
||||
break;
|
||||
default:
|
||||
jQuery('.toolbar form input#page').val(1);
|
||||
}
|
||||
self._reloadDatatable();
|
||||
});
|
||||
|
||||
jQuery('.toolbar form select').change(function (event) {
|
||||
let reload = true;
|
||||
switch (event.target.id) {
|
||||
case 'customer':
|
||||
if (jQuery('.toolbar form select#project').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'project':
|
||||
if (jQuery('.toolbar form select#activity').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
jQuery('.toolbar form input#page').val(1);
|
||||
if (reload) {
|
||||
self._reloadDatatable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_reloadDatatable() {
|
||||
this.getContainer().getPlugin('datatable').reload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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] Toolbar: some helper scripts for data-table filter, toolbar and navigation
|
||||
*/
|
||||
$(document).ready(function () {
|
||||
|
||||
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
|
||||
$('body').on('click', 'div.navigation ul.pagination li a', function(event) {
|
||||
var $pager = $(".toolbar form input[name='page']");
|
||||
if ($pager.length === 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var $urlParts = $(this).attr('href').split('/');
|
||||
var page = $urlParts[$urlParts.length-1];
|
||||
$pager.val(page);
|
||||
$pager.trigger('change');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Reset the page if any other value is changed, otherwise we might end up with a limited set
|
||||
// of data which does not support the given page - and it would be just wrong to stay in the same page
|
||||
$('.toolbar form input').change(function (event) {
|
||||
switch (event.target.id) {
|
||||
case 'page':
|
||||
break;
|
||||
default:
|
||||
$('.toolbar form input#page').val(1);
|
||||
}
|
||||
$.kimai.reloadDatatableWithToolbarFilter();
|
||||
});
|
||||
|
||||
$('.toolbar form select').change(function (event) {
|
||||
var reload = true;
|
||||
switch (event.target.id) {
|
||||
case 'customer':
|
||||
if ($('.toolbar form select#project').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'project':
|
||||
if ($('.toolbar form select#activity').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$('.toolbar form input#page').val(1);
|
||||
if (reload) {
|
||||
$.kimai.reloadDatatableWithToolbarFilter();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -17,6 +17,9 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.messages-menu>.dropdown-menu>li .menu>li>a>h4>span {
|
||||
margin-right: 55px;
|
||||
}
|
||||
.start_record {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"jquery-slimscroll": "^1.3.8",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"js-cookie": "^2.2.0",
|
||||
"moment": "^2.24.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"sass-loader": "^7.0.2",
|
||||
"webpack-notifier": "^1.5.1"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build/app.js": "./app.js?[contenthash]",
|
||||
"build/app.css": "./app.css?361d4520765f893b9b0fb94bc9f761c1",
|
||||
"build/app.js": "./app.js?513aa9647de2030610d0",
|
||||
"build/app.css": "./app.css?b3b98e5f8744bf95f3f8d8352977d4f3",
|
||||
"build/fonts/fa-solid-900.woff2": "./fonts/fa-solid-900.woff2?e8a92a29",
|
||||
"build/images/fa-solid-900.svg": "./images/fa-solid-900.svg?666a82cb",
|
||||
"build/images/glyphicons-halflings-regular.svg": "./images/glyphicons-halflings-regular.svg?89889688",
|
||||
|
||||
@@ -171,26 +171,26 @@
|
||||
{% block javascripts %}
|
||||
{# 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).ready(function () {
|
||||
$.kimai.init({
|
||||
locale: '{{ app.request.locale }}',
|
||||
apply: '{{ 'daterangepicker.apply'|trans({}, 'daterangepicker') }}',
|
||||
cancel: '{{ 'daterangepicker.cancel'|trans({}, 'daterangepicker') }}',
|
||||
today: '{{ 'daterangepicker.today'|trans({}, 'daterangepicker') }}',
|
||||
yesterday: '{{ 'daterangepicker.yesterday'|trans({}, 'daterangepicker') }}',
|
||||
lastWeek: '{{ 'daterangepicker.lastWeek'|trans({}, 'daterangepicker') }}',
|
||||
thisWeek: '{{ 'daterangepicker.thisWeek'|trans({}, 'daterangepicker') }}',
|
||||
lastMonth: '{{ 'daterangepicker.lastMonth'|trans({}, 'daterangepicker') }}',
|
||||
thisMonth: '{{ 'daterangepicker.thisMonth'|trans({}, 'daterangepicker') }}',
|
||||
lastYear: '{{ 'daterangepicker.lastYear'|trans({}, 'daterangepicker') }}',
|
||||
thisYear: '{{ 'daterangepicker.thisYear'|trans({}, 'daterangepicker') }}',
|
||||
customRange: '{{ 'daterangepicker.customRange'|trans({}, 'daterangepicker') }}',
|
||||
twentyFourHours: {{ 'true'|hour24('false') }}
|
||||
});
|
||||
{# $.kimai.pauseRecord('li.messages-menu ul.menu li'); #}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new KimaiActiveRecordsDuration('[data-since]').updateRecords().registerUpdates(5000);
|
||||
new KimaiWebLoader(
|
||||
{
|
||||
locale: '{{ app.request.locale }}',
|
||||
twentyFourHours: {{ 'true'|hour24('false') }}
|
||||
},
|
||||
{
|
||||
apply: '{{ 'daterangepicker.apply'|trans({}, 'daterangepicker') }}',
|
||||
cancel: '{{ 'daterangepicker.cancel'|trans({}, 'daterangepicker') }}',
|
||||
today: '{{ 'daterangepicker.today'|trans({}, 'daterangepicker') }}',
|
||||
yesterday: '{{ 'daterangepicker.yesterday'|trans({}, 'daterangepicker') }}',
|
||||
lastWeek: '{{ 'daterangepicker.lastWeek'|trans({}, 'daterangepicker') }}',
|
||||
thisWeek: '{{ 'daterangepicker.thisWeek'|trans({}, 'daterangepicker') }}',
|
||||
lastMonth: '{{ 'daterangepicker.lastMonth'|trans({}, 'daterangepicker') }}',
|
||||
thisMonth: '{{ 'daterangepicker.thisMonth'|trans({}, 'daterangepicker') }}',
|
||||
lastYear: '{{ 'daterangepicker.lastYear'|trans({}, 'daterangepicker') }}',
|
||||
thisYear: '{{ 'daterangepicker.thisYear'|trans({}, 'daterangepicker') }}',
|
||||
customRange: '{{ 'daterangepicker.customRange'|trans({}, 'daterangepicker') }}'
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::JAVASCRIPT')) %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
{% macro data_table_column_modal(name, columns) %}
|
||||
<div class="modal fade" id="modal_{{ name }}" tabindex="-1" role="dialog" aria-labelledby="data_table_modal_label">
|
||||
<div class="modal fade" id="modal_{{ name }}" data-column-visibility="{{ name }}" tabindex="-1" role="dialog" aria-labelledby="data_table_modal_label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -28,11 +28,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new KimaiDatatableColumnView('{{ name }}');
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_column_class(name, columns, column) %}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<h4>
|
||||
<span>{{ entry.activity.name }}</span>
|
||||
<small>
|
||||
<i class="{{ 'timesheet'|icon }}"></i>
|
||||
<span data-title="true" data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}" data-format="{{ get_format_duration() }}">{{ entry|duration }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
{{ tables.data_table_header(tableName, columns) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<tr{% if is_granted('edit', entry) %} class="open-edit" onclick="location.href='{{ path('user_profile_edit', {'username': entry.username}) }}'"{% endif %}>
|
||||
<tr{% if is_granted('edit', entry) %} class="open-edit alternative-link" data-href="{{ path('user_profile_edit', {'username': entry.username}) }}"{% endif %}>
|
||||
<td>{{ widgets.username(entry) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}">{{ entry.username }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
|
||||
|
||||
@@ -41,7 +41,7 @@ Encore
|
||||
|
||||
// add hash after file name
|
||||
.configureFilenames({
|
||||
js: '[name].js?[contenthash]',
|
||||
js: '[name].js?[chunkhash]',
|
||||
css: '[name].css?[contenthash]',
|
||||
images: 'images/[name].[ext]?[hash:8]',
|
||||
fonts: 'fonts/[name].[ext]?[hash:8]'
|
||||
|
||||
@@ -3888,6 +3888,11 @@ moment@^2.10.2, moment@^2.18.1, moment@^2.20.1, moment@^2.9.0:
|
||||
version "2.22.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
||||
|
||||
moment@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
morris.js@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/morris.js/-/morris.js-0.5.0.tgz#725767135cfae059aae75999bb2ce6a1c5d1b44b"
|
||||
|
||||
Reference in New Issue
Block a user