Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
63
templates/user/2fa.html.twig
Normal file
63
templates/user/2fa.html.twig
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends 'user/form.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block form_body %}
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<p>
|
||||
{{ 'profile.2fa_intro'|trans }}
|
||||
</p>
|
||||
<p>
|
||||
<img src="{{ path('user_profile_2fa_image', {'username': user.username}) }}" alt="TOTP QR Code" style="max-width: 200px; max-height: 200px;" />
|
||||
</p>
|
||||
{% if user.totpAuthenticationEnabled %}
|
||||
<div class="row mb-3">
|
||||
<div class="form-selectgroup form-selectgroup-boxes d-flex">
|
||||
<label class="form-selectgroup-item flex-column">
|
||||
<input type="radio" id="user_two_factor_enable" name="user_two_factor[enabled]" class="form-selectgroup-input" value="1" checked="checked">
|
||||
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<div class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-check-label" for="user_two_factor_enable">{{ 'activated'|trans }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-selectgroup-item flex-column">
|
||||
<input type="radio" id="user_two_factor_disable" name="user_two_factor[enabled]" class="form-selectgroup-input" value="0">
|
||||
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<div class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-check-label" for="user_two_factor_disable">{{ 'deactivated'|trans }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_start(deactivate, {'attr': {'id': 'user_two_factor_form'}}) }}
|
||||
{{ form_end(deactivate) }}
|
||||
{% else %}
|
||||
{{ form_start(form, {'attr': {'id': 'user_two_factor_form'}}) }}
|
||||
<p>{{ 'profile.2fa_confirmation'|trans }}</p>
|
||||
{{ form_widget(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" id="user_two_factor_button" />
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script>
|
||||
document.getElementById('user_two_factor_button').addEventListener('click', function(){
|
||||
document.getElementById('user_two_factor_form').submit();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,15 +1,3 @@
|
||||
{% macro users(view, query) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'users', view, {'query': query}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user_permissions(view) %}
|
||||
{% deprecated 'The "user_permissions" macro from user/action.html.twig is deprecated since 1.14 and will be removed with 2.0. Use "permission/actions.html.twig" instead.' %}
|
||||
{% import "permission/actions.html.twig" as realMacro %}
|
||||
{{ realMacro.user_permissions(view) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user(user, view, options) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'user', view, {'user': user}) %}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% extends 'user/form.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block profile_content %}
|
||||
{% block form_pre_content %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ ('profile.' ~ tab)|trans }}{% endblock %}
|
||||
{% block box_tools %}
|
||||
<a class="btn btn-box-tool" target="_blank" href="{{ path('app.swagger_ui') }}"><i class="{{ 'api'|icon('fas fa-book') }}"></i></a>
|
||||
<a class="btn btn-box-tool" target="_blank" href="{{ 'rest-api.html'|docu_link }}"><i class="{{ 'help'|icon }}"></i></a>
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% if user.apiToken is empty %}
|
||||
<div class="row mb-3">
|
||||
<div class="">
|
||||
{{ widgets.alert('warning', 'api_password.missing_description'|trans, 'api_password.missing_title'|trans, 'warning', false) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex">
|
||||
{{ 'api_password.intro'|trans }}
|
||||
|
||||
<div class="ms-auto">
|
||||
<a class="btn btn-icon" target="_blank" href="{{ path('api.swagger_ui') }}" data-toggle="tooltip" title="Swagger API Docs">{{ icon('documentation', true) }}</a>
|
||||
<a class="btn btn-icon" target="_blank" href="{{ 'rest-api.html'|docu_link }}" data-toggle="tooltip" title="{{ 'help'|trans({}, 'about') }}">{{ icon('help', true) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}{{ 'users'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formEditTemplate = kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': 'create'|trans,
|
||||
'form': form,
|
||||
@@ -12,25 +10,25 @@
|
||||
{% embed formEditTemplate with formOptions %}
|
||||
{% block form_body %}
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.username) }}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.email) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.alias) }}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.title) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.accountNumber) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +72,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ form_widget(form) }}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_title %}{{ 'users'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.recordsTotal > 0) %}
|
||||
@@ -12,7 +10,7 @@
|
||||
'%duration%': stats.durationTotal|duration
|
||||
} %}
|
||||
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', {
|
||||
'message': ("delete_warning.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans),
|
||||
'form': form,
|
||||
'used': inUse,
|
||||
|
||||
@@ -2,15 +2,33 @@
|
||||
|
||||
{% block profile_content %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ ('profile.' ~ tab)|trans }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{% import "user/actions.html.twig" as actions %}
|
||||
{% set event = actions(app.user, 'user_forms', tab, {'user': user}) %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-pills card-header-pills" data-bs-toggle="tabs">
|
||||
{% for name, settings in event.actions %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="{{ settings.url }}" class="nav-link{% if tab == name %} active{% endif %}" role="tab">{{ settings.title|trans({}, settings.translation_domain ?? 'messages') }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% block form_body %}
|
||||
{{ form_start(form) }}
|
||||
<div class="card-body">
|
||||
{% block form_pre_content %}{% endblock %}
|
||||
{% block form_content %}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
{% block form_post_content %}{% endblock %}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,79 +1,44 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% extends 'datatable.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% import "user/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'avatar': {'class': 'alwaysVisible w-min', 'title': null, 'orderBy': false},
|
||||
'alias': {'class': 'alwaysVisible'},
|
||||
'account_number': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'title': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'email': {'class': 'hidden-xs hidden-sm hidden', 'orderBy': false},
|
||||
'lastLogin': {'class': 'hidden-xs hidden-sm hidden', 'orderBy': false},
|
||||
'roles': {'class': 'hidden-sm', 'orderBy': false},
|
||||
} %}
|
||||
{% for pref in preferences %}
|
||||
{% set columns = columns|merge({
|
||||
('mf_' ~ pref.name): {'title': pref.label|trans, 'class': 'hidden-xs hidden-sm mf_' ~ pref.name, 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% set columns = columns|merge({
|
||||
'team': {'class': 'text-center hidden-xs w-min', 'orderBy': false},
|
||||
'active': {'class': 'hidden-xs hidden w-min', 'orderBy': false},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
{% block datatable_row_attr %} {{ widgets.user_row_attr(entry) }}{% endblock %}
|
||||
|
||||
{% set tableName = 'user_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'users'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.users('index', query) }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns, 'kimai.userUpdate') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.userUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<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, 'account_number') }}">{{ entry.accountNumber }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'lastLogin') }}">{% if entry.lastLogin is not null %}{{ entry.lastLogin|date_full }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'roles') }}">
|
||||
{% set showUserRole = entry.roles|length == 1 %}
|
||||
{% for role in entry.roles %}
|
||||
{% if showUserRole or role != 'ROLE_USER' %}
|
||||
{{ widgets.label_role(role) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% for pref in preferences %}
|
||||
<td class="{{ 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, 'team') }}">
|
||||
{{ widgets.badge_team_access(entry.teams) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'active') }}">{{ widgets.label_visible(entry.enabled) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">{{ actions.user(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% block datatable_column_value %}
|
||||
{% if column == 'avatar' %}
|
||||
{{ widgets.user_avatar(entry, false) }}
|
||||
{% elseif column == 'alias' %}
|
||||
{{ entry.alias }}
|
||||
{% elseif column == 'user' %}
|
||||
{{ widgets.username(entry) }}
|
||||
{% elseif column == 'username' %}
|
||||
{{ entry.username }}
|
||||
{% elseif column == 'account_number' %}
|
||||
{{ entry.accountNumber }}
|
||||
{% elseif column == 'title' %}
|
||||
{{ entry.title }}
|
||||
{% elseif column == 'email' %}
|
||||
{{ entry.email }}
|
||||
{% elseif column == 'lastLogin' %}
|
||||
{% if entry.lastLogin is not null %}{{ entry.lastLogin|date_time }}{% endif %}
|
||||
{% elseif column == 'roles' %}
|
||||
{% set showUserRole = entry.roles|length == 1 %}
|
||||
{% for role in entry.roles %}
|
||||
{% if showUserRole or role != 'ROLE_USER' %}
|
||||
{{ widgets.label_role(role) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(entries, 'admin_user_paginated') }}
|
||||
{% elseif column == 'team' %}
|
||||
{{ widgets.badge_team_access(entry.teams) }}
|
||||
{% elseif column == 'active' %}
|
||||
{{ widgets.label_visible(entry.enabled) }}
|
||||
{% elseif column == 'actions' %}
|
||||
{{ actions.user(entry, 'index') }}
|
||||
{% elseif column starts with 'mf_' %}
|
||||
{% set metaField = entry.preference(data.name) %}
|
||||
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
|
||||
{{ widgets.form_type_value(data.type, metaField.value, entry) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,24 +7,81 @@
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-9 col-md-9 col-lg-10">
|
||||
<div class="col-xs-12">
|
||||
{% block profile_intro %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
{{ widgets.user_avatar(user, false, 'avatar-md') }}
|
||||
{{ user.displayName }}
|
||||
{% if user.title is not empty %} – {{ user.title }} {% endif %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body box-user-profile">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto" data-toggle="tooltip" title="{{ 'id'|trans }}: {{ user.id }}">
|
||||
{{ widgets.user_avatar(user, false, 'avatar-lg') }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4 class="card-title m-0">{{ widgets.username(user) }}</h4>
|
||||
{% if user.title is not empty %}
|
||||
<div class="text-muted">
|
||||
{{ user.title }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-auto" id="profile-buttons">
|
||||
{% from '@theme/components/buttons.html.twig' import link_button %}
|
||||
{% if app.user == user %}
|
||||
{{ link_button('notifications'|trans, 'javascript:notificationStatus()', 'fas fa-bell', 'default') }}
|
||||
{% endif %}
|
||||
{{ link_button('email'|trans, ('mailto:' ~ user.email), 'mail', 'default') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block profile_content %}{% endblock %}
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-2">
|
||||
<div class="hidden-xs">
|
||||
{% set event = actions(app.user, 'user', tab, {'user': user}) %}
|
||||
{{ widgets.list_group_actions(event.actions, app.request.requestUri) }}
|
||||
</div>
|
||||
{% block profile_navbar %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
{% if app.user == user %}
|
||||
<script>
|
||||
function notificationStatus()
|
||||
{
|
||||
const notification = window.kimai.getPlugin('notification');
|
||||
if (!notification.isSupported()) {
|
||||
notification.request((result) => {
|
||||
if (result === false) {
|
||||
window.kimai.getPlugin('alert').info('{{ 'notifications.denied'|trans|e('js') }}');
|
||||
} else if (result === true) {
|
||||
notification.notify(null, '{{ 'notifications.welcome'|trans|e('js') }}');
|
||||
}
|
||||
buttonStatusCheck(window.kimai);
|
||||
});
|
||||
} else {
|
||||
notification.notify(null, '{{ 'notifications.welcome'|trans|e('js') }}');
|
||||
}
|
||||
}
|
||||
|
||||
function buttonStatusCheck(kimai)
|
||||
{
|
||||
const button = document.querySelector('div#profile-buttons a');
|
||||
const icon = button.querySelector('i');
|
||||
const notification = kimai.getPlugin('notification');
|
||||
|
||||
if (notification.isSupported()) {
|
||||
icon.classList.remove('fa-bell-slash');
|
||||
icon.classList.add('fa-bell');
|
||||
icon.classList.remove('text-danger');
|
||||
icon.classList.add('text-success');
|
||||
} else {
|
||||
icon.classList.remove('fa-bell');
|
||||
icon.classList.add('fa-bell-slash');
|
||||
icon.classList.remove('text-success');
|
||||
icon.classList.add('text-danger');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('kimai.initialized', function(event) {
|
||||
buttonStatusCheck(event.detail.kimai);
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% from "macros/widgets.html.twig" import form_errors %}
|
||||
|
||||
{% block profile_content %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_errors(form) }}
|
||||
{% for section in sections %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% block box_body %}
|
||||
{% for pref in form.children.preferences|filter(pref => pref.vars.data.section == section)|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(pref) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% set show = false %}
|
||||
{% for pref in form.children.preferences|filter(pref => pref.vars.data.section == section) %}
|
||||
{% if (pref.children|length > 0) %}
|
||||
{% set show = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if show %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_body %}
|
||||
{% for pref in form.children.preferences|filter(pref => pref.vars.data.section == section)|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_widget(pref) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
|
||||
{{ form_end(form) }}
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% extends 'user/form.html.twig' %}
|
||||
|
||||
{% block profile_content %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ ('profile.' ~ tab)|trans }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.alias) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
{% block form_pre_content %}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.alias) }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.title) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.accountNumber) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
{{ form_widget(form) }}
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.title) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.accountNumber) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,55 +12,100 @@
|
||||
{{ encore_entry_script_tags('chart') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block profile_navbar %}
|
||||
{{ _self.about_me(user, stats, firstTimesheet) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block profile_intro %}{% endblock %}
|
||||
|
||||
{% block profile_content %}
|
||||
|
||||
{{ _self.profile_box_horizontal(user, stats) }}
|
||||
|
||||
{% if workMonths is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% set datagrid = {
|
||||
'username' : user.userIdentifier,
|
||||
} %}
|
||||
{% if user.accountNumber is not empty %}
|
||||
{% set datagrid = datagrid|merge({
|
||||
'account_number' : (user.accountNumber ?? '–'),
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set datagrid = datagrid|merge({
|
||||
'profile.first_entry' : (firstTimesheet is not null ? firstTimesheet|date_short : '–'),
|
||||
'profile.registration_date' : user.registeredAt|date_short,
|
||||
}) %}
|
||||
{% set seeOwnRate = false %}
|
||||
{% if stats is not null %}
|
||||
{% set seeOwnRate = is_granted('view_rate_own_timesheet') %}
|
||||
{% set datagrid = datagrid|merge({
|
||||
'stats.durationTotal' : stats.durationTotal|duration,
|
||||
'stats.durationMonth' : stats.durationThisMonth|duration
|
||||
}) %}
|
||||
{#% if seeOwnRate %}
|
||||
{% set datagrid = datagrid|merge({
|
||||
'stats.amountMonth' : stats.rateThisMonthBillable|money,
|
||||
'stats.amountTotal' : stats.rateTotalBillable|money
|
||||
}) %}
|
||||
{% endif %#}
|
||||
{% endif %}
|
||||
{% if not seeOwnRate and is_granted('hourly-rate', user) and user.preferenceValue('hourly_rate') is not null %}
|
||||
{% set datagrid = datagrid|merge({
|
||||
'hourlyRate' : user.preferenceValue('hourly_rate'),
|
||||
'internalRate' : user.preferenceValue('internal_rate')
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="datagrid">
|
||||
{% for name, value in datagrid %}
|
||||
<div class="datagrid-item">
|
||||
<div class="datagrid-title">{{ name|trans }}</div>
|
||||
<div class="datagrid-content">{{ value }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set monthRoute = null %}
|
||||
{% set canSeeReport = user.enabled and is_granted('view_reporting') and (app.user.id == user.id or is_granted('view_other_timesheet')) %}
|
||||
{% set canSeeOwnReport = app.user.id == user.id and is_granted('report:user') %}
|
||||
{% set canSeeOtherReport = app.user.id != user.id and is_granted('report:other') %}
|
||||
{% set canSeeReport = user.enabled and (canSeeOwnReport or canSeeOtherReport) %}
|
||||
|
||||
{%- if canSeeReport -%}
|
||||
{% set monthRoute = path('report_user_month', {'user': user.id, 'date': '__MONTH__'}) %}
|
||||
{% endif %}
|
||||
|
||||
{{ charts.bar_javascript({'footer': 'footer', 'label': 'tooltip', 'onclick': {'url': monthRoute, 'replacer': {'__MONTH__': 'month'}}}) }}
|
||||
{{ charts.bar_javascript({'legend': {'display': false}, 'footer': 'footer', 'label': 'tooltip', 'onclick': {'url': monthRoute, 'replacer': {'__MONTH__': 'month'}}}) }}
|
||||
|
||||
{% for year in workMonths.years|reverse %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% set totalDuration = 0 %}
|
||||
{% for month in workMonths.year(year) %}
|
||||
{% set totalDuration = totalDuration + month.duration %}
|
||||
{% endfor %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/charts.html.twig" as charts %}
|
||||
{% block box_title %}{{ year }}{% endblock %}
|
||||
{% import "macros/widgets.html.twig" as macros %}
|
||||
{% from "macros/status.html.twig" import status_duration %}
|
||||
{% block box_title -%}
|
||||
{{ year }}
|
||||
{{ status_duration(totalDuration|duration) }}
|
||||
{%- endblock %}
|
||||
{% block box_tools %}
|
||||
{%- if canSeeReport -%}
|
||||
<a class="btn btn-default btn-sm" href="{{ path('report_user_year', {'user': user.id, 'date': year ~ '-01-01'}) }}"><i class="{{ 'reporting'|icon }}"></i></a>
|
||||
{% from '@theme/components/buttons.html.twig' import action_cardtoolbutton %}
|
||||
{{ action_cardtoolbutton('reporting', {'url': path('report_user_year', {'user': user.id, 'date': year ~ '-01-01'})}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set dataset = [] %}
|
||||
{% for month in workMonths.year(year) %}
|
||||
{% set monthDate = create_date(year ~ '-' ~ month.date.format('m') ~ '-01') %}
|
||||
{% set dataset = dataset|merge([{'value': month.duration|chart_duration, 'tooltip': month.duration|duration, 'footer': ('label.billable'|trans ~ ': ' ~ month.billableDuration|duration), 'month': monthDate|report_date}]) %}
|
||||
{% set dataset = dataset|merge([{'value': month.duration|chart_duration, 'tooltip': month.duration|duration, 'footer': ('billable'|trans ~ ': ' ~ month.billableDuration|duration), 'month': monthDate|report_date}]) %}
|
||||
{% endfor %}
|
||||
{{ charts.bar_chart('userProfileChart'~year, month_names(), [dataset], {}) }}
|
||||
{{ charts.bar_chart('userProfileChart'~year, month_names(), [dataset]) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endfor %}
|
||||
|
||||
{% if user.teams is not empty and (is_granted('teams', user) or is_granted('view_team_member', user)) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'label.my_teams'|trans }}{% endblock %}
|
||||
{% block box_title %}{{ 'my_teams'|trans }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ widgets.team_list(user.teams) }}
|
||||
{{ widgets.team_list(user.teams, {collapse: 22}) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
@@ -69,110 +114,5 @@
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.teamUpdate', true);
|
||||
});
|
||||
</script>
|
||||
{{ widgets.page_reloader('kimai.teamUpdate', true) }}
|
||||
{% endblock %}
|
||||
|
||||
{% macro about_me(user, stats, firstTimesheet) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
<a href="mailto:{{ user.email }}" title="{{ 'label.email'|trans }}">{{ widgets.icon('mail') }}</a>
|
||||
<span data-toggle="tooltip" title="{{ 'label.id'|trans }}: {{ user.id }}">{{ 'profile.about_me'|trans }}</span>
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
<p>
|
||||
<strong>{{ 'label.username'|trans }}</strong><br>
|
||||
{{ user.username }}
|
||||
</p>
|
||||
{% if user.accountNumber is not empty %}
|
||||
<p>
|
||||
<strong>{{ 'label.account_number'|trans }}</strong><br>
|
||||
{{ user.accountNumber }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<strong>{{ 'profile.first_entry'|trans }}</strong><br>
|
||||
{% if firstTimesheet is not null %}
|
||||
{{ firstTimesheet|date_short }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ 'profile.registration_date'|trans }}</strong><br>
|
||||
{{ user.registeredAt|date_short }}
|
||||
</p>
|
||||
{% if is_granted('hourly-rate', user) and user.preferenceValue('hourly_rate') is not null %}
|
||||
<p>
|
||||
<strong>{{ 'label.hourlyRate'|trans }}</strong><br>
|
||||
{{ user.preferenceValue('hourly_rate') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro profile_box_horizontal(user, stats) %}
|
||||
{% import "@AdminLTE/Macros/default.html.twig" as macro %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set color = config('theme.chart.background_color') %}
|
||||
{% set event = trigger('user.profile_background', {'color': color, 'font': color|font_contrast, 'user': user, 'image': null}) %}
|
||||
{% set config = event.payload %}
|
||||
|
||||
<div class="box box-widget widget-user box-user-profile">
|
||||
<div class="widget-user-header" style="{% if config.image is not null %}background: url('{{ config.image }}') center center;{% else %}background-color: {{ config.color }}{% endif %}">
|
||||
<h3 class="widget-user-username" style="color: {{ config.font }}">
|
||||
{{ widgets.username(user) }}
|
||||
{% if user.displayName != user.username %}
|
||||
({{ user.username }})
|
||||
{% endif %}
|
||||
</h3>
|
||||
<h5 class="widget-user-desc" style="color: {{ config.font }}">{{ user.title }}</h5>
|
||||
</div>
|
||||
<div class="widget-user-image">
|
||||
{{ widgets.user_avatar(user) }}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{% if stats is not null %}
|
||||
{% set seeOwnRate = is_granted('view_rate_own_timesheet') %}
|
||||
{% set columnLength = seeOwnRate ? 3 : 6 %}
|
||||
<div class="row">
|
||||
<div class="col-sm-{{ columnLength }} border-right">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header">{{ stats.durationThisMonth|duration }}</h5>
|
||||
<span class="description-text">{{ 'stats.durationMonth'|trans }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if seeOwnRate %}
|
||||
<div class="col-sm-{{ columnLength }} border-right">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header">{{ stats.rateThisMonthBillable|money }}</h5>
|
||||
<span class="description-text">{{ 'stats.amountMonth'|trans }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-{{ columnLength }} border-right">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header">{{ stats.durationTotal|duration }}</h5>
|
||||
<span class="description-text">{{ 'stats.durationTotal'|trans }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if seeOwnRate %}
|
||||
<div class="col-sm-{{ columnLength }} border-right">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header">{{ stats.rateTotalBillable|money }}</h5>
|
||||
<span class="description-text">{{ 'stats.amountTotal'|trans }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
Reference in New Issue
Block a user