detail pages for customers and projects (#1371)

This commit is contained in:
Kevin Papst
2020-01-16 16:25:28 +01:00
committed by GitHub
parent 28c5357f11
commit 6a44dbfe83
131 changed files with 3055 additions and 1742 deletions

View File

@@ -28,6 +28,9 @@
{% if is_granted('budget', activity) %}
{% set actions = actions|merge({'report': {'url': path('admin_activity_budget', {'id': activity.id})}}) %}
{% endif %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% 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 %}
@@ -39,12 +42,12 @@
{% endif %}
{% endif %}
{% if view != 'index' %}
{% if view != 'index' and view != 'custom' %}
{% set actions = actions|merge({'back': path('admin_activity')}) %}
{% endif %}
{% set event = trigger('actions.activity', {'actions': actions, 'view': view, 'activity': activity}) %}
{% if view == 'index' %}
{% if view == 'index' or view == 'custom' %}
{{ widgets.table_actions(event.payload.actions) }}
{% else %}
{{ widgets.entity_actions(event.payload.actions) }}

View File

@@ -4,19 +4,23 @@
{% import "macros/toolbar.html.twig" as toolbar %}
{% import "activity/actions.html.twig" as actions %}
{% set showVisibility = query.visibility != 1 %}
{% set columns = {
'name': 'alwaysVisible',
'customer': 'hidden-xs',
'project': 'hidden-xs',
'comment': 'hidden-xs hidden-sm',
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
} %}
{% for field in metaColumns %}
{% set columns = columns|merge({
('mf_' ~ field.name): {'title': field.label, 'class': 'hidden-xs hidden-sm', 'orderBy': false}
}) %}
{% endfor %}
{% if showVisibility %}
{% set columns = columns|merge({
'visible': {'class': 'text-center', 'orderBy': false},
}) %}
{% endif %}
{% set columns = columns|merge({
'visible': {'class': 'text-center', 'orderBy': false},
'actions': 'actions alwaysVisible',
}) %}
@@ -40,16 +44,16 @@
{% for entry in entries %}
<tr{% if is_granted('edit', entry) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_activity_edit', {'id': entry.id}) }}"{% endif %}>
<td>{{ widgets.label_color_dot('activity', true, entry.name, null, entry.color) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">
{% if entry.project and entry.project.customer %}
{# only none-global activities have a project and customer assigned #}
{{ widgets.label_customer(entry.project.customer) }}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
{# only none-global activities have a project and customer assigned #}
{% if entry.project %}
{# only none-global activities have a project and customer assigned #}
{{ widgets.label_project(entry.project) }}
{% if entry.project.customer %}
<br>
<small>
{{ widgets.label_customer(entry.project.customer) }}
</small>
{% endif %}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
@@ -58,7 +62,9 @@
{{ tables.datatable_meta_column(entry, field) }}
</td>
{% endfor %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% if showVisibility %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% endif %}
<td class="actions">
{{ actions.activity(entry, 'index') }}
</td>

View File

@@ -198,7 +198,7 @@
{% block javascripts %}
{# no call to parent(), as we use a custom built for the frontend assets and don't want the default <script> #}
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
window.addEventListener('load', function() {
var loader = new KimaiWebLoader(
{
login: '{{ path('fos_user_security_login') }}',

View File

@@ -18,6 +18,9 @@
{% set actions = {} %}
{% if customer.id is not empty %}
{% if view != 'details' and is_granted('view', customer) %}
{% set actions = actions|merge({'details': path('customer_details', {'id': customer.id})}) %}
{% endif %}
{% if is_granted('edit', customer) %}
{% set class = '' %}
{% if view != 'edit' %}
@@ -25,13 +28,17 @@
{% 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})}}) %}
{% set class = '' %}
{% if view != 'permissions' %}
{% set class = 'modal-ajax-form' %}
{% endif %}
{% set actions = actions|merge({'permissions': {'url': path('admin_customer_permissions', {'id': customer.id}), 'class': class}}) %}
{% endif %}
{% if is_granted('view_project') %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% endif %}
{% if is_granted('view_project') or is_granted('view_teamlead_project') or is_granted('view_team_project') %}
{% set actions = actions|merge({'project': path('admin_project', {'customer': customer.id})}) %}
{% endif %}
{% if is_granted('view_activity') %}
@@ -48,12 +55,12 @@
{% endif %}
{% endif %}
{% if view != 'index' %}
{% if view != 'index' and view != 'custom' %}
{% set actions = actions|merge({'back': path('admin_customer')}) %}
{% endif %}
{% set event = trigger('actions.customer', {'actions': actions, 'view': view, 'customer': customer}) %}
{% if view == 'index' %}
{% if view == 'index' or view == 'custom' %}
{{ widgets.table_actions(event.payload.actions) }}
{% else %}
{{ widgets.entity_actions(event.payload.actions) }}

View File

@@ -1,31 +0,0 @@
{% extends 'base.html.twig' %}
{% import "customer/actions.html.twig" as actions %}
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
{% block page_actions %}{{ actions.customer(customer, 'delete') }}{% endblock %}
{% block main %}
{% set params = {
'%activity%': '<strong>' ~ stats.activityAmount ~ '</strong>',
'%project%': '<strong>' ~ stats.projectAmount ~ '</strong>',
'%customer%': '<strong>' ~ customer.name ~ '</strong>',
'%records%': '<strong>' ~ stats.recordAmount ~ '</strong>',
'%duration%': '<strong>' ~ stats.recordDuration|duration ~ '</strong>',
'%rate%': '<strong>' ~ stats.recordRate|money ~ '</strong>'
} %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
{% block box_title %}{{ customer.name }}{% endblock %}
{% block box_body %}
<p>
{{ 'admin_customer.short_stats'|trans(params)|raw }}
</p>
{{ progress.progressbar(customer.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(customer.currency) ~ ' / ' ~ customer.budget|money(customer.currency) ) }}
{{ progress.progressbar(customer.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ customer.timeBudget|duration ) }}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,174 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "customer/actions.html.twig" as actions %}
{% block page_actions %}{{ actions.customer(customer, 'details') }}{% endblock %}
{% block page_title %}{{ 'admin_customer.title'|trans }}{% endblock %}
{% block main %}
{% set can_edit = is_granted('edit', customer) %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_attributes %}id="customer_details_box"{% endblock %}
{% block box_title %}
{{ widgets.label_customer(customer) }}
{% if customer.company is not empty %} &ndash; {{ customer.company }}{% endif %}
{% endblock %}
{% block box_tools %}
{% if can_edit %}
<a class="modal-ajax-form open-edit btn btn-box-tool" data-href="{{ path('admin_customer_edit', {'id': customer.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'action.edit'|trans }}"><i class="{{ 'edit'|icon }}"></i></a>
{% endif %}
{% endblock %}
{% block box_body %}
{% if customer.comment is not empty %}
{{ customer.comment|comment2html(true) }}
{% endif %}
<table class="table table-hover">
{% if not customer.visible %}
<tr>
<th>{{ 'label.visible'|trans }}</th>
<td>
{{ widgets.label_boolean(customer.visible) }}
</td>
</tr>
{% endif %}
{% if customer.address is not empty %}
<tr>
<th>{{ 'label.address'|trans }}</th>
<td>
{{ customer.address|nl2br }}
{% if customer.country is not empty %}
<br>
{{ customer.country|country }}
{% endif %}
</td>
</tr>
{% endif %}
{% if customer.contact is not empty %}
<tr>
<th>{{ 'label.contact'|trans }}</th>
<td>{{ customer.contact }}</td>
</tr>
{% endif %}
{% if customer.phone is not empty %}
<tr>
<th>{{ 'label.phone'|trans }}</th>
<td><a href="tel:{{ customer.phone }}">{{ customer.phone }}</a></td>
</tr>
{% endif %}
{% if customer.mobile is not empty %}
<tr>
<th>{{ 'label.mobile'|trans }}</th>
<td><a href="tel:{{ customer.mobile }}">{{ customer.mobile }}</a></td>
</tr>
{% endif %}
{% if customer.email is not empty %}
<tr>
<th>{{ 'label.email'|trans }}</th>
<td><a href="mailto:{{ customer.email }}">{{ customer.email }}</a></td>
</tr>
{% endif %}
{% if customer.homepage is not empty %}
<tr>
<th>{{ 'label.homepage'|trans }}</th>
<td><a href="{{ customer.homepage }}" target="_blank">{{ customer.homepage|replace({'https://': '', 'http://': ''}) }}</a></td>
</tr>
{% endif %}
{% if customer.fax is not empty %}
<tr>
<th>{{ 'label.fax'|trans }}</th>
<td>{{ customer.fax }}</td>
</tr>
{% endif %}
{% if customer.timezone is not empty %}
<tr>
<th>{{ 'label.timezone'|trans }}</th>
<td><span data-toggle="tooltip" data-placement="top" title="{{ customer.timezone }}">{{ now|date_full(false) }}</span></td>
</tr>
{% endif %}
{% if customer.currency is not empty %}
<tr>
<th>{{ 'label.currency'|trans }}</th>
<td>{{ customer.currency }}</td>
</tr>
{% endif %}
{% if is_granted('details', customer) %}
{% if customer.number is not empty %}
<tr>
<th>{{ 'label.number'|trans }}</th>
<td>{{ customer.number }}</td>
</tr>
{% endif %}
{% if customer.vatId is not empty %}
<tr>
<th>{{ 'label.vat_id'|trans }}</th>
<td>{{ customer.vatId }}</td>
</tr>
{% endif %}
{% if can_edit %}
<tr>
<th>{{ 'label.fixedRate'|trans }}</th>
<td>
{% if customer.fixedRate is not empty %}
{{ customer.fixedRate|money(customer.currency) }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
<tr>
<th>{{ 'label.hourlyRate'|trans }}</th>
<td>
{% if customer.hourlyRate is not empty %}
{{ customer.hourlyRate|money(customer.currency) }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
{% endif %}
{% for metaField in customer.visibleMetaFields %}
<tr>
<th>{{ metaField.name }}</th>
<td>{{ widgets.form_type_value(metaField.type, metaField.value, customer) }}</td>
</tr>
{% endfor %}
{% endif %}
</table>
{% endblock %}
{% endembed %}
{{ 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}) }}
{% endif %}
{% if teams is not null %}
{% set options = {'teams': teams, 'team': team} %}
{% if is_granted('permissions', customer) %}
{% set options = options|merge({'route_create': path('customer_team_create', {'id': customer.id}), 'route_edit': path('admin_customer_permissions', {'id': customer.id})}) %}
{% endif %}
{{ include('embeds/teams.html.twig', options) }}
{% endif %}
{% if comments is not null %}
{% set options = {'form': commentForm, 'comments': comments} %}
{% if can_edit %}
{% set options = options|merge({'route_pin': 'customer_comment_pin', 'route_delete': 'customer_comment_delete'}) %}
{% endif %}
{{ include('embeds/comments.html.twig', options) }}
{% endif %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
KimaiReloadPageWidget.create('kimai.customerTeamUpdate kimai.customerUpdate kimai.teamUpdate');
});
</script>
{% endblock %}

View File

@@ -23,65 +23,60 @@
</div>
</div>
{{ form_row(form.comment) }}
<fieldset>
{#<legend>Company data</legend>#}
<div class="row">
<div class="col-md-8">
{{ form_row(form.company) }}
</div>
<div class="col-md-4">
{{ form_row(form.vatId) }}
</div>
{{ form_row(form.address) }}
<div class="row">
<div class="col-md-4">
{{ form_row(form.company) }}
</div>
{{ form_row(form.address) }}
<div class="row">
<div class="col-md-4">
{{ form_row(form.country) }}
</div>
<div class="col-md-4">
{{ form_row(form.currency) }}
</div>
<div class="col-md-4">
{{ form_row(form.timezone) }}
</div>
<div class="col-md-4">
{{ form_row(form.number) }}
</div>
</fieldset>
<fieldset>
{#<legend>Contact data</legend>#}
<div class="row">
<div class="col-md-4">
{{ form_row(form.contact) }}
</div>
<div class="col-md-4">
{{ form_row(form.email) }}
</div>
<div class="col-md-4">
{{ form_row(form.homepage) }}
</div>
<div class="col-md-4">
{{ form_row(form.vatId) }}
</div>
<div class="row">
<div class="col-md-4">
{{ form_row(form.mobile) }}
</div>
<div class="col-md-4">
{{ form_row(form.phone) }}
</div>
<div class="col-md-4">
{{ form_row(form.fax) }}
</div>
</div>
<div class="row">
<div class="col-md-4">
{{ form_row(form.country) }}
</div>
</fieldset>
<fieldset>
{#<legend>Financial data</legend>#}
<div class="row">
<div class="col-md-6">
{{ form_row(form.fixedRate) }}
</div>
<div class="col-md-6">
{{ form_row(form.hourlyRate) }}
</div>
<div class="col-md-4">
{{ form_row(form.currency) }}
</div>
{% if form.budget is defined %}
<div class="col-md-4">
{{ form_row(form.timezone) }}
</div>
</div>
<div class="row">
<div class="col-md-4">
{{ form_row(form.contact) }}
</div>
<div class="col-md-4">
{{ form_row(form.email) }}
</div>
<div class="col-md-4">
{{ form_row(form.homepage) }}
</div>
</div>
<div class="row">
<div class="col-md-4">
{{ form_row(form.mobile) }}
</div>
<div class="col-md-4">
{{ form_row(form.phone) }}
</div>
<div class="col-md-4">
{{ form_row(form.fax) }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.fixedRate) }}
</div>
<div class="col-md-6">
{{ form_row(form.hourlyRate) }}
</div>
</div>
{% if form.budget is defined %}
<div class="row">
<div class="col-md-6">
{{ form_row(form.budget) }}
@@ -90,14 +85,10 @@
{{ form_row(form.timeBudget) }}
</div>
</div>
{% endif %}
</fieldset>
{% endif %}
{{ form_row(form.visible) }}
{% if form.metaFields is defined and form.metaFields is not empty %}
<fieldset>
{#<legend>Custom fields</legend>#}
{{ form_row(form.metaFields) }}
</fieldset>
{{ form_row(form.metaFields) }}
{% endif %}
{{ form_widget(form) }}
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% 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 params = {
'%activity%': '<strong>' ~ stats.activityAmount ~ '</strong>',
'%project%': '<strong>' ~ stats.projectAmount ~ '</strong>',
'%customer%': '<strong>' ~ customer.name ~ '</strong>',
'%records%': '<strong>' ~ stats.recordAmount ~ '</strong>',
'%duration%': '<strong>' ~ stats.recordDuration|duration ~ '</strong>',
'%rate%': '<strong>' ~ stats.recordRate|money ~ '</strong>'
} %}
<p>
{{ 'admin_customer.short_stats'|trans(params)|raw }}
</p>
{{ progress.progressbar(customer.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(customer.currency) ~ ' / ' ~ customer.budget|money(customer.currency) ) }}
{{ progress.progressbar(customer.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ customer.timeBudget|duration ) }}
{% endblock %}
{% endembed %}

View File

@@ -0,0 +1,30 @@
{% if projects|length > 0 %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'customer': customer, 'projects': projects, 'page': page} %}
{% import "project/actions.html.twig" as actions %}
{% 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"
{% endblock %}
{% block box_tools %}
{{ pagerfanta(projects, 'twitter_bootstrap3_translated', { proximity: 1, css_container_class: 'pagination pagination-sm inline', routeName: 'customer_projects', routeParams: {'id': customer.id} }) }}
{% endblock %}
{% block box_body_class %}no-padding{% endblock %}
{% block box_tools_attributes %}data-page="{{ page }}"{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable">
<tbody>
{% for project in projects %}
<tr>
<td>{{ project.name }}</td>
<td>{{ project.comment|comment2html }}</td>
<td class="actions">{{ actions.project(project, 'custom') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% endembed %}
<script type="text/javascript">
KimaiPaginatedBoxWidget.create('#project_list_box');
</script>
{% endif %}

View File

@@ -4,11 +4,10 @@
{% import "macros/toolbar.html.twig" as toolbar %}
{% import "customer/actions.html.twig" as actions %}
{% set showVisibility = query.visibility != 1 %}
{% set columns = {
'name': 'alwaysVisible',
'comment': 'hidden-xs',
'country': 'hidden-xs hidden-sm',
'number': 'hidden-xs',
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
} %}
{% for field in metaColumns %}
{% set columns = columns|merge({
@@ -16,12 +15,14 @@
}) %}
{% endfor %}
{% set columns = columns|merge({
'email': {'class': 'text-center hidden hidden-xs', 'orderBy': false},
'homepage': {'class': 'text-center hidden hidden-xs', 'orderBy': false},
'mobile': {'class': 'text-center hidden hidden-xs', 'orderBy': false},
'phone': {'class': 'text-center hidden hidden-xs', 'orderBy': false},
'team': {'class': 'text-center', 'orderBy': false},
'visible': {'class': 'text-center', 'orderBy': false},
}) %}
{% if showVisibility %}
{% set columns = columns|merge({
'visible': {'class': 'text-center', 'orderBy': false},
}) %}
{% endif %}
{% set columns = columns|merge({
'actions': 'actions alwaysVisible',
}) %}
@@ -40,47 +41,21 @@
{% if entries.count == 0 %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% else %}
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate'}) }}
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerTeamUpdate'}) }}
{% for entry in entries %}
<tr{% if is_granted('edit', entry) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_customer_edit', {'id': entry.id}) }}"{% endif %}>
<td>{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }} {% if entry.company is not empty %}({{ entry.company }}){% endif %}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'country') }}">{{ entry.country|country }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
<tr class="alternative-link open-edit" data-href="{{ path('customer_details', {'id': entry.id}) }}">
<td>
{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">
{{ entry.comment|comment2html }}
</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, 'email') }}">
{% if entry.email is not empty %}
<a href="mailto:{{ entry.email }}"><i class="{{ 'mail'|icon }}"></i></a>
{% else %}
&ndash;
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'homepage') }}">
{% if entry.homepage is not empty %}
<a href="{{ entry.homepage }}" target="_blank"><i class="{{ 'home'|icon }}"></i></a>
{% else %}
&ndash;
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'mobile') }}">
{% if entry.mobile is not empty %}
<a href="tel:{{ entry.mobile }}"><i class="{{ 'mobile'|icon }}"></i></a>
{% else %}
&ndash;
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'phone') }}">
{% if entry.phone is not empty %}
<a href="tel:{{ entry.phone }}"><i class="{{ 'phone'|icon }}"></i></a>
{% else %}
&ndash;
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'team') }}">
{% if entry.teams|length > 0 %}
{{ widgets.badge_counter(entry.teams|length) }}
@@ -88,7 +63,9 @@
{{ widgets.icon('unlocked') }}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% if showVisibility %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% endif %}
<td class="actions">
{{ actions.customer(entry, 'index') }}
</td>

