added dynamic column filter #174 (#184)

This commit is contained in:
Kevin Papst
2018-06-28 22:56:04 +02:00
committed by GitHub
parent 2f0c64ae17
commit b299302da4
20 changed files with 383 additions and 152 deletions

View File

@@ -28,6 +28,7 @@ require('../../vendor/almasaeed2010/adminlte/dist/js/app.js');
// ------ Kimai itself ------
require('./kimai.js');
require('./datatable.js');
require('./toolbar.js');
require('../images/default_avatar.png');

66
assets/js/datatable.js Normal file
View File

@@ -0,0 +1,66 @@
/*
* 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.
*/
/** global: Cookies */
global.Cookies = require('js-cookie');
if (typeof jQuery === 'undefined') {
throw new Error('Kimai requires jQuery');
}
/* datatable
*
* @type Object
* @description $.datatable is the main object for views with data-tables.
* It's used for implementing functions and options related
* to the datatables.
*/
$.datatable = {};
$(function() {
"use strict";
$.datatable = {
saveVisibility: function (modalSelector) {
var settings = {};
var cookieName = $(modalSelector).find('form').attr('name');
$(modalSelector).find('form').find('input:checkbox:not(:checked)').each(
function () {
settings[$(this).attr('name')] = $(this).is(':checked');
}
);
if (jQuery.isEmptyObject(settings)) {
Cookies.remove(cookieName);
} else {
Cookies.set(cookieName, JSON.stringify(settings), {expires: 365});
}
$(modalSelector).modal('toggle');
location.reload();
},
changeVisibility: function (column) {
var tbl = $('table.dataTable');
var amount = tbl.find('th').length -1;
if (column < 0 || column >= amount) {
return;
}
var header = $(tbl.find('th').get(column));
if (header.css("display") === "none") {
header.show();
tbl.find('tr').each(function(){
$($(this).find('td').get(column)).show('ease');
});
} else {
header.hide();
tbl.find('tr').each(function(){
$($(this).find('td').get(column)).hide('ease');
});
}
}
};
});

View File

