refactored search with free search term support (#1064)
This commit is contained in:
@@ -8,6 +8,12 @@ you can upgrade your Kimai installation to the latest stable release.
|
||||
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
||||
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||
|
||||
## [1.3](https://github.com/kevinpapst/kimai2/releases/tag/1.3)
|
||||
|
||||
### Possible BC breaks
|
||||
|
||||
- Refactored toolbars and search, plugins needs to be checked
|
||||
|
||||
## [1.2](https://github.com/kevinpapst/kimai2/releases/tag/1.2)
|
||||
|
||||
### Possible BC breaks
|
||||
|
||||
@@ -32,6 +32,7 @@ import KimaiAPILink from "./plugins/KimaiAPILink";
|
||||
import KimaiAlert from "./plugins/KimaiAlert";
|
||||
import KimaiAutocomplete from "./plugins/KimaiAutocomplete";
|
||||
import KimaiToolbarAction from "./plugins/KimaiToolbarAction";
|
||||
import KimaiSearchButtons from "./plugins/KimaiSearchButtons";
|
||||
|
||||
export default class KimaiLoader {
|
||||
|
||||
@@ -54,7 +55,8 @@ export default class KimaiLoader {
|
||||
kimai.registerPlugin(new KimaiDateRangePicker('.content-wrapper'));
|
||||
kimai.registerPlugin(new KimaiDateTimePicker('.content-wrapper'));
|
||||
kimai.registerPlugin(new KimaiDatatable('table.dataTable'));
|
||||
kimai.registerPlugin(new KimaiToolbar());
|
||||
kimai.registerPlugin(new KimaiToolbar('form.header-search'));
|
||||
kimai.registerPlugin(new KimaiSearchButtons('.content-header'));
|
||||
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
|
||||
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
|
||||
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
|
||||
|
||||
@@ -44,16 +44,24 @@ export default class KimaiDatatable extends KimaiPlugin {
|
||||
for (let eventName of events.split(' ')) {
|
||||
document.addEventListener(eventName, handle);
|
||||
}
|
||||
|
||||
if (this.getContainer().getConfiguration().get('autoReloadDatatable')) {
|
||||
document.addEventListener('toolbar-change', handle);
|
||||
} else {
|
||||
document.addEventListener('pagination-change', handle);
|
||||
}
|
||||
}
|
||||
|
||||
reloadDatatable() {
|
||||
const durations = this.getContainer().getPlugin('timesheet-duration');
|
||||
const form = jQuery('.toolbar form');
|
||||
const toolbarSelector = this.getContainer().getPlugin('toolbar').getSelector();
|
||||
|
||||
const form = jQuery(toolbarSelector);
|
||||
let loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
|
||||
jQuery('section.content').append(loading);
|
||||
|
||||
// remove the empty fields to prevent errors
|
||||
let formData = jQuery('.toolbar form :input')
|
||||
let formData = jQuery(toolbarSelector + ' :input')
|
||||
.filter(function(index, element) {
|
||||
return jQuery(element).val() != '';
|
||||
})
|
||||
|
||||
@@ -19,6 +19,10 @@ export default class KimaiJqueryPluginInitializer extends KimaiPlugin {
|
||||
jQuery('.dropdown-toggle').dropdown();
|
||||
// activate the tooltip functionality
|
||||
jQuery('[data-toggle="tooltip"]').tooltip();
|
||||
// enable all selectpicker in adhoc forms (like invoice and export)
|
||||
$('.selectpicker').selectpicker({
|
||||
container: 'body'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
49
assets/js/plugins/KimaiSearchButtons.js
Normal file
49
assets/js/plugins/KimaiSearchButtons.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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] KimaiSearchButtons: handles events of search buttons and the filter dropdown
|
||||
*/
|
||||
|
||||
import jQuery from 'jquery';
|
||||
import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
/**
|
||||
* FIXME refactor me and merge with KimaiToolbar
|
||||
*/
|
||||
export default class KimaiSearchButtons extends KimaiPlugin {
|
||||
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
init() {
|
||||
const self = this;
|
||||
|
||||
$(document).on('click', this.selector + ' .search-toggle', function (e) {
|
||||
e.stopPropagation();
|
||||
jQuery(self.selector).toggleClass('search-open');
|
||||
jQuery(self.selector + ' form.header-search').toggleClass('hidden-xs');
|
||||
jQuery(self.selector + ' form.header-search .dropdown-toggle').dropdown('toggle');
|
||||
jQuery(self.selector + ' form.header-search input#searchTerm').focus();
|
||||
});
|
||||
|
||||
$(document).on('click', this.selector + ' .search-cancel', function (e) {
|
||||
e.preventDefault();
|
||||
jQuery(self.selector).toggleClass('search-open');
|
||||
jQuery(self.selector + ' form.header-search .dropdown-toggle').dropdown('toggle');
|
||||
jQuery(self.selector + ' form.header-search').toggleClass('hidden-xs');
|
||||
});
|
||||
|
||||
// prevent that the dropdown closes, when a form input is changed - eg. a select option was clicked
|
||||
$(document).on('click', this.selector + ' .dropdown-menu', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,22 @@ import KimaiPlugin from "../KimaiPlugin";
|
||||
|
||||
export default class KimaiToolbar extends KimaiPlugin {
|
||||
|
||||
init() {
|
||||
const datatable = this.getContainer().getPlugin('datatable');
|
||||
constructor(selector) {
|
||||
super();
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
|
||||
getId() {
|
||||
return 'toolbar';
|
||||
}
|
||||
|
||||
init() {
|
||||
const formSelector = this.getSelector();
|
||||
const self = this;
|
||||
|
||||
// This catches all clicks on the pagination and prevents the default action, as we want to reload the page via JS
|
||||
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
|
||||
let pager = jQuery(".toolbar form input[name='page']");
|
||||
let pager = jQuery(formSelector + " input#page");
|
||||
if (pager.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -29,41 +39,59 @@ export default class KimaiToolbar extends KimaiPlugin {
|
||||
let page = urlParts[urlParts.length-1];
|
||||
pager.val(page);
|
||||
pager.trigger('change');
|
||||
self.getContainer().getPlugin('event').trigger('pagination-change');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Reset the page if any other value is changed, otherwise we might end up with a limited set
|
||||
// of data which does not support the given page - and it would be just wrong to stay in the same page
|
||||
jQuery('.toolbar form input').change(function (event) {
|
||||
jQuery(this.selector +' input').change(function (event) {
|
||||
switch (event.target.id) {
|
||||
case 'page':
|
||||
break;
|
||||
default:
|
||||
jQuery('.toolbar form input#page').val(1);
|
||||
jQuery(formSelector + ' input#page').val(1);
|
||||
}
|
||||
datatable.reloadDatatable();
|
||||
self.triggerChange();
|
||||
});
|
||||
|
||||
jQuery('.toolbar form select').change(function (event) {
|
||||
jQuery(formSelector + ' select').change(function (event) {
|
||||
let reload = true;
|
||||
switch (event.target.id) {
|
||||
case 'customer':
|
||||
if (jQuery('.toolbar form select#project').length > 0) {
|
||||
if (jQuery(formSelector + ' select#project').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'project':
|
||||
if (jQuery('.toolbar form select#activity').length > 0) {
|
||||
if (jQuery(formSelector + ' select#activity').length > 0) {
|
||||
reload = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
jQuery('.toolbar form input#page').val(1);
|
||||
jQuery(formSelector + ' input#page').val(1);
|
||||
|
||||
if (reload) {
|
||||
datatable.reloadDatatable();
|
||||
self.triggerChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an event, that everyone can listen for.
|
||||
*/
|
||||
triggerChange() {
|
||||
this.getContainer().getPlugin('event').trigger('toolbar-change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS selector to target the toolbar form.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getSelector() {
|
||||
return this.selector;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ export default class KimaiToolbarAction extends KimaiPlugin {
|
||||
|
||||
init() {
|
||||
const self = this;
|
||||
const toolbarSelector = this.getContainer().getPlugin('toolbar').getSelector();
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
let target = event.target;
|
||||
while (target !== null && !target.matches('body')) {
|
||||
if (target.classList.contains(self.selector)) {
|
||||
const form = document.querySelector('div.toolbar form.navbar-form');
|
||||
const form = document.querySelector(toolbarSelector);
|
||||
if (form === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@import 'error-page';
|
||||
@import 'print';
|
||||
@import 'content';
|
||||
@import 'content-header';
|
||||
@import 'toolbar';
|
||||
@import 'sidebar';
|
||||
@import 'footer';
|
||||
|
||||
52
assets/sass/content-header.scss
Normal file
52
assets/sass/content-header.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.content-header {
|
||||
height: 50px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
|
||||
background-color: #fff !important;
|
||||
padding: 10px 0 0 10px;
|
||||
|
||||
h1 {
|
||||
padding-top: 3px;
|
||||
float: left;
|
||||
small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Page based action buttons in the upper right corner of the content area */
|
||||
.content-header>.breadcrumb {
|
||||
position: absolute;
|
||||
float: right;
|
||||
background: transparent;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-md-min) {
|
||||
.content-header>.breadcrumb {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.content-header>.breadcrumb {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
h1 {
|
||||
small {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,43 +8,8 @@
|
||||
.content {
|
||||
padding: 15px 0;
|
||||
}
|
||||
.content-header {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
|
||||
background-color: #fff !important;
|
||||
padding: 15px 0 15px 10px;
|
||||
|
||||
h1 {
|
||||
small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-header>.breadcrumb {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
float: none;
|
||||
background: #d2d6de;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.content-header>.breadcrumb {
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.content-header {
|
||||
h1 {
|
||||
small {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/* Page based action buttons in the upper right corner of the content area */
|
||||
.content-header > .breadcrumb {
|
||||
position: absolute;
|
||||
float: right;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.toolbar-pad {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -19,6 +12,7 @@
|
||||
/* The filter form is available on most pages above the datatable */
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.toolbar {
|
||||
// @deprecated since 1.2 - using the toolbar is deprecated and will be removed with 2.0
|
||||
form.navbar-form {
|
||||
font-size: $font-size-base;
|
||||
.form-control {
|
||||
@@ -39,3 +33,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
public/build/app.dacf60f7.js
Normal file
1
public/build/app.dacf60f7.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
@@ -5,10 +5,10 @@
|
||||
"build/runtime.4ee6be68.js",
|
||||
"build/0.a87622f3.js",
|
||||
"build/1.c1bee41f.js",
|
||||
"build/app.98c93d11.js"
|
||||
"build/app.dacf60f7.js"
|
||||
],
|
||||
"css": [
|
||||
"build/app.e8d9cb98.css"
|
||||
"build/app.eb4ed947.css"
|
||||
]
|
||||
},
|
||||
"chart": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"build/runtime.4ee6be68.js": "sha384-xNNrNinl64G3nCUrIskgSjU0mUXXCB9lj6XCSInBTwxSKXk8uTMafnLHtdWdIGtd",
|
||||
"build/0.a87622f3.js": "sha384-ncT/BKhCsqH6jhxwdsSG95m1ei7ZZjeZtzH1262h+OPUU80TSFFE3dt+abcHHMok",
|
||||
"build/1.c1bee41f.js": "sha384-7UVWcP6Hefp2k/CrtGSITKXx4dSZqtvpAiU8WX7dClETkzMewrUjoCRtVXQ5j3KI",
|
||||
"build/app.98c93d11.js": "sha384-z6f94h/RD/8JXo1OWkUp7g6TXXV59JW70WA/SdusZh02FcwRvvBIX21k6O6pUBfw",
|
||||
"build/app.e8d9cb98.css": "sha384-UuTUHbhvRNk3t+1gL0JNACbAl5xXH8OhD9ah7jaXDbd32z8dNCjK+5oJSQF77rmz",
|
||||
"build/app.dacf60f7.js": "sha384-a93v/mUzZDopA6To/YJEjOAxgJ6T4H6gmXnGf0ja6Bq4M7ZVDQqqKMX7/9hIhRPM",
|
||||
"build/app.eb4ed947.css": "sha384-TQ5nEns/+JBxjuHlqGXkiS1qt9Zdzb7tREhc4DPunQkTib/iuv8MfwELt8OPhF10",
|
||||
"build/2.7be60d8d.js": "sha384-txR0QG+838LKYtPQ99Gx4OU7WmgN9J3joZEyGwIskSz74EN1T4/IBVnmNaKiFN1q",
|
||||
"build/chart.0af3f813.js": "sha384-I57c9DtU3AOG2kzKqIZkIu0hi1aGYHRZ5QG4LKC9+9slzJnAMttPGXoL2cQG3m6y",
|
||||
"build/calendar.5839778f.js": "sha384-BwovMgqL5Lk/dbN98yO2ZkiYQWBB/7JWQ3NGS0aDdoPhtergAOG3hieLbCyyz002",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"build/0.a87622f3.js": "build/0.a87622f3.js",
|
||||
"build/1.c1bee41f.js": "build/1.c1bee41f.js",
|
||||
"build/2.7be60d8d.js": "build/2.7be60d8d.js",
|
||||
"build/app.css": "build/app.e8d9cb98.css",
|
||||
"build/app.js": "build/app.98c93d11.js",
|
||||
"build/app.css": "build/app.eb4ed947.css",
|
||||
"build/app.js": "build/app.dacf60f7.js",
|
||||
"build/calendar.css": "build/calendar.b0551848.css",
|
||||
"build/calendar.js": "build/calendar.5839778f.js",
|
||||
"build/chart.js": "build/chart.0af3f813.js",
|
||||
|
||||
@@ -18,6 +18,11 @@ class ThemeConfiguration implements SystemBundleConfiguration
|
||||
return 'theme';
|
||||
}
|
||||
|
||||
public function isAutoReloadDatatable(): bool
|
||||
{
|
||||
return (bool) $this->find('auto_reload_datatable');
|
||||
}
|
||||
|
||||
public function getSelectPicker(): string
|
||||
{
|
||||
return (string) $this->find('select_type');
|
||||
|
||||
@@ -266,6 +266,14 @@ class SystemConfigurationController extends AbstractController
|
||||
->setLabel('theme.markdown_content')
|
||||
->setType(CheckboxType::class)
|
||||
->setTranslationDomain('system-configuration'),
|
||||
// FIXME should that be configurable per user?
|
||||
/*
|
||||
(new Configuration())
|
||||
->setName('theme.auto_reload_datatable')
|
||||
->setLabel('theme.auto_reload_datatable') // FIXME translation
|
||||
->setType(CheckboxType::class)
|
||||
->setTranslationDomain('system-configuration'),
|
||||
*/
|
||||
]),
|
||||
(new SystemConfigurationModel())
|
||||
->setSection(SystemConfigurationModel::SECTION_CALENDAR)
|
||||
|
||||
@@ -314,6 +314,9 @@ class Configuration implements ConfigurationInterface
|
||||
->scalarNode('select_type')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('auto_reload_datatable')
|
||||
->defaultFalse()
|
||||
->end()
|
||||
->booleanNode('show_about')
|
||||
->defaultTrue()
|
||||
->end()
|
||||
|
||||
48
src/Form/DataTransformer/SearchTermTransformer.php
Normal file
48
src/Form/DataTransformer/SearchTermTransformer.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use App\Utils\SearchTerm;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
class SearchTermTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* Transforms a SearchTerm object to a string.
|
||||
*
|
||||
* @param SearchTerm|null $searchTerm
|
||||
* @return string
|
||||
*/
|
||||
public function transform($searchTerm)
|
||||
{
|
||||
if (empty($searchTerm) || !$searchTerm instanceof SearchTerm) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $searchTerm->getOriginalSearch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a string to a SearchTerm object.
|
||||
*
|
||||
* @param string $searchTerm
|
||||
* @return SearchTerm|null
|
||||
* @throws TransformationFailedException if object (issue) is not found
|
||||
*/
|
||||
public function reverseTransform($searchTerm)
|
||||
{
|
||||
if (empty($searchTerm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SearchTerm($searchTerm);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Converts normal select boxes into javascript enhanced versions.
|
||||
@@ -49,6 +50,10 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($options['selectpicker']) && false === $options['selectpicker']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($view->vars['attr'])) {
|
||||
$view->vars['attr'] = [];
|
||||
}
|
||||
@@ -58,4 +63,14 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
['class' => 'selectpicker', 'data-live-search' => true, 'data-width' => '100%']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefined(['selectpicker']);
|
||||
$resolver->setAllowedTypes('selectpicker', 'boolean');
|
||||
$resolver->setDefault('selectpicker', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\DateRangeType;
|
||||
use App\Form\Type\PageSizeType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\SearchTermType;
|
||||
use App\Form\Type\TagsInputType;
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\Type\UserType;
|
||||
@@ -50,11 +51,17 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function getSelectpickerConfig(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function addUserChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('user', UserType::class, [
|
||||
'label' => 'label.user',
|
||||
'required' => false,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -64,6 +71,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'label' => 'label.user',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -88,6 +96,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
);
|
||||
@@ -98,7 +107,8 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
$builder->add('visibility', VisibilityType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'label' => $label
|
||||
'label' => $label,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -106,6 +116,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
$builder->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -113,6 +124,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
$builder->add('role', UserRoleType::class, [
|
||||
'required' => false,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -149,6 +161,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
);
|
||||
@@ -177,6 +190,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
);
|
||||
@@ -196,6 +210,11 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addSearchTermInputField(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('searchTerm', SearchTermType::class);
|
||||
}
|
||||
|
||||
protected function addTimesheetStateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('state', ChoiceType::class, [
|
||||
@@ -207,6 +226,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
|
||||
],
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -221,6 +241,7 @@ abstract class AbstractToolbarForm extends AbstractType
|
||||
'entryState.exported' => TimesheetQuery::STATE_EXPORTED,
|
||||
'entryState.not_exported' => TimesheetQuery::STATE_NOT_EXPORTED
|
||||
],
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ class ActivityToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
$builder->add('globalsOnly', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'yes' => 1,
|
||||
@@ -34,9 +35,10 @@ class ActivityToolbarForm extends AbstractToolbarForm
|
||||
'placeholder' => null,
|
||||
'required' => false,
|
||||
'label' => 'label.globalsOnly',
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addProjectChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ class CustomerToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
class ExportToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
protected function getSelectpickerConfig(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addExportStateChoice($builder);
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addUsersChoice($builder);
|
||||
|
||||
@@ -21,11 +21,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
class InvoiceToolbarForm extends AbstractToolbarForm
|
||||
{
|
||||
protected function getSelectpickerConfig(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addTemplateChoice($builder);
|
||||
$this->addUsersChoice($builder);
|
||||
$this->addDateRangeChoice($builder);
|
||||
@@ -52,6 +58,7 @@ class InvoiceToolbarForm extends AbstractToolbarForm
|
||||
$builder->add('template', InvoiceTemplateType::class, [
|
||||
'required' => true,
|
||||
'placeholder' => null,
|
||||
'selectpicker' => $this->getSelectpickerConfig(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ class ProjectToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addCustomerChoice($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class TagToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class TeamToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ class TimesheetToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addSearchTermInputField($builder);
|
||||
if ($options['include_user']) {
|
||||
$this->addUserChoice($builder);
|
||||
}
|
||||
@@ -33,6 +32,8 @@ class TimesheetToolbarForm extends AbstractToolbarForm
|
||||
$this->addProjectChoice($builder);
|
||||
$this->addActivityChoice($builder);
|
||||
$this->addTagInputField($builder);
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ class UserToolbarForm extends AbstractToolbarForm
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addVisibilityChoice($builder, 'label.active');
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addUserRoleChoice($builder);
|
||||
$this->addVisibilityChoice($builder, 'label.active');
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
}
|
||||
|
||||
|
||||
51
src/Form/Type/SearchTermType.php
Normal file
51
src/Form/Type/SearchTermType.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use App\Form\DataTransformer\SearchTermTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
class SearchTermType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->addModelTransformer(new SearchTermTransformer());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'search',
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
new Length(['min' => 3])
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
}
|
||||
@@ -298,6 +298,37 @@ class ActivityRepository extends EntityRepository
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser());
|
||||
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$qb->leftJoin('a.meta', 'meta');
|
||||
$searchAnd->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('meta.name', ':metaName'),
|
||||
$qb->expr()->like('meta.value', ':metaValue')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('metaName', $metaName);
|
||||
$qb->setParameter('metaValue', '%' . $metaValue . '%');
|
||||
}
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('a.name', ':searchTerm'),
|
||||
$qb->expr()->like('a.comment', ':searchTerm')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,6 @@ class CustomerRepository extends EntityRepository
|
||||
|
||||
$qb->select('c')
|
||||
->from(Customer::class, 'c')
|
||||
->leftJoin('c.meta', 'meta')
|
||||
->orderBy('c.' . $query->getOrderBy(), $query->getOrder());
|
||||
|
||||
if (CustomerQuery::SHOW_VISIBLE == $query->getVisibility()) {
|
||||
@@ -223,6 +222,42 @@ class CustomerRepository extends EntityRepository
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
||||
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$qb->leftJoin('c.meta', 'meta');
|
||||
$searchAnd->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('meta.name', ':metaName'),
|
||||
$qb->expr()->like('meta.value', ':metaValue')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('metaName', $metaName);
|
||||
$qb->setParameter('metaValue', '%' . $metaValue . '%');
|
||||
}
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('c.name', ':searchTerm'),
|
||||
$qb->expr()->like('c.comment', ':searchTerm'),
|
||||
$qb->expr()->like('c.number', ':searchTerm'),
|
||||
$qb->expr()->like('c.contact', ':searchTerm'),
|
||||
$qb->expr()->like('c.phone', ':searchTerm'),
|
||||
$qb->expr()->like('c.email', ':searchTerm'),
|
||||
$qb->expr()->like('c.address', ':searchTerm')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ class ProjectRepository extends EntityRepository
|
||||
->select('p')
|
||||
->from(Project::class, 'p')
|
||||
->leftJoin('p.customer', 'c')
|
||||
->addOrderBy('p.' . $query->getOrderBy(), $query->getOrder())
|
||||
;
|
||||
|
||||
if (in_array($query->getVisibility(), [ProjectQuery::SHOW_VISIBLE, ProjectQuery::SHOW_HIDDEN])) {
|
||||
@@ -236,7 +237,37 @@ class ProjectRepository extends EntityRepository
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser());
|
||||
|
||||
$qb->orderBy('p.' . $query->getOrderBy(), $query->getOrder());
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$qb->leftJoin('p.meta', 'meta');
|
||||
$searchAnd->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('meta.name', ':metaName'),
|
||||
$qb->expr()->like('meta.value', ':metaValue')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('metaName', $metaName);
|
||||
$qb->setParameter('metaValue', '%' . $metaValue . '%');
|
||||
}
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('p.name', ':searchTerm'),
|
||||
$qb->expr()->like('p.comment', ':searchTerm'),
|
||||
$qb->expr()->like('p.orderNumber', ':searchTerm')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace App\Repository\Query;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Utils\SearchTerm;
|
||||
|
||||
/**
|
||||
* Base class for advanced Repository queries.
|
||||
@@ -55,6 +56,10 @@ class BaseQuery
|
||||
* @var Team[]
|
||||
*/
|
||||
private $teams = [];
|
||||
/**
|
||||
* @var SearchTerm|null
|
||||
*/
|
||||
private $searchTerm;
|
||||
|
||||
public function addTeam(Team $team): self
|
||||
{
|
||||
@@ -188,6 +193,27 @@ class BaseQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasSearchTerm(): bool
|
||||
{
|
||||
return null !== $this->searchTerm;
|
||||
}
|
||||
|
||||
public function getSearchTerm(): ?SearchTerm
|
||||
{
|
||||
return $this->searchTerm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchTerm|null $searchTerm
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function setSearchTerm(?SearchTerm $searchTerm)
|
||||
{
|
||||
$this->searchTerm = $searchTerm;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the query has changed fields, compared to the original state.
|
||||
*
|
||||
@@ -203,6 +229,14 @@ class BaseQuery
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($this->teams)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null !== $this->searchTerm) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,26 @@ class TagRepository extends EntityRepository
|
||||
->leftJoin('tag.timesheets', 'timesheets')
|
||||
->addGroupBy('tag.id')
|
||||
->addGroupBy('tag.name')
|
||||
->orderBy('tag.' . $query->getOrderBy(), $query->getOrder());
|
||||
->addOrderBy('tag.' . $query->getOrderBy(), $query->getOrder())
|
||||
;
|
||||
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('tag.name', ':searchTerm')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = new Pagerfanta(new DoctrineORMAdapter($qb->getQuery(), false));
|
||||
$paginator->setMaxPerPage($query->getPageSize());
|
||||
|
||||
@@ -104,9 +104,17 @@ class TeamRepository extends EntityRepository
|
||||
$qb
|
||||
->select('t')
|
||||
->from(Team::class, 't')
|
||||
->addOrderBy('t.' . $query->getOrderBy(), $query->getOrder())
|
||||
;
|
||||
|
||||
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
|
||||
if (!empty($query->getSearchTerm())) {
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('t.name', ':likeContains')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('likeContains', '%' . $query->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
@@ -551,6 +551,7 @@ class TimesheetRepository extends EntityRepository
|
||||
->select('t')
|
||||
->from(Timesheet::class, 't')
|
||||
->leftJoin('t.project', 'p')
|
||||
->addOrderBy('t.' . $query->getOrderBy(), $query->getOrder())
|
||||
;
|
||||
|
||||
$user = [];
|
||||
@@ -636,7 +637,33 @@ class TimesheetRepository extends EntityRepository
|
||||
|
||||
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
||||
|
||||
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$qb->leftJoin('t.meta', 'meta');
|
||||
$searchAnd->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('meta.name', ':metaName'),
|
||||
$qb->expr()->like('meta.value', ':metaValue')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('metaName', $metaName);
|
||||
$qb->setParameter('metaValue', '%' . $metaValue . '%');
|
||||
}
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->like('t.description', ':searchTerm')
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,39 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
|
||||
$qb->andWhere($rolesWhere);
|
||||
}
|
||||
|
||||
if ($query->hasSearchTerm()) {
|
||||
$searchAnd = $qb->expr()->andX();
|
||||
$searchTerm = $query->getSearchTerm();
|
||||
|
||||
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
|
||||
$qb->leftJoin('u.preferences', 'meta');
|
||||
$searchAnd->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('meta.name', ':metaName'),
|
||||
$qb->expr()->like('meta.value', ':metaValue')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('metaName', $metaName);
|
||||
$qb->setParameter('metaValue', '%' . $metaValue . '%');
|
||||
}
|
||||
|
||||
if ($searchTerm->hasSearchTerm()) {
|
||||
$searchAnd->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->like('u.alias', ':searchTerm'),
|
||||
$qb->expr()->like('u.title', ':searchTerm'),
|
||||
$qb->expr()->like('u.email', ':searchTerm'),
|
||||
$qb->expr()->like('u.username', ':searchTerm')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
|
||||
}
|
||||
|
||||
if ($searchAnd->count() > 0) {
|
||||
$qb->andWhere($searchAnd);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getBaseQueryResult($qb, $query);
|
||||
}
|
||||
|
||||
|
||||
46
src/Twig/ConfigExtension.php
Normal file
46
src/Twig/ConfigExtension.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Twig;
|
||||
|
||||
use App\Configuration\ThemeConfiguration;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class ConfigExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var ThemeConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
public function __construct(ThemeConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('theme_config', [$this, 'getThemeConfig']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getThemeConfig(string $name)
|
||||
{
|
||||
return $this->configuration->find($name);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ final class IconExtension extends AbstractExtension
|
||||
'print' => 'fas fa-print',
|
||||
'project' => 'fas fa-briefcase',
|
||||
'repeat' => 'fas fa-redo-alt',
|
||||
'search' => 'fas fa-search',
|
||||
'start' => 'fas fa-play-circle',
|
||||
'start-small' => 'far fa-play-circle',
|
||||
'stop' => 'fas fa-stop',
|
||||
|
||||
@@ -25,9 +25,6 @@ class TitleExtension extends AbstractExtension
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface $translator
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator, ThemeConfiguration $configuration)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
|
||||
90
src/Utils/SearchTerm.php
Normal file
90
src/Utils/SearchTerm.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?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\Utils;
|
||||
|
||||
final class SearchTerm
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $originalTerm;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $term;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $fields = [];
|
||||
|
||||
public function __construct(string $searchTerm)
|
||||
{
|
||||
$this->originalTerm = $searchTerm;
|
||||
$this->parse($searchTerm);
|
||||
}
|
||||
|
||||
private function parse(string $searchTerm)
|
||||
{
|
||||
$terms = explode(' ', $searchTerm);
|
||||
$fields = [];
|
||||
$finalTerm = [];
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$tmp = explode(':', $term);
|
||||
if (count($tmp) === 2) {
|
||||
$fields[$tmp[0]] = $tmp[1];
|
||||
} else {
|
||||
$finalTerm[] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
$this->term = implode(' ', $finalTerm);
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
public function hasSearchField(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->fields);
|
||||
}
|
||||
|
||||
public function getSearchField(string $name): ?string
|
||||
{
|
||||
if (!$this->hasSearchField($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->fields[$name];
|
||||
}
|
||||
|
||||
public function getSearchFields(): array
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
public function getSearchTerm(): ?string
|
||||
{
|
||||
return $this->term;
|
||||
}
|
||||
|
||||
public function hasSearchTerm(): bool
|
||||
{
|
||||
return !empty($this->term);
|
||||
}
|
||||
|
||||
public function getOriginalSearch(): string
|
||||
{
|
||||
return $this->originalTerm;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->originalTerm;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
{% extends 'about/layout.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_subtitle %}{{ 'about.subtitle'|trans({}, 'about') }}{% endblock %}
|
||||
|
||||
{% block about_box %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
52
templates/activity/actions.html.twig
Normal file
52
templates/activity/actions.html.twig
Normal file
@@ -0,0 +1,52 @@
|
||||
{% macro activities(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}, 'visibility': '#modal_activity_admin'} %}
|
||||
|
||||
{% if is_granted('create_activity') %}
|
||||
{% set actions = actions|merge({'create': path('admin_activity_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'activity.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.activities', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro activity(activity, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if activity.id is not empty %}
|
||||
{% if is_granted('edit', activity) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_activity_edit', {'id': activity.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', activity) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_activity_budget', {'id': activity.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': activity.project ? activity.project.customer.id : null, 'project': activity.project ? activity.project.id : null, 'activity': activity.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('create_other_timesheet') %}
|
||||
{% set actions = actions|merge({'create-timesheet': {'url': path('admin_timesheet_create', {'project': activity.project ? activity.project.id : null, 'activity': activity.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', activity) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_activity_delete', {'id': activity.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_activity')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.activity', {'actions': actions, 'view': view, 'activity': activity}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': 'alwaysVisible',
|
||||
@@ -16,11 +16,10 @@
|
||||
{% set tableName = 'activity_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activities('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseActivityAdmin', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{% block page_search %}{% endblock %}
|
||||
{% block page_actions %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -201,7 +202,8 @@
|
||||
var loader = new KimaiWebLoader(
|
||||
{
|
||||
locale: '{{ app.request.locale }}',
|
||||
twentyFourHours: {{ 'true'|hour24('false') }}
|
||||
twentyFourHours: {{ 'true'|hour24('false') }},
|
||||
autoReloadDatatable: {% if theme_config('auto_reload_datatable') %}true{% else %}false{% endif %}
|
||||
},
|
||||
{
|
||||
'confirm': '{{ 'confirm'|trans }}',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'calendar.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'calendar.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.calendar('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
60
templates/customer/actions.html.twig
Normal file
60
templates/customer/actions.html.twig
Normal file
@@ -0,0 +1,60 @@
|
||||
{% macro customers(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'visibility': '#modal_customer_admin'} %}
|
||||
{% if is_granted('create_customer') %}
|
||||
{% set actions = actions|merge({'create': path('admin_customer_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'customer.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.customers', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro customer(customer, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if customer.id is not empty %}
|
||||
{% if is_granted('edit', customer) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_customer_edit', {'id': customer.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', customer) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_customer_budget', {'id': customer.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('permissions', customer) %}
|
||||
{% set actions = actions|merge({'permissions': {'url': path('admin_customer_permissions', {'id': customer.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_project') %}
|
||||
{% set actions = actions|merge({'project': path('admin_project', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_activity') %}
|
||||
{% set actions = actions|merge({'activity': path('admin_activity', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if customer.visible and is_granted('create_project') %}
|
||||
{% set actions = actions|merge({'create-project': path('admin_project_create_with_customer', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', customer) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_customer_delete', {'id': customer.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_customer')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.customer', {'actions': actions, 'view': view, 'customer': customer}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': 'alwaysVisible',
|
||||
'comment': 'hidden-xs',
|
||||
'country': 'hidden-xs',
|
||||
'country': 'hidden-xs hidden-sm',
|
||||
'number': 'hidden-xs',
|
||||
'team': '',
|
||||
'visible': 'hidden-xs',
|
||||
@@ -17,11 +17,10 @@
|
||||
{% set tableName = 'customer_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customers('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseCustomerAdmin', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'permissions') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
{% block box_title %}{{ 'export.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_row(form.searchTerm) }}
|
||||
{{ form_row(form.daterange) }}
|
||||
{{ form_row(form.customer) }}
|
||||
{{ form_row(form.project) }}
|
||||
@@ -44,7 +45,7 @@
|
||||
<div class="btn-group" id="export-buttons" role="group">
|
||||
{% for button in renderer %}
|
||||
<button type="button" id="export-{{ button.id }}-button" class="btn btn-success startExportBtn" data-type="{{ button.id }}">
|
||||
<i class="{{ button.icon|icon }}"></i> {{ ('button.' ~ button.title)|trans }}
|
||||
{{ ('button.' ~ button.title)|trans }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
16
templates/form/search-form.html.twig
Normal file
16
templates/form/search-form.html.twig
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends "@AdminLTE/layout/form-theme-horizontal.html.twig" %}
|
||||
{#
|
||||
{% block widget_attributes %}
|
||||
{% if type is not defined or type not in ['file', 'hidden'] %}
|
||||
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' input-sm')|trim}) -%}
|
||||
{% endif %}
|
||||
{{- parent() -}}
|
||||
{% endblock widget_attributes %}
|
||||
#}
|
||||
{% block form_label_class -%}
|
||||
col-sm-3 col-xs-4
|
||||
{%- endblock form_label_class %}
|
||||
|
||||
{% block form_group_class -%}
|
||||
col-sm-9 col-xs-8
|
||||
{%- endblock form_group_class %}
|
||||
@@ -28,6 +28,7 @@
|
||||
{% block box_title %}{{ 'invoice.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_row(form.searchTerm) }}
|
||||
{{ form_row(form.daterange) }}
|
||||
{{ form_row(form.customer) }}
|
||||
{{ form_row(form.project) }}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_invoice_template.subtitle'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_invoice_template.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoice_templates('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,246 +1,3 @@
|
||||
{# This file contains twig macros used to display possible actions for all available entities #}
|
||||
{% macro activities(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {'filter': '#collapseActivityAdmin', 'visibility': '#modal_activity_admin'} %}
|
||||
|
||||
{% if is_granted('create_activity') %}
|
||||
{% set actions = actions|merge({'create': path('admin_activity_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'activity.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.activities', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro activity(activity, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if activity.id is not empty %}
|
||||
{% if is_granted('edit', activity) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_activity_edit', {'id': activity.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', activity) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_activity_budget', {'id': activity.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': activity.project ? activity.project.customer.id : null, 'project': activity.project ? activity.project.id : null, 'activity': activity.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('create_other_timesheet') %}
|
||||
{% set actions = actions|merge({'create-timesheet': {'url': path('admin_timesheet_create', {'project': activity.project ? activity.project.id : null, 'activity': activity.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', activity) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_activity_delete', {'id': activity.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_activity')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.activity', {'actions': actions, 'view': view, 'activity': activity}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro users(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'filter': '#collapseUserAdmin', 'visibility': '#modal_user_admin'}) %}
|
||||
{% else %}
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'permissions' and is_granted('role_permissions') %}
|
||||
{% set actions = actions|merge({'permissions': path('admin_user_permissions')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('create_user') %}
|
||||
{% set actions = actions|merge({'create': path('admin_user_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% elseif view == 'permissions' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.users', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user_permissions(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.user_permissions', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user(user, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if user.id is not empty %}
|
||||
{% if is_granted('view', user) %}
|
||||
{% set actions = {'profile-stats': {'url': path('user_profile', {'username' : user.username})}} %}
|
||||
{% endif %}
|
||||
{% if is_granted('edit', user) %}
|
||||
{% set actions = actions|merge({'edit': path('user_profile_edit', {'username' : user.username})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('preferences', user) %}
|
||||
{% set actions = actions|merge({'settings': {'url': path('user_profile_preferences', {'username' : user.username})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'user' : user.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', user) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_user_delete', {'id': user.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.user', {'actions': actions, 'view': view, 'user': user}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro projects(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {'filter': '#collapseProjectAdmin', 'visibility': '#modal_project_admin'} %}
|
||||
|
||||
{% if is_granted('create_project') %}
|
||||
{% set actions = actions|merge({'create': path('admin_project_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'project.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.projects', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project(project, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if project.id is not empty %}
|
||||
{% if is_granted('edit', project) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_project_edit', {'id': project.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', project) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_project_budget', {'id': project.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('permissions', project) %}
|
||||
{% set actions = actions|merge({'permissions': {'url': path('admin_project_permissions', {'id': project.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_activity') %}
|
||||
{% set actions = actions|merge({'activity': path('admin_activity', {'customer': project.customer.id, 'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': project.customer.id, 'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('create_activity') and project.visible and project.customer.visible %}
|
||||
{% set actions = actions|merge({'create-activity': path('admin_activity_create_with_project', {'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', project) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_project_delete', {'id': project.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_project')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.project', {'actions': actions, 'view': view, 'project': project}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro customers(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'filter': '#collapseCustomerAdmin', 'visibility': '#modal_customer_admin'} %}
|
||||
{% if is_granted('create_customer') %}
|
||||
{% set actions = actions|merge({'create': path('admin_customer_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'customer.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.customers', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro customer(customer, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if customer.id is not empty %}
|
||||
{% if is_granted('edit', customer) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_customer_edit', {'id': customer.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', customer) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_customer_budget', {'id': customer.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('permissions', customer) %}
|
||||
{% set actions = actions|merge({'permissions': {'url': path('admin_customer_permissions', {'id': customer.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_project') %}
|
||||
{% set actions = actions|merge({'project': path('admin_project', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_activity') %}
|
||||
{% set actions = actions|merge({'activity': path('admin_activity', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if customer.visible and is_granted('create_project') %}
|
||||
{% set actions = actions|merge({'create-project': path('admin_project_create_with_customer', {'customer': customer.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', customer) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_customer_delete', {'id': customer.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_customer')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.customer', {'actions': actions, 'view': view, 'customer': customer}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro calendar(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
@@ -253,120 +10,6 @@
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheets(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'filter': '#collapseTimesheet'} %}
|
||||
{% if is_granted('export_own_timesheet') %}
|
||||
{% set actions = actions|merge({'download': {'url': path('timesheet_export'), 'class': 'toolbar-action'}}) %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'visibility': '#modal_timesheet'}) %}
|
||||
{% if is_granted('create_own_timesheet') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.timesheets', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheet(timesheet, view) %}
|
||||
{%- apply spaceless -%}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if timesheet.id is not empty %}
|
||||
{% if not timesheet.end and is_granted('stop', timesheet) %}
|
||||
{% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', timesheet) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('timesheet')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.timesheet', {'actions': actions, 'view': view, 'timesheet': timesheet}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{%- endapply -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheets_team(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'filter': '#collapseTimesheetAdmin'} %}
|
||||
{% if is_granted('export_own_timesheet') %}
|
||||
{% set actions = actions|merge({'download': {'url': path('admin_timesheet_export'), 'class': 'toolbar-action'}}) %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'visibility': '#modal_timesheet_admin'}) %}
|
||||
{% if is_granted('create_other_timesheet') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_timesheet_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.timesheets_team', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheet_team(timesheet, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if timesheet.id is not empty %}
|
||||
{% if not timesheet.end and is_granted('stop', timesheet) %}
|
||||
{% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', timesheet) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_timesheet')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.timesheet_team', {'actions': actions, 'view': view, 'timesheet': timesheet}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro plugins(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
@@ -402,33 +45,6 @@
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro tags(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {'filter': '#collapseTags'} %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'tags.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.tags', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro tag(tag, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'tags': tag.name})}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('delete_tag') %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_tag', {'id' : tag.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.tagDelete kimai.tagUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.tag', {'actions': actions, 'view': view, 'tag': tag}) %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro system_configuration(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
@@ -438,40 +54,3 @@
|
||||
{% set event = trigger('actions.system_configuration', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro teams(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
{% if is_granted('create_team') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_team_create')}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'teams.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.teams', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro team(team, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
{% if team.id is not empty %}
|
||||
{% if is_granted('edit', team) %}
|
||||
{% set class = '' %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_team_edit', {'id': team.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', team) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_team', {'id' : team.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.teamDelete kimai.teamUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.team', {'actions': actions, 'view': view, 'team': team}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% macro toolbar(form, collapsible, displayInitial) %}
|
||||
{% deprecated 'The macro "toolbar" is deprecated since 1.2 and will be removed with 2.0, use dropDownSearch() instead' %}
|
||||
<div class="toolbar no-print">
|
||||
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
|
||||
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
|
||||
@@ -7,3 +8,47 @@
|
||||
{% if collapsible %}</div>{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro dropDownSearch(form) %}
|
||||
{% form_theme form 'form/search-form.html.twig' %}
|
||||
{{ form_start(form, { 'attr': {'class': 'form-horizontal header-search hidden-xs'}}) }}
|
||||
{% set searchTermClass = 'dropdown-toggle input-sm' %}
|
||||
{% if not form.searchTerm.vars.valid %}
|
||||
{% set searchTermClass = searchTermClass ~ ' search-has-error' %}
|
||||
{% endif %}
|
||||
{% if not form.vars.valid %}
|
||||
{% set searchTermClass = searchTermClass ~ ' has-error' %}
|
||||
{% endif %}
|
||||
<div class="dropdown">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon {% if not form.vars.valid %} has-error{% endif %}">
|
||||
<i class="{{ 'search'|icon }}"></i>
|
||||
</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>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endmacro %}
|
||||
|
||||
58
templates/project/actions.html.twig
Normal file
58
templates/project/actions.html.twig
Normal file
@@ -0,0 +1,58 @@
|
||||
{% macro projects(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}, 'visibility': '#modal_project_admin'} %}
|
||||
|
||||
{% if is_granted('create_project') %}
|
||||
{% set actions = actions|merge({'create': path('admin_project_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'project.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.projects', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project(project, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if project.id is not empty %}
|
||||
{% if is_granted('edit', project) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_project_edit', {'id': project.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', project) %}
|
||||
{% set actions = actions|merge({'report': {'url': path('admin_project_budget', {'id': project.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('permissions', project) %}
|
||||
{% set actions = actions|merge({'permissions': {'url': path('admin_project_permissions', {'id': project.id})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_activity') %}
|
||||
{% set actions = actions|merge({'activity': path('admin_activity', {'customer': project.customer.id, 'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'customer': project.customer.id, 'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('create_activity') and project.visible and project.customer.visible %}
|
||||
{% set actions = actions|merge({'create-activity': path('admin_activity_create_with_project', {'project': project.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', project) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_project_delete', {'id': project.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_project')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.project', {'actions': actions, 'view': view, 'project': project}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'delete') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': 'alwaysVisible',
|
||||
@@ -16,11 +16,10 @@
|
||||
{% set tableName = 'project_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.projects('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseProjectAdmin', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'permissions') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
27
templates/tags/actions.html.twig
Normal file
27
templates/tags/actions.html.twig
Normal file
@@ -0,0 +1,27 @@
|
||||
{% macro tags(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'tags.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.tags', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro tag(tag, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'tags': tag.name})}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('delete_tag') %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_tag', {'id' : tag.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.tagDelete kimai.tagUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.tag', {'actions': actions, 'view': view, 'tag': tag}) %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
@@ -1,17 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "tags/actions.html.twig" as actions %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
|
||||
{% block page_title %}{{ 'tags.title'|trans({}, 'tags') }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'tags.subtitle'|trans({}, 'tags') }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.tags('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseTags', showFilter) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if tags|length == 0 %}
|
||||
|
||||
37
templates/team/actions.html.twig
Normal file
37
templates/team/actions.html.twig
Normal file
@@ -0,0 +1,37 @@
|
||||
{% macro teams(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %}
|
||||
|
||||
{% if is_granted('create_team') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_team_create')}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'teams.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.teams', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro team(team, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
{% if team.id is not empty %}
|
||||
{% if is_granted('edit', team) %}
|
||||
{% set class = '' %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_team_edit', {'id': team.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', team) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_team', {'id' : team.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.teamDelete kimai.teamUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.team', {'actions': actions, 'view': view, 'team': team}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "team/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'teams.title'|trans({}, 'teams') }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'teams.subtitle'|trans({}, 'teams') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.team(team, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "team/actions.html.twig" as actions %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
|
||||
{% block page_title %}{{ 'teams.title'|trans({}, 'teams') }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'teams.subtitle'|trans({}, 'teams') }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.teams('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseTeams', showFilter) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if teams|length == 0 %}
|
||||
|
||||
55
templates/timesheet-team/actions.html.twig
Normal file
55
templates/timesheet-team/actions.html.twig
Normal file
@@ -0,0 +1,55 @@
|
||||
{% macro timesheets_team(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %}
|
||||
{% if is_granted('export_own_timesheet') %}
|
||||
{% set actions = actions|merge({'download': {'url': path('admin_timesheet_export'), 'class': 'toolbar-action'}}) %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'visibility': '#modal_timesheet_admin'}) %}
|
||||
{% if is_granted('create_other_timesheet') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_timesheet_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.timesheets_team', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheet_team(timesheet, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if timesheet.id is not empty %}
|
||||
{% if not timesheet.end and is_granted('stop', timesheet) %}
|
||||
{% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('admin_timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', timesheet) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('admin_timesheet')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.timesheet_team', {'actions': actions, 'view': view, 'timesheet': timesheet}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "timesheet-team/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_timesheet.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_timesheet.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.timesheet_team(timesheet, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "timesheet-team/actions.html.twig" as actions %}
|
||||
|
||||
{% set tableName = 'timesheet_admin' %}
|
||||
{% set columns = {
|
||||
@@ -29,11 +29,10 @@
|
||||
}) %}
|
||||
|
||||
{% block page_title %}{{ 'admin_timesheet.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_timesheet.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.timesheets_team('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseTimesheetAdmin', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
57
templates/timesheet/actions.html.twig
Normal file
57
templates/timesheet/actions.html.twig
Normal file
@@ -0,0 +1,57 @@
|
||||
{% macro timesheets(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %}
|
||||
{% if is_granted('export_own_timesheet') %}
|
||||
{% set actions = actions|merge({'download': {'url': path('timesheet_export'), 'class': 'toolbar-action'}}) %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'visibility': {'modal': '#modal_timesheet'}}) %}
|
||||
{% if is_granted('create_own_timesheet') %}
|
||||
{% set actions = actions|merge({'create': {'url': path('timesheet_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'timesheet.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.timesheets', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro timesheet(timesheet, view) %}
|
||||
{%- apply spaceless -%}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if timesheet.id is not empty %}
|
||||
{% if not timesheet.end and is_granted('stop', timesheet) %}
|
||||
{% set actions = actions|merge({'stop': {'url': path('stop_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.stop.error', 'data-msg-success': 'timesheet.stop.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if timesheet.end and is_granted('start', timesheet) %}
|
||||
{% set actions = actions|merge({'repeat': {'url': path('restart_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-payload': '{"copy": "all"}', 'data-event': 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method': 'PATCH', 'data-msg-error': 'timesheet.start.error', 'data-msg-success': 'timesheet.start.success'}}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('edit', timesheet) %}
|
||||
{% set class = '' %}
|
||||
{% if view != 'edit' %}
|
||||
{% set class = 'modal-ajax-form' %}
|
||||
{% endif %}
|
||||
{% set actions = actions|merge({'edit': {'url': path('timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' and is_granted('delete', timesheet) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('delete_timesheet', {'id' : timesheet.id}), 'class': 'api-link', 'attr': {'data-event': 'kimai.timesheetDelete kimai.timesheetUpdate', 'data-method': 'DELETE', 'data-question': 'confirm.delete', 'data-msg-error': 'action.delete.error', 'data-msg-success': 'action.delete.success'}}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'index' %}
|
||||
{% set actions = actions|merge({'back': path('timesheet')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.timesheet', {'actions': actions, 'view': view, 'timesheet': timesheet}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{%- endapply -%}
|
||||
{% endmacro %}
|
||||
@@ -1,9 +1,8 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "timesheet/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'timesheet.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'timesheet.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.timesheet(timesheet, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "timesheet/actions.html.twig" as actions %}
|
||||
{% import _self as timesheet %}
|
||||
|
||||
{% set tableName = 'timesheet' %}
|
||||
@@ -31,11 +31,10 @@
|
||||
}) %}
|
||||
|
||||
{% block page_title %}{{ 'timesheet.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'timesheet.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.timesheets('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseTimesheet', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
71
templates/user/actions.html.twig
Normal file
71
templates/user/actions.html.twig
Normal file
@@ -0,0 +1,71 @@
|
||||
{% macro users(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'search': {'class': 'search-toggle visible-xs-inline'}} %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'visibility': '#modal_user_admin'}) %}
|
||||
{% else %}
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'permissions' and is_granted('role_permissions') %}
|
||||
{% set actions = actions|merge({'permissions': path('admin_user_permissions')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('create_user') %}
|
||||
{% set actions = actions|merge({'create': path('admin_user_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% elseif view == 'permissions' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.users', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user_permissions(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.user_permissions', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user(user, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if user.id is not empty %}
|
||||
{% if is_granted('view', user) %}
|
||||
{% set actions = {'profile-stats': {'url': path('user_profile', {'username' : user.username})}} %}
|
||||
{% endif %}
|
||||
{% if is_granted('edit', user) %}
|
||||
{% set actions = actions|merge({'edit': path('user_profile_edit', {'username' : user.username})}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('preferences', user) %}
|
||||
{% set actions = actions|merge({'settings': {'url': path('user_profile_preferences', {'username' : user.username})}}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'user' : user.id})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', user) %}
|
||||
{% set actions = actions|merge({'trash': {'url': path('admin_user_delete', {'id': user.id}), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.user', {'actions': actions, 'view': view, 'user': user}) %}
|
||||
{% if view == 'index' %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.entity_actions(event.payload.actions) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -3,7 +3,6 @@
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ include('default/_form.html.twig', {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "user/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'alias': 'alwaysVisible',
|
||||
@@ -17,11 +17,10 @@
|
||||
{% set tableName = 'user_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ toolbar.dropDownSearch(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.users('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(toolbarForm, 'collapseUserAdmin', showFilter) }}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "user/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'profile.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'profile.subtitle'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{% if not user.alias is empty %}{{ user.alias }} - {% endif %}{{ user.username }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.user(user, tab) }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "user/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'label.name': 'alwaysVisible',
|
||||
@@ -17,7 +17,6 @@
|
||||
{% set tableName = 'user_admin_permissions' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.user_permissions('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block stylesheets %}
|
||||
|
||||
67
tests/Configuration/ThemeConfigurationTest.php
Normal file
67
tests/Configuration/ThemeConfigurationTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?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\Configuration;
|
||||
|
||||
use App\Configuration\ThemeConfiguration;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Configuration\ThemeConfiguration
|
||||
* @covers \App\Configuration\StringAccessibleConfigTrait
|
||||
*/
|
||||
class ThemeConfigurationTest extends TestCase
|
||||
{
|
||||
protected function getSut(array $settings, array $loaderSettings = []): ThemeConfiguration
|
||||
{
|
||||
$loader = new TestConfigLoader($loaderSettings);
|
||||
|
||||
return new ThemeConfiguration($loader, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultSettings()
|
||||
{
|
||||
return [
|
||||
'active_warning' => 3,
|
||||
'box_color' => 'green',
|
||||
'select_type' => null,
|
||||
'show_about' => true,
|
||||
'chart' => [
|
||||
'background_color' => 'rgba(0,115,183,0.7)',
|
||||
'border_color' => '#3b8bba',
|
||||
'grid_color' => 'rgba(0,0,0,.05)',
|
||||
'height' => '200'
|
||||
],
|
||||
'branding' => [
|
||||
'logo' => null,
|
||||
'mini' => null,
|
||||
'company' => null,
|
||||
'title' => null,
|
||||
],
|
||||
'auto_reload_datatable' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function testPrefix()
|
||||
{
|
||||
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||
$this->assertEquals('theme', $sut->getPrefix());
|
||||
}
|
||||
|
||||
public function testConfigs()
|
||||
{
|
||||
$sut = $this->getSut($this->getDefaultSettings(), []);
|
||||
$this->assertFalse($sut->isAutoReloadDatatable());
|
||||
$this->assertEquals('', $sut->getSelectPicker());
|
||||
$this->assertNull($sut->getTitle());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\ActivityMeta;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\ActivityFixtures;
|
||||
@@ -36,6 +37,36 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
$this->assertHasDataTable($client);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new ActivityFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setCallback(function (Activity $activity) {
|
||||
$activity->setVisible(true);
|
||||
$activity->setComment('I am a foobar with tralalalala some more content');
|
||||
$activity->setMetaField((new ActivityMeta())->setName('location')->setValue('homeoffice'));
|
||||
$activity->setMetaField((new ActivityMeta())->setName('feature')->setValue('timetracking'));
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/activity/');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'feature:timetracking foo',
|
||||
'visibility' => 1,
|
||||
'pageSize' => 50,
|
||||
'page' => 1,
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_activity_admin', 5);
|
||||
}
|
||||
|
||||
public function testBudgetAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
@@ -208,7 +208,6 @@ abstract class ControllerBaseTest extends WebTestCase
|
||||
protected function assertPageActions(Client $client, array $buttons)
|
||||
{
|
||||
$node = $client->getCrawler()->filter('section.content-header div.breadcrumb div.box-tools div.btn-group a.btn');
|
||||
self::assertEquals(count($buttons), $node->count());
|
||||
|
||||
foreach ($node->getIterator() as $element) {
|
||||
$expectedClass = str_replace('btn btn-default btn-', '', $element->getAttribute('class'));
|
||||
@@ -216,6 +215,8 @@ abstract class ControllerBaseTest extends WebTestCase
|
||||
$expectedUrl = $buttons[$expectedClass];
|
||||
self::assertEquals($expectedUrl, $element->getAttribute('href'));
|
||||
}
|
||||
|
||||
self::assertEquals(count($buttons), $node->count(), 'Invalid amount of page actions');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerMeta;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
@@ -38,6 +39,36 @@ class CustomerControllerTest extends ControllerBaseTest
|
||||
$this->assertHasDataTable($client);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new CustomerFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setCallback(function (Customer $customer) {
|
||||
$customer->setVisible(true);
|
||||
$customer->setComment('I am a foobar with tralalalala some more content');
|
||||
$customer->setMetaField((new CustomerMeta())->setName('location')->setValue('homeoffice'));
|
||||
$customer->setMetaField((new CustomerMeta())->setName('feature')->setValue('timetracking'));
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/customer/');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'feature:timetracking foo',
|
||||
'visibility' => 1,
|
||||
'pageSize' => 50,
|
||||
'page' => 1,
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_customer_admin', 5);
|
||||
}
|
||||
|
||||
public function testBudgetAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\ProjectMeta;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\CustomerFixtures;
|
||||
@@ -38,6 +39,36 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
$this->assertHasDataTable($client);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new ProjectFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setCallback(function (Project $project) {
|
||||
$project->setVisible(true);
|
||||
$project->setComment('I am a foobar with tralalalala some more content');
|
||||
$project->setMetaField((new ProjectMeta())->setName('location')->setValue('homeoffice'));
|
||||
$project->setMetaField((new ProjectMeta())->setName('feature')->setValue('timetracking'));
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/project/');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'feature:timetracking foo',
|
||||
'visibility' => 1,
|
||||
'pageSize' => 50,
|
||||
'page' => 1,
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_project_admin', 5);
|
||||
}
|
||||
|
||||
public function testBudgetAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
@@ -43,4 +43,21 @@ class TagControllerTest extends ControllerBaseTest
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_admin_tags', 10);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||
|
||||
$this->request($client, '/admin/tags/');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'Support',
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_admin_tags', 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,35 @@ class TeamControllerTest extends ControllerBaseTest
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/teams/');
|
||||
$this->assertPageActions($client, ['create' => $this->createUrl('/admin/teams/create'), 'help' => 'https://www.kimai.org/documentation/teams.html']);
|
||||
$this->assertPageActions($client, [
|
||||
'search search-toggle visible-xs-inline' => '#',
|
||||
'create' => $this->createUrl('/admin/teams/create'),
|
||||
'help' => 'https://www.kimai.org/documentation/teams.html'
|
||||
]);
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_admin_teams', 5);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TeamFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setCallback(function (Team $team) {
|
||||
$team->setName($team->getName() . '- fantastic team with foooo bar magic');
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/teams/');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'foo',
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_admin_teams', 5);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Entity\User;
|
||||
use App\Form\Type\DateRangeType;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
@@ -33,16 +34,13 @@ class TimesheetControllerTest extends ControllerBaseTest
|
||||
|
||||
// there are no records by default in the test database
|
||||
$this->assertHasNoEntriesWithFilter($client);
|
||||
|
||||
$result = $client->getCrawler()->filter('div.breadcrumb div.box-tools div.btn-group a.btn');
|
||||
$this->assertEquals(5, count($result));
|
||||
|
||||
foreach ($result as $item) {
|
||||
$this->assertContains('btn btn-default', $item->getAttribute('class'));
|
||||
/** @var \DOMElement $domElement */
|
||||
$domElement = $item->firstChild;
|
||||
$this->assertEquals('i', $domElement->tagName);
|
||||
}
|
||||
$this->assertPageActions($client, [
|
||||
'search search-toggle visible-xs-inline' => '#',
|
||||
'download toolbar-action' => $this->createUrl('/timesheet/export'),
|
||||
'visibility' => '#',
|
||||
'create modal-ajax-form' => $this->createUrl('/timesheet/create'),
|
||||
'help' => 'https://www.kimai.org/documentation/timesheet.html'
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIndexActionWithQuery()
|
||||
@@ -63,7 +61,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');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.navbar-form')->form();
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'state' => 1,
|
||||
'pageSize' => 25,
|
||||
@@ -80,6 +78,44 @@ class TimesheetControllerTest extends ControllerBaseTest
|
||||
self::assertEquals(2, $node->count());
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$start = new \DateTime('first day of this month');
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setStartDate($start);
|
||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
||||
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
||||
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
||||
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setAmountRunning(5);
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setStartDate($start);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/timesheet/');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$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();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'location:homeoffice foobar',
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_timesheet', 5);
|
||||
}
|
||||
|
||||
public function testExportAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser();
|
||||
@@ -96,7 +132,7 @@ class TimesheetControllerTest extends ControllerBaseTest
|
||||
|
||||
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.navbar-form')->form();
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/timesheet/export'));
|
||||
$client->submit($form, [
|
||||
'state' => 1,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Entity\User;
|
||||
use App\Form\Type\DateRangeType;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
@@ -34,15 +35,13 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
||||
// there are no records by default in the test database
|
||||
$this->assertHasNoEntriesWithFilter($client);
|
||||
|
||||
$result = $client->getCrawler()->filter('div.breadcrumb div.box-tools div.btn-group a.btn');
|
||||
$this->assertEquals(5, count($result));
|
||||
|
||||
foreach ($result as $item) {
|
||||
$this->assertContains('btn btn-default', $item->getAttribute('class'));
|
||||
/** @var \DOMElement $domElement */
|
||||
$domElement = $item->firstChild;
|
||||
$this->assertEquals('i', $domElement->tagName);
|
||||
}
|
||||
$this->assertPageActions($client, [
|
||||
'search search-toggle visible-xs-inline' => '#',
|
||||
'download toolbar-action' => $this->createUrl('/team/timesheet/export'),
|
||||
'visibility' => '#',
|
||||
'create modal-ajax-form' => $this->createUrl('/team/timesheet/create'),
|
||||
'help' => 'https://www.kimai.org/documentation/timesheet.html'
|
||||
]);
|
||||
}
|
||||
|
||||
public function testIndexActionWithQuery()
|
||||
@@ -65,7 +64,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');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.navbar-form')->form();
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'state' => 1,
|
||||
'user' => $user->getId(),
|
||||
@@ -83,6 +82,44 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
||||
self::assertEquals(3, $node->count());
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$start = new \DateTime('first day of this month');
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setStartDate($start);
|
||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
||||
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
||||
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
||||
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
||||
});
|
||||
$this->importFixture($em, $fixture);
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setAmount(5);
|
||||
$fixture->setAmountRunning(5);
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setStartDate($start);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/team/timesheet/');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$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();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'location:homeoffice foobar',
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_timesheet_admin', 5);
|
||||
}
|
||||
|
||||
public function testExportAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||
@@ -104,7 +141,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
|
||||
|
||||
$dateRange = (new \DateTime('-10 days'))->format('Y-m-d') . DateRangeType::DATE_SPACER . (new \DateTime())->format('Y-m-d');
|
||||
|
||||
$form = $client->getCrawler()->filter('form.navbar-form')->form();
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$form->getFormNode()->setAttribute('action', $this->createUrl('/team/timesheet/export'));
|
||||
$client->submit($form, [
|
||||
'state' => 1,
|
||||
|
||||
@@ -32,6 +32,27 @@ class UserControllerTest extends ControllerBaseTest
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin', 5);
|
||||
}
|
||||
|
||||
public function testIndexActionWithSearchTermQuery()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
|
||||
$this->request($client, '/admin/user/');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form.header-search')->form();
|
||||
$client->submit($form, [
|
||||
'searchTerm' => 'hourly_rate:35 tony',
|
||||
'role' => 'ROLE_TEAMLEAD',
|
||||
'visibility' => 1,
|
||||
'pageSize' => 50,
|
||||
'page' => 1,
|
||||
]);
|
||||
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin', 1);
|
||||
}
|
||||
|
||||
public function testCreateAction()
|
||||
{
|
||||
$username = '亚历山德拉';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user