View File

@@ -1,21 +1,30 @@
<div class="box box-solid box-danger">
<div class="box-header">
<h3 class="box-title">{{ title|default('confirm.delete'|trans) }}</h3>
</div>
<div class="box-body">
{{ message|raw }}
</div>
{{ form_start(form) }}
<div class="box-body">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'boxtype': 'danger'} %}
{% block box_before %}
{{ form_start(form) }}
{% endblock %}
{% block box_title %}
{{ title|default('confirm.delete'|trans) }}
{% endblock %}
{% block box_body %}
{% block form_body %}
{{ form_widget(form) }}
{% if used is same as (false) %}
{{ 'delete.not_in_use'|trans }}
<div class="hidden">
{{ form_widget(form) }}
</div>
{% else %}
<p>{{ message|raw }}</p>
{{ form_widget(form) }}
{% endif %}
{% endblock %}
</div>
<div class="box-footer">
{% endblock %}
{% block box_footer %}
<input type="submit" value="{{ 'action.delete'|trans }}" class="btn btn-danger" />
{% if back %}
<a href="{{ back }}" class="btn btn-link">{{ 'action.back'|trans }}</a>
{% endif %}
</div>
{{ form_end(form) }}
</div>
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% endembed %}

View File

@@ -0,0 +1,53 @@
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'form': form, 'comments': comments, 'route_pin': route_pin|default(null), 'route_delete': route_delete|default(null)} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ 'label.comment'|trans }}{% endblock %}
{% block box_attributes %}id="comments_box"{% endblock %}
{% block box_body_class %}box-comments{% endblock %}
{% block box_body %}
{% set replacer = {} %}
{% for pref in app.user.preferences %}
{% set replacer = replacer|merge({('{{user.' ~ pref.name ~ '}}'): pref.value}) %}
{% endfor %}
{% if comments|length == 0 %}
{{ 'error.no_comments_found'|trans }}
{% endif %}
{% for comment in comments %}
<div class="box-comment">
{{ widgets.user_avatar(comment.createdBy, false, 'img-sm') }}
<div class="comment-text">
<span class="username">
{{ widgets.username(comment.createdBy) }}
<span class="text-muted pull-right">
{% if route_pin is not null %}
<a href="{{ path(route_pin, {'id': comment.id}) }}" class="btn btn-default btn-xs {% if comment.pinned %}active{% endif %}"><i class="{{ 'pin'|icon }}"></i></a>
{% endif %}
{% if route_delete is not null %}
<a href="{{ path(route_delete, {'id': comment.id}) }}" class="confirmation-link btn btn-default btn-xs" data-method="POST" data-question="confirm.delete" data-msg-error="action.delete.error" data-msg-success="action.delete.success"><i class="{{ 'delete'|icon }}"></i></a>
{% endif %}
{{ comment.createdAt|date_full }}
</span>
</span>
{{ comment.message|replace(replacer)|md2html }}
</div>
</div>
{% endfor %}
{% endblock %}
{% block box_footer -%}
{% if form is not null %}
{{ form_start(form) }}
<div class="input-group">
{{ widgets.user_avatar(app.user, false, 'img-responsive img-sm') }}
<div class="img-push">
{{ form_widget(form.message) }}
</div>
<span class="input-group-btn">
<button type="submit" class="btn btn-default">
{{ 'action.save'|trans }}
</button>
</span>
</div>
{{ form_widget(form) }}
{{ form_end(form) }}
{% endif %}
{%- endblock %}
{% endembed %}