@@ -16,6 +16,7 @@
"jquery": "^3.3.1",
"jquery-slimscroll": "^1.3.8",
"jquery-ui": "^1.12.1",
"js-cookie": "^2.2.0",
"node-sass": "^4.9.0",
"sass-loader": "^7.0.2",
"webpack-notifier": "^1.5.1"

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{
"build/app.js": "/build/app.js?aa153be6f298fddd7ee1",
"build/app.js": "/build/app.js?604773492e023a83c73b",
"build/app.css": "/build/app.css?ec2e5a6f655d60e8c23adb0967d24d6f",
"build/fonts/fa-solid-900.woff": "/build/fonts/fa-solid-900.woff?128d2b0b",
"build/images/boxed-bg.jpg": "/build/images/boxed-bg.jpg?7799dece",

View File

@@ -46,6 +46,41 @@ class Extensions extends \Twig_Extension
*/
protected $requestStack;
/**
* @var array
*/
protected $cookies = [];
/**
* @var string[]
*/
protected static $icons = [
'user' => 'fas fa-user',
'customer' => 'fas fa-users',
'project' => 'fas fa-project-diagram',
'activity' => 'fas fa-tasks',
'admin' => 'fas fa-wrench',
'invoice' => 'fas fa-file-invoice',
'timesheet' => 'far fa-clock',
'dashboard' => 'fas fa-tachometer-alt',
'logout' => 'fas fa-sign-out-alt',
'trash' => 'far fa-trash-alt',
'delete' => 'far fa-trash-alt',
'repeat' => 'fas fa-redo-alt',
'edit' => 'far fa-edit',
'manual' => 'fas fa-book',
'help' => 'far fa-question-circle',
'start' => 'fas fa-play-circle',
'start-small' => 'fas fa-play-circle',
'stop' => 'fas fa-stop',
'stop-small' => 'far fa-stop-circle',
'filter' => 'fas fa-filter',
'create' => 'far fa-plus-square',
'list' => 'fas fa-list',
'print' => 'fas fa-print',
'visibility' => 'far fa-eye',
];
/**
* Extensions constructor.
* @param string $locales
@@ -79,9 +114,42 @@ class Extensions extends \Twig_Extension
{
return [
new \Twig_SimpleFunction('locales', [$this, 'getLocales']),
new \Twig_SimpleFunction('is_visible_column', [$this, 'isColumnVisible']),
];
}
/**
* This is only for datatables, do not use it outside this context.
*
* @param string $dataTable
* @param string $column
* @return bool
*/
public function isColumnVisible(string $dataTable, string $column)
{
// TODO name handling could be improved, as now this info is spread in datatables.html.twig and here
$dataTable = $dataTable . '_visibility';
if (!isset($this->cookies[$dataTable])) {
$visibility = false;
if ($this->requestStack->getCurrentRequest()->cookies->has($dataTable)) {
$visibility = json_decode($this->requestStack->getCurrentRequest()->cookies->get($dataTable), true);
}
$this->cookies[$dataTable] = $visibility;
}
$values = $this->cookies[$dataTable];
if (empty($values) || !is_array($values)) {
return true;
}
if (isset($values[$column]) && $values[$column] === false) {
return false;
}
return true;
}
/**
* Transforms seconds into a duration string.
*
@@ -127,33 +195,7 @@ class Extensions extends \Twig_Extension
*/
public function icon($name, $default = '')
{
$icons = [
'user' => 'fas fa-user',
'customer' => 'fas fa-users',
'project' => 'fas fa-project-diagram',
'activity' => 'fas fa-tasks',
'admin' => 'fas fa-wrench',
'invoice' => 'fas fa-file-invoice',
'timesheet' => 'far fa-clock',
'dashboard' => 'fas fa-tachometer-alt',
'logout' => 'fas fa-sign-out-alt',
'trash' => 'far fa-trash-alt',
'delete' => 'far fa-trash-alt',
'repeat' => 'fas fa-redo-alt',
'edit' => 'far fa-edit',
'manual' => 'fas fa-book',
'help' => 'far fa-question-circle',
'start' => 'fas fa-play-circle',
'start-small' => 'fas fa-play-circle',
'stop' => 'fas fa-stop',
'stop-small' => 'far fa-stop-circle',
'filter' => 'fas fa-filter',
'create' => 'far fa-plus-square',
'list' => 'fas fa-list',
'print' => 'fas fa-print',
];
return $icons[$name] ?? $default;
return self::$icons[$name] ?? $default;
}
/**

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }} {{ 'subtitle.amount'|trans({'%count%': entries.count}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseActivityAdmin', 'create': path('admin_activity_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseActivityAdmin', 'visibility': '#modal_activity_admin', 'create': path('admin_activity_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseActivityAdmin') }}
@@ -16,26 +16,30 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{{ tables.data_table_header({
'label.name': '',
'label.customer': 'hidden-xs',
'label.project': '',
'label.comment': 'hidden-xs',
'label.visible': 'hidden-xs',
'label.actions': '',
}) }}
{% set columns = {
'name': 'alwaysVisible',
'customer': 'hidden-xs',
'project': '',
'comment': 'hidden-xs',
'visible': 'hidden-xs',
'actions': 'alwaysVisible',
} %}
{% set tableName = 'activity_admin' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td>{{ entry.name }}</td>
<td class="hidden-xs">
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">
<a href="{{ path('admin_customer_edit', {'id' : entry.project.customer.id}) }}">{{ widgets.label_customer(entry.project.customer) }}</a>
</td>
<td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
<a href="{{ path('admin_project_edit', {'id' : entry.project.id}) }}">{{ widgets.label_project(entry.project) }}</a>
</td>
<td class="hidden-xs">{{ entry.comment }}</td>
<td class="hidden-xs">{{ widgets.label_visible(entry.visible) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
<td>
{% set actionButtons = {} %}
{% if is_granted('edit', entry) %}

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_customer.subtitle'|trans }} {{ 'subtitle.amount'|trans({'%count%': entries.count}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseCustomerAdmin', 'create': path('admin_customer_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseCustomerAdmin', 'visibility': '#modal_customer_admin', 'create': path('admin_customer_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseCustomerAdmin') }}
@@ -16,31 +16,35 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{{ tables.data_table_header({
'label.name': '',
'label.project': '',
'label.comment': 'hidden-xs',
'label.country': 'hidden-xs',
'label.customer_number': 'hidden-xs',
'label.currency': 'hidden-xs',
'label.visible': 'hidden-xs',
'label.actions': '',
}) }}
{% set columns = {
'name': 'alwaysVisible',
'project': '',
'comment': 'hidden-xs',
'country': 'hidden-xs',
'customer_number': 'hidden-xs',
'currency': 'hidden-xs',
'visible': 'hidden-xs',
'actions': 'alwaysVisible',
} %}
{% set tableName = 'customer_admin' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td>{{ entry.name }} {% if entry.company is not empty %}({{ entry.company }}){% endif %}</td>
<td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
{% for project in entry.projects %}
<a href="{{ path('admin_project_edit', {'id' : project.id}) }}">{{ widgets.label_project(project) }}</a>
<br class="visible-xs-block">
{% endfor %}
</td>
<td class="hidden-xs">{{ entry.comment }}</td>
<td class="hidden-xs">{{ entry.country|country }}</td>
<td class="hidden-xs">{{ entry.number }}</td>
<td class="hidden-xs">{{ entry.currency }} {{ entry.currency|currency }}</td>
<td class="hidden-xs">{{ widgets.label_visible(entry.visible) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'country') }}">{{ entry.country|country }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer_number') }}">{{ entry.number }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'currency') }}">{{ entry.currency }} {{ entry.currency|currency }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
<td>
{% set actionButtons = {} %}
{% if is_granted('edit', entry) %}

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }} {{ 'subtitle.amount'|trans({'%count%': entries.count}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseProjectAdmin', 'create': path('admin_project_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseProjectAdmin', 'visibility': '#modal_project_admin', 'create': path('admin_project_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseProjectAdmin') }}
@@ -16,24 +16,28 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{{ tables.data_table_header({
'label.name': '',
'label.customer': '',
'label.comment': 'hidden-xs hidden-sm',
'label.budget': 'hidden-xs',
'label.visible': 'hidden-xs',
'label.actions': '',
}) }}
{% set columns = {
'name': 'alwaysVisible',
'customer': '',
'comment': 'hidden-xs hidden-sm',
'budget': 'hidden-xs',
'visible': 'hidden-xs',
'actions': 'alwaysVisible',
} %}
{% set tableName = 'project_admin' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td>{{ entry.name }}</td>
<td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">
<a href="{{ path('admin_customer_edit', {'id' : entry.customer.id}) }}">{{ widgets.label_customer(entry.customer) }}</a>
</td>
<td class="hidden-xs hidden-sm">{{ entry.comment }}</td>
<td class="hidden-xs">{{ entry.budget|money(entry.customer.currency) }}</td>
<td class="hidden-xs">{{ widgets.label_visible(entry.visible) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'budget') }}">{{ entry.budget|money(entry.customer.currency) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
<td>
{% set actionButtons = {} %}
{% if is_granted('edit', entry) %}

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'admin_timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_timesheet.subtitle'|trans }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseTimesheetAdmin', 'create': path('admin_timesheet_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseTimesheetAdmin', 'visibility': '#modal_timesheet_admin', 'create': path('admin_timesheet_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseTimesheetAdmin') }}
@@ -16,48 +16,52 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{% set columns = {'label.date': ''} %}
{% set columns = {'date': 'alwaysVisible'} %}
{% if not duration_only %}
{% set columns = columns|merge({'label.starttime': 'hidden-xs', 'label.endtime': 'hidden-xs'}) %}
{% set columns = columns|merge({'starttime': 'hidden-xs', 'endtime': 'hidden-xs'}) %}
{% endif %}
{{ tables.data_table_header(columns|merge({
'label.duration': '',
'label.rate': '',
'label.activity': 'hidden-xs hidden-sm',
'label.username': 'hidden-xs',
'label.description': 'hidden-xs hidden-sm',
'label.actions': '',
})) }}
{% set columns = columns|merge({
'duration': '',
'rate': '',
'activity': 'hidden-xs hidden-sm',
'username': 'hidden-xs',
'description': 'hidden-xs hidden-sm',
'actions': 'alwaysVisible',
}) %}
{% set tableName = 'timesheet_admin' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date_short }}</td>
{% if not duration_only %}
<td class="hidden-xs">{{ entry.begin|date("H:i") }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'starttime') }}">{{ entry.begin|date("H:i") }}</td>
{% endif %}
{% if entry.end %}
{% if not duration_only %}
<td class="hidden-xs">{{ entry.end|date("H:i") }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'endtime') }}">{{ entry.end|date("H:i") }}</td>
{% endif %}
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.rate|money(entry.activity.project.customer.currency) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}">{{ entry.duration|duration }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate') }}">{{ entry.rate|money(entry.activity.project.customer.currency) }}</td>
{% else %}
{% if not duration_only %}
<td class="hidden-xs">&dash;</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'endtime') }}">&dash;</td>
{% endif %}
<td><i>{{ entry|duration }}</i></td>
<td>&dash;</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}"><i>{{ entry|duration }}</i></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate') }}">&dash;</td>
{% endif %}
<td class="hidden-xs hidden-sm">
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}">
<a href="{{ path('admin_activity_edit', {'id': entry.activity.id}) }}">{{ widgets.label_activity(entry.activity) }}</a>
</td>
<td class="hidden-xs"><a href="{{ path('profile'|route_alias, {'username' : entry.user.username}) }}">{{ widgets.label_user(entry.user) }}</a></td>
<td class="hidden-xs hidden-sm">{{ entry.description }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}"><a href="{{ path('profile'|route_alias, {'username' : entry.user.username}) }}">{{ widgets.label_user(entry.user) }}</a></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">{{ entry.description }}</td>
<td>
{% set actionButtons = {} %}
{% if is_granted('edit', entry) %}

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }} {{ 'subtitle.amount'|trans({'%count%': entries.count}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseUserAdmin', 'create': path('admin_user_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseUserAdmin', 'visibility': '#modal_user_admin', 'create': path('admin_user_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseUserAdmin') }}
@@ -16,26 +16,30 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{{ tables.data_table_header({
'label.id': 'hidden-xs',
'label.alias': '',
'label.username': 'hidden-xs',
'label.email': 'hidden-xs hidden-sm',
'label.title': 'hidden-xs',
'label.active': '',
'label.roles': '',
'label.actions': '',
}) }}
{% set columns = {
'id': 'hidden-xs',
'alias': 'alwaysVisible',
'username': 'hidden-xs',
'email': 'hidden-xs hidden-sm',
'title': 'hidden-xs',
'active': '',
'roles': '',
'actions': 'alwaysVisible',
} %}
{% set tableName = 'user_admin' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td class="hidden-xs">{{ entry.id }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'id') }}">{{ entry.id }}</td>
<td>{{ widgets.username(entry) }}</td>
<td class="hidden-xs">{{ entry.username }}</td>
<td class="hidden-xs hidden-sm">{{ entry.email }}</td>
<td class="hidden-xs">{{ entry.title }}</td>
<td>{{ widgets.label_visible(entry.active) }}</td>
<td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}">{{ entry.username }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.active) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'roles') }}">
{% for role in entry.roles %}
{{ widgets.label_role(role) }}
{% endfor %}

View File

@@ -11,13 +11,13 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{{ tables.data_table_header({
'label.name': '',
'label.title': '',
'label.due_days': 'hidden-xs',
'label.vat': 'hidden-xs',
'label.invoice_renderer': 'hidden-xs',
'label.actions': '',
{{ tables.data_table_header('invoice_template', {
'name': 'alwaysVisible',
'title': '',
'due_days': 'hidden-xs',
'vat': 'hidden-xs',
'invoice_renderer': 'hidden-xs',
'actions': 'alwaysVisible',
}) }}
{% for entry in entries %}

View File

@@ -1,10 +1,62 @@
{% macro data_table_header(entries, form, collapseClass) %}
<div class="box data_table">
{% macro data_table_column_modal(name, entries) %}
<div class="modal fade" id="modal_{{ name }}" tabindex="-1" role="dialog" aria-labelledby="data_table_modal_label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">{{ 'modal.columns.title'|trans }}</h4>
</div>
<div class="modal-body">
<form name="{{ name }}_visibility">
{% for title, class in entries %}
{% if 'alwaysVisible' not in class %}
<div class="form-group {{ class }}">
<input type="checkbox" id="column_{{ title }}" name="{{ title }}"{% if is_visible_column(name, title) %} checked="checked"{% endif %}>
<label class="control-label required" for="column_{{ title }}">{{ ('label.' ~ title)|trans }}</label>
</div>
{% endif %}
{% endfor %}
</form>
<p>{{ 'modal.columns.description'|trans }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'action.close'|trans }}</button>
<button type="button" class="btn btn-primary" onclick="$.datatable.saveVisibility('#modal_{{ name }}')">{{ 'action.save'|trans }}</button>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro data_table_column_class(name, entries, column) %}
{% spaceless %}
{% set class = '' %}
{% set always = false %}
{% if entries[column] is defined %}
{% set class = entries[column] %}
{% if 'alwaysVisible' in entries[column] %}
{% set always = true %}
{% endif %}
{% endif %}
{% if (always == false) and (not is_visible_column(name, column)) %}
{% set class = class ~ ' hidden' %}
{% endif %}
{% if not class is empty %}
{{ class }}
{% endif %}
{% endspaceless %}
{% endmacro %}
{% macro data_table_header(name, entries, form, collapseClass) %}
{% import _self as macro %}
{{ macro.data_table_column_modal(name, entries) }}
<div class="box data_table" id="datatable_{{ name }}">
{% if form %}
{% import "macros/toolbar.html.twig" as macro %}
{{ macro.toolbar(form, collapseClass) }}
{% endif %}
<div class="box-body no-padding">
<div class="dataTables_wrapper form-inline dt-bootstrap">
<div class="row">
@@ -17,9 +69,9 @@
<thead>
<tr>
{% for title, class in entries %}
<th{% if class %} class="{{ class }}"{% endif %}>
{{ title|trans }}
</th>
<th class="{{ macro.data_table_column_class(name, entries, title) }}">
{{ ('label.' ~ title)|trans }}
</th>
{% endfor %}
</tr>
</thead>

View File

@@ -3,9 +3,15 @@
<div class="breadcrumb">
<div class="box-tools">
<div class="btn-group">
{% for icon,url in tools %}
<a class="btn btn-default" href="{{ url }}"{% if '#collapse' in url %} data-toggle="collapse" {% endif %}><i class="{{ icon|icon(icon) }}"></i></a>
{% endfor %}
{%- for icon,url in tools %}
<a class="btn btn-default" href="{{ url }}"
{% if '#collapse' in url %}
data-toggle="collapse"
{% elseif '#modal' in url %}
data-toggle="modal" data-target="{{ url }}"
{% endif %}
><i class="{{ icon|icon(icon) }}"></i></a>
{% endfor -%}
</div>
</div>
</div>

View File

@@ -5,7 +5,7 @@
{% block page_title %}{{ 'timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'timesheet.subtitle'|trans }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseTimesheet', 'create': path('timesheet_create')}) }}{% endblock %}
{% block page_actions %}{{ widgets.page_actions({'filter': '#collapseTimesheet', 'visibility': '#modal_timesheet', 'create': path('timesheet_create')}) }}{% endblock %}
{% block main_before %}
{{ toolbar.toolbar(toolbarForm, 'collapseTimesheet') }}
@@ -17,46 +17,48 @@
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}
{% set columns = {'label.date': ''} %}
{% set columns = {'date': 'alwaysVisible'} %}
{% if not duration_only %}
{% set columns = columns|merge({'label.starttime': '', 'label.endtime': ''}) %}
{% set columns = columns|merge({'starttime': '', 'endtime': ''}) %}
{% endif %}
{% set columns = columns|merge({
'label.duration': 'hidden-xs',
'label.rate': 'hidden-xs',
'label.activity': 'hidden-xs hidden-sm',
'label.description': 'hidden-xs hidden-sm',
'label.actions': '',
'duration': 'hidden-xs',
'rate': 'hidden-xs',
'activity': 'hidden-xs hidden-sm',
'description': 'hidden-xs hidden-sm',
'actions': 'alwaysVisible',
}) %}
{{ tables.data_table_header(columns) }}
{% set tableName = 'timesheet' %}
{{ tables.data_table_header(tableName, columns) }}
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date_short }}</td>
{% if not duration_only %}
<td>{{ entry.begin|date("H:i") }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'starttime') }}">{{ entry.begin|date("H:i") }}</td>
{% endif %}
{% if entry.end %}
{% if not duration_only %}
<td>{{ entry.end|date("H:i") }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'endtime') }}">{{ entry.end|date("H:i") }}</td>
{% endif %}
<td class="hidden-xs">{{ entry.duration|duration }}</td>
<td class="hidden-xs">{{ entry.rate|money(entry.activity.project.customer.currency) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}">{{ entry.duration|duration }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate') }}">{{ entry.rate|money(entry.activity.project.customer.currency) }}</td>
{% else %}
{% if not duration_only %}
<td>&dash;</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'endtime') }}">&dash;</td>
{% endif %}
<td class="hidden-xs"><i>{{ entry|duration }}</i></td>
<td class="hidden-xs">&dash;</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}"><i>{{ entry|duration }}</i></td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate') }}">&dash;</td>
{% endif %}
<td class="hidden-xs hidden-sm">{{ widgets.label_activity(entry.activity) }}</td>
<td class="hidden-xs hidden-sm">{{ entry.description }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}">{{ widgets.label_activity(entry.activity) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">{{ entry.description }}</td>
<td>
{% set actionButtons = {'edit': path('timesheet_edit', {'id' : entry.id, 'page': page})} %}

View File

@@ -51,7 +51,7 @@ class ExtensionsTest extends TestCase
public function testGetFunctions()
{
$functions = ['locales'];
$functions = ['locales', 'is_visible_column'];
$sut = $this->getSut('de');
$twigFunctions = $sut->getFunctions();
$this->assertCount(count($functions), $twigFunctions);
@@ -184,6 +184,7 @@ class ExtensionsTest extends TestCase
'create' => 'far fa-plus-square',
'list' => 'fas fa-list',
'print' => 'fas fa-print',
'visibility' => 'far fa-eye',
];
$sut = $this->getSut('en');

View File

@@ -235,6 +235,10 @@
<source>action.back</source>
<target>Zurück</target>
</trans-unit>
<trans-unit id="action.close">
<source>action.close</source>
<target>Schließen</target>
</trans-unit>
<!--
Dashboard
@@ -303,6 +307,14 @@
<source>label.end</source>
<target>Bis</target>
</trans-unit>
<trans-unit id="modal.columns.title">
<source>modal.columns.title</source>
<target>Ändere Spalten-Sichtbarkeit</target>
</trans-unit>
<trans-unit id="modal.columns.description">
<source>modal.columns.description</source>
<target>Beim Speichern werden die nicht ausgewählten Spalten ausgeblendet und diese Einstellungen in einem Browser Cookie hinterlegt. Sollten Sie Ihre Cookies löschen, werden die Einstellungen rückgängig gemacht.</target>
</trans-unit>
<!--
User profile

View File

@@ -243,6 +243,10 @@
<source>action.back</source>
<target>Back</target>
</trans-unit>
<trans-unit id="action.close">
<source>action.close</source>
<target>Close</target>
</trans-unit>
<!--
Dashboard
@@ -311,6 +315,14 @@
<source>label.end</source>
<target>To</target>
</trans-unit>
<trans-unit id="modal.columns.title">
<source>modal.columns.title</source>
<target>Change column visibility</target>
</trans-unit>
<trans-unit id="modal.columns.description">
<source>modal.columns.description</source>
<target>Upon saving the un-checked columns will be hidden and this setting will be stored in a browser cookie. If you delete your cookies, the settings will be reversed.</target>
</trans-unit>
<!--
User profile

View File

@@ -275,6 +275,10 @@
<source>action.back</source>
<target>Назад</target>
</trans-unit>
<trans-unit id="action.close">
<source>action.close</source>
<target>Close</target>
</trans-unit>
<!--
Dashboard
@@ -343,6 +347,14 @@
<source>label.end</source>
<target>До</target>
</trans-unit>
<trans-unit id="modal.columns.title">
<source>modal.columns.title</source>
<target>Change column visibility</target>
</trans-unit>
<trans-unit id="modal.columns.description">
<source>modal.columns.description</source>
<target>Upon saving the un-checked columns will be hidden and this setting will be stored in a browser cookie. If you delete your cookies, the settings will be reversed.</target>
</trans-unit>
<!--
User profile

View File

@@ -2958,6 +2958,10 @@ js-base64@^2.1.8, js-base64@^2.1.9:
version "2.4.5"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92"
js-cookie@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"