save default search options (#2445)
* set default times for daterange objects * allow to show order and order by fields * move search to modal * more options for page size * save export visibility in cookie
This commit is contained in:
@@ -92,6 +92,7 @@ require('./sass/app.scss');
|
|||||||
require('./js/KimaiWebLoader.js');
|
require('./js/KimaiWebLoader.js');
|
||||||
global.KimaiPaginatedBoxWidget = require('./js/widgets/KimaiPaginatedBoxWidget').default;
|
global.KimaiPaginatedBoxWidget = require('./js/widgets/KimaiPaginatedBoxWidget').default;
|
||||||
global.KimaiReloadPageWidget = require('./js/widgets/KimaiReloadPageWidget').default;
|
global.KimaiReloadPageWidget = require('./js/widgets/KimaiReloadPageWidget').default;
|
||||||
|
global.KimaiCookies = require('./js/widgets/KimaiCookies').default;
|
||||||
|
|
||||||
// ------ Autocomplete for tags only ------
|
// ------ Autocomplete for tags only ------
|
||||||
require('jquery-ui/ui/widgets/autocomplete');
|
require('jquery-ui/ui/widgets/autocomplete');
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default class KimaiLoader {
|
|||||||
kimai.registerPlugin(new KimaiDateTimePicker('input[data-datetimepicker="on"]'));
|
kimai.registerPlugin(new KimaiDateTimePicker('input[data-datetimepicker="on"]'));
|
||||||
kimai.registerPlugin(new KimaiDatePicker('input[data-datepickerenable="on"]'));
|
kimai.registerPlugin(new KimaiDatePicker('input[data-datepickerenable="on"]'));
|
||||||
kimai.registerPlugin(new KimaiDatatable('section.content', 'table.dataTable'));
|
kimai.registerPlugin(new KimaiDatatable('section.content', 'table.dataTable'));
|
||||||
kimai.registerPlugin(new KimaiToolbar('form.header-search', 'toolbar-action'));
|
kimai.registerPlugin(new KimaiToolbar('form.searchform', 'toolbar-action'));
|
||||||
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
|
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
|
||||||
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
|
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
|
||||||
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
|
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
|
|||||||
});
|
});
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
this.getContainer().getPlugin('toolbar').hide();
|
|
||||||
remoteModal.modal('show');
|
remoteModal.modal('show');
|
||||||
|
|
||||||
// the new form that was loaded via ajax
|
// the new form that was loaded via ajax
|
||||||
|
|||||||
@@ -48,12 +48,8 @@ export default class KimaiDatatable extends KimaiPlugin {
|
|||||||
document.addEventListener(eventName, handle);
|
document.addEventListener(eventName, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getConfiguration('autoReloadDatatable')) {
|
document.addEventListener('pagination-change', handle);
|
||||||
document.addEventListener('toolbar-change', handle);
|
document.addEventListener('filter-change', handle);
|
||||||
} else {
|
|
||||||
document.addEventListener('pagination-change', handle);
|
|
||||||
document.addEventListener('filter-change', handle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadDatatable() {
|
reloadDatatable() {
|
||||||
@@ -69,7 +65,7 @@ export default class KimaiDatatable extends KimaiPlugin {
|
|||||||
// remove the empty fields to prevent errors
|
// remove the empty fields to prevent errors
|
||||||
let formData = jQuery(toolbarSelector + ' :input')
|
let formData = jQuery(toolbarSelector + ' :input')
|
||||||
.filter(function(index, element) {
|
.filter(function(index, element) {
|
||||||
return jQuery(element).val() != '';
|
return jQuery(element).val() !== '';
|
||||||
})
|
})
|
||||||
.serialize();
|
.serialize();
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default class KimaiDatatableColumnView extends KimaiPlugin {
|
|||||||
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||||
settings[checkbox.getAttribute('name')] = checkbox.checked;
|
settings[checkbox.getAttribute('name')] = checkbox.checked;
|
||||||
}
|
}
|
||||||
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
|
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365, SameSite: 'Strict'});
|
||||||
jQuery(this.modal).modal('toggle');
|
jQuery(this.modal).modal('toggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
|
|||||||
jQuery('[data-toggle="tooltip"]').tooltip();
|
jQuery('[data-toggle="tooltip"]').tooltip();
|
||||||
// activate all form plugins
|
// activate all form plugins
|
||||||
this.getContainer().getPlugin('form').activateForm('.content-wrapper form', 'body');
|
this.getContainer().getPlugin('form').activateForm('.content-wrapper form', 'body');
|
||||||
|
this.getContainer().getPlugin('form').activateForm('form.searchform', 'body');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,32 +32,8 @@ export default class KimaiToolbar extends KimaiPlugin {
|
|||||||
this._registerPagination(formSelector, EVENT);
|
this._registerPagination(formSelector, EVENT);
|
||||||
this._registerSortableTables(formSelector, EVENT);
|
this._registerSortableTables(formSelector, EVENT);
|
||||||
this._registerAlternativeSubmitActions(formSelector, this.actionClass);
|
this._registerAlternativeSubmitActions(formSelector, this.actionClass);
|
||||||
this._registerSearchButtons(formSelector);
|
|
||||||
|
|
||||||
jQuery('body')
|
// Reset the page if filter values are changed, otherwise we might end up with a limited set of data,
|
||||||
// prevent that the dropdown closes, when a form input is changed - eg. a select option was clicked
|
|
||||||
.on('click', formSelector + ' .dropdown-menu', function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
})
|
|
||||||
// prevent that a click into the search field will close the dropdown
|
|
||||||
.on('click', '.select2-search__field', function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
})
|
|
||||||
// prevent that the dropdown closes when a optgroup header is clicked
|
|
||||||
.on('click', '.select2-results__group', function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
})
|
|
||||||
// prevent that a click into a daterangepicker will close the dropdown
|
|
||||||
.on('click', '.daterangepicker', function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
})
|
|
||||||
// prevent that clicks in the dropdown elements, but outside of elements will close the dropdown (eg border besides the search field)
|
|
||||||
.on('click', '.select2-container', function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
})
|
|
||||||
;
|
|
||||||
|
|
||||||
// Reset the page if filter values are 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
|
// which does not support the given page - and it would be just wrong to stay in the same page
|
||||||
jQuery(formSelector +' input').change(function (event) {
|
jQuery(formSelector +' input').change(function (event) {
|
||||||
switch (event.target.id) {
|
switch (event.target.id) {
|
||||||
@@ -95,38 +71,6 @@ export default class KimaiToolbar extends KimaiPlugin {
|
|||||||
self.triggerChange();
|
self.triggerChange();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// close all open selectpicker upon choosing any dropdown option
|
|
||||||
jQuery(formSelector + ' select.selectpicker').on('change', function(event) {
|
|
||||||
jQuery('.bootstrap-select.open').removeClass('open');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The search toggle button is not part of this component, but it is directly connected to it.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_registerSearchButtons(formSelector) {
|
|
||||||
jQuery('body')
|
|
||||||
// only for mobile experience currently: show the search form field
|
|
||||||
.on('click', '.btn-search.search-toggle', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
jQuery(formSelector).parent('section').toggleClass('search-open');
|
|
||||||
jQuery(formSelector).toggleClass('hidden-xs');
|
|
||||||
jQuery(formSelector + ' input#searchTerm').dropdown('toggle');
|
|
||||||
jQuery(formSelector + ' input#searchTerm').focus();
|
|
||||||
})
|
|
||||||
// hide the search form field
|
|
||||||
.on('click', formSelector + ' a.search-cancel', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
jQuery(formSelector).parent('section').toggleClass('search-open');
|
|
||||||
jQuery(formSelector + ' input#searchTerm').dropdown('toggle');
|
|
||||||
jQuery(formSelector).toggleClass('hidden-xs');
|
|
||||||
})
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,10 +119,14 @@ export default class KimaiToolbar extends KimaiPlugin {
|
|||||||
if ($header.hasClass('sorting_desc')) {
|
if ($header.hasClass('sorting_desc')) {
|
||||||
order = 'ASC';
|
order = 'ASC';
|
||||||
}
|
}
|
||||||
jQuery(formSelector + ' input#orderBy').val(orderBy);
|
|
||||||
jQuery(formSelector + ' input#order').val(order);
|
jQuery(formSelector + ' #orderBy').val(orderBy);
|
||||||
// triggers the page reset - see below
|
jQuery(formSelector + ' #order').val(order);
|
||||||
jQuery(formSelector + ' input#order').trigger('change');
|
|
||||||
|
// re-render the selectboxes
|
||||||
|
jQuery(formSelector + ' #orderBy').trigger('change');
|
||||||
|
jQuery(formSelector + ' #order').trigger('change');
|
||||||
|
|
||||||
// triggers the datatable reload - search for the event name
|
// triggers the datatable reload - search for the event name
|
||||||
EVENT.trigger('filter-change');
|
EVENT.trigger('filter-change');
|
||||||
});
|
});
|
||||||
@@ -206,13 +154,6 @@ export default class KimaiToolbar extends KimaiPlugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
|
||||||
const toggle = jQuery(this.getSelector() + ' #searchTerm.dropdown-toggle');
|
|
||||||
if (toggle.parent().hasClass('open')) {
|
|
||||||
toggle.dropdown('toggle');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers an event, that everyone can listen for.
|
* Triggers an event, that everyone can listen for.
|
||||||
*/
|
*/
|
||||||
|
|||||||
28
assets/js/widgets/KimaiCookies.js
Normal file
28
assets/js/widgets/KimaiCookies.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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] KimaiCookies: simple wrapper to handle cookies
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
export default class KimaiCookies {
|
||||||
|
|
||||||
|
static set(name, values, options) {
|
||||||
|
Cookies.set(name, values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(name) {
|
||||||
|
return Cookies.getJSON(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static remove(name) {
|
||||||
|
Cookies.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,4 +5,11 @@ form fieldset {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: $highlight-bg;
|
background-color: $highlight-bg;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To be used with .form-horizontal - to reduce the space between rows, eg. used in the search modals */
|
||||||
|
form.form-narrow {
|
||||||
|
.form-group {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,9 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The filter form is available on most pages above the datatable */
|
|
||||||
@media (min-width: $screen-sm-min) {
|
@media (min-width: $screen-sm-min) {
|
||||||
.toolbar {
|
.toolbar {
|
||||||
// @deprecated since 1.2 - using the toolbar is deprecated and will be removed with 2.0
|
// @deprecated since 1.2 - using the navbar-form is deprecated and will be removed with 2.0
|
||||||
form.navbar-form {
|
form.navbar-form {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
.form-control {
|
.form-control {
|
||||||
@@ -32,59 +31,3 @@
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search form with filter dropdown */
|
|
||||||
.content-header {
|
|
||||||
form {
|
|
||||||
width: 200px;
|
|
||||||
float: left;
|
|
||||||
.container-fluid {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
.form-group {
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
}
|
|
||||||
input.search-has-error {
|
|
||||||
color: $red;
|
|
||||||
}
|
|
||||||
.input-group-addon.has-error {
|
|
||||||
color: $red;
|
|
||||||
border-color: $red;
|
|
||||||
}
|
|
||||||
input.has-error {
|
|
||||||
border-color: $red;
|
|
||||||
}
|
|
||||||
ul.dropdown-menu {
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-top: 10px;
|
|
||||||
width: 500px;
|
|
||||||
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
|
||||||
max-width: 90vw;
|
|
||||||
}
|
|
||||||
a.search-cancel {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $screen-xs-min) {
|
|
||||||
.content-header.search-open {
|
|
||||||
h1 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.breadcrumb {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
form.header-search {
|
|
||||||
padding: 0 10px 0 0;
|
|
||||||
float: none;
|
|
||||||
width: 100%;
|
|
||||||
ul.dropdown-menu {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -240,3 +240,8 @@ services:
|
|||||||
class: Doctrine\ORM\EntityRepository
|
class: Doctrine\ORM\EntityRepository
|
||||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||||
arguments: ['App\Entity\Invoice']
|
arguments: ['App\Entity\Invoice']
|
||||||
|
|
||||||
|
App\Repository\BookmarkRepository:
|
||||||
|
class: Doctrine\ORM\EntityRepository
|
||||||
|
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||||
|
arguments: ['App\Entity\Bookmark']
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
1
public/build/app.b19b6399.js
Normal file
1
public/build/app.b19b6399.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,10 +5,10 @@
|
|||||||
"build/runtime.098eaae1.js",
|
"build/runtime.098eaae1.js",
|
||||||
"build/0.79dbdbb9.js",
|
"build/0.79dbdbb9.js",
|
||||||
"build/1.32489d92.js",
|
"build/1.32489d92.js",
|
||||||
"build/app.f451ae28.js"
|
"build/app.b19b6399.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"build/app.7c0ca7bb.css"
|
"build/app.b22111d1.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"invoice": {
|
"invoice": {
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
"build/runtime.098eaae1.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
|
"build/runtime.098eaae1.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
|
||||||
"build/0.79dbdbb9.js": "sha384-U2Ao0ORAZ8PCeDmyRsqQFET3hc7pfUBimq0PrqFdG4/s0Bdi+qBj4TJK3o70bCd5",
|
"build/0.79dbdbb9.js": "sha384-U2Ao0ORAZ8PCeDmyRsqQFET3hc7pfUBimq0PrqFdG4/s0Bdi+qBj4TJK3o70bCd5",
|
||||||
"build/1.32489d92.js": "sha384-wVkjh5FzjFhMV4S4uNP23E/OLBOf+Zi7t3lpm9eWzoMr/tm2pydT+q0Op1XHuoUP",
|
"build/1.32489d92.js": "sha384-wVkjh5FzjFhMV4S4uNP23E/OLBOf+Zi7t3lpm9eWzoMr/tm2pydT+q0Op1XHuoUP",
|
||||||
"build/app.f451ae28.js": "sha384-uFHnZPiRrWKf2q8N4zNE1YguKVtOXbES0+rKlehQAj7bIDvCHOcpVo8djMU6VtE6",
|
"build/app.b19b6399.js": "sha384-giiZt81GRzPg3SJzQLRg7+bLAhpzPPP0XCqzZBcznH/JfE3Z3wQElGPKuJR84RXl",
|
||||||
"build/app.7c0ca7bb.css": "sha384-0I0CpC2eA9LQewNIo/c6Qkhra9sOsqadi0bi9DfC2YWQxBi+ZRYAl4BVt9jMOyGE",
|
"build/app.b22111d1.css": "sha384-ZMJkA2sXnH7J/uMTKDBf8+bLNHulT+yZiz1h5fxzxJshqw9Ccs3brjAJPqb+0/6S",
|
||||||
"build/invoice.74279541.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
|
"build/invoice.74279541.js": "sha384-2BXic5Sgorf2tXai6zSAN4wLY2dbg06L03/xMKW6itMcszvtnRArKzfBh6DNcF3f",
|
||||||
"build/invoice.13d8ef4e.css": "sha384-B6RN/wZJToSBCZk2JeLokIqWEhbh+Eb9arYbt9dM+YoC2Z6PnCeTwTqSGyexWWJh",
|
"build/invoice.13d8ef4e.css": "sha384-B6RN/wZJToSBCZk2JeLokIqWEhbh+Eb9arYbt9dM+YoC2Z6PnCeTwTqSGyexWWJh",
|
||||||
"build/invoice-pdf.0efd7a97.js": "sha384-bSdIeRCtEJiYYuc2reb0e5CpJ1Kbd1lQNEkElMTiq1SX0IINzdwJJYf6WnCcHrNC",
|
"build/invoice-pdf.0efd7a97.js": "sha384-bSdIeRCtEJiYYuc2reb0e5CpJ1Kbd1lQNEkElMTiq1SX0IINzdwJJYf6WnCcHrNC",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"build/0.79dbdbb9.js": "build/0.79dbdbb9.js",
|
"build/0.79dbdbb9.js": "build/0.79dbdbb9.js",
|
||||||
"build/1.32489d92.js": "build/1.32489d92.js",
|
"build/1.32489d92.js": "build/1.32489d92.js",
|
||||||
"build/2.7ab75d0a.js": "build/2.7ab75d0a.js",
|
"build/2.7ab75d0a.js": "build/2.7ab75d0a.js",
|
||||||
"build/app.css": "build/app.7c0ca7bb.css",
|
"build/app.css": "build/app.b22111d1.css",
|
||||||
"build/app.js": "build/app.f451ae28.js",
|
"build/app.js": "build/app.b19b6399.js",
|
||||||
"build/calendar.css": "build/calendar.1408f57e.css",
|
"build/calendar.css": "build/calendar.1408f57e.css",
|
||||||
"build/calendar.js": "build/calendar.070aab88.js",
|
"build/calendar.js": "build/calendar.070aab88.js",
|
||||||
"build/chart.js": "build/chart.34d60a88.js",
|
"build/chart.js": "build/chart.34d60a88.js",
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class ThemeConfiguration implements SystemBundleConfiguration, \ArrayAccess
|
|||||||
|
|
||||||
public function isAutoReloadDatatable(): bool
|
public function isAutoReloadDatatable(): bool
|
||||||
{
|
{
|
||||||
return (bool) $this->find('auto_reload_datatable');
|
@trigger_error('The configuration auto_reload_datatable is deprecated and was removed with 1.4', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAllowTagCreation(): bool
|
public function isAllowTagCreation(): bool
|
||||||
|
|||||||
@@ -10,11 +10,16 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Configuration\LanguageFormattings;
|
use App\Configuration\LanguageFormattings;
|
||||||
|
use App\Entity\Bookmark;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Repository\BookmarkRepository;
|
||||||
|
use App\Repository\Query\BaseQuery;
|
||||||
use App\Timesheet\DateTimeFactory;
|
use App\Timesheet\DateTimeFactory;
|
||||||
use App\Utils\LocaleFormats;
|
use App\Utils\LocaleFormats;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Translation\DataCollectorTranslator;
|
use Symfony\Component\Translation\DataCollectorTranslator;
|
||||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
@@ -167,4 +172,72 @@ abstract class AbstractController extends BaseAbstractController implements Serv
|
|||||||
{
|
{
|
||||||
return new LocaleFormats($this->container->get(LanguageFormattings::class), $locale);
|
return new LocaleFormats($this->container->get(LanguageFormattings::class), $locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleSearch(FormInterface $form, Request $request): bool
|
||||||
|
{
|
||||||
|
$data = $form->getData();
|
||||||
|
if (!($data instanceof BaseQuery)) {
|
||||||
|
throw new \InvalidArgumentException('handleSearchForm() requires an instanceof BaseQuery as form data');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var BookmarkRepository $bookmarkRepo */
|
||||||
|
$bookmarkRepo = $this->getDoctrine()->getRepository(Bookmark::class);
|
||||||
|
$bookmark = $bookmarkRepo->getSearchDefaultOptions($this->getUser(), $data->getName());
|
||||||
|
|
||||||
|
$submitData = $request->query->all();
|
||||||
|
|
||||||
|
// remove bookmark
|
||||||
|
if ($bookmark !== null && $request->query->has('removeDefaultQuery')) {
|
||||||
|
$bookmarkRepo->deleteBookmark($bookmark);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply bookmark ONLY if search form was not submitted manually
|
||||||
|
if ($bookmark !== null && !$request->query->has('performSearch')) {
|
||||||
|
$data->setBookmark($bookmark);
|
||||||
|
$submitData = array_merge($bookmark->getContent(), $submitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up parameters from unknown search values
|
||||||
|
foreach ($submitData as $name => $values) {
|
||||||
|
if (!$form->has($name)) {
|
||||||
|
unset($submitData[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->submit($submitData, false);
|
||||||
|
|
||||||
|
if (!$form->isValid()) {
|
||||||
|
$data->resetByFormError($form->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->query->has('setDefaultQuery')) {
|
||||||
|
$params = [];
|
||||||
|
foreach ($form->all() as $name => $child) {
|
||||||
|
$params[$name] = $child->getViewData();
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = ['page', 'setDefaultQuery', 'removeDefaultQuery', 'performSearch'];
|
||||||
|
foreach ($filter as $name) {
|
||||||
|
if (isset($params[$name])) {
|
||||||
|
unset($params[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bookmark === null) {
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setType(Bookmark::SEARCH_DEFAULT);
|
||||||
|
$bookmark->setUser($this->getUser());
|
||||||
|
$bookmark->setName(substr($data->getName(), 0, 50));
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmark->setContent($params);
|
||||||
|
$bookmarkRepo->saveBookmark($bookmark);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ use App\Repository\Query\ActivityFormTypeQuery;
|
|||||||
use App\Repository\Query\ActivityQuery;
|
use App\Repository\Query\ActivityQuery;
|
||||||
use App\Repository\TeamRepository;
|
use App\Repository\TeamRepository;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Pagerfanta\Pagerfanta;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
@@ -77,14 +76,10 @@ final class ActivityController extends AbstractController
|
|||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('admin_activity');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @var $entries Pagerfanta */
|
|
||||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||||
|
|
||||||
return $this->render('activity/index.html.twig', [
|
return $this->render('activity/index.html.twig', [
|
||||||
|
|||||||
@@ -76,11 +76,8 @@ final class CustomerController extends AbstractController
|
|||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('admin_customer');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||||
|
|||||||
@@ -88,14 +88,10 @@ final class ProjectController extends AbstractController
|
|||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('admin_project');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @var $entries Pagerfanta */
|
|
||||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||||
|
|
||||||
return $this->render('project/index.html.twig', [
|
return $this->render('project/index.html.twig', [
|
||||||
|
|||||||
@@ -429,14 +429,6 @@ final class SystemConfigurationController extends AbstractController
|
|||||||
->setLabel('theme.tags_create')
|
->setLabel('theme.tags_create')
|
||||||
->setType(CheckboxType::class)
|
->setType(CheckboxType::class)
|
||||||
->setTranslationDomain('system-configuration'),
|
->setTranslationDomain('system-configuration'),
|
||||||
// TODO should that be configurable per user?
|
|
||||||
/*
|
|
||||||
(new Configuration())
|
|
||||||
->setName('theme.auto_reload_datatable')
|
|
||||||
->setLabel('theme.auto_reload_datatable') // TODO translation
|
|
||||||
->setType(CheckboxType::class)
|
|
||||||
->setTranslationDomain('system-configuration'),
|
|
||||||
*/
|
|
||||||
]),
|
]),
|
||||||
(new SystemConfigurationModel())
|
(new SystemConfigurationModel())
|
||||||
->setSection(SystemConfigurationModel::SECTION_CALENDAR)
|
->setSection(SystemConfigurationModel::SECTION_CALENDAR)
|
||||||
|
|||||||
@@ -43,11 +43,8 @@ class TagController extends AbstractController
|
|||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('tags');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$tags = $repository->getTagCount($query);
|
$tags = $repository->getTagCount($query);
|
||||||
|
|||||||
@@ -55,11 +55,8 @@ final class TeamController extends AbstractController
|
|||||||
$query->setCurrentUser($this->getUser());
|
$query->setCurrentUser($this->getUser());
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('admin_team');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$teams = $repository->getPagerfantaForQuery($query);
|
$teams = $repository->getPagerfantaForQuery($query);
|
||||||
|
|||||||
@@ -77,24 +77,11 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
return $this->service->getActiveTrackingMode();
|
return $this->service->getActiveTrackingMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function index($page, Request $request, string $renderTemplate, string $location): Response
|
protected function index(TimesheetQuery $query, Request $request, string $route, string $renderTemplate, string $location): Response
|
||||||
{
|
{
|
||||||
$query = new TimesheetQuery();
|
|
||||||
$query->setPage($page);
|
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute($route);
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $query->getBegin()) {
|
|
||||||
$query->getBegin()->setTime(0, 0, 0);
|
|
||||||
}
|
|
||||||
if (null !== $query->getEnd()) {
|
|
||||||
$query->getEnd()->setTime(23, 59, 59);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$tags = $query->getTags(true);
|
$tags = $query->getTags(true);
|
||||||
@@ -486,11 +473,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getToolbarForm(TimesheetQuery $query): FormInterface
|
||||||
* @param TimesheetQuery $query
|
|
||||||
* @return FormInterface
|
|
||||||
*/
|
|
||||||
protected function getToolbarForm(TimesheetQuery $query)
|
|
||||||
{
|
{
|
||||||
return $this->createForm(TimesheetToolbarForm::class, $query, [
|
return $this->createForm(TimesheetToolbarForm::class, $query, [
|
||||||
'action' => $this->generateUrl($this->getTimesheetRoute(), [
|
'action' => $this->generateUrl($this->getTimesheetRoute(), [
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use App\Entity\Timesheet;
|
|||||||
use App\Event\TimesheetMetaDisplayEvent;
|
use App\Event\TimesheetMetaDisplayEvent;
|
||||||
use App\Repository\ActivityRepository;
|
use App\Repository\ActivityRepository;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Repository\TagRepository;
|
use App\Repository\TagRepository;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -36,7 +37,11 @@ class TimesheetController extends TimesheetAbstractController
|
|||||||
*/
|
*/
|
||||||
public function indexAction($page, Request $request)
|
public function indexAction($page, Request $request)
|
||||||
{
|
{
|
||||||
return $this->index($page, $request, 'timesheet/index.html.twig', TimesheetMetaDisplayEvent::TIMESHEET);
|
$query = new TimesheetQuery();
|
||||||
|
$query->setPage($page);
|
||||||
|
$query->setName('MyTimesListing');
|
||||||
|
|
||||||
|
return $this->index($query, $request, 'timesheet', 'timesheet/index.html.twig', TimesheetMetaDisplayEvent::TIMESHEET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ class TimesheetTeamController extends TimesheetAbstractController
|
|||||||
*/
|
*/
|
||||||
public function indexAction($page, Request $request)
|
public function indexAction($page, Request $request)
|
||||||
{
|
{
|
||||||
return $this->index($page, $request, 'timesheet-team/index.html.twig', TimesheetMetaDisplayEvent::TEAM_TIMESHEET);
|
$query = new TimesheetQuery();
|
||||||
|
$query->setPage($page);
|
||||||
|
$query->setName('TeamTimesListing');
|
||||||
|
|
||||||
|
return $this->index($query, $request, 'admin_timesheet', 'timesheet-team/index.html.twig', TimesheetMetaDisplayEvent::TEAM_TIMESHEET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use App\Repository\Query\UserFormTypeQuery;
|
|||||||
use App\Repository\Query\UserQuery;
|
use App\Repository\Query\UserQuery;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use Pagerfanta\Pagerfanta;
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
@@ -78,14 +77,10 @@ final class UserController extends AbstractController
|
|||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
$form = $this->getToolbarForm($query);
|
$form = $this->getToolbarForm($query);
|
||||||
$form->setData($query);
|
if ($this->handleSearch($form, $request)) {
|
||||||
$form->submit($request->query->all(), false);
|
return $this->redirectToRoute('admin_user');
|
||||||
|
|
||||||
if (!$form->isValid()) {
|
|
||||||
$query->resetByFormError($form->getErrors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @var $entries Pagerfanta */
|
|
||||||
$entries = $this->getRepository()->getPagerfantaForQuery($query);
|
$entries = $this->getRepository()->getPagerfantaForQuery($query);
|
||||||
|
|
||||||
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::USERS);
|
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::USERS);
|
||||||
|
|||||||
@@ -404,9 +404,6 @@ class Configuration implements ConfigurationInterface
|
|||||||
->defaultValue('selectpicker')
|
->defaultValue('selectpicker')
|
||||||
->setDeprecated()
|
->setDeprecated()
|
||||||
->end()
|
->end()
|
||||||
->booleanNode('auto_reload_datatable')
|
|
||||||
->defaultFalse()
|
|
||||||
->end()
|
|
||||||
->booleanNode('tags_create')
|
->booleanNode('tags_create')
|
||||||
->defaultTrue()
|
->defaultTrue()
|
||||||
->end()
|
->end()
|
||||||
|
|||||||
120
src/Entity/Bookmark.php
Normal file
120
src/Entity/Bookmark.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Table(name="kimai2_bookmarks",
|
||||||
|
* uniqueConstraints={
|
||||||
|
* @ORM\UniqueConstraint(columns={"user_id", "name"})
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* @UniqueEntity(fields={"user", "name"})
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\BookmarkRepository")
|
||||||
|
*/
|
||||||
|
class Bookmark
|
||||||
|
{
|
||||||
|
public const SEARCH_DEFAULT = 'search-default';
|
||||||
|
public const COLUMN_VISIBILITY = 'column-visibility';
|
||||||
|
public const TIMESHEET = 'timesheet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int|null
|
||||||
|
* @ORM\Column(name="id", type="integer")
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
||||||
|
* @ORM\JoinColumn(onDelete="CASCADE", nullable=false)
|
||||||
|
* @Assert\NotNull()
|
||||||
|
*/
|
||||||
|
private $user;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @ORM\Column(name="type", type="string", length=20, nullable=false)
|
||||||
|
* @Assert\NotBlank()
|
||||||
|
* @Assert\Length(min=2, max=20, allowEmptyString=false)
|
||||||
|
*/
|
||||||
|
private $type;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @ORM\Column(name="name", type="string", length=50, nullable=false)
|
||||||
|
* @Assert\NotBlank()
|
||||||
|
* @Assert\Length(min=2, max=50, allowEmptyString=false)
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @ORM\Column(name="content", type="text", nullable=false)
|
||||||
|
*/
|
||||||
|
private $content;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): void
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(string $type): void
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): ?string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContent(array $content): void
|
||||||
|
{
|
||||||
|
$this->content = json_encode($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): array
|
||||||
|
{
|
||||||
|
if ($this->content === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($this->content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
if ($this->id) {
|
||||||
|
$this->id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ class PageActionsEvent extends ThemeEvent
|
|||||||
|
|
||||||
public function addSearchToggle(): void
|
public function addSearchToggle(): void
|
||||||
{
|
{
|
||||||
$this->addAction('search', ['class' => 'search-toggle visible-xs-inline']);
|
$this->addAction('search', ['modal' => '#modal_search']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addQuickExport(string $url): void
|
public function addQuickExport(string $url): void
|
||||||
|
|||||||
@@ -13,13 +13,7 @@ use DateTime;
|
|||||||
|
|
||||||
final class DateRange
|
final class DateRange
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var DateTime
|
|
||||||
*/
|
|
||||||
private $begin;
|
private $begin;
|
||||||
/**
|
|
||||||
* @var DateTime
|
|
||||||
*/
|
|
||||||
private $end;
|
private $end;
|
||||||
|
|
||||||
public function getBegin(): ?DateTime
|
public function getBegin(): ?DateTime
|
||||||
@@ -30,6 +24,7 @@ final class DateRange
|
|||||||
public function setBegin(DateTime $begin): DateRange
|
public function setBegin(DateTime $begin): DateRange
|
||||||
{
|
{
|
||||||
$this->begin = $begin;
|
$this->begin = $begin;
|
||||||
|
$this->begin->setTime(0, 0, 0);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -42,6 +37,7 @@ final class DateRange
|
|||||||
public function setEnd(DateTime $end): DateRange
|
public function setEnd(DateTime $end): DateRange
|
||||||
{
|
{
|
||||||
$this->end = $end;
|
$this->end = $end;
|
||||||
|
$this->end->setTime(23, 59, 59);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,8 @@ abstract class AbstractToolbarForm extends AbstractType
|
|||||||
|
|
||||||
protected function addHiddenOrder(FormBuilderInterface $builder)
|
protected function addHiddenOrder(FormBuilderInterface $builder)
|
||||||
{
|
{
|
||||||
|
@trigger_error('addHiddenOrder() is deprecated and will be removed with 2.0, use the new search modal instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$builder->add('order', HiddenType::class, [
|
$builder->add('order', HiddenType::class, [
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new Choice(['choices' => [BaseQuery::ORDER_ASC, BaseQuery::ORDER_DESC]])
|
new Choice(['choices' => [BaseQuery::ORDER_ASC, BaseQuery::ORDER_DESC]])
|
||||||
@@ -307,8 +309,21 @@ abstract class AbstractToolbarForm extends AbstractType
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function addOrder(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$builder->add('order', ChoiceType::class, [
|
||||||
|
'label' => 'label.order',
|
||||||
|
'choices' => [
|
||||||
|
'label.asc' => BaseQuery::ORDER_ASC,
|
||||||
|
'label.desc' => BaseQuery::ORDER_DESC
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
protected function addHiddenOrderBy(FormBuilderInterface $builder, array $allowedColumns)
|
protected function addHiddenOrderBy(FormBuilderInterface $builder, array $allowedColumns)
|
||||||
{
|
{
|
||||||
|
@trigger_error('addHiddenOrderBy() is deprecated and will be removed with 2.0, use the new search modal instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$builder->add('orderBy', HiddenType::class, [
|
$builder->add('orderBy', HiddenType::class, [
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new Choice(['choices' => $allowedColumns])
|
new Choice(['choices' => $allowedColumns])
|
||||||
@@ -316,6 +331,19 @@ abstract class AbstractToolbarForm extends AbstractType
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function addOrderBy(FormBuilderInterface $builder, array $allowedColumns)
|
||||||
|
{
|
||||||
|
$all = [];
|
||||||
|
foreach ($allowedColumns as $id => $name) {
|
||||||
|
$label = \is_int($id) ? 'label.' . $name : $id;
|
||||||
|
$all[$label] = $name;
|
||||||
|
}
|
||||||
|
$builder->add('orderBy', ChoiceType::class, [
|
||||||
|
'label' => 'label.orderBy',
|
||||||
|
'choices' => $all
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
protected function addTagInputField(FormBuilderInterface $builder)
|
protected function addTagInputField(FormBuilderInterface $builder)
|
||||||
{
|
{
|
||||||
$builder->add('tags', TagsType::class, [
|
$builder->add('tags', TagsType::class, [
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ class ActivityToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addVisibilityChoice($builder);
|
$this->addVisibilityChoice($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, ActivityQuery::ACTIVITY_ORDER_ALLOWED);
|
$this->addOrderBy($builder, ActivityQuery::ACTIVITY_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class CustomerToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addVisibilityChoice($builder);
|
$this->addVisibilityChoice($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, CustomerQuery::CUSTOMER_ORDER_ALLOWED);
|
$this->addOrderBy($builder, CustomerQuery::CUSTOMER_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class ProjectToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addVisibilityChoice($builder);
|
$this->addVisibilityChoice($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, ProjectQuery::PROJECT_ORDER_ALLOWED);
|
$this->addOrderBy($builder, ProjectQuery::PROJECT_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ class TagToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addSearchTermInputField($builder);
|
$this->addSearchTermInputField($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, TagQuery::TAG_ORDER_ALLOWED);
|
$this->addOrderBy($builder, TagQuery::TAG_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class TeamToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addSearchTermInputField($builder);
|
$this->addSearchTermInputField($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, TeamQuery::TEAM_ORDER_ALLOWED);
|
$this->addOrderBy($builder, TeamQuery::TEAM_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class TimesheetToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addExportStateChoice($builder);
|
$this->addExportStateChoice($builder);
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, TimesheetQuery::TIMESHEET_ORDER_ALLOWED);
|
$this->addOrderBy($builder, TimesheetQuery::TIMESHEET_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class UserToolbarForm extends AbstractToolbarForm
|
|||||||
$this->addVisibilityChoice($builder, 'label.active');
|
$this->addVisibilityChoice($builder, 'label.active');
|
||||||
$this->addPageSizeChoice($builder);
|
$this->addPageSizeChoice($builder);
|
||||||
$this->addHiddenPagination($builder);
|
$this->addHiddenPagination($builder);
|
||||||
$this->addHiddenOrder($builder);
|
$this->addOrder($builder);
|
||||||
$this->addHiddenOrderBy($builder, UserQuery::USER_ORDER_ALLOWED);
|
$this->addOrderBy($builder, UserQuery::USER_ORDER_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,13 +27,28 @@ class PageSizeType extends AbstractType
|
|||||||
'label' => 'label.pageSize',
|
'label' => 'label.pageSize',
|
||||||
'choices' => [
|
'choices' => [
|
||||||
10 => 10,
|
10 => 10,
|
||||||
|
15 => 15,
|
||||||
|
20 => 20,
|
||||||
25 => 25,
|
25 => 25,
|
||||||
|
30 => 30,
|
||||||
|
35 => 35,
|
||||||
|
40 => 40,
|
||||||
|
45 => 45,
|
||||||
50 => 50,
|
50 => 50,
|
||||||
75 => 75,
|
60 => 60,
|
||||||
|
70 => 70,
|
||||||
|
80 => 80,
|
||||||
|
90 => 90,
|
||||||
100 => 100,
|
100 => 100,
|
||||||
|
125 => 125,
|
||||||
|
150 => 150,
|
||||||
|
175 => 175,
|
||||||
200 => 200,
|
200 => 200,
|
||||||
|
250 => 250,
|
||||||
300 => 300,
|
300 => 300,
|
||||||
|
350 => 350,
|
||||||
400 => 400,
|
400 => 400,
|
||||||
|
450 => 450,
|
||||||
500 => 500
|
500 => 500
|
||||||
],
|
],
|
||||||
'placeholder' => null,
|
'placeholder' => null,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SearchTermType extends AbstractType
|
|||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'label' => 'search',
|
'label' => 'label.searchTerm',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new Length(['min' => 3, 'allowEmptyString' => true])
|
new Length(['min' => 3, 'allowEmptyString' => true])
|
||||||
|
|||||||
46
src/Migrations/Version20210316224358.php
Normal file
46
src/Migrations/Version20210316224358.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use App\Doctrine\AbstractMigration;
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the bookmark table, to store default search settings.
|
||||||
|
*
|
||||||
|
* @version 1.14
|
||||||
|
*/
|
||||||
|
final class Version20210316224358 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Adds the bookmark table';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$bookmarks = $schema->createTable('kimai2_bookmarks');
|
||||||
|
$bookmarks->addColumn('id', 'integer', ['length' => 11, 'autoincrement' => true, 'notnull' => true]);
|
||||||
|
$bookmarks->addColumn('user_id', 'integer', ['length' => 11, 'notnull' => true]);
|
||||||
|
$bookmarks->addColumn('type', 'string', ['length' => 20, 'notnull' => true]);
|
||||||
|
$bookmarks->addColumn('name', 'string', ['length' => 50, 'notnull' => true]);
|
||||||
|
$bookmarks->addColumn('content', 'text', ['notnull' => true]);
|
||||||
|
$bookmarks->addForeignKeyConstraint('kimai2_users', ['user_id'], ['id'], ['onDelete' => 'CASCADE'], 'FK_4016EF25A76ED395');
|
||||||
|
$bookmarks->addUniqueIndex(['user_id', 'name'], 'UNIQ_4016EF25A76ED3955E237E06');
|
||||||
|
$bookmarks->setPrimaryKey(['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TABLE kimai2_bookmarks');
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Repository/BookmarkRepository.php
Normal file
52
src/Repository/BookmarkRepository.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Bookmark;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\ORMException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Doctrine\ORM\EntityRepository<Bookmark>
|
||||||
|
*/
|
||||||
|
class BookmarkRepository extends EntityRepository
|
||||||
|
{
|
||||||
|
public function saveBookmark(Bookmark $bookmark)
|
||||||
|
{
|
||||||
|
$entityManager = $this->getEntityManager();
|
||||||
|
$entityManager->persist($bookmark);
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteBookmark(Bookmark $bookmark)
|
||||||
|
{
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$em->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$em->remove($bookmark);
|
||||||
|
$em->flush();
|
||||||
|
$em->commit();
|
||||||
|
} catch (ORMException $ex) {
|
||||||
|
$em->rollback();
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSearchDefaultOptions(User $user, string $name): ?Bookmark
|
||||||
|
{
|
||||||
|
return $this->findOneBy([
|
||||||
|
'user' => $user->getId(),
|
||||||
|
'type' => Bookmark::SEARCH_DEFAULT,
|
||||||
|
'name' => substr($name, 0, 50)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -229,7 +229,16 @@ class CustomerRepository extends EntityRepository
|
|||||||
->from(Customer::class, 'c')
|
->from(Customer::class, 'c')
|
||||||
;
|
;
|
||||||
|
|
||||||
$qb->orderBy('c.' . $query->getOrderBy(), $query->getOrder());
|
$orderBy = $query->getOrderBy();
|
||||||
|
switch ($orderBy) {
|
||||||
|
case 'vat_id':
|
||||||
|
$orderBy = 'c.vatId';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$orderBy = 'c.' . $orderBy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$qb->orderBy($orderBy, $query->getOrder());
|
||||||
|
|
||||||
if ($query->isShowVisible()) {
|
if ($query->isShowVisible()) {
|
||||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
||||||
|
|||||||
@@ -271,6 +271,12 @@ class ProjectRepository extends EntityRepository
|
|||||||
case 'customer':
|
case 'customer':
|
||||||
$orderBy = 'c.name';
|
$orderBy = 'c.name';
|
||||||
break;
|
break;
|
||||||
|
case 'project_start':
|
||||||
|
$orderBy = 'p.start';
|
||||||
|
break;
|
||||||
|
case 'project_end':
|
||||||
|
$orderBy = 'p.end';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$orderBy = 'p.' . $orderBy;
|
$orderBy = 'p.' . $orderBy;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use App\Entity\Project;
|
|||||||
*/
|
*/
|
||||||
class ActivityQuery extends ProjectQuery
|
class ActivityQuery extends ProjectQuery
|
||||||
{
|
{
|
||||||
public const ACTIVITY_ORDER_ALLOWED = ['id', 'name', 'comment', 'customer', 'project', 'visible'];
|
public const ACTIVITY_ORDER_ALLOWED = ['id', 'name', 'comment', 'customer', 'project', 'budget', 'timeBudget', 'visible'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Project[]|int[]
|
* @var Project[]|int[]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace App\Repository\Query;
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
|
use App\Entity\Bookmark;
|
||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Utils\SearchTerm;
|
use App\Utils\SearchTerm;
|
||||||
@@ -78,6 +79,14 @@ class BaseQuery
|
|||||||
* @var SearchTerm|null
|
* @var SearchTerm|null
|
||||||
*/
|
*/
|
||||||
private $searchTerm;
|
private $searchTerm;
|
||||||
|
/**
|
||||||
|
* @var Bookmark
|
||||||
|
*/
|
||||||
|
private $bookmark;
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Team[] $teams
|
* @param Team[] $teams
|
||||||
@@ -274,6 +283,32 @@ class BaseQuery
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setBookmark(Bookmark $bookmark): void
|
||||||
|
{
|
||||||
|
$this->bookmark = $bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBookmark(): ?Bookmark
|
||||||
|
{
|
||||||
|
return $this->bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): void
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
if (null !== $this->name) {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortClass = explode('\\', static::class);
|
||||||
|
|
||||||
|
return array_pop($shortClass);
|
||||||
|
}
|
||||||
|
|
||||||
public function copyTo(BaseQuery $query): BaseQuery
|
public function copyTo(BaseQuery $query): BaseQuery
|
||||||
{
|
{
|
||||||
$query->setDefaults($this->defaults);
|
$query->setDefaults($this->defaults);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class CustomerQuery extends BaseQuery implements VisibilityInterface
|
|||||||
|
|
||||||
public const CUSTOMER_ORDER_ALLOWED = [
|
public const CUSTOMER_ORDER_ALLOWED = [
|
||||||
'id', 'name', 'comment', 'country', 'number', 'homepage', 'email', 'mobile', 'fax',
|
'id', 'name', 'comment', 'country', 'number', 'homepage', 'email', 'mobile', 'fax',
|
||||||
'phone', 'currency', 'address', 'contact', 'company', 'vatId', 'visible'
|
'phone', 'currency', 'address', 'contact', 'company', 'vat_id', 'budget', 'timeBudget', 'visible'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
|
|||||||
use VisibilityTrait;
|
use VisibilityTrait;
|
||||||
|
|
||||||
public const PROJECT_ORDER_ALLOWED = [
|
public const PROJECT_ORDER_ALLOWED = [
|
||||||
'id', 'name', 'comment', 'customer', 'orderNumber', 'projectStart',
|
'id', 'name', 'comment', 'customer', 'orderNumber', 'orderDate', 'project_start', 'project_end', 'budget', 'timeBudget', 'visible'
|
||||||
'projectEnd', 'orderNumber', 'orderDate', 'start', 'end', 'visible'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ final class ConfigExtension extends AbstractExtension
|
|||||||
public function getThemeConfig(string $name)
|
public function getThemeConfig(string $name)
|
||||||
{
|
{
|
||||||
switch ($name) {
|
switch ($name) {
|
||||||
|
case 'auto_reload_datatable':
|
||||||
|
@trigger_error('The configuration auto_reload_datatable is deprecated and was removed with 1.4', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
case 'soft_limit':
|
case 'soft_limit':
|
||||||
return $this->configuration->getTimesheetActiveEntriesSoftLimit();
|
return $this->configuration->getTimesheetActiveEntriesSoftLimit();
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ final class IconExtension extends AbstractExtension
|
|||||||
'avatar' => 'fas fa-user',
|
'avatar' => 'fas fa-user',
|
||||||
'back' => 'fas fa-long-arrow-alt-left',
|
'back' => 'fas fa-long-arrow-alt-left',
|
||||||
'barcode' => 'fas fa-barcode',
|
'barcode' => 'fas fa-barcode',
|
||||||
|
'bookmark' => 'far fa-star',
|
||||||
|
'bookmarked' => 'fas fa-star',
|
||||||
'calendar' => 'far fa-calendar-alt',
|
'calendar' => 'far fa-calendar-alt',
|
||||||
'clock' => 'far fa-clock',
|
'clock' => 'far fa-clock',
|
||||||
'comment' => 'far fa-comment',
|
'comment' => 'far fa-comment',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
{% import "activity/actions.html.twig" as actions %}
|
{% import "activity/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
{% set tableName = 'activity_admin' %}
|
{% set tableName = 'activity_admin' %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.activities('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.activities('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main_before %}
|
{% block main_before %}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
{% block modal_body %}{% endblock %}
|
{% block modal_body %}{% endblock %}
|
||||||
{% block modal_footer %}{% endblock %}
|
{% block modal_footer %}{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
|
{% block page_search %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content_start %}
|
{% block page_content_start %}
|
||||||
@@ -251,7 +252,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}
|
{% block breadcrumb %}
|
||||||
{% block page_search %}{% endblock %}
|
|
||||||
{% block page_actions %}{% endblock %}
|
{% block page_actions %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -280,7 +280,6 @@
|
|||||||
locale: '{{ app.request.locale }}',
|
locale: '{{ app.request.locale }}',
|
||||||
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
||||||
twentyFourHours: {{ 'true'|hour24('false') }},
|
twentyFourHours: {{ 'true'|hour24('false') }},
|
||||||
autoReloadDatatable: {% if theme_config('auto_reload_datatable') %}true{% else %}false{% endif %},
|
|
||||||
autoComplete: {{ theme_config('autocomplete_chars') }},
|
autoComplete: {{ theme_config('autocomplete_chars') }},
|
||||||
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
||||||
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
{% import "customer/actions.html.twig" as actions %}
|
{% import "customer/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||||
'number': {'class': 'hidden-xs hidden-sm hidden'},
|
'number': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'company': {'class': 'hidden-xs hidden-sm hidden'},
|
'company': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'vat_id': {'class': 'hidden-xs hidden-sm hidden', 'orderBy': 'vatId'},
|
'vat_id': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'contact': {'class': 'hidden-xs hidden-sm hidden'},
|
'contact': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'address': {'class': 'hidden-xs hidden-sm hidden'},
|
'address': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'country': {'class': 'hidden-xs hidden-sm hidden'},
|
'country': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
}) %}
|
}) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% set columns = columns|merge({
|
{% set columns = columns|merge({
|
||||||
'budget': {'class': 'hidden-xs hidden-sm hidden text-center w-min', 'title': 'label.budget'|trans},
|
'budget': {'class': 'hidden-xs hidden-sm hidden text-center w-min'},
|
||||||
'timeBudget': {'class': 'hidden-xs hidden-sm hidden text-center w-min', 'title': 'label.timeBudget'|trans},
|
'timeBudget': {'class': 'hidden-xs hidden-sm hidden text-center w-min'},
|
||||||
'team': {'class': 'text-center w-min', 'orderBy': false},
|
'team': {'class': 'text-center w-min', 'orderBy': false},
|
||||||
'visible': {'class': 'text-center hidden w-min'},
|
'visible': {'class': 'text-center hidden w-min'},
|
||||||
'actions': {'class': 'actions alwaysVisible'},
|
'actions': {'class': 'actions alwaysVisible'},
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
{% set tableName = 'customer_admin' %}
|
{% set tableName = 'customer_admin' %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.customers('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.customers('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main_before %}
|
{% block main_before %}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
{{ encore_entry_link_tags('app') }}
|
{{ encore_entry_link_tags('app') }}
|
||||||
{{ encore_entry_script_tags('app') }}
|
{{ encore_entry_script_tags('app') }}
|
||||||
{% block styles %}{% endblock %}
|
{% block styles %}{% endblock %}
|
||||||
|
{% if block('document_title') is defined %}
|
||||||
|
<title>{{ block('document_title') }}</title>
|
||||||
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
<body class="pad export_print">
|
<body class="pad export_print">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% extends 'export/layout.html.twig' %}
|
{% extends 'export/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block document_title %}{{ 'export.title'|trans }}{% endblock %}
|
||||||
|
|
||||||
{% set showRateBudget = false %}
|
{% set showRateBudget = false %}
|
||||||
{% set showTimeBudget = false %}
|
{% set showTimeBudget = false %}
|
||||||
{% for budget in budgets %}
|
{% for budget in budgets %}
|
||||||
@@ -57,12 +59,51 @@
|
|||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
let config = KimaiCookies.get('kimai_html_export');
|
||||||
|
if (config !== undefined && config !== null) {
|
||||||
|
console.log(config);
|
||||||
|
for (const key in config) {
|
||||||
|
let elName = key;
|
||||||
|
let checked = config[key];
|
||||||
|
let el = document.getElementById(elName);
|
||||||
|
if (el === undefined || el === null) {
|
||||||
|
el = document.getElementById('records-column-' + elName);
|
||||||
|
if (el === undefined || el === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (checked !== el.checked) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveVisibility()
|
||||||
|
{
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.getElementsByTagName('form')[0];
|
||||||
|
let settings = {};
|
||||||
|
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
|
||||||
|
settings[checkbox.getAttribute('name')] = checkbox.checked;
|
||||||
|
}
|
||||||
|
KimaiCookies.set('kimai_html_export', settings, {expires: 365, SameSite: 'Strict'});
|
||||||
|
}
|
||||||
document.getElementById('summary-by-activities').addEventListener('click', function(event) {
|
document.getElementById('summary-by-activities').addEventListener('click', function(event) {
|
||||||
document.getElementById('summary-project').style.display = event.target.checked ? 'none' : 'table';
|
document.getElementById('summary-project').style.display = event.target.checked ? 'none' : 'table';
|
||||||
document.getElementById('summary-activity').style.display = event.target.checked ? 'table' : 'none';
|
document.getElementById('summary-activity').style.display = event.target.checked ? 'table' : 'none';
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
document.getElementById('summary-show').addEventListener('click', function(event) {
|
document.getElementById('summary-show').addEventListener('click', function(event) {
|
||||||
document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
|
document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
{% if showTimeBudget %}
|
{% if showTimeBudget %}
|
||||||
document.getElementById('summary-timeBudget').addEventListener('click', function(event) {
|
document.getElementById('summary-timeBudget').addEventListener('click', function(event) {
|
||||||
@@ -70,6 +111,7 @@
|
|||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if showRateBudget %}
|
{% if showRateBudget %}
|
||||||
@@ -78,6 +120,7 @@
|
|||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
document.getElementById('summary-duration').addEventListener('click', function(event) {
|
document.getElementById('summary-duration').addEventListener('click', function(event) {
|
||||||
@@ -85,24 +128,29 @@
|
|||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
document.getElementById('summary-rate').addEventListener('click', function(event) {
|
document.getElementById('summary-rate').addEventListener('click', function(event) {
|
||||||
var cells = document.getElementsByClassName('summary-rate');
|
var cells = document.getElementsByClassName('summary-rate');
|
||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
document.getElementById('summary-rate-internal').addEventListener('click', function(event) {
|
document.getElementById('summary-rate-internal').addEventListener('click', function(event) {
|
||||||
var cells = document.getElementsByClassName('summary-rate-internal');
|
var cells = document.getElementsByClassName('summary-rate-internal');
|
||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
document.getElementById('summary-show').addEventListener('click', function(event) {
|
document.getElementById('summary-show').addEventListener('click', function(event) {
|
||||||
document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
|
document.getElementById('export-summary').style.display = event.target.checked ? 'block' : 'none';
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
document.getElementById('records-show').addEventListener('click', function(event) {
|
document.getElementById('records-show').addEventListener('click', function(event) {
|
||||||
document.getElementById('export-records').style.display = event.target.checked ? 'block' : 'none';
|
document.getElementById('export-records').style.display = event.target.checked ? 'block' : 'none';
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
var columnCheckboxes = document.getElementsByClassName('column-visibility-changer');
|
var columnCheckboxes = document.getElementsByClassName('column-visibility-changer');
|
||||||
for (var checkbox of columnCheckboxes) {
|
for (var checkbox of columnCheckboxes) {
|
||||||
@@ -111,6 +159,7 @@
|
|||||||
for (var columnCell of cells) {
|
for (var columnCell of cells) {
|
||||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||||
}
|
}
|
||||||
|
saveVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -118,8 +167,6 @@
|
|||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<style>
|
<style>
|
||||||
.items tr.summary {
|
|
||||||
}
|
|
||||||
.items td.totals {
|
.items td.totals {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -141,77 +188,81 @@
|
|||||||
{% block export %}
|
{% block export %}
|
||||||
<div class="row no-print toolbar">
|
<div class="row no-print toolbar">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<form class="navbar-form">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="row">
|
||||||
{{ 'export.summary'|trans }}:
|
<div class="col-xs-1">
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="control-label" for="summary-show">
|
||||||
<label class="control-label" for="summary-show">
|
<input type="checkbox" id="summary-show" name="summary-show" checked="checked">
|
||||||
<input type="checkbox" id="summary-show" name="summary-show" checked="checked">
|
{{ 'export.summary'|trans }}
|
||||||
{{ 'label.visible'|trans }}
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-by-activities">
|
|
||||||
<input type="checkbox" id="summary-by-activities" name="summary-by-activities">
|
|
||||||
{{ 'label.activity'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% if showTimeBudget %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-timeBudget">
|
|
||||||
<input type="checkbox" id="summary-timeBudget" name="summary-timeBudget" checked>
|
|
||||||
{{ 'label.timeBudget'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if showRateBudget %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-budget">
|
|
||||||
<input type="checkbox" id="summary-budget" name="summary-budget" checked>
|
|
||||||
{{ 'label.budget'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-duration">
|
|
||||||
<input type="checkbox" id="summary-duration" name="summary-duration" checked>
|
|
||||||
{{ 'label.duration'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-rate-internal">
|
|
||||||
<input type="checkbox" id="summary-rate-internal" name="summary-rate-internal" checked>
|
|
||||||
{{ 'label.rate_internal'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="summary-rate">
|
|
||||||
<input type="checkbox" id="summary-rate" name="summary-rate" checked>
|
|
||||||
{{ 'label.rate'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="navbar-form">
|
|
||||||
<div class="form-group">
|
|
||||||
{{ 'export.full_list'|trans }}:
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="records-show">
|
|
||||||
<input type="checkbox" id="records-show" name="records-show" checked="checked">
|
|
||||||
{{ 'label.visible'|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% for columnId, visibility in columns %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label" for="records-column-{{ columnId }}">
|
|
||||||
<input type="checkbox" class="column-visibility-changer" id="records-column-{{ columnId }}" name="{{ columnId }}" {% if visibility %}checked="checked"{% endif %}>
|
|
||||||
{{ columnTitles[columnId]|default('label.'~columnId)|trans }}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
<div class="col-xs-11">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-by-activities">
|
||||||
|
<input type="checkbox" id="summary-by-activities" name="summary-by-activities">
|
||||||
|
{{ 'label.activity'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% if showTimeBudget %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-timeBudget">
|
||||||
|
<input type="checkbox" id="summary-timeBudget" name="summary-timeBudget" checked>
|
||||||
|
{{ 'label.timeBudget'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if showRateBudget %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-budget">
|
||||||
|
<input type="checkbox" id="summary-budget" name="summary-budget" checked>
|
||||||
|
{{ 'label.budget'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-duration">
|
||||||
|
<input type="checkbox" id="summary-duration" name="summary-duration" checked>
|
||||||
|
{{ 'label.duration'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-rate-internal">
|
||||||
|
<input type="checkbox" id="summary-rate-internal" name="summary-rate-internal" checked>
|
||||||
|
{{ 'label.rate_internal'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="summary-rate">
|
||||||
|
<input type="checkbox" id="summary-rate" name="summary-rate" checked>
|
||||||
|
{{ 'label.rate'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-1">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="records-show">
|
||||||
|
<input type="checkbox" id="records-show" name="records-show" checked="checked">
|
||||||
|
{{ 'export.full_list'|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-11">
|
||||||
|
{% for columnId, visibility in columns %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="records-column-{{ columnId }}">
|
||||||
|
<input type="checkbox" class="column-visibility-changer" id="records-column-{{ columnId }}" name="{{ columnId }}" {% if visibility %}checked="checked"{% endif %}>
|
||||||
|
{{ columnTitles[columnId]|default('label.'~columnId)|trans }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
54
templates/macros/search.html.twig
Normal file
54
templates/macros/search.html.twig
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{% macro searchModal(form) %}
|
||||||
|
{% form_theme form 'form/search-form.html.twig' %}
|
||||||
|
<div class="modal fade" id="modal_search" tabindex="-1" role="dialog" aria-labelledby="modal_search_label">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
{{ form_start(form, {'attr': {'class': 'form-narrow searchform'}}) }}
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="modal_search_label">
|
||||||
|
{{ 'search'|trans }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% set orderBy = form_widget(form.orderBy) %}
|
||||||
|
{% set order = form_widget(form.order) %}
|
||||||
|
{{ form(form) }}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form_label(form.orderBy) }}
|
||||||
|
<div class="col-sm-5 col-xs-5">
|
||||||
|
{{ orderBy|raw }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-xs-3">
|
||||||
|
{{ order|raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="btn-toolbar pull-left" role="toolbar">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="submit" name="performSearch" value="performSearch" class="btn btn-primary pull-left" data-type="submit">{{ 'search'|trans }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
{% if form.vars.data.bookmark %}
|
||||||
|
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-default" title="{{ 'label.set_as_default'|trans }}"><i class="{{ 'bookmarked'|icon }}"></i></button>
|
||||||
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a href="?removeDefaultQuery={{ form.vars.data.bookmark.name }}" id="removeDefaultQuery">{{ 'action.delete'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-default" title="{{ 'label.set_as_default'|trans }}"><i class="{{ 'bookmark'|icon }}"></i></button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ form_end(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{% macro toolbar(form, collapsible, displayInitial) %}
|
{% macro toolbar(form, collapsible, displayInitial) %}
|
||||||
{% deprecated 'The macro "toolbar" is deprecated since 1.2 and will be removed with 2.0, use dropDownSearch() instead' %}
|
{% deprecated 'The macro "toolbar" is deprecated since 1.2 and will be removed with 2.0, use search.searchModal() instead' %}
|
||||||
<div class="toolbar no-print">
|
<div class="toolbar no-print">
|
||||||
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
|
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
|
||||||
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
|
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
|
||||||
@@ -10,45 +10,27 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro dropDownSearch(form) %}
|
{% macro dropDownSearch(form) %}
|
||||||
{% form_theme form 'form/search-form.html.twig' %}
|
{% deprecated 'The macro "dropDownSearch" is deprecated since 1.4 and will be removed with 2.0, use search.searchModal() instead' %}
|
||||||
{{ form_start(form, { 'attr': {'class': 'form-horizontal header-search hidden-xs'}}) }}
|
<div class="modal fade" id="modal_search" tabindex="-1" role="dialog" aria-labelledby="modal_search_label">
|
||||||
{% set searchTermClass = 'dropdown-toggle input-sm' %}
|
<div class="modal-dialog" role="document">
|
||||||
{% if not form.searchTerm.vars.valid %}
|
{% form_theme form 'form/search-form.html.twig' %}
|
||||||
{% set searchTermClass = searchTermClass ~ ' search-has-error' %}
|
{{ form_start(form, {'attr': {'class': 'form-narrow searchform'}}) }}
|
||||||
{% endif %}
|
<div class="modal-content">
|
||||||
{% if not form.vars.valid %}
|
<div class="modal-header">
|
||||||
{% set searchTermClass = searchTermClass ~ ' has-error' %}
|
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||||
{% endif %}
|
<h4 class="modal-title" id="modal_search_label">
|
||||||
<div class="dropdown">
|
{{ 'search'|trans }}
|
||||||
<div class="form-group">
|
</h4>
|
||||||
<div class="input-group">
|
</div>
|
||||||
<div class="input-group-addon {% if not form.vars.valid %} has-error{% endif %}">
|
<div class="modal-body">
|
||||||
<i class="{{ 'search'|icon }}"></i>
|
{{ form(form) }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary pull-left" data-type="submit">{{ 'search'|trans }}</button>
|
||||||
|
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||||
</div>
|
</div>
|
||||||
{{ form_widget(form.searchTerm, {'attr': {'placeholder': 'search'|trans, 'autocomplete': 'off', 'data-toggle': 'dropdown', 'class': searchTermClass, 'aria-haspopup': 'true', 'aria-expanded': 'true'}}) }}
|
|
||||||
<ul class="dropdown-menu pre-scrollable" role="menu" aria-labelledby="searchTerm">
|
|
||||||
<li>
|
|
||||||
<div class="container-fluid">
|
|
||||||
{% if not form.searchTerm.vars.valid %}
|
|
||||||
{{ form_errors(form.searchTerm) }}
|
|
||||||
<br>
|
|
||||||
{% endif %}
|
|
||||||
{{ form_widget(form) }}
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-3 col-xs-4"></div>
|
|
||||||
<div class="col-sm-9 col-xs-8">
|
|
||||||
{% if not theme_config('auto_reload_datatable') %}
|
|
||||||
{# FIXME this should trigger the datatable reload and not "really" submit the form #}
|
|
||||||
<input type="submit" value="{{ 'search'|trans }}" class="btn btn-primary" />
|
|
||||||
{% endif %}
|
|
||||||
<a href="#" class="search-cancel pull-right">{{ 'action.close'|trans }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ form_end(form) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ form_end(form) }}
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
{% import "project/actions.html.twig" as actions %}
|
{% import "project/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||||
'orderNumber': {'class': 'hidden-xs hidden-sm hidden'},
|
'orderNumber': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'orderDate': {'class': 'hidden-xs hidden-sm hidden'},
|
'orderDate': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'start': {'class': 'hidden-xs hidden-sm hidden', 'title': 'label.project_start'|trans},
|
'project_start': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
'end': {'class': 'hidden-xs hidden-sm hidden', 'title': 'label.project_end'|trans},
|
'project_end': {'class': 'hidden-xs hidden-sm hidden'},
|
||||||
} %}
|
} %}
|
||||||
{% for field in metaColumns %}
|
{% for field in metaColumns %}
|
||||||
{% set columns = columns|merge({
|
{% set columns = columns|merge({
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{% set tableName = 'project_admin' %}
|
{% set tableName = 'project_admin' %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.projects('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.projects('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main_before %}
|
{% block main_before %}
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full }}{% endif %}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'start') }}">{% if entry.start is not null %}{{ entry.start|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full }}{% endif %}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'end') }}">{% if entry.end is not null %}{{ entry.end|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_end') }}">{% if entry.end is not null %}{{ entry.end|date_full }}{% endif %}</td>
|
||||||
{% for field in metaColumns %}
|
{% for field in metaColumns %}
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
||||||
{{ tables.datatable_meta_column(entry, field) }}
|
{{ tables.datatable_meta_column(entry, field) }}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "tags/actions.html.twig" as actions %}
|
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
|
{% import "tags/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'menu.tags'|trans }}{% endblock %}
|
{% block page_title %}{{ 'menu.tags'|trans }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.tags('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.tags('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "team/actions.html.twig" as actions %}
|
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
|
{% import "team/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'teams.title'|trans({}, 'teams') }}{% endblock %}
|
{% block page_title %}{{ 'teams.title'|trans({}, 'teams') }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.teams('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.teams('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/search.html.twig" as search %}
|
||||||
|
|
||||||
{% if not showSummary is defined %}
|
{% if not showSummary is defined %}
|
||||||
{% set showSummary = false %}
|
{% set showSummary = false %}
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
'actions': {'class': 'actions alwaysVisible'},
|
'actions': {'class': 'actions alwaysVisible'},
|
||||||
}) %}
|
}) %}
|
||||||
|
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
|
|
||||||
{% block main_before %}
|
{% block main_before %}
|
||||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "macros/datatables.html.twig" as tables %}
|
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
{% import "macros/datatables.html.twig" as tables %}
|
||||||
|
{% import "macros/search.html.twig" as search %}
|
||||||
{% import "user/actions.html.twig" as actions %}
|
{% import "user/actions.html.twig" as actions %}
|
||||||
|
|
||||||
{% set columns = {
|
{% set columns = {
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{% set tableName = 'user_admin' %}
|
{% set tableName = 'user_admin' %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.users('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.users('index') }}{% endblock %}
|
||||||
{% block page_icon %}{{ 'user'|icon }}{% endblock %}
|
{% block page_icon %}{{ 'user'|icon }}{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -182,10 +182,12 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
|||||||
$commandTester = $this->createInvoice(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--set-exported' => null, '--customer' => 1, '--template' => 'Invoice', '--start' => '2020-01-01', '--end' => '2020-03-01']);
|
$commandTester = $this->createInvoice(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--set-exported' => null, '--customer' => 1, '--template' => 'Invoice', '--start' => '2020-01-01', '--end' => '2020-03-01']);
|
||||||
|
|
||||||
$output = $commandTester->getDisplay();
|
$output = $commandTester->getDisplay();
|
||||||
$this->assertStringContainsString('+----+----------+-------+------------- Created 1 invoice(s) --------------------------------------+', $output);
|
$this->assertStringContainsString('Created 1 invoice(s)', $output);
|
||||||
$this->assertStringContainsString('| ID | Customer | Total | Filename |', $output);
|
$this->assertStringContainsString('| ID', $output);
|
||||||
$this->assertStringContainsString('+----+----------+-------+-------------------------------------------------------------------------+', $output);
|
$this->assertStringContainsString('| Customer', $output);
|
||||||
$this->assertStringContainsString('| Test | 0 EUR | /', $output);
|
$this->assertStringContainsString('| Total', $output);
|
||||||
|
$this->assertStringContainsString('| Filename', $output);
|
||||||
|
$this->assertStringContainsString('0 EUR', $output);
|
||||||
$this->assertStringContainsString('/tests/_data/invoices/' . ((new \DateTime())->format('Y')) . '-001-Test.html |', $output);
|
$this->assertStringContainsString('/tests/_data/invoices/' . ((new \DateTime())->format('Y')) . '-001-Test.html |', $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class ThemeConfigurationTest extends TestCase
|
|||||||
'company' => null,
|
'company' => null,
|
||||||
'title' => null,
|
'title' => null,
|
||||||
],
|
],
|
||||||
'auto_reload_datatable' => false,
|
|
||||||
'tags_create' => true,
|
'tags_create' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -61,7 +60,6 @@ class ThemeConfigurationTest extends TestCase
|
|||||||
public function testConfigs()
|
public function testConfigs()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut($this->getDefaultSettings(), []);
|
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||||
$this->assertFalse($sut->isAutoReloadDatatable());
|
|
||||||
$this->assertTrue($sut->isAllowTagCreation());
|
$this->assertTrue($sut->isAllowTagCreation());
|
||||||
$this->assertNull($sut->getTitle());
|
$this->assertNull($sut->getTitle());
|
||||||
}
|
}
|
||||||
@@ -73,5 +71,6 @@ class ThemeConfigurationTest extends TestCase
|
|||||||
{
|
{
|
||||||
$sut = $this->getSut($this->getDefaultSettings(), []);
|
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||||
$this->assertEquals('', $sut->getSelectPicker());
|
$this->assertEquals('', $sut->getSelectPicker());
|
||||||
|
$this->assertFalse($sut->isAutoReloadDatatable());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class ActivityControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/activity/');
|
$this->assertAccessIsGranted($client, '/admin/activity/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
'visibility' => 1,
|
'visibility' => 1,
|
||||||
@@ -102,7 +102,7 @@ class ActivityControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/activity/');
|
$this->assertAccessIsGranted($client, '/admin/activity/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/activity/export'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/activity/export'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class CustomerControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/customer/');
|
$this->assertAccessIsGranted($client, '/admin/customer/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
'visibility' => 1,
|
'visibility' => 1,
|
||||||
@@ -92,7 +92,7 @@ class CustomerControllerTest extends ControllerBaseTest
|
|||||||
$this->request($client, '/admin/customer/');
|
$this->request($client, '/admin/customer/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/customer/export'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/customer/export'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ProjectControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/project/');
|
$this->assertAccessIsGranted($client, '/admin/project/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
'visibility' => 1,
|
'visibility' => 1,
|
||||||
@@ -108,7 +108,7 @@ class ProjectControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/project/');
|
$this->assertAccessIsGranted($client, '/admin/project/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/project/export'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/project/export'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'feature:timetracking foo',
|
'searchTerm' => 'feature:timetracking foo',
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class TagControllerTest extends ControllerBaseTest
|
|||||||
$this->request($client, '/admin/tags/');
|
$this->request($client, '/admin/tags/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'Support',
|
'searchTerm' => 'Support',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class TeamControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/teams/');
|
$this->assertAccessIsGranted($client, '/admin/teams/');
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
'search search-toggle visible-xs-inline' => '#',
|
'search' => '#',
|
||||||
'create' => $this->createUrl('/admin/teams/create'),
|
'create' => $this->createUrl('/admin/teams/create'),
|
||||||
'help' => 'https://www.kimai.org/documentation/teams.html'
|
'help' => 'https://www.kimai.org/documentation/teams.html'
|
||||||
]);
|
]);
|
||||||
@@ -62,7 +62,7 @@ class TeamControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$this->assertAccessIsGranted($client, '/admin/teams/');
|
$this->assertAccessIsGranted($client, '/admin/teams/');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'foo',
|
'searchTerm' => 'foo',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class TimesheetControllerTest extends ControllerBaseTest
|
|||||||
// there are no records by default in the test database
|
// there are no records by default in the test database
|
||||||
$this->assertHasNoEntriesWithFilter($client);
|
$this->assertHasNoEntriesWithFilter($client);
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
'search search-toggle visible-xs-inline' => '#',
|
'search' => '#',
|
||||||
'toolbar-action exporter-csv' => $this->createUrl('/timesheet/export/csv'),
|
'toolbar-action exporter-csv' => $this->createUrl('/timesheet/export/csv'),
|
||||||
'toolbar-action exporter-print' => $this->createUrl('/timesheet/export/print'),
|
'toolbar-action exporter-print' => $this->createUrl('/timesheet/export/print'),
|
||||||
'toolbar-action exporter-pdf' => $this->createUrl('/timesheet/export/pdf'),
|
'toolbar-action exporter-pdf' => $this->createUrl('/timesheet/export/pdf'),
|
||||||
@@ -67,7 +67,7 @@ class TimesheetControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'state' => 1,
|
'state' => 1,
|
||||||
'pageSize' => 25,
|
'pageSize' => 25,
|
||||||
@@ -113,7 +113,7 @@ class TimesheetControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'location:homeoffice foobar',
|
'searchTerm' => 'location:homeoffice foobar',
|
||||||
]);
|
]);
|
||||||
@@ -138,7 +138,7 @@ class TimesheetControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/timesheet/export/print'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/timesheet/export/print'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'state' => 1,
|
'state' => 1,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
|||||||
$this->assertHasNoEntriesWithFilter($client);
|
$this->assertHasNoEntriesWithFilter($client);
|
||||||
|
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
'search search-toggle visible-xs-inline' => '#',
|
'search' => '#',
|
||||||
'toolbar-action exporter-csv' => $this->createUrl('/team/timesheet/export/csv'),
|
'toolbar-action exporter-csv' => $this->createUrl('/team/timesheet/export/csv'),
|
||||||
'toolbar-action exporter-print' => $this->createUrl('/team/timesheet/export/print'),
|
'toolbar-action exporter-print' => $this->createUrl('/team/timesheet/export/print'),
|
||||||
'toolbar-action exporter-pdf' => $this->createUrl('/team/timesheet/export/pdf'),
|
'toolbar-action exporter-pdf' => $this->createUrl('/team/timesheet/export/pdf'),
|
||||||
@@ -73,7 +73,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'state' => 1,
|
'state' => 1,
|
||||||
'users' => [$user->getId()],
|
'users' => [$user->getId()],
|
||||||
@@ -119,7 +119,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
$dateRange = ($start)->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime('last day of this month'))->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'location:homeoffice foobar',
|
'searchTerm' => 'location:homeoffice foobar',
|
||||||
]);
|
]);
|
||||||
@@ -150,7 +150,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/team/timesheet/export/print'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/team/timesheet/export/print'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'state' => 1,
|
'state' => 1,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class UserControllerTest extends ControllerBaseTest
|
|||||||
$this->assertHasDataTable($client);
|
$this->assertHasDataTable($client);
|
||||||
$this->assertDataTableRowCount($client, 'datatable_user_admin', 7);
|
$this->assertDataTableRowCount($client, 'datatable_user_admin', 7);
|
||||||
$this->assertPageActions($client, [
|
$this->assertPageActions($client, [
|
||||||
'search search-toggle visible-xs-inline' => '#',
|
'search' => '#',
|
||||||
'visibility' => '#',
|
'visibility' => '#',
|
||||||
'download toolbar-action' => $this->createUrl('/admin/user/export'),
|
'download toolbar-action' => $this->createUrl('/admin/user/export'),
|
||||||
'create' => $this->createUrl('/admin/user/create'),
|
'create' => $this->createUrl('/admin/user/create'),
|
||||||
@@ -50,7 +50,7 @@ class UserControllerTest extends ControllerBaseTest
|
|||||||
$this->request($client, '/admin/user/');
|
$this->request($client, '/admin/user/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'hourly_rate:35 tony',
|
'searchTerm' => 'hourly_rate:35 tony',
|
||||||
'role' => 'ROLE_TEAMLEAD',
|
'role' => 'ROLE_TEAMLEAD',
|
||||||
@@ -83,7 +83,7 @@ class UserControllerTest extends ControllerBaseTest
|
|||||||
$this->request($client, '/admin/user/');
|
$this->request($client, '/admin/user/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
$form = $client->getCrawler()->filter('form.searchform')->form();
|
||||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/user/export'));
|
$form->getFormNode()->setAttribute('action', $this->createUrl('/admin/user/export'));
|
||||||
$client->submit($form, [
|
$client->submit($form, [
|
||||||
'searchTerm' => 'hourly_rate:35 tony',
|
'searchTerm' => 'hourly_rate:35 tony',
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ class AppExtensionTest extends TestCase
|
|||||||
'title' => null,
|
'title' => null,
|
||||||
'translation' => null,
|
'translation' => null,
|
||||||
],
|
],
|
||||||
'auto_reload_datatable' => false,
|
|
||||||
'autocomplete_chars' => 3,
|
'autocomplete_chars' => 3,
|
||||||
'tags_create' => true,
|
'tags_create' => true,
|
||||||
'calendar' => [
|
'calendar' => [
|
||||||
|
|||||||
@@ -343,7 +343,6 @@ class ConfigurationTest extends TestCase
|
|||||||
'active_warning' => 3,
|
'active_warning' => 3,
|
||||||
'box_color' => 'blue',
|
'box_color' => 'blue',
|
||||||
'select_type' => 'selectpicker',
|
'select_type' => 'selectpicker',
|
||||||
'auto_reload_datatable' => false,
|
|
||||||
'show_about' => true,
|
'show_about' => true,
|
||||||
'chart' => [
|
'chart' => [
|
||||||
'background_color' => '#3c8dbc',
|
'background_color' => '#3c8dbc',
|
||||||
|
|||||||
52
tests/Entity/BookmarkTest.php
Normal file
52
tests/Entity/BookmarkTest.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Entity;
|
||||||
|
|
||||||
|
use App\Entity\Bookmark;
|
||||||
|
use App\Entity\User;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Entity\Bookmark
|
||||||
|
*/
|
||||||
|
class BookmarkTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testDefaultValues()
|
||||||
|
{
|
||||||
|
$sut = new Bookmark();
|
||||||
|
$this->assertNull($sut->getId());
|
||||||
|
$this->assertNull($sut->getName());
|
||||||
|
$this->assertIsArray($sut->getContent());
|
||||||
|
$this->assertEquals([], $sut->getContent());
|
||||||
|
$this->assertNull($sut->getType());
|
||||||
|
$this->assertNull($sut->getUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetterAndGetter()
|
||||||
|
{
|
||||||
|
$sut = new Bookmark();
|
||||||
|
$sut->setName('foo-bar');
|
||||||
|
$this->assertEquals('foo-bar', $sut->getName());
|
||||||
|
|
||||||
|
$sut->setContent(['test' => 'foo-bar', 'hello' => 'world']);
|
||||||
|
$this->assertEquals(['test' => 'foo-bar', 'hello' => 'world'], $sut->getContent());
|
||||||
|
|
||||||
|
$sut->setType('sdsdsd');
|
||||||
|
$this->assertEquals('sdsdsd', $sut->getType());
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$sut->setUser($user);
|
||||||
|
$this->assertEquals($user, $sut->getUser());
|
||||||
|
|
||||||
|
$sut2 = clone $sut;
|
||||||
|
$this->assertEquals('sdsdsd', $sut2->getType());
|
||||||
|
$this->assertEquals('foo-bar', $sut2->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,7 +95,7 @@ class PageActionsEventTest extends TestCase
|
|||||||
$this->assertEquals(8, $sut->countActions());
|
$this->assertEquals(8, $sut->countActions());
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'search' => ['class' => 'search-toggle visible-xs-inline'],
|
'search' => ['modal' => '#modal_search'],
|
||||||
'divider0' => null,
|
'divider0' => null,
|
||||||
'back' => ['url' => 'foo1', 'translation_domain' => 'actions'],
|
'back' => ['url' => 'foo1', 'translation_domain' => 'actions'],
|
||||||
'visibility' => ['modal' => '#foo2'],
|
'visibility' => ['modal' => '#foo2'],
|
||||||
|
|||||||
40
tests/Form/Model/DateRangeTest.php
Normal file
40
tests/Form/Model/DateRangeTest.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Form\Model;
|
||||||
|
|
||||||
|
use App\Form\Model\DateRange;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Form\Model\DateRange
|
||||||
|
*/
|
||||||
|
class DateRangeTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testDefaultValues()
|
||||||
|
{
|
||||||
|
$sut = new DateRange();
|
||||||
|
self::assertNull($sut->getBegin());
|
||||||
|
self::assertNull($sut->getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetterAndGetter()
|
||||||
|
{
|
||||||
|
$begin = new \DateTime('now');
|
||||||
|
$end = new \DateTime('2018-11-25 18:45:32');
|
||||||
|
|
||||||
|
$sut = new DateRange();
|
||||||
|
|
||||||
|
self::assertInstanceOf(DateRange::class, $sut->setBegin($begin));
|
||||||
|
self::assertInstanceOf(DateRange::class, $sut->setEnd($end));
|
||||||
|
|
||||||
|
self::assertEquals($begin->format('Y-m-d') . ' 00:00:00', $sut->getBegin()->format('Y-m-d H:i:s'));
|
||||||
|
self::assertEquals('2018-11-25 23:59:59', $sut->getEnd()->format('Y-m-d H:i:s'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,15 +61,22 @@ class ConfigExtensionTest extends TestCase
|
|||||||
'company' => null,
|
'company' => null,
|
||||||
'title' => null,
|
'title' => null,
|
||||||
],
|
],
|
||||||
'auto_reload_datatable' => false,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPrefix()
|
public function testPrefix()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut($this->getDefaultSettings(), []);
|
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||||
self::assertFalse($sut->getThemeConfig('auto_reload_datatable'));
|
|
||||||
self::assertEquals(3, $sut->getThemeConfig('active_warning'));
|
self::assertEquals(3, $sut->getThemeConfig('active_warning'));
|
||||||
self::assertEquals('green', $sut->getThemeConfig('box_color'));
|
self::assertEquals('green', $sut->getThemeConfig('box_color'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
public function testDeprecation()
|
||||||
|
{
|
||||||
|
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||||
|
self::assertFalse($sut->getThemeConfig('auto_reload_datatable'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,26 @@
|
|||||||
<source>search</source>
|
<source>search</source>
|
||||||
<target>Suchen</target>
|
<target>Suchen</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="label.searchTerm">
|
||||||
|
<source>label.searchTerm</source>
|
||||||
|
<target>Suchbegriff</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.set_as_default">
|
||||||
|
<source>label.set_as_default</source>
|
||||||
|
<target>Als Standard-Einstellung speichern</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.asc">
|
||||||
|
<source>label.asc</source>
|
||||||
|
<target>Aufsteigend</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.desc">
|
||||||
|
<source>label.desc</source>
|
||||||
|
<target>Absteigend</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.orderBy">
|
||||||
|
<source>label.orderBy</source>
|
||||||
|
<target>Sortieren nach</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="my.profile">
|
<trans-unit id="my.profile">
|
||||||
<source>my.profile</source>
|
<source>my.profile</source>
|
||||||
<target>Mein Profil</target>
|
<target>Mein Profil</target>
|
||||||
|
|||||||
@@ -60,6 +60,26 @@
|
|||||||
<source>search</source>
|
<source>search</source>
|
||||||
<target>Search</target>
|
<target>Search</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="label.searchTerm">
|
||||||
|
<source>label.searchTerm</source>
|
||||||
|
<target>Search term</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.set_as_default">
|
||||||
|
<source>label.set_as_default</source>
|
||||||
|
<target>Save as default setting</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.asc">
|
||||||
|
<source>label.asc</source>
|
||||||
|
<target>Ascending</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.desc">
|
||||||
|
<source>label.desc</source>
|
||||||
|
<target>Descending</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.orderBy">
|
||||||
|
<source>label.orderBy</source>
|
||||||
|
<target>Order by</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="my.profile">
|
<trans-unit id="my.profile">
|
||||||
<source>my.profile</source>
|
<source>my.profile</source>
|
||||||
<target>My profile</target>
|
<target>My profile</target>
|
||||||
|
|||||||
Reference in New Issue
Block a user