View File

@@ -0,0 +1,32 @@
{%
set options = {
'teams': teams,
'team': team|default(null),
'route_create': route_create|default(null),
'route_edit': route_edit|default(null),
'empty_message': empty_message|default('team.visibility_global')
}
%}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with options %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_tools %}
{% if teams|length == 0 and team is null and route_create is not null and is_granted('create_team') %}
<a class="btn-box-tool" href="{{ route_create }}" data-toggle="tooltip" data-placement="top" title="{{ 'team.create_default'|trans({}, 'teams') }}"><i class="{{ 'create'|icon }}"></i></a>
{% endif %}
{% if route_edit is not null %}
<a class="modal-ajax-form open-edit btn btn-box-tool" href="{{ route_edit }}" data-toggle="tooltip" data-placement="top" title="{{ 'action.edit'|trans }}"><i class="{{ 'edit'|icon }}"></i></a>
{% endif %}
{% endblock %}
{% block box_attributes %}id="team_listing_box"{% endblock %}
{% block box_title %}
{{ 'permissions'|trans({}, 'actions') }}
{% endblock %}
{% block box_body %}
{% if teams|length > 0 %}
<p>{{ 'team.visibility_restricted'|trans({}, 'teams') }}</p>
{{ widgets.team_list(teams) }}
{% else %}
<p>{{ empty_message|default('team.visibility_global')|trans({}, 'teams') }}</p>
{% endif %}
{% endblock %}
{% endembed %}

