refactored search with free search term support (#1064)

This commit is contained in:
Kevin Papst
2019-09-04 18:54:06 +02:00
committed by GitHub
parent 49e1a1c410
commit e99b170d0a
147 changed files with 2071 additions and 1322 deletions

View File

@@ -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

View File

@@ -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'));

View File

@@ -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() != '';
})

View File

@@ -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'
});
}
}

View 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();
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -10,6 +10,7 @@
@import 'error-page';
@import 'print';
@import 'content';
@import 'content-header';
@import 'toolbar';
@import 'sidebar';
@import 'footer';

View 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;
}
}
}
}

View File

@@ -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;
}

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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",

View File

@@ -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",

View File

@@ -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');

View File

@@ -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)

View File

@@ -314,6 +314,9 @@ class Configuration implements ConfigurationInterface
->scalarNode('select_type')
->defaultNull()
->end()
->scalarNode('auto_reload_datatable')
->defaultFalse()
->end()
->booleanNode('show_about')
->defaultTrue()
->end()

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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(),
]);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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(),
]);
}

View File

@@ -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);
}

View File

@@ -20,6 +20,7 @@ class TagToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -20,6 +20,7 @@ class TeamToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View 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);
}
}

View File

@@ -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',

View File

@@ -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
View 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;
}
}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 }}',

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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>

View 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 %}

View File

@@ -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) }}

View File

@@ -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', {

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -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 %}

View File

@@ -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', {

View File

@@ -1,5 +1,4 @@
{% extends 'user/layout.html.twig' %}
{% import "macros/actions.html.twig" as actions %}
{% block main %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View 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());
}
}

View File

@@ -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);

View File

@@ -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');
}
/**

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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