billable timesheets, inactive projects report, bookmark export search (#2503)
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro activity(activity, view, options) %}
|
||||
{% macro activity(activity, view, isTable) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'activity', view, {'activity': activity}) %}
|
||||
{% if view == 'index' or view == 'custom' %}
|
||||
{% if view == 'index' or view == 'custom' or isTable is not null %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'details') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'activity_details') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set can_edit = is_granted('edit', activity) %}
|
||||
@@ -42,7 +42,7 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if not activity.global %}
|
||||
<tr class="{{ widgets.class_customer_row(activity.project.customer, now) }}">
|
||||
<tr {{ widgets.customer_row_attr(activity.project.customer, now) }}>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_customer(activity.project.customer) }}
|
||||
@@ -54,7 +54,7 @@
|
||||
{{ customerActions.customer(activity.project.customer, 'custom') }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="{{ widgets.class_project_row(activity.project, now) }}">
|
||||
<tr {{ widgets.project_row_attr(activity.project, now) }}>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_project(activity.project) }}
|
||||
@@ -78,7 +78,11 @@
|
||||
{% endembed %}
|
||||
|
||||
{% if stats is not null %}
|
||||
{{ include('activity/embed_budget.html.twig', {'activity': activity, 'stats': stats}) }}
|
||||
{% set currency = null %}
|
||||
{% if activity.project is not null %}
|
||||
{% set currency = activity.project.customer.currency %}
|
||||
{% endif %}
|
||||
{{ include('embeds/budgets.html.twig', {'entity': activity, 'stats': stats, 'currency': currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if can_edit %}
|
||||
@@ -102,7 +106,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.activityUpdate kimai.teamUpdate kimai.activityTeamUpdate kimai.projectTeamUpdate kimai.customerTeamUpdate kimai.projectUpdate kimai.rateUpdate');
|
||||
KimaiReloadPageWidget.create('kimai.activityUpdate kimai.teamUpdate kimai.activityTeamUpdate kimai.projectTeamUpdate kimai.customerTeamUpdate kimai.customerUpdate kimai.projectUpdate kimai.rateUpdate');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% block box_title %}{{ 'label.budget'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set params = {
|
||||
'%activity%': activity.name,
|
||||
'%project%': '-',
|
||||
'%customer%': '-',
|
||||
'%records%': stats.recordAmount,
|
||||
'%duration%': stats.recordDuration|duration
|
||||
} %}
|
||||
|
||||
{% set currency = null %}
|
||||
{% if activity.project is not null %}
|
||||
{% set currency = activity.project.customer.currency %}
|
||||
{% endif %}
|
||||
|
||||
{% if activity.project is not null %}
|
||||
{% set params = params|merge({
|
||||
'%project%': activity.project.name,
|
||||
'%customer%': activity.project.customer.name,
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
{{ 'admin_activity.short_stats'|trans(params) }}
|
||||
{{ 'label.rate_internal'|trans }}: {{ stats.recordInternalRate|money(currency) }}.
|
||||
</p>
|
||||
|
||||
{{ progress.progressbar(activity.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(currency) ~ ' / ' ~ activity.budget|money(currency) ) }}
|
||||
{{ progress.progressbar(activity.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ activity.timeBudget|duration ) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -37,16 +37,10 @@
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.activityUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate kimai.activityTeamUpdate'}) }}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate kimai.activityDelete kimai.activityTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
{% set class = widgets.class_activity_row(entry, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', entry) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('activity_details', {'id': entry.id}) %}
|
||||
{% endif %}
|
||||
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
||||
<tr {{ widgets.activity_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('activity', true, entry.name, null, entry.color) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
|
||||
{# only none-global activities have a project and customer assigned #}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_actions %}{{ actions.customer(customer, 'details') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'customer_details') }}{% endblock %}
|
||||
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
@@ -121,7 +121,7 @@
|
||||
{{ render(controller('App\\Controller\\CustomerController::projectsAction', {'customer': customer.id, 'page': 1})) }}
|
||||
|
||||
{% if stats is not null %}
|
||||
{{ include('customer/embed_budget.html.twig', {'customer': customer, 'stats': stats}) }}
|
||||
{{ include('embeds/budgets.html.twig', {'entity': customer, 'stats': stats, 'currency': customer.currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if can_edit %}
|
||||
@@ -150,7 +150,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.customerTeamUpdate kimai.customerUpdate kimai.teamUpdate kimai.projectTeamUpdate kimai.projectUpdate kimai.rateUpdate');
|
||||
KimaiReloadPageWidget.create('kimai.customerTeamUpdate kimai.customerUpdate kimai.teamUpdate kimai.projectTeamUpdate kimai.rateUpdate');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'customer': customer, 'stats': stats} %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% block box_title %}{{ 'label.budget'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set currency = customer.currency %}
|
||||
|
||||
{% set params = {
|
||||
'%activity%': stats.activityAmount,
|
||||
'%project%': stats.projectAmount,
|
||||
'%customer%': customer.name,
|
||||
'%records%': stats.recordAmount,
|
||||
'%duration%': stats.recordDuration|duration,
|
||||
'%rate%': stats.recordRate|money(currency),
|
||||
'%internal_rate%': stats.recordInternalRate|money(currency)
|
||||
} %}
|
||||
|
||||
<p>
|
||||
{{ 'admin_customer.short_stats'|trans(params) }}
|
||||
{{ 'label.rate_internal'|trans }}: {{ stats.recordInternalRate|money(currency) }}.
|
||||
</p>
|
||||
|
||||
{{ progress.progressbar(customer.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(currency) ~ ' / ' ~ customer.budget|money(currency) ) }}
|
||||
{{ progress.progressbar(customer.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ customer.timeBudget|duration ) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -3,7 +3,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'menu.admin_project'|trans }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="project_list_box" data-href="{{ path('customer_projects', {'id': customer.id}) }}" data-reload="kimai.projectUpdate"
|
||||
id="project_list_box" data-href="{{ path('customer_projects', {'id': customer.id}) }}" data-reload="kimai.projectUpdate kimai.projectDelete"
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if is_granted('view_reporting') and is_granted('budget_project') %}
|
||||
@@ -35,7 +35,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr class="{{ widgets.class_project_row(project, now) }}">
|
||||
<tr {{ widgets.project_row_attr(project, now) }}>
|
||||
<td>{{ widgets.label_project(project) }}</td>
|
||||
<td class="hidden-xs">{{ project.comment|comment1line(not app.user.smallLayout) }}</td>
|
||||
<td class="w-min text-center">
|
||||
@@ -47,7 +47,7 @@
|
||||
</td>
|
||||
<td class="w-min text-center">{{ widgets.label_visible(project.visible) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.badge_team_access(project.teams) }}</td>
|
||||
<td class="actions">{{ actions.project(project, 'custom') }}</td>
|
||||
<td class="actions">{{ actions.project(project, 'customer_details', true) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -48,16 +48,10 @@
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.customerUpdate kimai.customerTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerTeamUpdate'}) }}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerDelete kimai.customerTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
{% set class = widgets.class_customer_row(entry, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', entry) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('customer_details', {'id': entry.id}) %}
|
||||
{% endif %}
|
||||
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
||||
<tr {{ widgets.customer_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
|
||||
|
||||
23
templates/embeds/budgets.html.twig
Normal file
23
templates/embeds/budgets.html.twig
Normal file
@@ -0,0 +1,23 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% block box_title %}{{ 'label.timeBudget'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="time_budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ progress.progressbar(stats.recordDuration, stats.durationBillable, 'label.billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.recordDuration|duration, true) }}
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ progress.progressbar(entity.timeBudget, stats.durationBillable, 'label.timeBudget'|trans, stats.durationBillable|duration ~ ' / ' ~ entity.timeBudget|duration) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% block box_title %}{{ 'label.budget'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ progress.progressbar(stats.recordRate, stats.rateBillable, 'label.billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.recordRate|money(currency), true) }}
|
||||
{% if entity.budget > 0 %}
|
||||
{{ progress.progressbar(entity.budget, stats.rateBillable, 'label.budget'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.rateBillable, stats.recordInternalRate, 'label.rate_internal'|trans, stats.recordInternalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -11,7 +11,7 @@
|
||||
'description': {'class': 'hidden-xs hidden-sm hidden-md timesheet-description', 'orderBy': false},
|
||||
'unit_price': {'class': 'hidden hidden-xs text-nowrap', 'orderBy': false},
|
||||
'duration': {'class': 'text-nowrap', 'orderBy': false},
|
||||
'rate_internal': {'class': 'hidden-xs text-nowrap', 'orderBy': false},
|
||||
'rate_internal': {'class': 'hidden text-nowrap', 'orderBy': false},
|
||||
'total_rate': {'class': 'text-nowrap', 'orderBy': false},
|
||||
'exported': {'class': 'alwaysVisible', 'orderBy': false},
|
||||
} %}
|
||||
@@ -32,6 +32,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||
{% block box_title %}{{ 'export.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
@@ -46,43 +47,13 @@
|
||||
{% if form.users is defined %}
|
||||
{{ form_row(form.users) }}
|
||||
{% endif %}
|
||||
{{ form_row(form.billable) }}
|
||||
{{ form_row(form.exported) }}
|
||||
{{ form_row(form.state) }}
|
||||
{{ form_row(form.markAsExported) }}
|
||||
{% endblock %}
|
||||
{% block box_footer%}
|
||||
{% set buttons = {} %}
|
||||
{% for button in renderer %}
|
||||
{% set title = button.title %}
|
||||
{% set group = [] %}
|
||||
{% if buttons[(title)] is defined %}
|
||||
{% set group = buttons[(title)] %}
|
||||
{% endif %}
|
||||
{% set group = group|merge([button]) %}
|
||||
{% set buttons = buttons|merge({(title): group}) %}
|
||||
{% endfor %}
|
||||
<div class="btn-group" id="export-buttons" role="group">
|
||||
{% for group in buttons %}
|
||||
{% set button = group.0 %}
|
||||
{% if group|length == 1 %}
|
||||
<button type="button" id="export-{{ button.id }}-button" class="btn btn-success startExportBtn" data-type="{{ button.id }}">
|
||||
{{ ('button.' ~ button.title)|trans }}
|
||||
</button>
|
||||
{% elseif group|length > 1 %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ ('button.' ~ button.title)|trans }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for button in group %}
|
||||
<li><a href="#" class="startExportBtn" data-type="{{ button.id }}">{{ (button.id)|trans({}, 'export') }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ form_widget(form.preview) }}
|
||||
{{ search.searchButton(form) }}
|
||||
{% endblock %}
|
||||
{% block box_after %}{{ form_end(form) }}{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -91,28 +62,100 @@
|
||||
{% if entries is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'button.preview'|trans }}: {{ 'export.title'|trans }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_footer %}
|
||||
{% set buttons = {} %}
|
||||
{% for button in renderer %}
|
||||
{% set title = button.title %}
|
||||
{% set group = [] %}
|
||||
{% if buttons[(title)] is defined %}
|
||||
{% set group = buttons[(title)] %}
|
||||
{% endif %}
|
||||
{% set group = group|merge([button]) %}
|
||||
{% set buttons = buttons|merge({(title): group}) %}
|
||||
{% endfor %}
|
||||
<div class="btn-group" id="export-buttons" role="group">
|
||||
{% for group in buttons %}
|
||||
{% set button = group.0 %}
|
||||
{% if group|length == 1 %}
|
||||
<button type="button" id="export-{{ button.id }}-button" class="btn btn-success startExportBtn" data-type="{{ button.id }}">
|
||||
{{ ('button.' ~ button.title)|trans }}
|
||||
</button>
|
||||
{% elseif group|length > 1 %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ ('button.' ~ button.title)|trans }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for button in group %}
|
||||
<li><a href="#" class="startExportBtn" data-type="{{ button.id }}">{{ (button.id)|trans({}, 'export') }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th class="w-min text-center hidden-xs">{{ 'label.duration'|trans }}</th>
|
||||
{# <th class="w-min text-right hidden-xs">{{ 'label.internalRate'|trans }}</th> #}
|
||||
<th class="w-min text-right">{{ 'label.total_rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in by_customer %}
|
||||
{% set currency = row.customer.currency %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ widgets.label_customer(row.customer) }}
|
||||
</td>
|
||||
<td class="w-min text-center hidden-xs">
|
||||
{{ row.duration|duration(decimal) }}
|
||||
</td>
|
||||
{#
|
||||
<td class="w-min text-right hidden-xs">
|
||||
{{ row.internalRate|money(currency) }}
|
||||
</td>
|
||||
#}
|
||||
<td class="w-min text-right">
|
||||
{{ row.rate|money(currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% set itemsAmount = entries|length %}
|
||||
{% if preview_limit %}
|
||||
{% set entries = entries|slice(0, preview_limit) %}
|
||||
{% endif %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||
{% set totalAmount = {} %}
|
||||
{% set totalInternalAmount = {} %}
|
||||
{% set totalDuration = 0 %}
|
||||
{% for entry in entries %}
|
||||
{% set currency = entry.project.customer.currency %}
|
||||
{% set duration = entry.duration|duration %}
|
||||
{% if totalAmount[currency] is not defined %}
|
||||
{% set totalAmount = totalAmount|merge({(currency): 0}) %}
|
||||
{% set totalInternalAmount = totalInternalAmount|merge({(currency): 0}) %}
|
||||
{% endif %}
|
||||
{% if entry.fixedRate is not null %}
|
||||
{% set rate = entry.fixedRate %}
|
||||
{% else %}
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
{% set totalDuration = totalDuration + entry.duration %}
|
||||
{% set totalAmount = totalAmount|merge({(currency): totalAmount[currency] + entry.rate}) %}
|
||||
{% set totalInternalAmount = totalInternalAmount|merge({(currency): totalInternalAmount[currency] + entry.internalRate}) %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.begin|date_short }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.label_user(entry.user) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">
|
||||
{{ entry.begin|date_short }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">
|
||||
{{ widgets.label_user(entry.user) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
<br>
|
||||
@@ -130,7 +173,7 @@
|
||||
{{ rate|money(currency) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}" data-duration="{{ entry.duration }}">
|
||||
{{ duration }}
|
||||
{{ entry.duration|duration(decimal) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate_internal') }}">
|
||||
{{ entry.internalRate|money(currency) }}
|
||||
@@ -161,30 +204,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'date') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'user') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'project') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'description') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'unit_price') }}"></th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}">
|
||||
{{ totalDuration|duration }}
|
||||
</th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'rate_internal') }}">
|
||||
{% for currency, amount in totalInternalAmount %}
|
||||
{{ amount|money(currency) }}
|
||||
{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">
|
||||
{% for currency, amount in totalAmount %}
|
||||
{{ amount|money(currency) }}
|
||||
{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</th>
|
||||
<th class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}"></th>
|
||||
</tr>
|
||||
{% if preview_limit and itemsAmount > preview_limit %}
|
||||
<tr class="warning">
|
||||
<td colspan="10">» {{ 'preview.skipped_rows'|trans({'%rows%': (itemsAmount - preview_limit)}) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{{ tables.data_table_footer(entries) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -201,7 +225,7 @@
|
||||
var message = '{{ 'export.clear_all'|trans }}';
|
||||
var hasActive = true;
|
||||
|
||||
if (!$('#export-toggle-button').hasClass('export-off')) {
|
||||
if (!jQuery('#export-toggle-button').hasClass('export-off')) {
|
||||
message = '{{ 'export.mark_all'|trans }}';
|
||||
hasActive = false;
|
||||
}
|
||||
@@ -210,8 +234,8 @@
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
var btn = $(button);
|
||||
var exportButtons = $('.exportBtn');
|
||||
var btn = jQuery(button);
|
||||
var exportButtons = jQuery('.exportBtn');
|
||||
// disabling does not yet work...
|
||||
if (exportButtons.length > 0) {
|
||||
btn.addClass('disabled');
|
||||
@@ -219,8 +243,8 @@
|
||||
|
||||
// ... as the clicks are asynchronous and the each comes back too early ...
|
||||
exportButtons.each(function () {
|
||||
if (hasActive === $(this).hasClass('active')) {
|
||||
$(this).click();
|
||||
if (hasActive === jQuery(this).hasClass('active')) {
|
||||
jQuery(this).click();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -254,11 +278,11 @@
|
||||
} else {
|
||||
button.button('clean');
|
||||
}
|
||||
if ($('select#exported').val() !== '{{ constant('App\\Repository\\Query\\TimesheetQuery::STATE_ALL') }}') {
|
||||
if (jQuery('select#exported').val() !== '{{ constant('App\\Repository\\Query\\TimesheetQuery::STATE_ALL') }}') {
|
||||
button.closest('tr').hide('ease', function() {
|
||||
$(this).remove();
|
||||
if($(this).closest('table').find('tbody tr:visible').length === 0) {
|
||||
$('#export-buttons button').prop('disabled', true);
|
||||
jQuery(this).remove();
|
||||
if(jQuery(this).closest('table').find('tbody tr:visible').length === 0) {
|
||||
jQuery('#export-buttons button').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -286,24 +310,24 @@
|
||||
}
|
||||
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
$('body').on('click', '.exportBtn', function() {
|
||||
var button = $(this);
|
||||
jQuery('body').on('click', '.exportBtn', function() {
|
||||
var button = jQuery(this);
|
||||
var id = button.attr('data-timesheet');
|
||||
updateTimesheetExportState(button, id, !button.hasClass('active'));
|
||||
});
|
||||
|
||||
$('#export-toggle-button').on('click', function () {
|
||||
jQuery('#export-toggle-button').on('click', function () {
|
||||
confirmToggleState(this);
|
||||
});
|
||||
|
||||
$('body').on('click', '#export-buttons .startExportBtn', function() {
|
||||
$('#renderer').val($(this).attr('data-type'));
|
||||
var $form = $("#export-form");
|
||||
jQuery('body').on('click', '#export-buttons .startExportBtn', function() {
|
||||
jQuery('#renderer').val(jQuery(this).attr('data-type'));
|
||||
var $form = jQuery("#export-form");
|
||||
var prevAction = $form.attr('action');
|
||||
var prevMethod = $form.attr('method');
|
||||
$form.attr('target', '_blank').attr('method', 'POST').attr('action', '{{ path('export_data') }}');
|
||||
$form.submit();
|
||||
$('#renderer').val('');
|
||||
jQuery('#renderer').val('');
|
||||
$form.removeAttr('target').attr('action', prevAction).attr('method', prevMethod);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_footer %}
|
||||
<a href="#" onclick="return saveAllInvoices(this);" class="btn btn-primary" id="create_all_invoices">
|
||||
<a href="#" onclick="return saveAllInvoices(this);" class="btn btn-success" id="create_all_invoices">
|
||||
{{ 'action.save_all'|trans }}
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -109,8 +109,8 @@
|
||||
</td>
|
||||
<td class="w-min text-center">
|
||||
{{ widgets.action_button('show', {'url': '#invoice_preview_details_' ~ model.customer.id, 'title': 'timesheet.all'|trans, 'class': 'btn btn-sm hidden-xs hidden-sm'}, 'link') }}
|
||||
{{ widgets.action_button('print', {'url': '#', 'onclick': 'return singleInvoice(this)', 'title': 'button.preview'|trans, 'target': '_blank', 'class': 'btn btn-sm', 'attr': {'data-href': path('invoice_preview', {'customer': model.customer.id, 'template': model.template.id})}}, 'success') }}
|
||||
{{ widgets.action_button('save', {'url': '#', 'onclick': 'return singleInvoice(this)', 'title': 'action.save'|trans, 'class': 'btn btn-sm', 'attr': {'data-href': path('invoice_create', {'customer': model.customer.id, 'template': model.template.id})}}, 'primary') }}
|
||||
{{ widgets.action_button('print', {'url': '#', 'onclick': 'return singleInvoice(this)', 'title': 'button.preview'|trans, 'target': '_blank', 'class': 'btn btn-sm', 'attr': {'data-href': path('invoice_preview', {'customer': model.customer.id, 'template': model.template.id})}}) }}
|
||||
{{ widgets.action_button('save', {'url': '#', 'onclick': 'return singleInvoice(this)', 'title': 'action.save'|trans, 'class': 'btn btn-sm', 'attr': {'data-href': path('invoice_create', {'customer': model.customer.id, 'template': model.template.id})}}, 'success') }}
|
||||
</td>
|
||||
<td class="w-min text-center hidden-xs">
|
||||
{{ model.calculator.timeWorked|duration(isDecimal) }}
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
{% macro progressbar(max, current, title, subTitle) %}
|
||||
{% macro progressbar(max, current, title, subTitle, reverseColors) %}
|
||||
{% set percentReached = 0 %}
|
||||
{% if max > 0 %}
|
||||
{% set percentReached = (current / (max / 100)) %}
|
||||
{% endif %}
|
||||
{% set class = "progress-bar-info" %}
|
||||
{% set width = percentReached|number_format(1, '.', '') %}
|
||||
|
||||
{% if percentReached > 90 %}
|
||||
{% set class = "progress-bar-danger" %}
|
||||
{% elseif percentReached > 70 %}
|
||||
{% set class = "progress-bar-warning" %}
|
||||
{% elseif percentReached > 50 %}
|
||||
{% set class = "progress-bar-success" %}
|
||||
{% elseif percentReached > 30 %}
|
||||
{% set class = "progress-bar-primary" %}
|
||||
{% endif %}
|
||||
|
||||
{% if width > 100 %}
|
||||
{% set width = 100 %}
|
||||
{% endif %}
|
||||
@@ -35,36 +24,25 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar {{ class }}" role="progressbar" aria-valuenow="{{ width }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ width }}%"></div>
|
||||
<div class="progress-bar {{ progressbar_color(percentReached, reverseColors) }}" role="progressbar" aria-valuenow="{{ width }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ width }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro progressbar_small(max, current, leftValue, leftFormatted) %}
|
||||
{% macro progressbar_small(max, current, leftValue, leftFormatted, reverseColors) %}
|
||||
{% set percentReached = 0 %}
|
||||
{% if max > 0 %}
|
||||
{% set percentReached = (current / (max / 100)) %}
|
||||
{% endif %}
|
||||
{% set class = "progress-bar-info" %}
|
||||
{% set width = percentReached|number_format(1, '.', '') %}
|
||||
|
||||
{% if percentReached > 90 %}
|
||||
{% set class = "progress-bar-danger" %}
|
||||
{% elseif percentReached > 70 %}
|
||||
{% set class = "progress-bar-warning" %}
|
||||
{% elseif percentReached > 50 %}
|
||||
{% set class = "progress-bar-success" %}
|
||||
{% elseif percentReached > 30 %}
|
||||
{% set class = "progress-bar-primary" %}
|
||||
{% endif %}
|
||||
|
||||
{% if width > 100 %}
|
||||
{% set width = 100 %}
|
||||
{% endif %}
|
||||
|
||||
<div class="progress-group">
|
||||
<div class="progress progress-sm">
|
||||
<div class="progress-bar {{ class }}" role="progressbar" aria-valuenow="{{ width }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ width }}%"></div>
|
||||
<div class="progress-bar {{ progressbar_color(percentReached, reverseColors) }}" role="progressbar" aria-valuenow="{{ width }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ width }}%"></div>
|
||||
</div>
|
||||
{% if leftValue is not null and leftValue > 0 %}
|
||||
<small>{{ 'stats.percentUsedLeft'|trans({'%percent%': percentReached|number_format(0), '%left%': leftFormatted}) }}</small>
|
||||
|
||||
@@ -504,17 +504,105 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{# To be used mainly in <tr class="{{ class_customer_row(customer, now) }}"> #}
|
||||
{#
|
||||
To be used like this:
|
||||
<tr class="{{ class_customer_row(customer, now) }}">
|
||||
#}
|
||||
{% macro class_customer_row(customer, now) %}
|
||||
{%- if not customer.visible %}warning{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{# To be used mainly in <tr class="{{ class_project_row(project, now) }}"> #}
|
||||
{#
|
||||
To be used like this:
|
||||
<tr {{ customer_row_attr(customer, now) }}>
|
||||
#}
|
||||
{% macro customer_row_attr(customer, now) %}
|
||||
{%- set class = _self.class_customer_row(customer, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', customer) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('customer_details', {'id': customer.id}) %}
|
||||
{% endif -%}
|
||||
class="{{ class }}" data-href="{{ dataHref }}"
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
To be used like this:
|
||||
<tr class="{{ class_project_row(project, now) }}">
|
||||
#}
|
||||
{% macro class_project_row(project, now) %}
|
||||
{%- if not project.visible or (project.end is not null and project.end < now) %}warning{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{# To be used mainly in <tr class="{{ class_activity_row(activity, now) }}"> #}
|
||||
{#
|
||||
To be used like this:
|
||||
<tr {{ project_row_attr(project, now) }}>
|
||||
#}
|
||||
{% macro project_row_attr(project, now) %}
|
||||
{%- set class = _self.class_project_row(project, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', project) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('project_details', {'id': project.id}) %}
|
||||
{% endif -%}
|
||||
class="{{ class }}" data-href="{{ dataHref }}"
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
To be used like this:
|
||||
<tr {{ class_activity_row(activity, now) }}>
|
||||
#}
|
||||
{% macro class_activity_row(activity, now) %}
|
||||
{%- if not activity.visible %}warning{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
To be used like this:
|
||||
<tr {{ activity_row_attr(activity, now) }}>
|
||||
#}
|
||||
{% macro activity_row_attr(activity, now) %}
|
||||
{%- set class = _self.class_activity_row(activity, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', activity) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('activity_details', {'id': activity.id}) %}
|
||||
{% endif -%}
|
||||
class="{{ class }}" data-href="{{ dataHref }}"
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
To be used like this:
|
||||
<tr class="{{ class_user_row(user, now) }}">
|
||||
#}
|
||||
{% macro class_user_row(user) %}
|
||||
{%- if not user.enabled %}warning{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
To be used like this:
|
||||
<tr {{ user_row_attr(user) }}>
|
||||
#}
|
||||
{% macro user_row_attr(user) %}
|
||||
{%- set class = _self.class_user_row(user) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', user) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('user_profile', {'username': user.username}) %}
|
||||
{% endif -%}
|
||||
class="{{ class }}" data-href="{{ dataHref }}"
|
||||
{% endmacro %}
|
||||
|
||||
{# To be used mainly in <tr class="{{ class_user_row(user, now) }}"> #}
|
||||
{% macro short_stats_row(stats) %}
|
||||
<div class="row">
|
||||
{% set colLength = 12 / stats|length %}
|
||||
{% for title, value in stats %}
|
||||
<div class="col-sm-{{ colLength }} border-right">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header">{{ value }}</h5>
|
||||
<span class="description-text">{{ title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project(project, view) %}
|
||||
{% macro project(project, view, isTable) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'project', view, {'project': project}) %}
|
||||
{% if view == 'index' or view == 'custom' %}
|
||||
{% if view == 'index' or view == 'custom' or isTable is not null %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
{% else %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'details') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'project_details') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set can_edit = is_granted('edit', project) %}
|
||||
@@ -36,7 +36,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="{{ widgets.class_customer_row(project.customer, now) }}">
|
||||
<tr {{ widgets.customer_row_attr(project.customer, now) }}>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_customer(project.customer) }}
|
||||
@@ -99,7 +99,7 @@
|
||||
{{ render(controller('App\\Controller\\ProjectController::activitiesAction', {'project': project.id, 'page': 1})) }}
|
||||
|
||||
{% if stats is not null %}
|
||||
{{ include('project/embed_budget.html.twig', {'project': project, 'stats': stats}) }}
|
||||
{{ include('embeds/budgets.html.twig', {'entity': project, 'stats': stats, 'currency': project.customer.currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if can_edit %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'menu.admin_activity'|trans }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="activity_list_box" data-href="{{ path('project_activities', {'id': project.id}) }}" data-reload="kimai.activityUpdate"
|
||||
id="activity_list_box" data-href="{{ path('project_activities', {'id': project.id}) }}" data-reload="kimai.activityUpdate kimai.activityDelete"
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if project.visible and project.customer.visible and is_granted('create_activity') %}
|
||||
@@ -31,12 +31,12 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for activity in activities %}
|
||||
<tr class="{{ widgets.class_activity_row(activity, now) }}">
|
||||
<tr {{ widgets.activity_row_attr(activity, now) }}>
|
||||
<td>{{ widgets.label_activity(activity) }}</td>
|
||||
<td class="hidden-xs">{{ activity.comment|comment1line(not app.user.smallLayout) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.label_visible(activity.visible) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.badge_team_access(activity.teams) }}</td>
|
||||
<td class="actions">{{ actions.activity(activity, 'custom') }}</td>
|
||||
<td class="actions">{{ actions.activity(activity, 'project_details', true) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% block box_title %}{{ 'label.budget'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set params = {
|
||||
'%project%': project.name,
|
||||
'%customer%': project.customer.name,
|
||||
'%records%': stats.recordAmount,
|
||||
'%activities%': stats.activityAmount,
|
||||
'%duration%': stats.recordDuration|duration
|
||||
} %}
|
||||
|
||||
{% set currency = project.customer.currency %}
|
||||
|
||||
<p>
|
||||
{{ 'admin_project.short_stats'|trans(params) }}
|
||||
{{ 'label.rate_internal'|trans }}: {{ stats.recordInternalRate|money(currency) }}.
|
||||
</p>
|
||||
|
||||
{{ progress.progressbar(project.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(currency) ~ ' / ' ~ project.budget|money(currency) ) }}
|
||||
{{ progress.progressbar(project.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ project.timeBudget|duration ) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -41,16 +41,10 @@
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.projectUpdate kimai.projectTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectTeamUpdate'}) }}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectDelete kimai.projectTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
{% set class = widgets.class_project_row(entry, now) %}
|
||||
{% set dataHref = '' %}
|
||||
{% if is_granted('view', entry) %}
|
||||
{% set class = class ~ ' alternative-link open-edit' %}
|
||||
{% set dataHref = path('project_details', {'id': entry.id}) %}
|
||||
{% endif %}
|
||||
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
||||
<tr {{ widgets.project_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('project', true, entry.name, null, entry.color) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% extends 'reporting/layout.html.twig' %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block report_title %}{{ 'report_project_view'|trans({}, 'reporting') }}{% endblock %}
|
||||
{% block report_title %}{{ (title|default('report_project_view'))|trans({}, 'reporting') }}{% endblock %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'lastRecord': {'class': 'hidden text-center hw-min', 'title': 'label.last_record'|trans},
|
||||
'today': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans},
|
||||
'week': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans},
|
||||
'month': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans},
|
||||
@@ -17,7 +18,7 @@
|
||||
'comment': {'class': 'hidden-md hidden-sm hidden-xs hidden', 'title': 'label.comment'|trans},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
{% set tableName = 'project_view_reporting' %}
|
||||
{% set tableName = tableName|default('project_view_reporting') %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
@@ -32,7 +33,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "project/actions.html.twig" as projectActions %}
|
||||
{% block box_body_class %}project-view-reporting-box {% if hasData %}no-padding{% endif %}{% endblock %}
|
||||
{% block box_body_class %}{{ tableName }}-box {% if hasData %}no-padding{% endif %}{% endblock %}
|
||||
{% block box_before %}
|
||||
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'project-view-form'}}) }}
|
||||
{% endblock %}
|
||||
@@ -40,7 +41,7 @@
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{{ widgets.action_button('visibility', {'modal': '#modal_project_view_reporting', 'class': 'btn-sm'}) }}
|
||||
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
|
||||
{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ form_widget(form) }}
|
||||
@@ -51,57 +52,64 @@
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, null, {'bordered': true, 'boxClass': ''}) }}
|
||||
|
||||
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
|
||||
<tr class="summary">
|
||||
<td colspan="11">{{ widgets.label_customer(mapping.customer) }}</td>
|
||||
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
|
||||
<tr class="summary">
|
||||
<td colspan="{{ columns|length }}">{{ widgets.label_customer(mapping.customer) }}</td>
|
||||
</tr>
|
||||
{% for entry in mapping.projects|sort((a, b) => a.project.name <=> b.project.name) %}
|
||||
{% set project = entry.project %}
|
||||
{% set currency = project.customer.currency %}
|
||||
{% if is_granted('budget', project) %}
|
||||
<tr {{ widgets.project_row_attr(project, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_project(project) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'lastRecord') }}">
|
||||
{% if entry.lastRecord is not null %}
|
||||
{{ entry.lastRecord|date_short }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'today') }}">{{ entry.durationDay|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'week') }}">{{ entry.durationWeek|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'month') }}">{{ entry.durationMonth|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'durationTotal') }}">{{ entry.durationTotal|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if project.timeBudget > 0 %}
|
||||
{{ progress.progressbar(project.timeBudget, entry.billableDuration|default(0), entry.billableDuration|duration ~ ' / ' ~ project.timeBudget|duration, '') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'budget') }}">
|
||||
{% if project.budget > 0 %}
|
||||
{{ progress.progressbar(project.budget, entry.billableRate|default(0), entry.billableRate|money(currency) ~ ' / ' ~ project.budget|money(currency), '') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateDuration') }}">
|
||||
{% if is_granted('create_export') %}
|
||||
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateMoney') }}">
|
||||
{% if is_granted('view_invoice') %}
|
||||
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectEnd') }}">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ project.comment }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{{ projectActions.project(project, 'custom') }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for entry in mapping.projects|sort((a, b) => a.project.name <=> b.project.name) %}
|
||||
{% set project = entry.project %}
|
||||
{% set currency = project.customer.currency %}
|
||||
{% if is_granted('budget', project) %}
|
||||
<tr{% if is_granted('view', project) %} class="alternative-link open-edit" data-href="{{ path('project_details', {'id': project.id}) }}"{% endif %}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_project(project) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'today') }}">{{ entry.durationDay|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'week') }}">{{ entry.durationWeek|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'month') }}">{{ entry.durationMonth|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'durationTotal') }}">{{ entry.durationTotal|duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if project.timeBudget > 0 %}
|
||||
{{ progress.progressbar(project.timeBudget, entry.durationTotal|default(0), entry.durationTotal|duration ~ ' / ' ~ project.timeBudget|duration, '') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'budget') }}">
|
||||
{% if project.budget > 0 %}
|
||||
{{ progress.progressbar(project.budget, entry.rateTotal|default(0), entry.rateTotal|money(currency) ~ ' / ' ~ project.budget|money(currency), '') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateDuration') }}">
|
||||
{% if is_granted('create_export') %}
|
||||
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ entry.notExportedDuration|duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'stateMoney') }}">
|
||||
{% if is_granted('view_invoice') %}
|
||||
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ entry.notBilledRate|money(currency) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectEnd') }}">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ project.comment }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{{ projectActions.project(project, 'custom') }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(entries) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -88,6 +88,26 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.metaFields is defined and form.metaFields is not empty %}
|
||||
{% for meta in form.metaFields|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(meta) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.exported is defined or form.billable is defined %}
|
||||
{% set ebLength = (form.exported is defined and form.billable is defined) ? 6 : 12 %}
|
||||
<div class="row">
|
||||
{% if form.exported is defined %}
|
||||
<div class="col-md-{{ ebLength }}">
|
||||
{{ form_row(form.exported) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.billable is defined %}
|
||||
<div class="col-md-{{ ebLength }}">
|
||||
{{ form_row(form.billable) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
{% block form_after %}
|
||||
|
||||
@@ -30,13 +30,6 @@
|
||||
'project': {'class': 'hidden-xs hidden-sm hidden-md'},
|
||||
'activity': {'class': 'hidden-xs hidden-sm'},
|
||||
'description': {'class': 'hidden-xs hidden-sm'},
|
||||
}) %}
|
||||
{% if canSeeUsername %}
|
||||
{% set columns = columns|merge({
|
||||
'username': {'class': 'hidden-xs', 'orderBy': false},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'tags': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
|
||||
}) %}
|
||||
{% for field in metaColumns %}
|
||||
@@ -44,7 +37,13 @@
|
||||
('mf_' ~ field.name): {'title': field.label|trans, 'class': 'hidden-xs hidden-sm', 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% if canSeeUsername %}
|
||||
{% set columns = columns|merge({
|
||||
'username': {'class': 'hidden-xs', 'orderBy': false},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'billable': {'class': 'text-center hidden w-min', 'orderBy': false},
|
||||
'exported': {'class': 'text-center hidden w-min', 'orderBy': false},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
@@ -60,7 +59,7 @@
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.timesheetUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.timesheetUpdate'}) }}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.timesheetUpdate kimai.timesheetDelete'}) }}
|
||||
|
||||
{% set checkOverlappingDesc = false %}
|
||||
{% set checkOverlappingAsc = false %}
|
||||
@@ -150,19 +149,23 @@
|
||||
{{ entry.description|nl2br }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'tags') }}">{{ widgets.tag_list(entry.tags) }}</td>
|
||||
|
||||
{% for field in metaColumns %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
||||
{{ tables.datatable_meta_column(entry, field) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
{% if canSeeUsername %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}">
|
||||
{{ widgets.label_user(entry.user) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'tags') }}">{{ widgets.tag_list(entry.tags) }}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
||||
{{ tables.datatable_meta_column(entry, field) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'billable') }}">
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}">
|
||||
{{ widgets.label_boolean(entry.exported) }}
|
||||
</td>
|
||||
@@ -213,13 +216,15 @@
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}"></td>
|
||||
{% if canSeeUsername %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}"></td>
|
||||
{% endif %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'tags') }}"></td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}"></td>
|
||||
{% endfor %}
|
||||
{% if canSeeUsername %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'username') }}"></td>
|
||||
{% endif %}
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'billable') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}"></td>
|
||||
<td class="actions"></td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<tr{% if is_granted('view', entry) %} class="open-edit alternative-link" data-href="{{ path('user_profile', {'username': entry.username}) }}"{% endif %}>
|
||||
<tr {{ widgets.user_row_attr(entry) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'avatar') }}">{{ widgets.user_avatar(entry) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'alias') }}">{{ widgets.username(entry) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
|
||||
|
||||
@@ -53,8 +53,20 @@
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
return data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
}
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return tooltipData['duration'];
|
||||
},
|
||||
afterTitle: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
beforeFooter: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
footer: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return '{{ 'label.billable'|trans }}: ' + tooltipData['billable'];
|
||||
},
|
||||
}
|
||||
}
|
||||
{%- if user.enabled and is_granted('view_reporting') and (app.user.id == user.id or is_granted('view_other_timesheet')) -%}
|
||||
@@ -95,8 +107,9 @@
|
||||
],
|
||||
realData: [
|
||||
{% for month in yearStat.months %}
|
||||
'{{ month.totalDuration|duration }}'
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% set realDayData = {duration: month.totalDuration|duration, billable: month.billableDuration|duration} %}
|
||||
{{ realDayData|json_encode|raw }}
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
monthData: [
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
{%- for entry in day.details -%}
|
||||
{% set loopId = (entry.project.id ~ '_' ~ entry.activity.id) %}
|
||||
{%- if loopId == activityId -%}
|
||||
{% set realDayData = {duration: entry.duration|duration, project: entry.project.name, customer: entry.project.customer.name, activity: entry.activity.name, total: day.totalDuration|duration} %}
|
||||
{% set realDayData = {duration: entry.duration|duration, billable: day.totalDurationBillable|duration, project: entry.project.name, customer: entry.project.customer.name, activity: entry.activity.name, total: day.totalDuration|duration} %}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- if realDayData is not null -%}
|
||||
@@ -117,7 +117,7 @@
|
||||
footer: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return '{{ 'stats.durationTotal'|trans }}: ' + tooltipData.total;
|
||||
return '{{ 'stats.durationTotal'|trans }}: ' + tooltipData.total + '\n{{ 'label.billable'|trans }}: ' + tooltipData.billable;
|
||||
},
|
||||
beforeFooter: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
<td style="width:50%">
|
||||
{% if project.timeBudget is not empty and project.timeBudget > 0 %}
|
||||
{% set budgetLeft = project.timeBudget - stats.recordDuration %}
|
||||
{{ progress.progressbar_small(project.timeBudget, stats.recordDuration, budgetLeft, budgetLeft|duration) }}
|
||||
{{ progress.progressbar_small(project.timeBudget, stats.durationBillable, budgetLeft, budgetLeft|duration) }}
|
||||
{% elseif project.budget is not empty and project.budget > 0 %}
|
||||
{% set budgetLeft = project.budget - stats.recordRate %}
|
||||
{{ progress.progressbar_small(project.budget, stats.recordRate, budgetLeft, budgetLeft|money(project.customer.currency)) }}
|
||||
{{ progress.progressbar_small(project.budget, stats.rateBillable, budgetLeft, budgetLeft|money(project.customer.currency)) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user