View File

@@ -15,7 +15,17 @@
{% import _self as macro %}
<div class="breadcrumb">
<div class="box-tools">
{{ macro.table_actions(tools) }}
<div class="btn-group">
{% set actions = {} %}
{%- for icon, values in tools %}
{% if icon == 'back' %}
{{ macro.action_button(icon, values) }}
{% else %}
{% set actions = actions|merge({(icon): values}) %}
{% endif %}
{% endfor %}
{{ macro.table_actions(actions, '') }}
</div>
</div>
</div>
{%- endmacro -%}
@@ -69,11 +79,10 @@
{% macro user_avatar(user, tooltip, class) %}
{% set avatar = avatar(user, admin_lte_context.default_avatar) %}
{% set showTooltip = tooltip|default(true) %}
{% if tooltip is not defined or tooltip is same as (false)%}
<img src="{{ avatar }}" class="{{ class|default('img-circle') }}" alt="{{ user.displayName }}" />
{% if tooltip is same as (false) %}
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" alt="{{ user.displayName }}" />
{% else %}
<img src="{{ avatar }}" class="{{ class|default('img-circle') }}" data-toggle="tooltip" data-placement="top" alt="{{ user.displayName }}" title="{{ user.displayName }}" />
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" data-toggle="tooltip" data-placement="top" alt="{{ user.displayName }}" title="{{ tooltip|default(user.displayName) }}" />
{% endif %}
{% endmacro %}
@@ -200,30 +209,50 @@
</div>
{% endmacro %}
{% macro table_actions(actions) %}
{% macro table_actions(actions, class) %}
{%- import _self as macro -%}
{% if actions|length >= 1 %}
<div class="btn-group">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false">{{ 'label.actions'|trans }}
<span class="fa fa-caret-down"></span></button>
{% if class is null %}
{% set class = 'btn-sm' %}
{% endif %}
{% set trash = null %}
{% set divider = false %}
<div class="btn-group dropdown">
<button type="button" class="btn btn-default {{ class }} dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="fa fa-chevron-down"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
{%- apply spaceless -%}
{%- for icon,values in actions %}
{% set class = '' %}
{% if icon == 'trash' %}
{% set class = 'delete' %}
{% if actions|length > 1 %}
<li class="divider"></li>
{% if icon == 'divider' and values is null %}
{% if not loop.last and divider is same as (false) %}
<li class="divider"></li>
{% endif %}
{% set divider = true %}
{% else %}
{% if values is iterable %}
{% set values = values|merge({'title': icon|trans({}, 'actions')}) %}
{% else %}
{% set values = {'url': values, 'title': icon|trans({}, 'actions')} %}
{% endif %}
{% if icon == 'trash' %}
{% set trash = values %}
{% else %}
{% set divider = false %}
<li>
{{ macro.action_button(icon, values, false) }}
</li>
{% endif %}
{% endif %}
<li class="{{ class }}">
{% if values is iterable %}
{{ macro.action_button(icon, values|merge({'title': icon|trans({}, 'actions')}), false) }}
{% else %}
{{ macro.action_button(icon, {'url': values, 'title': icon|trans({}, 'actions')}, false) }}
{% endif %}
</li>
{% endfor -%}
{%- if trash is not null %}
{% if actions|length > 1 and divider is same as (false) %}
<li class="divider"></li>
{% endif %}
<li class="delete">
{{ macro.action_button('trash', trash, false) }}
</li>
{% endif -%}
{% endapply %}
</ul>
</div>
@@ -371,3 +400,49 @@
{{ value }}
{% endif %}
{% endmacro %}
{% macro team_list(teams, showTitle) %}
{% if showTitle is null %}
{% set showTitle = true %}
{% endif %}
{% import _self as macro %}
<table class="table table-hover dataTable" role="grid">
{% if showTitle %}
<thead>
<tr>
<th>{{ 'label.team'|trans }}</th>
<th>{{ 'label.user'|trans }}</th>
</tr>
</thead>
{% endif %}
<tbody>
{% for team in teams %}
<tr{% if is_granted('edit', team) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_team_member', {'id': team.id}) }}"{% endif %}>
<td>
{{ team.name }}
</td>
<td class="avatars">
{% set userTeamCount = team.users|length %}
{{ macro.user_avatar(team.teamlead, ('label.teamlead'|trans ~ ': ' ~ team.teamlead.displayName), 'teamlead') }}
{% set teamHiddenId = 'team_' ~ team.id ~ '_hiddenUser' ~ random() %}
{% set counter = 0 %}
{% for user in team.users %}
{% if user != team.teamlead %}
{{ macro.user_avatar(user) }}
{% set counter = counter + 1 %}
{% endif %}
{% if userTeamCount > 5 and counter == 4 and not loop.last %}
<a href="#" onclick="$('#{{ teamHiddenId }}').toggleClass('hidden');$(this).hide();return false;" class="badge">{{ 'label.plus_more'|trans({'%count%': (userTeamCount - 5)}) }}</a>
<span class="hidden" id="{{ teamHiddenId }}">
{% set counter = counter + 1 %}
{% endif %}
{% if userTeamCount > 5 and counter != 4 and loop.last %}
</span>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}

View File

@@ -18,6 +18,9 @@
{% set actions = {} %}
{% if project.id is not empty %}
{% if view != 'details' and is_granted('view', project) %}
{% set actions = actions|merge({'details': path('project_details', {'id': project.id})}) %}
{% endif %}
{% if is_granted('edit', project) %}
{% set class = '' %}
{% if view != 'edit' %}
@@ -25,11 +28,15 @@
{% 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})}}) %}
{% set class = '' %}
{% if view != 'permissions' %}
{% set class = 'modal-ajax-form' %}
{% endif %}
{% set actions = actions|merge({'permissions': {'url': path('admin_project_permissions', {'id': project.id}), 'class': class}}) %}
{% endif %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% endif %}
{% if is_granted('view_activity') %}
{% set actions = actions|merge({'activity': path('admin_activity', {'customer': project.customer.id, 'project': project.id})}) %}
@@ -45,12 +52,12 @@
{% endif %}
{% endif %}
{% if view != 'index' %}
{% if view != 'index' and view != 'custom' %}
{% set actions = actions|merge({'back': path('admin_project')}) %}
{% endif %}
{% set event = trigger('actions.project', {'actions': actions, 'view': view, 'project': project}) %}
{% if view == 'index' %}
{% if view == 'index' or view == 'custom' %}
{{ widgets.table_actions(event.payload.actions) }}
{% else %}
{{ widgets.entity_actions(event.payload.actions) }}

View File

@@ -1,30 +0,0 @@
{% extends 'base.html.twig' %}
{% import "project/actions.html.twig" as actions %}
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
{% block page_actions %}{{ actions.project(project, 'delete') }}{% endblock %}
{% block main %}
{% set params = {
'%project%': '<strong>' ~ project.name ~ '</strong>',
'%customer%': '<strong>' ~ project.customer.name ~ '</strong>',
'%records%': '<strong>' ~ stats.recordAmount ~ '</strong>',
'%activities%': '<strong>' ~ stats.activityAmount ~ '</strong>',
'%duration%': '<strong>' ~ stats.recordDuration|duration ~ '</strong>'
} %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
{% block box_title %}{{ project.name }}{% endblock %}
{% block box_body %}
<p>
{{ 'admin_project.short_stats'|trans(params)|raw }}
</p>
{{ progress.progressbar(project.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(project.customer.currency) ~ ' / ' ~ project.budget|money(project.customer.currency) ) }}
{{ progress.progressbar(project.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ project.timeBudget|duration ) }}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,151 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "project/actions.html.twig" as actions %}
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
{% block page_actions %}{{ actions.project(project, 'details') }}{% endblock %}
{% block main %}
{% set can_edit = is_granted('edit', project) %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "customer/actions.html.twig" as customerActions %}
{% block box_attributes %}id="project_details_box"{% endblock %}
{% block box_tools %}
{% if can_edit %}
<a class="modal-ajax-form open-edit btn btn-box-tool" data-href="{{ path('admin_project_edit', {'id': project.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'action.edit'|trans }}"><i class="{{ 'edit'|icon }}"></i></a>
{% endif %}
{% endblock %}
{% block box_title %}
{{ widgets.label_project(project) }}
{% endblock %}
{% block box_body %}
{% if project.comment is not empty %}
{{ project.comment|comment2html(true) }}
{% endif %}
<table class="table table-hover">
{% if not project.visible %}
<tr>
<th>{{ 'label.visible'|trans }}</th>
<td>
{{ widgets.label_boolean(project.visible) }}
</td>
</tr>
{% endif %}
<tr>
<th>{{ 'label.customer'|trans }}</th>
<td>
{{ widgets.label_customer(project.customer) }}
{% if project.customer.teams|length == 0 %}
{{ widgets.icon('unlocked') }}
{% endif %}
&nbsp;
{{ customerActions.customer(project.customer, 'custom') }}
</td>
</tr>
{% if is_granted('details', project) %}
<tr>
<th>{{ 'label.orderNumber'|trans }}</th>
<td>
{{ project.orderNumber }}
</td>
</tr>
<tr>
<th>{{ 'label.orderDate'|trans }}</th>
<td>
{% if project.orderDate is not empty %}
{{ project.orderDate|date_full }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
<tr>
<th>{{ 'label.project_start'|trans }}</th>
<td>
{% if project.start is not empty %}
{{ project.start|date_full }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
<tr>
<th>{{ 'label.project_end'|trans }}</th>
<td>
{% if project.end is not empty %}
{{ project.end|date_full }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
{% if can_edit %}
<tr>
<th>{{ 'label.fixedRate'|trans }}</th>
<td>
{% if project.fixedRate is not empty %}
{{ project.fixedRate|money(project.customer.currency) }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
<tr>
<th>{{ 'label.hourlyRate'|trans }}</th>
<td>
{% if project.hourlyRate is not empty %}
{{ project.hourlyRate|money(project.customer.currency) }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
{% endif %}
{% for metaField in project.visibleMetaFields %}
<tr>
<th>{{ metaField.name }}</th>
<td>{{ widgets.form_type_value(metaField.type, metaField.value, project) }}</td>
</tr>
{% endfor %}
{% endif %}
</table>
{% endblock %}
{% endembed %}
{{ 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}) }}
{% endif %}
{% if teams is not null%}
{% set options = {'teams': teams, 'team': team} %}
{% if is_granted('permissions', project) %}
{% set options = options|merge({'route_create': path('project_team_create', {'id': project.id}), 'route_edit': path('admin_project_permissions', {'id': project.id})}) %}
{% endif %}
{% if project.customer.teams|length > 0 %}
{% set options = options|merge({'empty_message': 'team.project_visibility_inherited'}) %}
{% endif %}
{{ include('embeds/teams.html.twig', options) }}
{% endif %}
{% if comments is not null %}
{% set options = {'form': commentForm, 'comments': comments} %}
{% if can_edit %}
{% set options = options|merge({'route_pin': 'project_comment_pin', 'route_delete': 'project_comment_delete'}) %}
{% endif %}
{{ include('embeds/comments.html.twig', options) }}
{% endif %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
KimaiReloadPageWidget.create('kimai.projectTeamUpdate kimai.projectUpdate kimai.teamUpdate kimai.customerUpdate ');
});
</script>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% if activities|length > 0 %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'project': project, 'activities': activities, 'page': page} %}
{% import "activity/actions.html.twig" as actions %}
{% 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"
{% endblock %}
{% block box_tools %}
{{ pagerfanta(activities, 'twitter_bootstrap3_translated', { proximity: 1, css_container_class: 'pagination pagination-sm inline', routeName: 'project_activities', routeParams: {'id': project.id} }) }}
{% endblock %}
{% block box_body_class %}no-padding{% endblock %}
{% block box_tools_attributes %}data-page="{{ page }}"{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable">
<tbody>
{% for activity in activities %}
<tr>
<td>{{ activity.name }}</td>
<td class="actions">{{ actions.activity(activity, 'custom') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% endembed %}
<script type="text/javascript">
KimaiPaginatedBoxWidget.create('#activity_list_box');
</script>
{% endif %}

View File

@@ -0,0 +1,21 @@
{% 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%': '<strong>' ~ project.name ~ '</strong>',
'%customer%': '<strong>' ~ project.customer.name ~ '</strong>',
'%records%': '<strong>' ~ stats.recordAmount ~ '</strong>',
'%activities%': '<strong>' ~ stats.activityAmount ~ '</strong>',
'%duration%': '<strong>' ~ stats.recordDuration|duration ~ '</strong>'
} %}
<p>
{{ 'admin_project.short_stats'|trans(params)|raw }}
</p>
{{ progress.progressbar(project.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(project.customer.currency) ~ ' / ' ~ project.budget|money(project.customer.currency) ) }}
{{ progress.progressbar(project.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ project.timeBudget|duration ) }}
{% endblock %}
{% endembed %}

View File

@@ -4,12 +4,11 @@
{% import "macros/toolbar.html.twig" as toolbar %}
{% import "project/actions.html.twig" as actions %}
{% set showVisibility = query.visibility != 1 %}
{% set columns = {
'name': 'alwaysVisible',
'customer': 'hidden-xs',
'comment': 'hidden-xs hidden-sm',
'orderNumber': 'hidden-xs hidden-sm',
'orderDate': 'hidden-xs hidden-sm',
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
} %}
{% for field in metaColumns %}
{% set columns = columns|merge({
@@ -18,7 +17,13 @@
{% endfor %}
{% set columns = columns|merge({
'team': {'class': 'text-center', 'orderBy': false},
'visible': {'class': 'text-center', 'orderBy': false},
}) %}
{% if showVisibility %}
{% set columns = columns|merge({
'visible': {'class': 'text-center', 'orderBy': false},
}) %}
{% endif %}
{% set columns = columns|merge({
'actions': 'actions alwaysVisible',
}) %}
@@ -37,21 +42,15 @@
{% if entries.count == 0 %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% else %}
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate'}) }}
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectTeamUpdate'}) }}
{% for entry in entries %}
<tr{% if is_granted('edit', entry) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_project_edit', {'id': entry.id}) }}"{% endif %}>
<tr class="alternative-link open-edit" data-href="{{ path('project_details', {'id': entry.id}) }}">
<td>{{ 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|comment2html }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">
{% if entry.orderDate is not empty %}
{{ entry.orderDate|date_short }}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html() }}</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) }}
@@ -64,7 +63,9 @@
{{ widgets.icon('unlocked') }}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% if showVisibility %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
{% endif %}
<td class="actions">
{{ actions.project(entry, 'index') }}
</td>

View File

@@ -0,0 +1,14 @@
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "team/actions.html.twig" as actions %}
{% block page_title %}{{ 'teams.title'|trans({}, 'teams') }}{% endblock %}
{% block page_actions %}{{ actions.team(team, 'edit') }}{% endblock %}
{% block main %}
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
'title': team.name|default('create'|trans),
'form': form,
'back': path('admin_team')
}) }}
{% endblock %}

View File

@@ -46,6 +46,10 @@
{% set actions = actions|merge({'edit': {'url': path('admin_timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
{% endif %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% 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 %}

View File

@@ -47,6 +47,10 @@
{% set actions = actions|merge({'edit': {'url': path('timesheet_edit', {'id': timesheet.id}), 'class': class}}) %}
{% endif %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% 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 %}

View File

@@ -65,6 +65,9 @@
{% if is_granted('preferences', user) %}
{% set actions = actions|merge({'settings': {'url': path('user_profile_preferences', {'username' : user.username})}}) %}
{% endif %}
{% if actions|length > 0 %}
{% set actions = actions|merge({'divider': null}) %}
{% endif %}
{% if is_granted('view_other_timesheet') and user.enabled %}
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'users': [user.id]})}) %}
{% endif %}

View File

@@ -4,21 +4,28 @@
{% import "macros/toolbar.html.twig" as toolbar %}
{% import "user/actions.html.twig" as actions %}
{% set showVisibility = query.visibility != 1 %}
{% set columns = {
'alias': 'alwaysVisible',
'username': 'hidden-xs',
'email': 'hidden-xs hidden-md hidden-sm',
'title': 'hidden-xs hidden-sm',
} %}
{% set columns = columns|merge({
'roles': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
}) %}
{% for pref in preferences %}
{% set columns = columns|merge({
('mf_' ~ pref.name): {'title': pref.label, 'class': 'hidden-xs hidden-sm', 'orderBy': false}
}) %}
{% endfor %}
{% set columns = columns|merge({
'roles': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
'team': {'class': 'hidden-xs', 'orderBy': false},
'active': {'class': '', 'orderBy': false},
'team': {'class': 'text-center hidden-xs', 'orderBy': false},
}) %}
{% if showVisibility %}
{% set columns = columns|merge({
'active': {'class': '', 'orderBy': false},
}) %}
{% endif %}
{% set columns = columns|merge({
'actions': 'actions alwaysVisible',
}) %}
@@ -40,19 +47,9 @@
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
{% for entry in entries %}
<tr{% if is_granted('edit', entry) %} class="open-edit alternative-link" data-href="{{ path('user_profile_edit', {'username': entry.username}) }}"{% endif %}>
<tr{% if is_granted('view', entry) %} class="open-edit alternative-link" data-href="{{ path('user_profile', {'username': entry.username}) }}"{% endif %}>
<td>{{ widgets.user_avatar(entry) }} {{ widgets.username(entry) }}</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>
{% for pref in preferences %}
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'mf_' ~ pref.name) }}">
{% set metaField = entry.preference(pref.name) %}
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
{{ widgets.form_type_value(pref.type, metaField.value, entry) }}
{% endif %}
</td>
{% endfor %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'roles') }}">
{% set showUserRole = entry.roles|length == 1 %}
{% for role in entry.roles %}
@@ -61,16 +58,22 @@
{% endif %}
{% endfor %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'team') }}">
{% for team in entry.teams %}
{% if entry.isTeamleadOf(team) %}
{{ widgets.label_team(team, 'success') }}
{% else %}
{{ widgets.label_team(team) }}
{% for pref in preferences %}
<td class="text-nowrap {{ tables.data_table_column_class(tableName, columns, 'mf_' ~ pref.name) }}">
{% set metaField = entry.preference(pref.name) %}
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
{{ widgets.form_type_value(pref.type, metaField.value, entry) }}
{% endif %}
{% endfor %}
</td>
{% endfor %}
<td class="text-center {{ tables.data_table_column_class(tableName, columns, 'team') }}">
{% if entry.teams|length > 0 %}
{{ widgets.badge_counter(entry.teams|length) }}
{% endif %}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.enabled) }}</td>
{% if showVisibility %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.enabled) }}</td>
{% endif %}
<td class="actions">
{{ actions.user(entry, 'index') }}
</td>

View File

@@ -6,81 +6,104 @@
{% block page_actions %}{{ actions.user(user, tab) }}{% endblock %}
{% block main %}
{% import _self as macro %}
{% import "macros/widgets.html.twig" as widgets %}
<div class="row">
<div class="col-md-9">
<div class="col-md-8">
{% block profile_content %}{% endblock %}
</div>
<div class="col-md-3">
{{ macro.profile_box(user, stats) }}
{{ macro.profile_infos(user, stats) }}
<div class="col-md-4">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_body_class %}box-profile{% endblock %}
{% block box_body %}
<div class="text-center">
{{ widgets.user_avatar(user) }}
<h3 class="profile-username">{{ widgets.username(user) }}</h3>
<p class="text-muted">{{ user.title }}</p>
</div>
{% set seeOwnRate = is_granted('view_rate_own_timesheet') %}
<table class="table">
<tr>
<th>{{ 'stats.durationMonth'|trans }}</th>
<td class="text-nowrap pull-right">{{ stats.durationThisMonth|duration }}</td>
</tr>
{% if seeOwnRate %}
<tr>
<th>{{ 'stats.amountMonth'|trans }}</th>
<td class="text-nowrap pull-right">{{ stats.amountThisMonth|money }}</td>
</tr>
{% endif %}
<tr>
<th>{{ 'stats.durationTotal'|trans }}</th>
<td class="text-nowrap pull-right">{{ stats.durationTotal|duration }}</td>
</tr>
{% if seeOwnRate %}
<tr>
<th>{{ 'stats.amountTotal'|trans }}</th>
<td class="text-nowrap pull-right">{{ stats.amountTotal|money }}</td>
</tr>
{% endif %}
</table>
{% endblock %}
{% endembed %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_title %}{{ 'profile.about_me'|trans }}{% endblock %}
{% block box_body %}
{# colors = purple, blue, aqua, red, green #}
<table class="table no-border">
<tr>
<th>{{ 'label.id'|trans }}</th>
<td class="text-nowrap pull-right">{{ user.id }}</td>
</tr>
<tr>
<th>{{ 'label.username'|trans }}</th>
<td class="text-nowrap pull-right">{{ user.username }}</td>
</tr>
<tr>
<th>{{ 'profile.first_entry'|trans }}</th>
<td class="text-nowrap pull-right">{{ stats.firstEntry|date_short }}</td>
</tr>
{% if is_granted('hourly-rate', user) %}
<tr>
<th>{{ 'label.hourlyRate'|trans }}</th>
<td class="text-nowrap pull-right">{{ user.preferenceValue('hourly_rate') }}</td>
</tr>
{% endif %}
</table>
{% endblock %}
{% endembed %}
{% if user.teams is not empty and (is_granted('teams', user) or (app.user == user and is_granted('view_team_member'))) %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ 'label.my_teams'|trans }}{% endblock %}
{% block box_tools %}
{% if is_granted('roles', user) %}
<a class="btn-box-tool" href="{{ path('user_profile_teams', {'username': user.username}) }}"><i class="{{ 'edit'|icon }}"></i></a>
{% endif %}
{% endblock %}
{% block box_body %}
{{ widgets.team_list(user.teams) }}
{% endblock %}
{% endembed %}
{% endif %}
</div>
</div>
{% endblock %}
{% macro profile_infos(user, stats) %}
<div class="box box-{{ admin_lte_context.widget.type }}">
<div class="box-header with-border">
<h3 class="box-title">{{ 'profile.about_me'|trans }}</h3>
</div>
<div class="box-body">
<ul class="nav nav-stacked">
{# colors = purple, blue, aqua, red, green #}
<li><a href="#">{{ 'label.id'|trans }} <span class="pull-right badge bg-aqua">{{ user.id }}</span></a></li>
<li><a href="#">{{ 'label.username'|trans }} <span class="pull-right badge bg-blue">{{ user.username }}</span></a></li>
<li><a href="#">{{ 'profile.first_entry'|trans }} <span class="pull-right badge bg-purple">{{ stats.firstEntry|date_short }}</span></a></li>
{% if is_granted('view_rate_own_timesheet') %}
<li><a href="#">{{ 'label.hourlyRate'|trans }} <span class="pull-right badge bg-blue">{{ user.preferenceValue('hourly_rate') }}</span></a></li>
{% endif %}
</ul>
</div>
</div>
{% endmacro %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
KimaiReloadPageWidget.create('kimai.teamUpdate', true);
});
</script>
{% endblock %}
{% macro profile_box(user, stats) %}
{% import "@AdminLTE/Macros/default.html.twig" as macro %}
{% import "macros/widgets.html.twig" as widgets %}
<div class="box box-{{ admin_lte_context.widget.type }}">
<div class="box-body box-profile">
<div class="text-center">
{{ widgets.user_avatar(user) }}
<h3 class="profile-username">{{ widgets.username(user) }}</h3>
<p class="text-muted">{{ user.title }}</p>
</div>
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b>{{ 'stats.durationMonth'|trans }}</b> <a class="pull-right">{{ stats.durationThisMonth|duration }}</a>
</li>
{% if is_granted('view_rate_own_timesheet') %}
<li class="list-group-item">
<b>{{ 'stats.amountMonth'|trans }}</b> <a class="pull-right">{{ stats.amountThisMonth|money }}</a>
</li>
{% endif %}
<li class="list-group-item">
<b>{{ 'stats.durationTotal'|trans }}</b> <a class="pull-right">{{ stats.durationTotal|duration }}</a>
</li>
{% if is_granted('view_rate_own_timesheet') %}
<li class="list-group-item">
<b>{{ 'stats.amountTotal'|trans }}</b> <a class="pull-right">{{ stats.amountTotal|money }}</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endmacro %}
{# -------------------------------- UNUSED FOR NOW -------------------------------- #}
{% macro profile_list_unused(user, items) %}

View File

@@ -53,8 +53,6 @@
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.userRoleUpdate', function() {
document.location.reload();
});
KimaiReloadPageWidget.create('kimai.userRoleUpdate');
</script>
{% endblock %}

View File

@@ -1,99 +1,39 @@
<div class="row">
<div class="col-md-12">
<div class="box">
{% if not title is empty %}
<div class="box-header with-border">
<h3 class="box-title">{{ title|trans }}</h3>
{#
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<div class="btn-group">
<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-wrench"></i></button>
<ul class="dropdown-menu" role="menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</div>
<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
</div>
#}
</div>
{% endif %}
<div class="box-body">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_body %}
<div class="row">
<div class="col-md-12">
{{ render_widget(widgets.0) }}
</div>
{#
<div class="col-md-4">
<p class="text-center">
<strong>Goal Completion</strong>
</p>
<div class="progress-group">
<span class="progress-text">Add Products to Cart</span>
<span class="progress-number"><b>160</b>/200</span>
<div class="progress sm">
<div class="progress-bar progress-bar-aqua" style="width: 80%"></div>
</div>
{% endblock %}
{% block box_footer %}
{% if widgets|length > 1 %}
<div class="row">
{% set width = (widgets|length) - 1 %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
{% for widget in widgets|slice(1, width) %}
{% set data = widget.data %}
{% if widget.option('dataType') == 'duration' %}
{% set data = widget.data|duration %}
{% elseif widget.option('dataType') == 'money' %}
{% set data = widget.data|money %}
{% endif %}
<div class="col-sm-{{ columnWidth }} col-xs-{{ columnWidth * 2 }}">
<div class="description-block border-right">
<h5 class="description-header">{{ data }}</h5>
<span class="description-text">{{ widget.title|trans }}</span>
</div>
</div>
</div>
<div class="progress-group">
<span class="progress-text">Complete Purchase</span>
<span class="progress-number"><b>310</b>/400</span>
<div class="progress sm">
<div class="progress-bar progress-bar-red" style="width: 80%"></div>
</div>
</div>
<div class="progress-group">
<span class="progress-text">Visit Premium Page</span>
<span class="progress-number"><b>480</b>/800</span>
<div class="progress sm">
<div class="progress-bar progress-bar-green" style="width: 80%"></div>
</div>
</div>
<div class="progress-group">
<span class="progress-text">Send Inquiries</span>
<span class="progress-number"><b>250</b>/500</span>
<div class="progress sm">
<div class="progress-bar progress-bar-yellow" style="width: 80%"></div>
</div>
</div>
{% endfor %}
</div>
#}
</div>
</div>
{% if widgets|length > 1 %}
<div class="box-footer">
<div class="row">
{% set width = (widgets|length) - 1 %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
{% for widget in widgets|slice(1, width) %}
{% set data = widget.data %}
{% if widget.option('dataType') == 'duration' %}
{% set data = widget.data|duration %}
{% elseif widget.option('dataType') == 'money' %}
{% set data = widget.data|money %}
{% endif %}
<div class="col-sm-{{ columnWidth }} col-xs-{{ columnWidth * 2 }}">
<div class="description-block border-right">
{#<span class="description-percentage text-green"><i class="fa fa-caret-up"></i> 17%</span>#}
{#<span class="description-percentage text-yellow"><i class="fa fa-caret-left"></i> 0%</span>#}
{#<span class="description-percentage text-green"><i class="fa fa-caret-up"></i> 20%</span>#}
{#<span class="description-percentage text-red"><i class="fa fa-caret-down"></i> 18%</span>#}
<h5 class="description-header">{{ data }}</h5>
<span class="description-text">{{ widget.title|trans }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% endembed %}
</div>
</div>

View File

@@ -8,18 +8,17 @@
{% if projectStats|length > 0 %}
<div class="row">
<div class="col-md-12">
<div class="box box-{{ admin_lte_context.widget.type }} WidgetUserTeamProjects" id="{{ widgetId }}">
{% if not title is empty %}
<div class="box-header with-border">
<h3 class="box-title">{{ title|trans }}</h3>
</div>
{% endif %}
<div class="box-body">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_attributes %}id="{{ widgetId }}"{% endblock %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable" role="grid">
<thead>
<tr>
<th>{{ 'label.project'|trans }}</th>
<th class="hidden-xs">{{ 'label.team'|trans }}</th>
<th class="hidden-xs hidden-sm hidden-md">{{ 'label.team'|trans }}</th>
<th style="width:50%">{{ 'label.progress'|trans }}</th>
</tr>
</thead>
@@ -32,7 +31,7 @@
<br>
<small>{{ widgets.label_customer(stats.project.customer) }}</small>
</td>
<td class="hidden-xs">
<td class="hidden-xs hidden-sm hidden-md">
{% for team in project.teams %}
{% if app.user.isInTeam(team) %}
{{ widgets.label_team(team) }}
@@ -51,10 +50,9 @@
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</table>
{% endblock %}
{% endembed %}
</div>
</div>
{% endif %}

View File

@@ -1,58 +1,21 @@
{% import "macros/widgets.html.twig" as widgets %}
{% set teams = data %}
{% set title = options.title|default('label.my_teams') %}
{% set title = options.title|default(title|default('label.teams')) %}
{% set widgetId = options.id %}
{% if teams|length > 0 %}
<div class="row">
<div class="col-md-12">
<div class="box box-{{ admin_lte_context.widget.type }} WidgetUserTeams" id="{{ widgetId }}">
{% if not title is empty %}
<div class="box-header with-border">
<h3 class="box-title">{{ title|trans }}</h3>
</div>
{% endif %}
<div class="box-body">
<table class="table table-hover dataTable" role="grid">
<thead>
<tr>
<th>{{ 'label.team'|trans }}</th>
<th class="text-center">{{ 'label.teamlead'|trans }}</th>
<th>{{ 'label.user'|trans }}</th>
</tr>
</thead>
<tbody>
{% for team in teams %}
<tr>
<td>
{{ team.name }}
</td>
<td class="text-center">
{{ widgets.user_avatar(team.teamlead) }}
</td>
<td class="avatars">
{% set userTeamCount = team.users|length %}
{% for user in team.users %}
{% set teamHiddenId = widgetId ~ '_' ~ team.id ~ '_hiddenUser' %}
{{ widgets.user_avatar(user) }}
{% if userTeamCount > 5 and loop.index == 5 and not loop.last %}
<a href="#" onclick="$('#{{ teamHiddenId }}').toggleClass('hidden');$(this).hide();return false;" class="badge">{{ 'label.plus_more'|trans({'%count%': (userTeamCount - 5)}) }}</a>
<span class="hidden" id="{{ teamHiddenId }}">
{% endif %}
{% if userTeamCount > 5 and loop.index != 5 and loop.last %}
</span>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'border': false} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ title|trans }}{% endblock %}
{% block box_attributes %}id="{{ widgetId }}"{% endblock %}
{% block box_body_class %}no-padding{% endblock %}
{% block box_body %}
{{ widgets.team_list(teams, false) }}
{% endblock %}
{% endembed %}
</div>
</div>
{% endif %}